以scull驱动中的scullpipe设备为例分析学习。
在scull_init_module函数中有如下代码初始化scullpipe设备:
- dev = MKDEV(scull_major, scull_minor + scull_nr_devs); (1)
- 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函数结构基本一样
- int scull_p_init(dev_t firstdev)
- {
- int i, result;
- result = register_chrdev_region(firstdev, scull_p_nr_devs, "scullp");
- //这里指定设备编号注册
- if (result < 0) {
- printk(KERN_NOTICE "Unable to get scullp region, error %d\n", result);
- return 0;
- }
- scull_p_devno = firstdev;
- scull_p_devices = kmalloc(scull_p_nr_devs * sizeof(struct scull_pipe), GFP_KERNEL);
- if (scull_p_devices == NULL) {
- unregister_chrdev_region(firstdev, scull_p_nr_devs);
- return 0;
- }
- memset(scull_p_devices, 0, scull_p_nr_devs * sizeof(struct scull_pipe));
- for (i = 0; i < scull_p_nr_devs; i++) {
- init_waitqueue_head(&(scull_p_devices[i].inq)); //初始化读取等待队列
- init_waitqueue_head(&(scull_p_devices[i].outq)); //初始化写入等待队列
- init_MUTEX(&scull_p_devices[i].sem);
- scull_p_setup_cdev(scull_p_devices + i, i); //cdev注册设备编号
- }
- return scull_p_nr_devs;
- }
- static void scull_p_setup_cdev(struct scull_pipe *dev, int index)
- {
- int err, devno = scull_p_devno + index;
- //dev_t类型的低20位是次设备号 所以可以这样相加。但是书上说不建议这样做,应该始终
- //使用给出的宏MKDEV,但是却在自己的实例代码中出现这样的直接使用。囧
- cdev_init(&dev->cdev, &scull_pipe_fops);
- dev->cdev.owner = THIS_MODULE;
- err = cdev_add (&dev->cdev, devno, 1);
- /* Fail gracefully if need be */
- if (err)
- printk(KERN_NOTICE "Error %d adding scullpipe%d", err, index);
- }
scullpipe设备是实现了一个FIFO的设备,此设备工作过程大致如下,申请一片固定大小的内存区域中,在这片内存起始地址初始化读写指针。然后读写数据都向前移动指针,如果读取的多过写入的,则没有数据可以读取,则阻塞读取操作。
- struct scull_pipe{
- wait_queue_head_t inq, outq; //读取和写入等待队列
- char *buffer, *end; //缓存区其实结束地址
- int BufferSize; //缓冲区大小
- char *rp, *wp; //读取指针和写入指针
- int nreaders, nwriters; //读写打开的数量
- struct fasync_struct *async_queue; //异步读取者?
- struct semaphore sem; //信号量
- struct cdev cdev; //cdev结构变量
- };
static int scull_p_open(struct inode *inode, struct file *filp)
//此函数申请一片内存,并设置scull_pipe中定义的变量
阻塞就是让进程在某种操作需要等待时将进程进入休眠。
在用户空间调用打开一个设备时可以指定是不是阻塞型操作,如果open时设置为O_NONBLOCK。则所有操作不能立即完成时,进程不休眠等待,而是立即返回。
阻塞型操作进入休眠有两种方法,一种调用函数将休眠细节让内核处理,另一种是手动调用更底层的函数。
第一种休眠方法:
1. 首先要定义一个等待队列头
使用静态方法
- DECLARE_WAIT_QUEUE_HEAD(name);
或动态方法
- wait_queue_head_t my_queue;
- init_waitqueue_head(&my_queue);
2. 在不满足规定的条件时,进入休眠,实现等待。使用以下函数之一
- wait_event(queue, condition) //不可中断的进入休眠,queue是上面申请的等待队列头,condition是条件,即为真时不进入休眠,非真则进入休眠等待。
- wait_event_interruption(queue, condition) //可中断休眠。
- wait_event_timeout(queue,condition,timeout) //不可中断型最长等待timeout时间的休眠
- wait_event_timeout(queue,condition,timeout) //可中断型最长等待timeout时间的休眠
3. 唤醒休眠等待的进程函数为:
- void wake_up(wait_queue_head_t *queue);
- void wake_up_interruption(wait_queue_head_t *queue);
在进程进入休眠时,内核不会调度它。此进程被唤醒的条件是,conditiont的条件为真,并且有其他进程或中断等调用wake_up类函数唤醒等待在queue队列上的进程。
在实例程序中scull_p_read方法就是使用的这种方法,代码片段如下:
1.初始化等待队列的完成在sucll_p_init函数中,如
- init_waitqueue_head(&(scull_p_devices[i].inq));
2.进入等待,
- if(wait_event_interruption(dev->inq, (dev->rp != dev->wp)))
- reuturn -ERESTARTSYSY; //在阻塞的过程中被中断
3.触发唤醒其他等待队列上等待的进程。在此程序中读取如果休眠等待则需要写入来唤醒,反之亦然。
- wake_up_interruption(&dev->outq); //唤醒写入等待队列上的进程
第二中休眠方法,手动休眠,此方法就是内核帮你做的很多事你自己来做。
1.同第一种方法一样,先定义等待队列头。
2.创建一个等待队列入口,这是一个wait_queue_t的数据结构。wait_queue_head_t类型中有一个自旋锁和一个链表,而这个链表中就是保存的wait_queue_t结构的等待队列入口。创建等待队列入口方法如下:
使用宏
- DEFINE_WAIT(name); //name是你想要的等待队列入口变量,此宏会声明定义此变量。
或者自己定义来完成
- wait_queue_t my_wait;
- init_wait(&my_wait);
3.将等待队列入口(wait_queue_t)添加到等待队列(wait_queue_head_t)中
- prepare_to_wait(wait_queue_head_t * q,wait_queue_t * wait,int state);
- //q是要添加到的等待队列, wait要添加的等待队列入口 state进程将要成为的状态(应为TASK_INTERRUPTIBLE)
4.使用内核调度
5.schedule函数返回后,即表示此等待队列被唤醒了,此时就可以调用finish_wait来清除了。
- finish_wait(wait_queue_head_t * q,wait_queue_t * wait);
在此实例程序中scull_p_write使用的是这种方法.
1.初始化等待队列的完成在sucll_p_init函数中,如
- init_waitqueue_head(&(scull_p_devices[i].outq));
2.scull_p_write函数中调用scull_getwritespace检查是否可以写入还是等待,scull_getwritespace函数中,
- DEFINE_WAIT(wait); //定义等待队列入口变量
3.添加到写入等待队列中
- prepare_to_wait(&dev->outq,&wait,TASK_INTERRUPTIBLE);
4.调度。先检查是否有空间,因为在prepare_to_wait之前读取进程可能将空间完全清空,而不检查直接调度,则因为空间已经清空了,但是写入进程又休眠等待了。读取进程也再读不到数据也进入休眠,则都休眠了造成死锁。
- if(spacefree(dev) == 0)
- schedule();
5.最后清除等待队列
- finish_wait(&dev->outq,&wait);
阅读(1020) | 评论(0) | 转发(1) |