Chinaunix首页 | 论坛 | 博客
  • 博客访问: 58972
  • 博文数量: 7
  • 博客积分: 188
  • 博客等级: 入伍新兵
  • 技术积分: 100
  • 用 户 组: 普通用户
  • 注册时间: 2012-01-11 01:05
个人简介

自由软件工程师,come on, 改变世界的力量!

文章分类
文章存档

2013年(5)

2012年(2)

分类: LINUX

2012-11-14 21:38:23

《linux device drivers》是本好书啊,学习Linux内核必读!
The Design of scull这一节介绍了本书开发的所有scull设备的种类,这章只关心scull,其它的我就略过了。
Major and Minor Numbers 设备号。通过在文件系统中的字符设备名称访问字符设备。这些名称一般叫做设备文件,位于/dev中。字符设备由主设备号Major和从设备号Minor。这里可以理解为,在创建字符设备时必须给定设备号。有两种给定设备号的方法:

1) 静态指定:

  1. int register_chrdev_region(dev_t first, unsigned int count, char *name);
这里first就是你指定的设备号,count是连续的设备个数。name就是设备名称,会出现在/proc/devices文件中。像大多数内核函数一样,返回0代表成功,负数是错误码。

2) 动态指定

  1. int alloc_chrdev_region(dev_t *dev, unsigned int firstminorunsigned int count, char *name);
这里*dev是传出参数,即分配的连续设备的第一个设备号。

无论怎么给定设备号,都调用下面的函数释放。一般的在设备卸载的时候调用此函数。

  1. void unregister_chrdev_region(dev_t first, unsigned int count);

一般的,强烈建议用动态分配的方式给定设备号。动态分配的缺点是不能提前创建设备文件,因为主设备号是变化的(注:这里可以得出结论,用mknod命令创建/dev文件夹下设备节点时需要知道设备的主设备号~)。这不成问题,一旦分配了主设备号,可以从/proc/devices文件中读出主设备号。于是乎,就可以用一个脚本来加载设备(注:万能的脚本啊!)。下面是scull_load脚本代码:

点击(此处)折叠或打开

  1. #!/bin/sh
  2. module="scull"
  3. device="scull"
  4. mode="664"
  5. # invoke insmod with all arguments we got
  6. # and use a pathname, as newer modutils don't look in . by default
  7. /sbin/insmod ./$module.ko $* || exit 1
  8. # remove stale nodes
  9. rm -f /dev/${device}[0-3]
  10. major=$(awk "\\$2= =\"$module\" {print \\$1}" /proc/devices)
  11. mknod /dev/${device}0 c $major 0
  12. mknod /dev/${device}1 c $major 1
  13. mknod /dev/${device}2 c $major 2
  14. mknod /dev/${device}3 c $major 3
  15. # give appropriate group/permissions, and change the group.
  16. # Not all distributions have staff, some have "wheel" instead.
  17. group="staff"
  18. grep -q '^staff:' /etc/group || group="wheel"
  19. chgrp $group /dev/${device}[0-3]
  20. 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来代表字符设备。要用此结构体,需要包含
有两种初始化这个结构体的方法

点击(此处)折叠或打开

  1. struct cdev *my_cdev = cdev_alloc( );
  2. my_cdev->ops = &my_fops;
还有一种:

点击(此处)折叠或打开

  1. void cdev_init(struct cdev *cdev, struct file_operations *fops);
初始化玩cdev结构体后,添加到内核:

点击(此处)折叠或打开

  1. int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
scull中设备的注册。首先是自定义的scull设备结构体。

点击(此处)折叠或打开

  1. struct scull_dev {
  2. struct scull_qset *data; /* Pointer to first quantum set */
  3. int quantum; /* the current quantum size */
  4. int qset; /* the current array size */
  5. unsigned long size; /* amount of data stored here */
  6. unsigned int access_key; /* used by sculluid and scullpriv */
  7. struct semaphore sem; /* mutual exclusion semaphore */
  8. struct cdev cdev; /* Char device structure */
  9. };
然后是创建设备的函数:

