阻塞与非阻塞的概念:
阻塞IO: 当数据不可读或不可写,进程休眠,直到得到数据可读或可写时才返回。阻塞效率高,实时性比较好。
非阻塞IO:不管数据是否可读可写,都马上返回。
struct file 结构中有成员:
unsigned int f_flags;
存放的就是 open(path,flag);
中的flag参数。
驱动中每个接口函数都有一个file指针,通过这个参数就能取得应用程序打开方式(阻塞或非阻塞)
等待队列
内核对这种阻塞提供等待队列机制来实现,这样可以改善实时性问题。
等待队列头数据结构
内核使用这个结构来给进程一个休眠的地方。
定义如下:Wait.h (include\linux)
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
wait_event(wq, condition) ;建立不可以杀进程(信号不能唤醒,效果和msleep相同)。
wait_event_interruptible(wq, condition) ;它可以被被信号唤醒。休眠过程中,进程可以接收信号,收到后不管条件如何,直接返回。
wait_event_timeout(wq, condition, timeout) ;休眠期间效果和 wait_event ,但是有一个超时时间 ,时间到,不管条件如何,直接返回。
wait_event_interruptible_timeout(wq, condition, timeout);休眠期间效果和 wait_event_interruptible相同,区别是有超时功能。时间 到,不管条件如何,直接返回。
wq 是变量
通过调用 wake_up* 函数(表面上是宏)唤醒进程
wake_up(wq) 能用于唤醒各种方式进入休眠的进程,只唤醒队列上的一个进程。
wake_up_all(wq)效果和wake_up相同,只是能唤醒队列上所有的进程。
wake_up_interruptible(wq) 只能用于唤醒一个 使用wait_event_interruptible*休眠的进程。
wake_up_interruptible_all(wq)能唤醒队列所有 使用wait_event_interruptible*休眠的进程。
wq 是指针。
以上函数并不是说一调用,就一定可以把进程唤醒,其实它会先去检测进程休眠的条件,是否变成真了,如果为真才把它唤醒。
1)定义一个等待不队列头变量 ,并且 初始化。有两种方法:
方法1:动态定义:
wait_queue_head_t wq; //全局变量
在模块初始化函数中调用:
init_waitqueue_head(&wq); //安装模块时候执行了初始化
方法2:静态初始化
DECLARE_WAIT_QUEUE_HEAD(wq) ; //这句等效于上面两句。
DECLARE_WAIT_QUEUE_HEAD(name) 表示定义一个 名字为name 的等待队列头变量,并且进行初始化。
2)在需要休眠的地方调用 wait_event*类宏让进程休眠
示例:
在按键驱动代码基础上修改read函数的休眠代码
/* 按键驱动read接口 */
static int buttons_read(struct file *filp,
char __user *buff,
size_t count,
loff_t *offp)
{
unsigned long err;
/* count ==0 ,则返回0,不是错误 */
if(!count) {
return 0;
}
/* 没有按键动作 */
if ( press==0 ) {
if ( filp->f_flags & O_NONBLOCK) {
return -1;
}
else { /* 用户以阻塞方式打开 */
wait_event_interruptible(wq, press);
}
}
/*清按键动作标记*/
press = 0;
/* 修正大小 */
count = min(sizeof(key_values), count);
/* 复制按键缓冲数据给用户空间 */
err = copy_to_user((void __user *)buff, (const void *)(&key_values), count);
if(err) {
return -EFAULT;
}
return count;
}
3)在需要唤醒的地方(一般是要中断程序中)把 休眠条件设置真,然后调用 wake_up* 类宏唤醒进程。
接上一步修改 中断服务程序:
在原来的 press = 1 ;
后面添加 wake_up_interruptible 代码,如下。
press = 1; //设置按键动作标志位
// wake_up(&wq) ; //唤醒进程
wake_up_interruptible(&wq) ; //唤醒进程
现在要解决的问题是给应用程序提供一个接口,告诉它是否有按键动作发生了。这个接口就是驱动 中文件操作结构中的 poll 接口。
linux 驱动 poll 接口---------- 这个接口是用来告诉应用程序本设备是否可以读,或是否可写。
unsigned int xxxx_poll( struct file *file,struct poll_table_struct *wait)
poll 接口模板 :
1) 定义一个等待队列头,并且 初始化
2)按照以下的代码模板实现poll接口。
//静态定义名字是button_waitq的等待队列头, 并且对它进行初始化
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
/* 按键驱动poll接口 */
static unsigned int buttons_poll( struct file *file,struct poll_table_struct *wait)
{
unsigned int mask = 0;
poll_wait(file, &button_waitq, wait); //把等待队列加到查询表中,固定方式
//press 非0表示有按键动作
if (press)
mask |= POLLIN | POLLRDNORM;
//如存在一个变量wr_flag表示设备可以写,则是mask |= POLLOUT| POLLWRNORM
/* if(wr_flag)
mask |= POLLOUT| POLLWRNORM;
*/
return mask;
}
poll 的系统调用接口 select()函数
参考: