Chinaunix首页 | 论坛 | 博客
  • 博客访问: 329093
  • 博文数量: 67
  • 博客积分: 668
  • 博客等级: 上士
  • 技术积分: 1591
  • 用 户 组: 普通用户
  • 注册时间: 2011-10-16 22:08
文章分类

全部博文(67)

文章存档

2015年(1)

2014年(13)

2013年(28)

2012年(23)

2011年(2)

分类: LINUX

2012-03-21 11:55:16

以scull驱动中的scullpipe设备为例分析学习。
在scull_init_module函数中有如下代码初始化scullpipe设备:

  1.     dev = MKDEV(scull_major, scull_minor + scull_nr_devs);         (1)
  2.     dev += scull_p_init(dev);                     (2)
(1)生成scullpipe的设备号,主设备号就是本驱动程序共同的主设备号,次设备号scull_minor + scull_nr_devs是接着前面已经为scull分配了scull_nr_devs个设备号
(2)初始化scullpipe设备。并将设备编号累加,scull_p_init函数返回值为添加的scullpipe设备数。

分析scull_p_init(dev)
这个函数和scull_init_module函数结构基本一样

  1. int scull_p_init(dev_t firstdev)
  2. {
  3.     int i, result;

  4.     result = register_chrdev_region(firstdev, scull_p_nr_devs, "scullp");
  5.         //这里指定设备编号注册
  6.     if (result < 0) {
  7.         printk(KERN_NOTICE "Unable to get scullp region, error %d\n", result);
  8.         return 0;
  9.     }
  10.     scull_p_devno = firstdev;
  11.     scull_p_devices = kmalloc(scull_p_nr_devs * sizeof(struct scull_pipe), GFP_KERNEL);
  12.     if (scull_p_devices == NULL) {
  13.         unregister_chrdev_region(firstdev, scull_p_nr_devs);
  14.         return 0;
  15.     }
  16.     memset(scull_p_devices, 0, scull_p_nr_devs * sizeof(struct scull_pipe));
  17.     for (i = 0; i < scull_p_nr_devs; i++) {
  18.         init_waitqueue_head(&(scull_p_devices[i].inq)); //初始化读取等待队列
  19.         init_waitqueue_head(&(scull_p_devices[i].outq)); //初始化写入等待队列
  20.         init_MUTEX(&scull_p_devices[i].sem);
  21.         scull_p_setup_cdev(scull_p_devices + i, i); //cdev注册设备编号
  22.     }

  23.     return scull_p_nr_devs;
  24. }

  25. static void scull_p_setup_cdev(struct scull_pipe *dev, int index)
  26. {
  27.     int err, devno = scull_p_devno + index;
  28.             //dev_t类型的低20位是次设备号 所以可以这样相加。但是书上说不建议这样做,应该始终
  29.         //使用给出的宏MKDEV,但是却在自己的实例代码中出现这样的直接使用。囧
  30.     cdev_init(&dev->cdev, &scull_pipe_fops);
  31.     dev->cdev.owner = THIS_MODULE;
  32.     err = cdev_add (&dev->cdev, devno, 1);
  33.     /* Fail gracefully if need be */
  34.     if (err)
  35.         printk(KERN_NOTICE "Error %d adding scullpipe%d", err, index);
  36. }

scullpipe设备是实现了一个FIFO的设备,此设备工作过程大致如下,申请一片固定大小的内存区域中,在这片内存起始地址初始化读写指针。然后读写数据都向前移动指针,如果读取的多过写入的,则没有数据可以读取,则阻塞读取操作。


  1. struct scull_pipe{
  2.         wait_queue_head_t inq, outq;    //读取和写入等待队列
  3.         char *buffer, *end;        //缓存区其实结束地址
  4.         int BufferSize;            //缓冲区大小
  5.         char *rp, *wp;            //读取指针和写入指针
  6.         int nreaders, nwriters;        //读写打开的数量
  7.         struct fasync_struct *async_queue;    //异步读取者?
  8.         struct semaphore sem;            //信号量
  9.         struct cdev cdev;            //cdev结构变量
  10. };

static int scull_p_open(struct inode *inode, struct file *filp)
//此函数申请一片内存,并设置scull_pipe中定义的变量


