在上一篇的最后我们提到了scull设备的内存结构,今天主要介绍源程序中的几个重要方法,会用到那张内存区图。
首先是open方法,其原型是int (*open)(struct inode *inode, struct file *filp);下面先贴出open方法的函数代码,我们再来慢慢分析。
int scull_open(struct inode *inode, struct file *filp)
{
struct scull_dev *dev;
dev = container_of(inode->i_cdev, struct scull_dev, cdev);
filp->private_data = dev;
if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
scull_trim(dev);
up(&dev->sem);
}
return 0;
}
首先定义了一个我们在上篇中提到的表示一个设备的scull_dev指针,利用container_of宏来找到适当的设备结构,第一个参数为inode结构中的i_cdev指针,这是一个指向cdev结构的指针,而struct cdev是表示字符设备的内核的内部结构,在第二个参数scull_cdev这个结构体中也包含struct cdev,而第三个参数则就是第一个参数指针所指向的类型cdev。接着将获得的dev设备结构存入了filp(即file结构体)中的private_data里。
下面的代码涉及到中断和信号量的部分先暂时不看,主要调用了scull_trim(dev)这个函数,我们进入到这个函数中看看它完成了什么功能:
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) {
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;
}
要讲解这个函数还是要用到上一讲中的内存分配图
首先dptr指向第一个scull_qset结构,dptr->data指向的是存放指向量子的指针数组,而qset则是此数组的大小,因此利用for循环,kfree(dptr->data[i]),释放每个量子的内存空间,接着kfree(dptr->data);释放此数组的内存空间,将scull_qset中的数据域指针置空,将处理的scull_qset结构跳到下一结点(链表的处理方式),最后释放当前scull_qset结构,这样循环就可以释放该设备的所有内存空间,scull_trim(struct scull_dev *dev)函数就完成了这样一个功能。
下面来介绍read方法,其原型为ssize_t read(struct file *filp, char __user *buff, size_t count, loff_t *offp);参数filp是一个文件指针,buff是指向用户空间的缓冲区,count是请求传输的数据长度,offp是一个偏移量,表示用户在文件中进行操作的位置。另外,要注意驱动里的读和写是针对用户空间来说的,read方法的任务是从设备拷贝数据到用户空间,这样可能比较好理解一点。下面是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;
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 = 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;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum; q_pos = rest % quantum;
dptr = scull_follow(dev, item);
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;
}
首先从刚才open方法里存放在filp->private_data中的scull_dev指针取出,首先从结构体中取值初始化一些变量,其中int itemsize = quantum * qset,从内存图中可以看出它表示scull_qset数据指针指向的内存的大小。if (*f_pos >= dev->size),如果读取的位置超过保存在设备结构中的数据总量,则直接跳到out返回。if (*f_pos + count > dev->size) count = dev->size - *f_pos;如果读取位置与要读的大小超过数据总量,则重置count值,这很好理解,能读多少读多少嘛!
item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum; q_pos = rest % quantum;
这是寻找要读的具体位置的代码,首先item确定了要读的是哪个scull_qset,rest表示在此数据去上的偏移量,s_pos则确定了要读的是哪个quantum量子,q_pos确定了在要读的一个具体quantum量子中的位置,这段代码还是可以参照那张内存分布图来理解,因此我们要把那张图熟记于心。
下面是scull_follow的代码:
struct scull_qset *scull_follow(struct scull_dev *dev, int n)
{
struct scull_qset *qs = dev->data;
if (! qs) {
qs = dev->data = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);
if (qs == NULL)
return NULL; /* Never mind */
memset(qs, 0, sizeof(struct scull_qset));
}
while (n--) {
if (!qs->next) {
qs->next = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);
if (qs->next == NULL)
return NULL; /* Never mind */
memset(qs->next, 0, sizeof(struct scull_qset));
}
qs = qs->next;
continue;
}
return qs;
}
这很好理解,和scull_trim中的代码差不多,主要是建立scull_qset结构和分配内核空间
接下来是一些入口条件的判断,下面if (count > quantum - q_pos),如果读取数大于量子剩下来的大小时就是读取该量子直到量子结束的意思。
然后copy_to_user函数就是把这读取的起始位置、大小一定数据拷贝到指向用户空间的缓冲区了啊,这样就完成了读操作的要求,最后更新一下偏移量等等。
下面把write()的函数列出,和read()的方法基本差不多,只是拷贝的方向反啦,请大家参照read()方法自己分析。
ssize_t scull_write(struct file *filp, const 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;
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;
if (dev->size < *f_pos)
dev->size = *f_pos;
out:
up(&dev->sem);
return retval;
}
经过这样的仔细讨论,驱动也没有想象中的那么难,只要我们愿意迎难而上,不断克服,最后一定能自己编写设备的驱动,各位加油,大家共勉!