Chinaunix首页 | 论坛 | 博客
  • 博客访问: 276797
  • 博文数量: 21
  • 博客积分: 510
  • 博客等级: 下士
  • 技术积分: 545
  • 用 户 组: 普通用户
  • 注册时间: 2011-09-05 12:32
文章分类
文章存档

2013年(3)

2012年(13)

2011年(5)

分类: LINUX

2012-04-23 20:43:58

    结合源代码进行设备驱动学习是最好的方式。Linux Device Driver这本书的代码可以去这里下载, ,字符设备的程序在文件夹scull下。驱动程序主要是main.c这个文件实现的。在main.c的开头,声明了4个全局变量scullc_major,scullc_devs,scullc_qset,scullc_quantum分别存放SCULLC的主设备号,次设备号,量子数,量子集大小,再用module_parram()函数对这些变量进行声明,使得这些变量可以通过在模块加载的时候被我们手动设置,这样就提供了一种向模块传递参数的方法。
    通过insmod命令装载模块后,内核通过module_init()调用scullc_init进行初始化工作。相应地,rmmod命令卸载模块时通过module_exit调用scullc_cleanup()释放资源。


    初始化的工作首先是分配设备号,如果装载模块的时候没有设置scull_major,那么scull-major具有初始值SCULLC_MAJOR,定义在scullc.h中,等于0,因此调用alloc_chrdev_region()函数进行动态分配主字符设备号。scullcMAJOR()和MKDEV()这两个函数完成整数和dev_t类型的转换。当然,这个动态分配可能会出错,因此需要检查它的返回值。接下来给scull_devices分配内存,内存数量为设备数于每个设备结构体大小的乘积,这里使用kmalloc()函数和GFP_KERNEL标志获得内核空间进程使用的内存。然后使用memset()对结构体scullc_devices初始化,这两步可以合起来使用kzalloc()获得同样的效果。接着在循环体内对每个设备的参数,如量子数目,量子集大小,互斥信号量进行初始化,并调用scull_setup_cdev()初始化cdev结构体。scull_p_init()和scull_access_init()是初始化友设备的。
    scull_setup_cdev()初始化字符设备主要完成以下几个工作,1,初始化cdev结构体,记录scull_fops,也就是操作scull设备的函数集。2,初始化cdev.owner为THIS_MODULE。3,将scull_fops的地址赋给cdev.ops.4,用函数cdev_add()将设备添加到系统并且激活它。现在就可以使用scull设备了。
 stuct file_operations  scull_fops ={
         .owner = THIS_MODULE,
         .llseek = scull_llsek,
         .read   = scull_read,
         .write   = scull_write,
         .ioctl    = scull_ioctl,
         .open  = scull_open,
         .release=scull_release,
}; 这个结构体包含了操作scull的一些重要的方法,包括读写,偏移,打开,释放等方法。 
    在来看看这个scull_open()函数

    传递给scull_open的两个参数是struct inode 和 struct file. 宏contain_of()获得包含inode的scull_dev结构体,filp是文件描述符,所有打开的文件都拥有一个文件描述符,通过这个文件描述符和设备方法就可以读写文件,直到所有文件的实例被关闭,该文件描述符才会被内核释放。down_interruptible()是down()的可中断版本,当期望得到的信号sem小于或等于0,那么scull_open()会进入睡眠状态,与down函数的区别是
down函数会一直等待到信号量变为大于0,睡眠状态不可被中断,而down_interruptible()在休眠等待时可以被用户空间的程序中断。显然,第一次打开scull的时候,调用了init_MUTEX()将sem设置为1了(这个函数已经在新内核中弃用了,该为sema_init()),因此拥有信号,进程继续,不会休眠,同时sem减1变为0,其他等待该信号量的进程进入休眠,这样就完成了对scull的锁定,一个时刻只有一个线程能访问它。接下来,scull_trim()函数将文件截短,也就是将scull_qset的数据清空,将dev->quantum和dev->qset重新初始化,最后调用up()函数将sem信号加1,对scull进行解锁,其他等待sem信号的进程可以继续。
    scull_read和scull_write方法是字符设备的核心函数。用户空间系统调用open()和write()函数的时候实际是通过这两个函数实现的。这两个函数实现将长度为count的用户空间buffer的内容拷贝到文件偏移为f_ops处的scull_dev中,使用copy_to_user()和copy_from_user()函数安全地进行用户空间和内核空间的交互。scull_ioctl是ioctl()系统调用的回调函数。scull_fops.ioctl在新内核中应该改为scull_fops.unlocked_ioctl。这个方法在后面会进行仔细分析。
   以上这些就可以实现字符设备scull了。

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