阻塞就是让进程在某种操作需要等待时将进程进入休眠。
在用户空间调用打开一个设备时可以指定是不是阻塞型操作,如果open时设置为O_NONBLOCK。则所有操作不能立即完成时,进程不休眠等待,而是立即返回。
阻塞型操作进入休眠有两种方法,一种调用函数将休眠细节让内核处理,另一种是手动调用更底层的函数。
第一种休眠方法:

1. 首先要定义一个等待队列头
使用静态方法 
  1. DECLARE_WAIT_QUEUE_HEAD(name);
或动态方法
  1. wait_queue_head_t my_queue;
  2.         init_waitqueue_head(&my_queue);
2. 在不满足规定的条件时,进入休眠,实现等待。使用以下函数之一
  1. wait_event(queue, condition)    //不可中断的进入休眠,queue是上面申请的等待队列头,condition是条件,即为真时不进入休眠,非真则进入休眠等待。
  2.         wait_event_interruption(queue, condition) //可中断休眠。
  3.         wait_event_timeout(queue,condition,timeout) //不可中断型最长等待timeout时间的休眠
  4.         wait_event_timeout(queue,condition,timeout) //可中断型最长等待timeout时间的休眠
3. 唤醒休眠等待的进程函数为:
  1. void wake_up(wait_queue_head_t *queue);
  2.      void wake_up_interruption(wait_queue_head_t *queue);
在进程进入休眠时,内核不会调度它。此进程被唤醒的条件是,conditiont的条件为真,并且有其他进程或中断等调用wake_up类函数唤醒等待在queue队列上的进程。

在实例程序中scull_p_read方法就是使用的这种方法,代码片段如下:
1.初始化等待队列的完成在sucll_p_init函数中,如
  1. init_waitqueue_head(&(scull_p_devices[i].inq));
2.进入等待,
  1. if(wait_event_interruption(dev->inq, (dev->rp != dev->wp)))
  2.         reuturn -ERESTARTSYSY;    //在阻塞的过程中被中断
3.触发唤醒其他等待队列上等待的进程。在此程序中读取如果休眠等待则需要写入来唤醒,反之亦然。
  1. wake_up_interruption(&dev->outq); //唤醒写入等待队列上的进程
第二中休眠方法,手动休眠,此方法就是内核帮你做的很多事你自己来做。
1.同第一种方法一样,先定义等待队列头。
2.创建一个等待队列入口,这是一个wait_queue_t的数据结构。wait_queue_head_t类型中有一个自旋锁和一个链表,而这个链表中就是保存的wait_queue_t结构的等待队列入口。创建等待队列入口方法如下:
使用宏
  1. DEFINE_WAIT(name);    //name是你想要的等待队列入口变量,此宏会声明定义此变量。
或者自己定义来完成
  1. wait_queue_t my_wait;
  2. init_wait(&my_wait);
3.将等待队列入口(wait_queue_t)添加到等待队列(wait_queue_head_t)中
  1. prepare_to_wait(wait_queue_head_t * q,wait_queue_t * wait,int state);
  2.     //q是要添加到的等待队列, wait要添加的等待队列入口 state进程将要成为的状态(应为TASK_INTERRUPTIBLE)
4.使用内核调度 
  1. schedule();
5.schedule函数返回后,即表示此等待队列被唤醒了,此时就可以调用finish_wait来清除了。
  1. finish_wait(wait_queue_head_t * q,wait_queue_t * wait);
在此实例程序中scull_p_write使用的是这种方法.
1.初始化等待队列的完成在sucll_p_init函数中,如

  1. init_waitqueue_head(&(scull_p_devices[i].outq));
2.scull_p_write函数中调用scull_getwritespace检查是否可以写入还是等待,scull_getwritespace函数中,
  1. DEFINE_WAIT(wait); //定义等待队列入口变量
3.添加到写入等待队列中
  1. prepare_to_wait(&dev->outq,&wait,TASK_INTERRUPTIBLE);
4.调度。先检查是否有空间,因为在prepare_to_wait之前读取进程可能将空间完全清空,而不检查直接调度,则因为空间已经清空了,但是写入进程又休眠等待了。读取进程也再读不到数据也进入休眠,则都休眠了造成死锁。

  1. if(spacefree(dev) == 0)
  2.         schedule();
5.最后清除等待队列

  1. finish_wait(&dev->outq,&wait);
阅读(1020) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~