1.scull内存使用
1.1 内存管理函数
定义在 <linux/slab.h>
void *kmalloc(size_t size,int flags);
void kfree(void *ptr);
kmalloc:size代表的是分配空间的大小,flags代表分配方式(第八章讲细节),先默认它的值为GFP_KERNEL。
kfree:用来释放内存,除了kmalloc返回的地址和NULL之外,传入其他指针式非法的。
1.2 scull设备结构
整体结构类似一个链表,每个指针指向下一个scull_qset结构。
每个scull_qset结构中都有data指针指向一个数组,数组中包含1000个指针,每个指针指向一块4000byte大小的空间(称为一个Quantum量子),所以每个scull_qset都有1000*4000bytes大小的空间。
我们并不限制死量子的大小和量子集中量子的数目,这些都可以根据需求来改变,有一下几种方法可以改变这两个的值。
- 编译阶段:改变SCULL_QUANTUM 和 SCULL_QSET来改变,不过这样其实还是限制死了。
- 模块加载阶段:通过之前《模块接口》笔记中模块参数的方式,通过加载命令insmod传入参数,改变scull_quantum和scull_qset的值。
- 运行阶段:可以通过ioctl来设置
1.3 相关代码
-
scull_dev结构体
在上一份笔记字符设备(一)中有书写,在这里在回顾一下。struct scull_dev{ struct scull_qset *data; /*指向第一个scull_qset的指针*/ int quantum; /*量子大小*/ int qset; /*量子集中量子个数*/ unsigned long size; /*存储的数据量*/ unsigned int access_key; /*used by sculluid and scullpriv*/ struct semaphore sem; /*互斥信号量*/ struct cdev cdev; /*字符设备结构体*/ }
-
scull_qset 结构体
struct scull_qset{ void **data; struct scull_qset *next; }
-
scull_trim 释放数据区函数
这个函数在文件写权限打开时,会被scull_open调用,用于释放整个数据区域。同样在清除函数中也会调用它来归还内存资源。int scull_trim(struct scull_dev *dev) { struct scull_qset *next,*dptr; int qset = dev->qset; int i; for(dptr = dev->data;dptr;dptr = next)/*遍历链表,访问每个scull_qset节点*/ { if(dptr->data) { for(i=0;i<qset;i++) /*遍历量子集,释放每个量子*/ kfree(dptr->data[i]); kfree(dptr->data); /*释放量子集*/ dptr->data = NULL; } next = dptr->next; kfree(dptr); /*释放scull_qset节点*/ } dev->size = 0; dev->quantum = scull_quantum; dev->qset = scull_qset; dev->data = NULL; /*初始化scull_dev*/ return 0; }
2.read and write
ssize_t read (struct 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);
其中,filp是指向file结构体的指针。count是请求数据传输的大小。buff是指向用户空间的缓冲区。offp是指向“long offset type”(就是long long)对象的指针,这个对象指明了当前用户访问文件的位置。
需要注意的是,buff是用户空间的指针,所以我们不能直接引用它来读写数据,这样是不安全的。需要利用内核支持的函数来完成这一功能,函数定义在<asm/uaccess.h>。
static inline int copy_to_user(void __user volatile *to, const void *from,
unsigned long n);
static inline int copy_from_user(void *to, const void __user volatile *from,
unsigned long n);
在有了上面的工具之后,就可以写出scull的read和write的实现,代码中down_interruptible和up先不用在意,这个会在下一章讲到,其实凭借函数名称也能把它俩的功能猜出来。
-
scull_read
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中,我们把dev保存在了file的private_data域*/ struct scull_qset *dptr; int quantum = dev->quantum, qset = dev->qset; /*quantum是量子大小,qset是一个量子集有多少量子*/ int itemsize = quantum * qset; /*计算链表中一个scull_qset节点 指向的空间大小*/ int item,s_pos,q_pos,rest; ssize_t retval = 0; if(down_interruptible(&dev->sem)) return -ERESTARTSYS; if(*f_pos >= dev->size) /*判断文件当前位置是否超出设备区域*/ goto out; if(*f_pos + count > dev->size) /*判断当前位置+写入的字节是否会超出设备区域*/ count = dev->size - *f_pos; item = (long)*f_pos / itemsize; /*计算是第几个scull_qset节点*/ rest = (long)*f_pos % itemsize; s_pos = rest / quantum; /*计算是量子集中第几个量子*/ q_pos = rest % quantum; /*计算在量子中的偏移*/ dptr = scull_follow(dev,item); /*移动指针到相应scull_qset节点*/ if(dptr == NULL || !dptr->data || !dptr->data[s_pos]) goto out; 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; }
-
scull_write
scull_write 与 scull_read相似,仅对不同的地方进行了注释说明。ssize_t scull_write(struct file *flip,char __user *buf,size_t count,loff_t *f_pos) { struct scull_dev *dev = filp->private_data; struct scull_qset *dptr; int quantum = dev->quantum,qset = dev->qset; int itemsize = quantum * qset; int item,s_pos,q_pos,rest; ssize_t retval = -ENOMEM if(down_interruptible(&dev->sem)) return -ERESTARTSYS; item = (long)*f_pos / itemsize; rest = (long)*f_pos % itemsize; s_pos = rest / quantum; q_pos = rest % quantum; /*在此之前都与read相同, 不同的地方在于,写数据的时候要为量子集和量子开辟空间 */ dptr = scull_follow(dev,item); if(dptr == NULL ) goto out; if(!dptr->data)/*为量子集开辟空间*/ { dptr->data = kmalloc(qset * sizeof(char *),GFP_KERNEL); if(!dptr->data) goto out; memset(dptr->data,0,qset * sizeof(char *)); } if(!dptr->data[s_pos]) /*为量子开辟空间*/ { dptr->data[s_pos] = kmalloc(quantum,GFP_KERNEL); if(!dptr->data[s_pos]) goto out; } if(count > quantum - q_pos) count = quantum - q_pos; if(copy_from_user(dptr->data[s_pos]+q_pos,buf,count)) { retval = -EFAULT; goto out; } *f_pos += count; retval = count; /*更新设备size大小*/ if(dev->size < *f_pos) dev->size = *f_pos; out: up(&dev->sem); return retval; }