Linux设备驱动程序第三版学习(2)-字符设备驱动程序源码分析(续) .

前段时间一直在搞GPS导航系统的应用软件,最近告一段落,继续捡起linux设备驱动,啃之!

上次记录了模块初始化代码的学习,今次看看卸载模块的代码。

01.void scull_cleanup_module(void)  
02.  
03.{  
04.  
05.     int i;  
06.  
07.     dev_t devno = MKDEV(scull_major, scull_minor); //这个见过!得到当前模块的设备号    
08.  
09.        
10.  
11.     if(scull_devices){    
12.  
13.            for(i = 0; i < scull_nr_devs; i++){   
14.  
15.                 scull_trim(scull_devices +i);   //在下面分析这个函数    
16.  
17.                    cdev_del(&scull_devices[i].cdev);  /*系统调用。从系统中移除一个字符设备。该函数同cdev_alloc, cdev_init, cdev_add三个函数构成了字符设备注册所需的函数 */  
18.  
19.            }    
20.  
21.            kfree(scull_devices); /*此次和前面的kmalloc函数相对应,释放设备的内存空间(这里设备本身就是一段内存,哈哈) */  
22.  
23.     }    
24.  
25.}  

看一下scull_trim

/*

* Empty out the scull device; must be called with the device

* semaphore held.

*/

该函数的作用很简单:遍历整个数据区,释放所有找到的量子和量子集。内容也很简单,不解释。

01.int scull_trim(struct scull_dev*dev)  
02.  
03.{  
04.  
05.     struct scull_qset *next, *dptr;  
06.  
07.     int qset = dev->qset;  /*"dev" is not-null*/    
08.  
09.        
10.  
11.     for(dptr = dev->data; dptr; dptr = next)  //遍历整个链表    
12.  
13.     {   
14.  
15.          if(dptr->data)   
16.  
17.          {   
18.  
19.               for(i = 0; i < qset; i++)   
20.  
21.                    kfree(dptr->data[i]);   
22.  
23.               kfree(dptr->data);   
24.  
25.               dptr->data = NULL;    
26.  
27.          }    
28.  
29.          next = dptr->next;   
30.  
31.          kfree(dptr);    
32.  
33.     }    
34.  
35.     dev->size = 0;   
36.  
37.     dev->quantum = scull_quantum;   
38.  
39.     dev->qset = scull_qset;   
40.  
41.     dev->data = NULL;   
42.  
43.     return 0;    
44.  
45.}    
SCULL设备驱动程序实现了几个最重要的设备方法。如下file_operations结构
01.struct file_operations scull_fops = {  
02..owner = THIS_MODULE,  
03..llseek = scull_llseek,  
04..read = scull_real,  
05..write = scull_write,  
06..ioctl = scull_ioctl,  
07..open = scull_open,  
08..release = scull_release,  
09.};  

这些方法都要驱动程序设计者自己来实现的,每个设备驱动程序都需要设计者来实现这几个方法。
例如驱动程序使用者对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方法:
01.int scull_open(struct inode *inode, struct file *filp)  
02.{  
03.    struct scull_dev *dev; /* device information */  
04.  
05.    dev = container_of(inode->i_cdev, struct scull_dev, cdev); /*由于inode的i_cdev字段只是包含了指向struct cdev结构的指针,而我们要使用的是scull_dev结构指针,所有这里使用了container_of宏,用来取得包含cdev结构的scull_dev结构*/   
06.    filp->private_data = dev; /* 将得到的scull_dev结构指针保存起来,方便以后的访问。*/   
07.  
08.    /*这里只是当设备以“写”打开时,长度截为0*/      
09.    if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {  
10.        if (down_interruptible(&dev->sem)) /*获得信号量,成功获得则继续,没成功则返回-ERESTARSYS */   
11.            return -ERESTARTSYS;  
12.        scull_trim(dev); /* ignore errors */   
13.        up(&dev->sem); /*释放信号量*/   
14.    }  
15.    return 0;          /* success */   
16.}  

分析一下scull的release方法:基本的scull没有需要关闭的硬件,因此直接返回。
01.int scull_release(struct inode *inode, struct file *filp)  
02.{  
03.    return 0;  
04.}  

分析一下scull的read方法:
01.ssize_t scull_read(struct file *filp, char __user *buf, size_t count,  
02.                loff_t *f_pos)  
03.{  
04.    struct scull_dev *dev = filp->private_data;  /*取得open时分配的保存在filp->private_data中的scull_dev结构指针。*/   
05.    struct scull_qset *dptr;    /* the first listitem */   
06.    int quantum = dev->quantum, qset = dev->qset; /*取得量子大小(4000bytes), 取得量子集大小(1000个量子)*/   
07.    int itemsize = quantum * qset; /* how many bytes in the listitem,链表中每个item的大小(4 000 000bytes) */   
08.    int item, s_pos, q_pos, rest;  
09.    ssize_t retval = 0;  
10.  
11.    if (down_interruptible(&dev->sem)) /*下面要进行的是一个互斥的操作,所以这里要获得信号量*/   
12.        return -ERESTARTSYS;  
13.    if (*f_pos >= dev->size) /*如果要读取的偏移位置超过了设备文件的大小,则不能读取,直接跳到out*/   
14.        goto out;  
15.    if (*f_pos + count > dev->size) /*如果要读取的内容超过了设备文件的大小,则截短之,只读到文件尾就可以了*/   
16.        count = dev->size - *f_pos;  
17.  
18.    /* find listitem, qset index, and offset in the quantum */  
19.    item = (long)*f_pos / itemsize;  
20.    rest = (long)*f_pos % itemsize;  
21.    s_pos = rest / quantum; q_pos = rest % quantum;  
22.  
23.    /* follow the list up to the right position (defined elsewhere) */  
24.    dptr = scull_follow(dev, item);  
25.  
26.    if (dptr == NULL || !dptr->data || ! dptr->data[s_pos])  
27.        goto out; /* don't fill holes */  
28.  
29.    /* read only up to the end of this quantum */  
30.    if (count > quantum - q_pos)  
31.        count = quantum - q_pos;  
32.  
33.    if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count)) {  
34.        retval = -EFAULT;  
35.        goto out;  
36.    }  
37.    *f_pos += count;  
38.    retval = count;  
39.  
40.  out:  
41.    up(&dev->sem);  
42.    return retval;  
43.}  


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值