点击(此处)折叠或打开

  1. static void scull_setup_cdev(struct scull_dev *dev, int index)
  2. {
  3. int err, devno = MKDEV(scull_major, scull_minor + index);
  4. cdev_init(&dev->cdev, &scull_fops);
  5. dev->cdev.owner = THIS_MODULE;
  6. dev->cdev.ops = &scull_fops;
  7. err = cdev_add (&dev->cdev, devno, 1);
  8. /* Fail gracefully if need be */
  9. if (err)
  10. printk(KERN_NOTICE "Error %d adding scull%d", err, index);
  11. }


(该睡觉了,下次继续。。。)
2012年11月16日  明天加班,活没干完,没办法。继续学些LDD。

接下来是open和release方法。
open是做初始化工作的,为其后的操作做准备。open一般做的任务有:1)检查设备相关错误 2)第一次打开要初始化设备 3)update f_op,如果需要 4)申请并填写filp->private_data。
open函数原型如下:
  1. int (*open)(struct inode *inode, struct file *filp);
innode入参有我们需要的i_cdev信息。i_cdev包含我们刚才创建的cdev结构体。有个问题是我们不想要cdev本身,而是想要包含cdev我们自己定义的scull_dev。内核黑客提供了一个宏,即container_of,可以通过指向cdev的指针,找到指向scull_dev的指针。

  1. struct scull_dev *dev; /* device information */
  2. dev = container_of(inode->i_cdev, struct scull_dev, cdev);
  3. filp->private_data = dev; /* for other methods */

一旦获得了scull_dev结构体之后,将其存到private_data中,以备以后使用。

本章中的scull_open函数就如下所示(相比完整代码只是省去了信号量操作):

  1. int scull_open(struct inode *inode, struct file *filp)
  2. {
  3. struct scull_dev *dev; /* device information */
  4. dev = container_of(inode->i_cdev, struct scull_dev, cdev);
  5. filp->private_data = dev; /* for other methods */
  6. /* now trim to 0 the length of the device if open was write-only */
  7. if ( (filp->f_flags & O_ACCMODE) = = O_WRONLY) {
  8. scull_trim(dev); /* ignore errors */
  9. }
  10. return 0; /* success */
  11. }

这段代码看起来很稀松,因为并没有什么具体的设备操作。仅仅是scull_trim将scull所挂的内存全部释放。

release方法即与open相反。一般的1)释放挂到filp_private_data上的内存 2)最后一次release关闭设备。scull并没有硬件要关闭,所以release就是这样:

  1. int scull_release(struct inode *inode, struct file *filp)
  2. {
  3. return 0;
  4. }

接下来介绍scull设备是怎么用内存的。关键点事,一个4000bytes的内存块叫一个quantum,然后有1000个指向quantum内存块的指针,叫quantum set。quantum内存块的大小和qset的大小不作假设,可以通过宏和模块参数,甚至运行时的ioctl来改变。不要做任何假定,只提供机制,不设置具体的策略。

scull_trim的代码,释放任何挂在scull设备的内存。

  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) { /* all the list items */
  7.         if (dptr->data) {
  8.             for (i = 0; i < qset; i++)
  9.                 kfree(dptr->data[i]);
  10.             kfree(dptr->data);
  11.             dptr->data = NULL;
  12.         }
  13.         next = dptr->next;
  14.         kfree(dptr);
  15.     }
  16.     dev->size = 0;
  17.     dev->quantum = scull_quantum;
  18.     dev->qset = scull_qset;
  19.     dev->data = NULL;
  20.     return 0;
  21. }
read和write方法
read和write做着相似的工作,将数据从应用程序复制或者复制到应用程序。有着相似的原型。

  1. ssize_t read(struct file *filp, char __user *buff,
  2. size_t count, loff_t *offp);
  3. ssize_t write(struct file *filp, const char __user *buff,
  4. size_t count, loff_t *offp);
书中此处有解释buff是用户态的内存,不能被内核直接引用到。关键的一点是,用户态内存允许换页,而内核态不允许。
显然,只能用内核提供的接口才能访问用户空间内存。这里介绍在中定义的接口。

  1. unsigned long copy_to_user(void __user *to,
  2. const void *from,
  3. unsigned long count);
  4. unsigned long copy_from_user(void *to,
  5. const void __user *from,
  6. unsigned long count);
