阻塞型字符设备驱动
前面说到了 如何实现read write 等操作,但如果设备缓冲已满,现在想而用户此时又想写入设备,此请求就无法立即执行,那怎么办呢?第一种情况是:驱动程序想用户返回请求失败的信息。
第二种情况是:使调用进程阻塞等待设备可以被操作。
而用户更希望自己选择在请求无法满足时候如何操作,所以在用户空间有了O_NONBLOCK标志
在打开设备的时候如果用户指定了此标志(会保存到filp->f_flags中),表示用户希望以非阻塞的形式打开设备,读写时如果设备不能满足要求,则返回错误码(-EAGAIN).
如果用户不指定此标志位,则默认是阻塞型打开,在设备不能满足请求时候需要就阻塞直到设备可以被操作。
休眠的介绍
进程被阻塞即意味着释放CPU,直到另一个进程修改了某个状态,使调度器再次去调度他,所以进程休眠后不知道自己在休眠期间做了什么事情,所以在被唤醒之后还需要检查当前状态,看等待的条件是否为真在linux中等待队列通过等待队列头来管理,(wait_queue_head_t)
初始化队列头
- init_waitqueue_head(wait_queue_head_t * queue);
还有一种简单的方式去初始化队列头(静态初始化)
- DECLARE_WAIT_QUEUE_HEAD(name)
使进程休眠
让一个进程进入睡眠,在醒来时进程仍需要检测一个条件是否为真,正如上边提到的一样- #define wait_event(wq, condition)
- #define wait_event_timeout(wq, condition, timeout)
- #define wait_event_interruptible(wq, condition)
- #define wait_event_interruptible_timeout(wq, condition, timeout)
condition 表示检测的的条件,在condition为真时,睡眠终止。
含有 interruptible 的函数 表示睡眠可以被信号中断,当睡眠被信号中断事,函数返回负值(-ERESTARTSYS)
含有timeout 的函数 timeout 参数表示最长的等待时间,
进程唤醒
有睡眠就有唤醒
在其他进程操作了设备 使等待进程的 等待条件(condition)为真,则需要在此进程中唤醒睡眠的进程
- #define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)
- #define wake_up_all(x) __wake_up(x, TASK_NORMAL, 0, NULL)
- #define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
- #define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)
- #define wake_up_interruptible_all(x) __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)
wake_up_all 唤醒等待队列上的所有进程,不管独占 还是 非独占
wake_up_interruptible跟wake_up 类似, 不过他只会唤醒 队列中可中断的睡眠进程
wake_up_interruptible_all 会唤醒等待队列上的所有可中断睡眠,
wake_up_interruptible_nr 回唤醒等待队列上的nr个独占进程独占等待进程 : 因为在一个睡眠队列上,睡眠的进程可能会有很多,而每次唤醒之后只有一个进程唤醒执行,其他进程继续在等待队列上睡眠,这就导致了有些=重要的进程迟迟不呢个得到调用,为改善这个情况,有了独占进程的概念, 在睡眠时候我们可以指定一个参数WQ_FLAG_EXCLUSEVE标志会加到队列的尾部(不加此标志则默认加到队列头部), 在调用wake_up_xx 对应的函数时候会首先唤醒这些进程。
- while(dev->datawr == dev->datard)
- {
- while(dev->datard == dev->datawr) //循环检测等待条件是否真正的满足
- {
- up(&dev->semp)
- if(filp->f_flags & O_NONBLOCK) //判断是否是非阻塞打开
- {
- return -EAGAIN;
- }
- D("[%s] reading going to sleep!", current->comm);
- if(wait_event_interruptible(dev->rdque, dev->datard != dev->datawr)) //使当前进程睡眠在读睡眠队列
- {
- return -ERESTARTSYS;
- }
- D("waked up %d\n", __LINE__);
- ...
- if(down_interruptible(&dev->semp) < 0)//获取锁
- {
- printk(KERN_ERR "[%s]get the mutex lock error %d, %s",
- current->comm, __LINE__, __func__);
- return -ERESTARTSYS;
- }
- else
- {
- D("have get the mutex %d\n", __LINE__);
- }
- }
- /*read data*/
- wake_up_interruptible(dev->wrque)//读出数据 唤醒写睡眠队列上的进程
- }
poll 函数实现
- unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait);
这些系统调用都是通过poll函数驱动实现
当调用此函数时候内核会分配一个 poll_table_struct 结构,我们 多需要的动作有两步
1、在等待队列上调用poll_wait
- static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
- #define POLLIN 0x0001 //设备可读
- #define POLLPRI 0x0002 //无阻塞读取高优先级数据
- #define POLLOUT 0x0004 //设备可写入
- #define POLLERR 0x0008 //设备发生错误
- #define POLLHUP 0x0010 //设备挂起
- #define POLLRDNORM 0x0040 //normal data is ready now
- #define POLLWRNORM 0x0100 //normal data can be writen to device
- static int simple_poll(struct file *filp, struct poll_table_struct *wait)
- {
- struct simple_dev *dev = filp->private_data;
- unsigned int mask = 0;
- char *next_ptr;
- if(down_interruptible(&dev->semp))
- {
- printk(KERN_ERR "get the semphore err %d \n",__LINE__);
- return -ERESTARTSYS;
- }
- D("have get the semphore %d\n", __LINE__);
- poll_wait(filp, &dev->inq, wait);
- poll_wait(filp, &dev->outq, wait);
- if(dev->datard != dev->datawr)
- {
- mask |= POLLIN | POLLRDNORM; //can be read
- }
- if(dev->datawr+1 == dev->dataend)
- next_ptr = dev->data;
- else
- next_ptr = dev->datawr+1;
- if(next_ptr != dev->datard)
- {
- mask |= POLLOUT | POLLWRNORM; //can be write
- }
- up(&dev->semp);
- return mask;
- }
下一节 会以一个驱动程序 模拟管道 结束基于内存的模拟字符设备驱动程序 , 同时介绍 驱动测试的相关东东