《linux device drivers》是本好书啊,学习Linux内核必读!
The Design of scull这一节介绍了本书开发的所有scull设备的种类,这章只关心scull,其它的我就略过了。
Major and Minor Numbers 设备号。通过在文件系统中的字符设备名称访问字符设备。这些名称一般叫做设备文件,位于/dev中。字符设备由主设备号Major和从设备号Minor。这里可以理解为,在创建字符设备时必须给定设备号。有两种给定设备号的方法:
1) 静态指定:
- int register_chrdev_region(dev_t first, unsigned int count, char *name);
这里first就是你指定的设备号,count是连续的设备个数。name就是设备名称,会出现在/proc/devices文件中。像大多数内核函数一样,返回0代表成功,负数是错误码。
2) 动态指定
- int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
这里*dev是传出参数,即分配的连续设备的第一个设备号。
无论怎么给定设备号,都调用下面的函数释放。一般的在设备卸载的时候调用此函数。
- void unregister_chrdev_region(dev_t first, unsigned int count);
一般的,强烈建议用动态分配的方式给定设备号。动态分配的缺点是不能提前创建设备文件,因为主设备号是变化的(注:这里可以得出结论,用mknod命令创建/dev文件夹下设备节点时需要知道设备的主设备号~)。这不成问题,一旦分配了主设备号,可以从/proc/devices文件中读出主设备号。于是乎,就可以用一个脚本来加载设备(注:万能的脚本啊!)。下面是scull_load脚本代码:
- #!/bin/sh
- module="scull"
- device="scull"
- mode="664"
- # invoke insmod with all arguments we got
- # and use a pathname, as newer modutils don't look in . by default
- /sbin/insmod ./$module.ko $* || exit 1
- # remove stale nodes
- rm -f /dev/${device}[0-3]
- major=$(awk "\\$2= =\"$module\" {print \\$1}" /proc/devices)
- mknod /dev/${device}0 c $major 0
- mknod /dev/${device}1 c $major 1
- mknod /dev/${device}2 c $major 2
- mknod /dev/${device}3 c $major 3
- # give appropriate group/permissions, and change the group.
- # Not all distributions have staff, some have "wheel" instead.
- group="staff"
- grep -q '^staff:' /etc/group || group="wheel"
- chgrp $group /dev/${device}[0-3]
- chmod $mode /dev/${device}[0-3]
脚本就不做过多解释了,可以看出"major=$(awk "\\$2= =\"$module\" {print \\$1}" /proc/devices)"这句就是读到主设备号啊。书中有解释最后几行,只有超级用户才能运行这个脚本,那么创建的设备节点只有超级用户有读写权限,其它用户只有读权限。关于权限的讨论在第6章。脚本代码中也注明了并不是所有的发行版都有staff,有些有wheel。我用的ubuntu 12,我不知道ubunt 12有什么,这个遗留到下面实验时确定。
相应的有一个scull_unload脚本。更强大的,还提供了一个scull.init脚本,可以接收start,stop,restart作为参数。
作者提供了一种灵活的方法,即可以用宏和参数来指定主设备号。书中代码就不贴了。
接着就是介绍一些重要的结构体:File Operations, The file Structure, The inode Structure。这个我粗略一看,细节以后再研究。
接下来是字符设备注册,只有注册到内核才能操作你的设备。内核用struct cdev来代表字符设备。要用此结构体,需要包含
有两种初始化这个结构体的方法
- struct cdev *my_cdev = cdev_alloc( );
- my_cdev->ops = &my_fops;
还有一种:
- void cdev_init(struct cdev *cdev, struct file_operations *fops);
初始化玩cdev结构体后,添加到内核:
- int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
scull中设备的注册。首先是自定义的scull设备结构体。
- struct scull_dev {
- struct scull_qset *data; /* Pointer to first quantum set */
- int quantum; /* the current quantum size */
- int qset; /* the current array size */
- unsigned long size; /* amount of data stored here */
- unsigned int access_key; /* used by sculluid and scullpriv */
- struct semaphore sem; /* mutual exclusion semaphore */
- struct cdev cdev; /* Char device structure */
- };
然后是创建设备的函数:
- static void scull_setup_cdev(struct scull_dev *dev, int index)
- {
- int err, devno = MKDEV(scull_major, scull_minor + index);
- cdev_init(&dev->cdev, &scull_fops);
- dev->cdev.owner = THIS_MODULE;
- dev->cdev.ops = &scull_fops;
- err = cdev_add (&dev->cdev, devno, 1);
- /* Fail gracefully if need be */
- if (err)
- printk(KERN_NOTICE "Error %d adding scull%d", err, index);
- }
(该睡觉了,下次继续。。。)
2012年11月16日 明天加班,活没干完,没办法。继续学些LDD。
接下来是open和release方法。
open是做初始化工作的,为其后的操作做准备。open一般做的任务有:1)检查设备相关错误 2)第一次打开要初始化设备 3)update f_op,如果需要 4)申请并填写filp->private_data。
open函数原型如下:
- int (*open)(struct inode *inode, struct file *filp);
innode入参有我们需要的i_cdev信息。i_cdev包含我们刚才创建的cdev结构体。有个问题是我们不想要cdev本身,而是想要包含cdev我们自己定义的scull_dev。内核黑客提供了一个宏,即container_of,可以通过指向cdev的指针,找到指向scull_dev的指针。
- struct scull_dev *dev; /* device information */
- dev = container_of(inode->i_cdev, struct scull_dev, cdev);
- filp->private_data = dev; /* for other methods */
一旦获得了scull_dev结构体之后,将其存到private_data中,以备以后使用。
本章中的scull_open函数就如下所示(相比完整代码只是省去了信号量操作):
- 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);
- filp->private_data = dev; /* for other methods */
- /* now trim to 0 the length of the device if open was write-only */
- if ( (filp->f_flags & O_ACCMODE) = = O_WRONLY) {
- scull_trim(dev); /* ignore errors */
- }
- return 0; /* success */
- }
这段代码看起来很稀松,因为并没有什么具体的设备操作。仅仅是scull_trim将scull所挂的内存全部释放。
release方法即与open相反。一般的1)释放挂到filp_private_data上的内存 2)最后一次release关闭设备。scull并没有硬件要关闭,所以release就是这样:
- int scull_release(struct inode *inode, struct file *filp)
- {
- return 0;
- }
接下来介绍scull设备是怎么用内存的。关键点事,一个4000bytes的内存块叫一个quantum,然后有1000个指向quantum内存块的指针,叫quantum set。quantum内存块的大小和qset的大小不作假设,可以通过宏和模块参数,甚至运行时的ioctl来改变。不要做任何假定,只提供机制,不设置具体的策略。
scull_trim的代码,释放任何挂在scull设备的内存。
- int scull_trim(struct scull_dev *dev)
- {
- struct scull_qset *next, *dptr;
- int qset = dev->qset; /* "dev" is not-null */
- int i;
- for (dptr = dev->data; dptr; dptr = next) { /* all the list items */
- 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;
- }
read和write方法
read和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);
书中此处有解释buff是用户态的内存,不能被内核直接引用到。关键的一点是,用户态内存允许换页,而内核态不允许。
显然,只能用内核提供的接口才能访问用户空间内存。这里介绍在
中定义的接口。 - 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);
就像memcpy一样,需要注意的是,任何时候这两个操作都会睡眠,必须要注意驱动程序的可重入和并发。这俩函数还会检查用户内存地址的有效性,并且传输中断时会返回实际copy的字节数。如果不用检查用户空间内存,可以直接调用__copy_to_user和__copy_from_user。
下面是read方法的代码(暂时省去了down_interruptible and up,以后章节再看)。
- 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; /* the first listitem */
- int quantum = dev->quantum, qset = dev->qset;
- int itemsize = quantum * qset; /* how many bytes in the listitem */
- 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;
- /* 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;
- }
写方法:
- 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; /* value used in "goto out" statements */
- if (down_interruptible(&dev->sem))
- return -ERESTARTSYS;
- /* 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 */
- 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;
- }
- /* write only up to the end of this quantum */
- 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;
- /* update the size */
- if (dev->size < *f_pos)
- dev->size = *f_pos;
-
- out:
- up(&dev->sem);
- return retval;
- }
于是乎,有了四个方法,open,release,read,write。可以编译玩玩了。
(擦,太困了,read write方法的代码不分析了,暂时到此为止。。。)
阅读(1022) | 评论(0) | 转发(0) |