第三章 scull的内存使用 read write函数
在设备驱动简介时,就提到过要编写特定硬件的设备,首先要相当的了解这个设备。scull的设备就是一片内存,了解scull的设备就得知道scull的内存使用了。
如图所示:
事实上,可以把scull的内存直接理解为一个3维数组了(类似)。第一维的单元是Scull_device结构,Scull_device结构的代码如下:
1 struct scull_qset {
2 void **data;
3 struct scull_qset *next;
4 };
data指向一个指针数组,这个里面的指针指向Quantum,Quantum就处在第二维了,而Quantum中的一个个字节,就是第三维了(这个是我个人的理解)。
以这个三维数组的理解为基础。源码中的SCULL_QUANTUM宏用来设置第三维的长度(也就是Quantum中的字节数),SCULL_QSET宏用来设置第二维的长度(也就是一个Scull_device中Quantum的数量)。至于第一维的长度,Scull_device的数量是没有限制的,它直接由机器的内存决定。
在open函数处,曾调用过一个scull_trim函数,这个函数的作用是释放整个数据区,可以通过查看这个函数来进一步了解scull的内存使用,scull_trim的代码如下:
1 int scull_trim(struct scull_dev *dev)
2 {
3 struct scull_qset *next, *dptr;
4 int qset = dev->qset; /* "dev" is not-null */
5 int i;
6 for (dptr = dev->data; dptr; dptr = next)
7 { /* all the list items */
8 if (dptr->data) {
9 for (i = 0; i < qset; i++)
10 kfree(dptr->data[i]);
11 kfree(dptr->data);
12 dptr->data = NULL;
13 }
14 next = dptr->next;
15 kfree(dptr);
16 }
17 dev->size = 0;
18 dev->quantum = scull_quantum;
19 dev->qset = scull_qset;
20 dev->data = NULL;
21 return 0;
22 }
这个函数首先遍历所有的scull_qset ,然后遍历scull_qset 下的一个个Quantum,并将其释放。scull_qset 链表的首指针记录在scull_dev 的data中,一个Quantum的长度记录在scull_dev 的quantum中,而Quantum的个数记录在scull_dev的>qset中。要注意的是,linux开发下,就不能使用C标准库的函数了,这里使用kfree函数来释放内存,效果与c的free是一样的。值得再次注意的地方在于,在linux开发下写for循环时,循环标记应该在循环外定义。
read和write函数的原型如下:
1 ssize_t read(struct file *filp, char __user *buff,size_t count, loff_t *offp);
2 ssize_t write(struct file *filp, const char __user *buff,size_t count, loff_t *offp);
第一个参数为要操作的文件指针,buff指针指向要写入或读的用户空间缓存,count为要写或者读的字节数,offp表示要读或者写的文件的位置。返回值为负时表示出错,不为负的话则返回的是成功的读或者写的字节的数目。
read函数的代码如下:
1 ssize_t scull_read(struct file *filp, char __user *buf, size_t count,
2 loff_t *f_pos)
3 {
4 struct scull_dev *dev = filp->private_data;
5 struct scull_qset *dptr; /* the first listitem */
6 int quantum = dev->quantum, qset = dev->qset;
7 int itemsize = quantum * qset; /* how many bytes in the listitem */
8 int item, s_pos, q_pos, rest;
9 ssize_t retval = 0;
10 if (down_interruptible(&dev->sem))
11 return -ERESTARTSYS;
12 if (*f_pos >= dev->size)
13 goto out;
14 if (*f_pos + count > dev->size)
15 count = dev->size - *f_pos;
16 /* find listitem, qset index, and offset in the quantum */
17 item = (long)*f_pos / itemsize;
18 rest = (long)*f_pos % itemsize;
19 s_pos = rest / quantum; q_pos = rest % quantum;
20 /* follow the list up to the right position (defined elsewhere) */
21 dptr = scull_follow(dev, item);
22 if (dptr = = NULL || !dptr->data || ! dptr->data[s_pos])
23 goto out; /* don't fill holes */
24 /* read only up to the end of this quantum */
25 if (count > quantum - q_pos)
26 count = quantum - q_pos;
27 if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count)) {
28 retval = -EFAULT;
29 goto out;
30 }
31 *f_pos += count;
32 retval = count;
33 out:
34 up(&dev->sem);
35 return retval;
36 }
首先,根据offp提供的位置信息,找到要读或者写的位置位于哪(直接定位到第三维)。一些合理性的判断之后,就调用copy_to_user(void __user *to,const void *from,unsigned long count)函数,把数据读入到buf中。最后还要记得更新offp。copy_to_user函数的作用为把数据复制到用户空间的缓存,也就是读取信息了,类似c标准可的memcpy函数。write的函数体与read很类似,这里就不贴代码了。区别在于write的时候假如这一片内存不存在,就要自己建立这块内存,而且write最后调用的是unsigned long copy_from_user(void *to,const void __user *from,unsigned long count)函数来写。