Linux设备驱动程序第三版学习(1)(2)-字符设备驱动程序源码分析
2012-02-16 14:57
357 查看
一、insmod模块时调用module_init(scull_init_module),就来看一下这个函数: int scull_init_module(void)
二、
[cpp]
view plaincopyprint?
int scull_init_module(void)
{
int result, i; //声明两个整形变量 result,i
dev_t dev = 0; //声明一个dev_t类型的对象dev,默认初始值是0
//下面这段代码调用了alloc_chrdev_region方法动态生成设备编号给dev,设备的名称是"scull” ,并且抽取dev中的主设备号付给scull_major,方法是调用宏MAJOR(dev_t dev)
//alloc_chrdev_region函数原型是: int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
//各个参数的意义如下:
// dev_t *dev: dev_t型指针,是(存储生成的设备编号的)变量的地址
// firstminor: 要使用的被请求的第一个次设备号,通常为0。 这里在本文件中有定义:int scull_minor = 0;
// count:所请求的连续设备号的个数。这里是scull_nr_devs。在本文件中定义:int scull_nr_devs = SCULL_NR_DEVS;
// SCULL_NR_DEVS在头文件Scull.h中定义
// #ifndef SCULL_NR_DEVS
// #define SCULL_NR_DEVS 4 /* scull0 through scull3 */
// #endif
// name:char指针。是和该编号范围关联的设备名称。 "scull”这个名字会出现在/proc/devices和sysfs文件中
if (scull_major) {
dev = MKDEV(scull_major, scull_minor);
result = register_chrdev_region(dev, scull_nr_devs, "scull");
} else { //由于默认scull_major == 0; 所以采用动态分配的方法
result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,
"scull");
scull_major = MAJOR(dev);
}
if (result < 0) {
printk(KERN_WARNING "scull: can't get major %d/n", scull_major);
return result;
}
//scull_devices:是scull_dev结构声明的一个指针变量: struct scull_dev *scull_devices;
//而scull_dev结构则是自定义的,每个SCULL设备都有一个对应的scull_dev结构。在Scull.h中声明.
//struct scull_dev {
// struct scull_qset *data; /* 指向第一个量子集的指针
// int quantum; /*量子大小
// int qset; /* 量子集大小
// unsigned long size; /* 保存的数据总量*/
// unsigned int access_key; /* used by sculluid and scullpriv */
// struct semaphore sem; /* mutual exclusion semaphore */
// struct cdev cdev; /* 字符设备结构*/
// };
// kmalloc函数原型是:void * kmalloc (size_t size, int flags); 其作用是在设备驱动程序或者内核模块中动态开辟内存.各个参数意义如下:
// size:要分配内存的大小. 以字节为单位. 这里为(一个scull_dev结构的大小)*设备数量,设备数量由scull_nr_devs确定为4个
// flags:要分配内存的类型. 这里使用最常用的GFP_KERNEL标志。GFP = Get Free Page。
// 该函数返回分配到的内存首地址。这里把这个地址付给了scull_dev的指针变量scull_devices
// 如果没有分配成功,则goto fail
// 由于kmalloc并不对所获取的内存空间清零,所以又调用了memset函数来清零内存
// memset函数原型是:void *memset(void *s,int c,size_t n) ;总的作用是将已开辟内存空间 s 的首 n 个字节的值设为值 c。
scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);
if (!scull_devices) {
result = -ENOMEM;
goto fail; /* Make this more graceful */
}
memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev)); //将刚刚开辟的内存清零
//初始化并注册每个设备
// 1. 每个设备的量子大小设定为4000
// 2. 每个设备的数组大小设定为1000
// 3. 将每个设备的互斥信号量设置为1.函数原型是void init_MUTEX (struct semaphore *sem);
// 该函数用于初始化一个互斥锁,即它把信号量sem的值设置为1。
// 4. 使用自定义函数scull_setup_cdev来初始化字符设备结构并添加到系统中(注册设备), 总共加了4个设备。函数定义如下:
// static void scull_setup_cdev(struct scull_dev *dev, int index)
// {
// int err, devno = MKDEV(scull_major, scull_minor + index); //此处使用的是已经动态分配完的scull_major
// 下面这几步是非常重要的!!!是注册一个独立的cdev设备的基本过程!!!
// cdev_init(&dev->cdev, &scull_fops); //初始化struct cdev
// dev->cdev.owner = THIS_MODULE; //初始化cdev.owner
// dev->cdev.ops = &scull_fops; //初始化cdev.ops
// err = cdev_add (&dev->cdev, devno, 1); //在cdev结构设置好以后,告诉内核该结构的信息
// /* Fail gracefully if need be */
// if (err)
// printk(KERN_NOTICE "Error %d adding scull%d", err, index);
// }
for (i = 0; i < scull_nr_devs; i++) {
scull_devices[i].quantum = scull_quantum;
scull_devices[i].qset = scull_qset;
init_MUTEX(&scull_devices[i].sem);
scull_setup_cdev(&scull_devices[i], i);
}
/*At this point call the init function for any friend device*/
//此处暂时不考虑其他的设备
dev = MKDEV(scull_major, scull_minor + scull_nr_devs);
dev += scull_p_init(dev);
dev += scull_access_init(dev);
看一下scull_trim
/*
* Empty out the scull device; must be called with the device
* semaphore held.
*/
该函数的作用很简单:遍历整个数据区,释放所有找到的量子和量子集。内容也很简单,不解释。
[cpp]
view plaincopyprint?
int scull_trim(struct scull_dev*dev)
{
struct scull_qset *next, *dptr;
int qset = dev->qset; /*"dev" is not-null*/
for(dptr = dev->data; dptr; dptr = next) //遍历整个链表
{
if(dptr->data)
{
for(i = 0; i < qset; i++)
kfree(dptr->data[i]);
kfree(dptr->data);
dptr->data = NULL;
}
next = dptr->next;
kfree(dptr);
}
dev->size = 0;
dev->quantum = scull_quantum;
dev->qset = scull_qset;
dev->data = NULL;
return 0;
}
这些方法都要驱动程序设计者自己来实现的,每个设备驱动程序都需要设计者来实现这几个方法。
例如驱动程序使用者对open的调用将会调用scull_open函数。我认为这里可以理解为所有驱动程序的接口都是相同的,只是内部实现不同。
关于open和release方法的作用参考书中page62, page63。
read和write方法的原型如下:
ssize_t read(stuct file *filp, char _ _user *buff, size_t count, loff_t offp);
ssize_t write(struct file * filp, const char _ _user *buff, size_t count, loff_t *offp);
这两个方法的实质就是数据拷贝。read方法拷贝数据到用户地址空间,write方法拷贝数据到内核地址空间。实现这两种拷贝用到的内核函数如下(是大多数read和write方法实现的核心部分):
unsigned long copy_to_user(void _ _user *to, const void *from, unsigned long count);
unsigned long copy_from_user(void *to, const void _ _user *from, unsigned long count);
分析一下scull的open方法:
[cpp]
view plaincopyprint?
int scull_open(struct inode *inode, struct file *filp)
{
struct scull_dev *dev; /* device information */
dev = container_of(inode->i_cdev, struct scull_dev, cdev); /*由于inode的i_cdev字段只是包含了指向struct cdev结构的指针,而我们要使用的是scull_dev结构指针,所有这里使用了container_of宏,用来取得包含cdev结构的scull_dev结构*/
filp->private_data = dev; /* 将得到的scull_dev结构指针保存起来,方便以后的访问。*/
/*这里只是当设备以“写”打开时,长度截为0*/
if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {
if (down_interruptible(&dev->sem)) /*获得信号量,成功获得则继续,没成功则返回-ERESTARSYS */
return -ERESTARTSYS;
scull_trim(dev); /* ignore errors */
up(&dev->sem); /*释放信号量*/
}
return 0; /* success */
}
分析一下scull的read方法:
[cpp]
view plaincopyprint?
ssize_t scull_read(struct file *filp, char __user *buf, size_t count,
loff_t *f_pos)
{
struct scull_dev *dev = filp->private_data; /*取得open时分配的保存在filp->private_data中的scull_dev结构指针。*/
struct scull_qset *dptr; /* the first listitem */
int quantum = dev->quantum, qset = dev->qset; /*取得量子大小(4000bytes), 取得量子集大小(1000个量子)*/
int itemsize = quantum * qset; /* how many bytes in the listitem,链表中每个item的大小(4 000 000bytes) */
int item, s_pos, q_pos, rest;
ssize_t retval = 0;
if (down_interruptible(&dev->sem)) /*下面要进行的是一个互斥的操作,所以这里要获得信号量*/
return -ERESTARTSYS;
if (*f_pos >= dev->size) /*如果要读取的偏移位置超过了设备文件的大小,则不能读取,直接跳到out*/
goto out;
if (*f_pos + count > dev->size) /*如果要读取的内容超过了设备文件的大小,则截短之,只读到文件尾就可以了*/
count = dev->size - *f_pos;
/* find listitem, qset index, and offset in the quantum */
item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum; q_pos = rest % quantum;
/* follow the list up to the right position (defined elsewhere) */
dptr = scull_follow(dev, item);
if (dptr == NULL || !dptr->data || ! dptr->data[s_pos])
goto out; /* don't fill holes */
/* read only up to the end of this quantum */
if (count > quantum - q_pos)
count = quantum - q_pos;
if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count)) {
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;
out:
up(&dev->sem);
return retval;
}
原文出处:http://blog.csdn.net/ypoflyer/article/details/5955386
二、
[cpp]
view plaincopyprint?
int scull_init_module(void)
{
int result, i; //声明两个整形变量 result,i
dev_t dev = 0; //声明一个dev_t类型的对象dev,默认初始值是0
//下面这段代码调用了alloc_chrdev_region方法动态生成设备编号给dev,设备的名称是"scull” ,并且抽取dev中的主设备号付给scull_major,方法是调用宏MAJOR(dev_t dev)
//alloc_chrdev_region函数原型是: int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
//各个参数的意义如下:
// dev_t *dev: dev_t型指针,是(存储生成的设备编号的)变量的地址
// firstminor: 要使用的被请求的第一个次设备号,通常为0。 这里在本文件中有定义:int scull_minor = 0;
// count:所请求的连续设备号的个数。这里是scull_nr_devs。在本文件中定义:int scull_nr_devs = SCULL_NR_DEVS;
// SCULL_NR_DEVS在头文件Scull.h中定义
// #ifndef SCULL_NR_DEVS
// #define SCULL_NR_DEVS 4 /* scull0 through scull3 */
// #endif
// name:char指针。是和该编号范围关联的设备名称。 "scull”这个名字会出现在/proc/devices和sysfs文件中
if (scull_major) {
dev = MKDEV(scull_major, scull_minor);
result = register_chrdev_region(dev, scull_nr_devs, "scull");
} else { //由于默认scull_major == 0; 所以采用动态分配的方法
result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,
"scull");
scull_major = MAJOR(dev);
}
if (result < 0) {
printk(KERN_WARNING "scull: can't get major %d/n", scull_major);
return result;
}
//scull_devices:是scull_dev结构声明的一个指针变量: struct scull_dev *scull_devices;
//而scull_dev结构则是自定义的,每个SCULL设备都有一个对应的scull_dev结构。在Scull.h中声明.
//struct scull_dev {
// struct scull_qset *data; /* 指向第一个量子集的指针
// int quantum; /*量子大小
// int qset; /* 量子集大小
// unsigned long size; /* 保存的数据总量*/
// unsigned int access_key; /* used by sculluid and scullpriv */
// struct semaphore sem; /* mutual exclusion semaphore */
// struct cdev cdev; /* 字符设备结构*/
// };
// kmalloc函数原型是:void * kmalloc (size_t size, int flags); 其作用是在设备驱动程序或者内核模块中动态开辟内存.各个参数意义如下:
// size:要分配内存的大小. 以字节为单位. 这里为(一个scull_dev结构的大小)*设备数量,设备数量由scull_nr_devs确定为4个
// flags:要分配内存的类型. 这里使用最常用的GFP_KERNEL标志。GFP = Get Free Page。
// 该函数返回分配到的内存首地址。这里把这个地址付给了scull_dev的指针变量scull_devices
// 如果没有分配成功,则goto fail
// 由于kmalloc并不对所获取的内存空间清零,所以又调用了memset函数来清零内存
// memset函数原型是:void *memset(void *s,int c,size_t n) ;总的作用是将已开辟内存空间 s 的首 n 个字节的值设为值 c。
scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);
if (!scull_devices) {
result = -ENOMEM;
goto fail; /* Make this more graceful */
}
memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev)); //将刚刚开辟的内存清零
//初始化并注册每个设备
// 1. 每个设备的量子大小设定为4000
// 2. 每个设备的数组大小设定为1000
// 3. 将每个设备的互斥信号量设置为1.函数原型是void init_MUTEX (struct semaphore *sem);
// 该函数用于初始化一个互斥锁,即它把信号量sem的值设置为1。
// 4. 使用自定义函数scull_setup_cdev来初始化字符设备结构并添加到系统中(注册设备), 总共加了4个设备。函数定义如下:
// static void scull_setup_cdev(struct scull_dev *dev, int index)
// {
// int err, devno = MKDEV(scull_major, scull_minor + index); //此处使用的是已经动态分配完的scull_major
// 下面这几步是非常重要的!!!是注册一个独立的cdev设备的基本过程!!!
// cdev_init(&dev->cdev, &scull_fops); //初始化struct cdev
// dev->cdev.owner = THIS_MODULE; //初始化cdev.owner
// dev->cdev.ops = &scull_fops; //初始化cdev.ops
// err = cdev_add (&dev->cdev, devno, 1); //在cdev结构设置好以后,告诉内核该结构的信息
// /* Fail gracefully if need be */
// if (err)
// printk(KERN_NOTICE "Error %d adding scull%d", err, index);
// }
for (i = 0; i < scull_nr_devs; i++) {
scull_devices[i].quantum = scull_quantum;
scull_devices[i].qset = scull_qset;
init_MUTEX(&scull_devices[i].sem);
scull_setup_cdev(&scull_devices[i], i);
}
/*At this point call the init function for any friend device*/
//此处暂时不考虑其他的设备
dev = MKDEV(scull_major, scull_minor + scull_nr_devs);
dev += scull_p_init(dev);
dev += scull_access_init(dev);
[cpp] view plaincopyprint? void scull_cleanup_module(void) { int i; dev_t devno = MKDEV(scull_major, scull_minor); //这个见过!得到当前模块的设备号 if(scull_devices){ for(i = 0; i < scull_nr_devs; i++){ scull_trim(scull_devices +i); //在下面分析这个函数 cdev_del(&scull_devices[i].cdev); /*系统调用。从系统中移除一个字符设备。该函数同cdev_alloc, cdev_init, cdev_add三个函数构成了字符设备注册所需的函数 */ } kfree(scull_devices); /*此次和前面的kmalloc函数相对应,释放设备的内存空间(这里设备本身就是一段内存,哈哈) */ } } void scull_cleanup_module(void) { int i; dev_t devno = MKDEV(scull_major, scull_minor); //这个见过!得到当前模块的设备号 if(scull_devices){ for(i = 0; i < scull_nr_devs; i++){ scull_trim(scull_devices +i); //在下面分析这个函数 cdev_del(&scull_devices[i].cdev); /*系统调用。从系统中移除一个字符设备。该函数同cdev_alloc, cdev_init, cdev_add三个函数构成了字符设备注册所需的函数 */ } kfree(scull_devices); /*此次和前面的kmalloc函数相对应,释放设备的内存空间(这里设备本身就是一段内存,哈哈) */ } }
看一下scull_trim
/*
* Empty out the scull device; must be called with the device
* semaphore held.
*/
该函数的作用很简单:遍历整个数据区,释放所有找到的量子和量子集。内容也很简单,不解释。
[cpp]
view plaincopyprint?
int scull_trim(struct scull_dev*dev)
{
struct scull_qset *next, *dptr;
int qset = dev->qset; /*"dev" is not-null*/
for(dptr = dev->data; dptr; dptr = next) //遍历整个链表
{
if(dptr->data)
{
for(i = 0; i < qset; i++)
kfree(dptr->data[i]);
kfree(dptr->data);
dptr->data = NULL;
}
next = dptr->next;
kfree(dptr);
}
dev->size = 0;
dev->quantum = scull_quantum;
dev->qset = scull_qset;
dev->data = NULL;
return 0;
}
[c-sharp] view plaincopyprint? struct file_operations scull_fops = { .owner = THIS_MODULE, .llseek = scull_llseek, .read = scull_real, .write = scull_write, .ioctl = scull_ioctl, .open = scull_open, .release = scull_release, }; struct file_operations scull_fops = { .owner = THIS_MODULE, .llseek = scull_llseek, .read = scull_real, .write = scull_write, .ioctl = scull_ioctl, .open = scull_open, .release = scull_release, };
这些方法都要驱动程序设计者自己来实现的,每个设备驱动程序都需要设计者来实现这几个方法。
例如驱动程序使用者对open的调用将会调用scull_open函数。我认为这里可以理解为所有驱动程序的接口都是相同的,只是内部实现不同。
关于open和release方法的作用参考书中page62, page63。
read和write方法的原型如下:
ssize_t read(stuct file *filp, char _ _user *buff, size_t count, loff_t offp);
ssize_t write(struct file * filp, const char _ _user *buff, size_t count, loff_t *offp);
这两个方法的实质就是数据拷贝。read方法拷贝数据到用户地址空间,write方法拷贝数据到内核地址空间。实现这两种拷贝用到的内核函数如下(是大多数read和write方法实现的核心部分):
unsigned long copy_to_user(void _ _user *to, const void *from, unsigned long count);
unsigned long copy_from_user(void *to, const void _ _user *from, unsigned long count);
分析一下scull的open方法:
[cpp]
view plaincopyprint?
int scull_open(struct inode *inode, struct file *filp)
{
struct scull_dev *dev; /* device information */
dev = container_of(inode->i_cdev, struct scull_dev, cdev); /*由于inode的i_cdev字段只是包含了指向struct cdev结构的指针,而我们要使用的是scull_dev结构指针,所有这里使用了container_of宏,用来取得包含cdev结构的scull_dev结构*/
filp->private_data = dev; /* 将得到的scull_dev结构指针保存起来,方便以后的访问。*/
/*这里只是当设备以“写”打开时,长度截为0*/
if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {
if (down_interruptible(&dev->sem)) /*获得信号量,成功获得则继续,没成功则返回-ERESTARSYS */
return -ERESTARTSYS;
scull_trim(dev); /* ignore errors */
up(&dev->sem); /*释放信号量*/
}
return 0; /* success */
}
[cpp] view plaincopyprint? int scull_release(struct inode *inode, struct file *filp) { return 0; } int scull_release(struct inode *inode, struct file *filp) { return 0; }
分析一下scull的read方法:
[cpp]
view plaincopyprint?
ssize_t scull_read(struct file *filp, char __user *buf, size_t count,
loff_t *f_pos)
{
struct scull_dev *dev = filp->private_data; /*取得open时分配的保存在filp->private_data中的scull_dev结构指针。*/
struct scull_qset *dptr; /* the first listitem */
int quantum = dev->quantum, qset = dev->qset; /*取得量子大小(4000bytes), 取得量子集大小(1000个量子)*/
int itemsize = quantum * qset; /* how many bytes in the listitem,链表中每个item的大小(4 000 000bytes) */
int item, s_pos, q_pos, rest;
ssize_t retval = 0;
if (down_interruptible(&dev->sem)) /*下面要进行的是一个互斥的操作,所以这里要获得信号量*/
return -ERESTARTSYS;
if (*f_pos >= dev->size) /*如果要读取的偏移位置超过了设备文件的大小,则不能读取,直接跳到out*/
goto out;
if (*f_pos + count > dev->size) /*如果要读取的内容超过了设备文件的大小,则截短之,只读到文件尾就可以了*/
count = dev->size - *f_pos;
/* find listitem, qset index, and offset in the quantum */
item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum; q_pos = rest % quantum;
/* follow the list up to the right position (defined elsewhere) */
dptr = scull_follow(dev, item);
if (dptr == NULL || !dptr->data || ! dptr->data[s_pos])
goto out; /* don't fill holes */
/* read only up to the end of this quantum */
if (count > quantum - q_pos)
count = quantum - q_pos;
if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count)) {
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;
out:
up(&dev->sem);
return retval;
}
原文出处:http://blog.csdn.net/ypoflyer/article/details/5955386
相关文章推荐
- Linux设备驱动程序第三版学习(1)-字符设备驱动程序源码分析
- Linux设备驱动程序第三版学习(1)-字符设备驱动程序源码分析
- Linux设备驱动程序第三版学习(1)-字符设备驱动程序源码分析
- Linux设备驱动程序第三版学习(2)-字符设备驱动程序源码分析(续) .
- Linux设备驱动程序第三版学习(2)-字符设备驱动程序源码分析(续)
- Linux设备驱动程序第三版学习(2)-字符设备驱动程序源码分析(续)
- Linux设备驱动程序第三版学习(9)- 高级字符驱动程序操作(续4) - llseek定位设备
- Linux设备驱动程序第三版学习(9)- 高级字符驱动程序操作(续4) - llseek定位设备
- Linux设备驱动程序第三版学习(5)- 高级字符驱动程序操作 - ioctl .
- Linux设备驱动程序第三版学习(6)- 高级字符驱动程序操作(续1)- 进程休眠 .
- LDD3源码分析之字符设备驱动程序
- Linux设备驱动程序第三版学习(7)- 高级字符驱动程序操作(续2)- poll/select .
- Linux设备驱动程序第三版学习(8)- 高级字符驱动程序操作(续3)- 异步通知 .
- LDD3源码分析之字符设备驱动程序
- 学习笔记——《LINUX设备驱动程序(第三版)》Linux设备模型:内核添加、删除设备、驱动程序
- linux字符设备驱动程序源码(char_dev.c)分析
- LDD3源码分析之字符设备驱动程序
- LDD3源码分析之字符设备驱动程序
- 基于mini6410的linux驱动学习总结(五 字符设备驱动程序实例分析(虚拟设备驱动))
- Linux设备驱动程序第三版学习(6)- 高级字符驱动程序操作(续1) - 进程休眠 .