就像memcpy一样,需要注意的是,任何时候这两个操作都会睡眠,必须要注意驱动程序的可重入和并发。这俩函数还会检查用户内存地址的有效性,并且传输中断时会返回实际copy的字节数。如果不用检查用户空间内存,可以直接调用__copy_to_user和__copy_from_user。

下面是read方法的代码(暂时省去了down_interruptible and up,以后章节再看)。

  1. ssize_t scull_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
  2. {
  3.     struct scull_dev *dev = filp->private_data;
  4.     struct scull_qset *dptr; /* the first listitem */
  5.     int quantum = dev->quantum, qset = dev->qset;
  6.     int itemsize = quantum * qset; /* how many bytes in the listitem */
  7.     int item, s_pos, q_pos, rest;
  8.     ssize_t retval = 0;
  9.     if (down_interruptible(&dev->sem))
  10.         return -ERESTARTSYS;
  11.     if (*f_pos >= dev->size)
  12.         goto out;
  13.     if (*f_pos + count > dev->size)
  14.         count = dev->size - *f_pos;
  15.     /* find listitem, qset index, and offset in the quantum */
  16.     item = (long)*f_pos / itemsize;
  17.     rest = (long)*f_pos % itemsize;
  18.     s_pos = rest / quantum; q_pos = rest % quantum;
  19.     /* follow the list up to the right position (defined elsewhere) */
  20.     dptr = scull_follow(dev, item);
  21.     if (dptr = = NULL || !dptr->data || ! dptr->data[s_pos])
  22.         goto out; /* don't fill holes */
  23.     /* read only up to the end of this quantum */
  24.     if (count > quantum - q_pos)
  25.         count = quantum - q_pos;
  26.     if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count)) {
  27.         retval = -EFAULT;
  28.         goto out;
  29.     }
  30.     *f_pos += count;
  31.     retval = count;
  32.     
  33.     out:
  34.     up(&dev->sem);
  35.     return retval;
  36. }
写方法:

  1. ssize_t scull_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
  2. {
  3.     struct scull_dev *dev = filp->private_data;
  4.     struct scull_qset *dptr;
  5.     int quantum = dev->quantum, qset = dev->qset;
  6.     int itemsize = quantum * qset;
  7.     int item, s_pos, q_pos, rest;
  8.     ssize_t retval = -ENOMEM; /* value used in "goto out" statements */
  9.     if (down_interruptible(&dev->sem))
  10.         return -ERESTARTSYS;
  11.     /* find listitem, qset index and offset in the quantum */
  12.     item = (long)*f_pos / itemsize;
  13.     rest = (long)*f_pos % itemsize;
  14.     s_pos = rest / quantum; q_pos = rest % quantum;
  15.     /* follow the list up to the right position */
  16.     dptr = scull_follow(dev, item);
  17.     if (dptr = = NULL)
  18.         goto out;
  19.     if (!dptr->data) {
  20.         dptr->data = kmalloc(qset * sizeof(char *), GFP_KERNEL);
  21.     if (!dptr->data)
  22.         goto out;
  23.         memset(dptr->data, 0, qset * sizeof(char *));
  24.     }
  25.     if (!dptr->data[s_pos]) {
  26.         dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
  27.     if (!dptr->data[s_pos])
  28.         goto out;
  29.     }
  30.     /* write only up to the end of this quantum */
  31.     if (count > quantum - q_pos)
  32.         count = quantum - q_pos;
  33.     if (copy_from_user(dptr->data[s_pos]+q_pos, buf, count)) {
  34.         retval = -EFAULT;
  35.         goto out;
  36.     }
  37.     *f_pos += count;
  38.     retval = count;
  39.     /* update the size */
  40.     if (dev->size < *f_pos)
  41.         dev->size = *f_pos;
  42.         
  43.     out:
  44.     up(&dev->sem);
  45.     return retval;
  46. }

于是乎,有了四个方法,open,release,read,write。可以编译玩玩了。

(擦,太困了,read write方法的代码不分析了,暂时到此为止。。。)










阅读(1022) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~