您的位置:首页 > 运维架构 > Linux

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);

[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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