阻塞和非阻塞I/O是设备访问的两种不同模式,驱动程序可以灵活的支持用户空间对设备的这两种访问形式。
阻塞操作是指在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作的条件后在进行操作。被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。
而非阻塞操作的进程在不能进行设备操作时,并不挂起,他或者放弃,或者不停的查询,直到可以进行操作为止。
注:阻塞听起来意味着低效率,其实不然,如果设备部阻塞,则用户想获取设备资源时,只能不停的查询,这反而会增加CPU资源的耗费,而阻塞方式是,不能获取资源的进程将进入休眠,他将CPU资源让给其他的进程。
阻塞会进入休眠,因此必须有一个地方能够唤醒休眠的进程,否则,这个进程就“寿终正寝”了。这个地方最大的可能是在中断,因为硬件的资源的获得往往伴随着中断。下列演示了以阻塞和非阻塞方式读取串口的一个字符。
阻塞方式:
char buf;
fd = open("/dev/ttyS1",O_RDWR);
...
res = read(fd,&buf,1); //当串口有输入时才返回
if(res==1)
printf("%c\n",buf);
非阻塞方式:
char buf;
fd = open("/dev/ttyS1",O_RDWR | O_NONBLOCK);
...
while (read(fd,&buf,1)!=1)
continue; //串口无输入时也返回,所以要循环尝试读取串口
printf("%c\n",buf);
等待队列
linux驱动程序中,可以使用等待队列来实现阻塞进程的唤醒。
操作如下:
1 定义等待队列头
wait_queue_head_t my_queue;
2 初始化等待队列头
init_waitqueue_head (& my_queue);
3 定义等待队列
DECLARE_WAITQUEUE(name,tsk);
4 添加/移除队列
void fastcall add_wait_queue(wait_queue_head_t *q,wait_queue_t,*wait);
void fastcall remove_wait_queue(wait_queue_head_t *q,wait_queue_t,*wait);
5 等待事件
wait_event(queue,condition);
wait_event_interruptible(queue,condition);
wait_event_timeout(queue,condition,timeout);
wait_event_interruptible_timeout(queue,condition,timeout);
6 唤醒队列
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);
7 在等待队列上睡眠
void sleep_on(wait_queue_head_t *q);
void interruptible_sleep_on(wait_queue_head_t *q);
轮询操作
使用非阻塞的应用程序通常会使用select 和poll 系统调用查询是否可以对设备进行无阻塞访问。select 和poll 系统调用最终会引发设备驱动中的poll函数的执行。select 和poll 系统调用本质是一样的。
应用程序的轮询编程:
select()系统调用:原型如下:
int select(int numfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);
其中:readfds , writefds exceptfds 分别是select 监视的读,写和异常处理的文件描述符集合,numfds 的值是需要检查的号码最高的文件描述符加1 ,timeout 参数是一个指向structtimeval 类型的指针,他可以使select在等待timeout时间后,若没有文件描述符准备好则返回。
设备驱动程序中的轮询编程
poll 函数系统调用
unsigned int (*poll )(struct file *filp,struct poll_table * wait);
第一参数为file 结构体的指针,第二个参数是轮询表指针,该函数完成两项工作:
(1)对可能引起设备文件状态变化的等待队列调用poll_wait()函数,将对应的等待队列头添加到poll_table
(2) 返回表示是否能对设备进行无阻塞读,写 访问的掩码。
关键的,用于向poll_table注册等待队列额poll_wait()函数原型如下:
void poll_wait(struct file*filp ,wait_queue_head_t *queue,poll_table *wait);
注:poll_wait()并不会引起阻塞,poll_wait() 函数的工作是把当前的进程添加到wait 参数指定的等待列表(poll_table)中。
驱动程序poll 函数应该返回设备资源的可获取状态,即:
POLLIN
POLLOUT
POLLPRI
POLLERR
POLLNVAL
等宏的位“或”结果。每个宏的含义都表明设备的一种状态。
设备驱动中的poll 模型如下:
static unsigned int xxx_poll(struct file *filp,poll_table *wait)
{
unsigned int mask = 0;
struct xxx_dev *dev = filp->private_data; //获得设备结构体指针
...
poll_wait(filp,&dev->r_wait,wait); //加读等待队列头
poll_wait(filp,&dev->w_wait,wait);//加写等待队列头
if(...) //可读
mask |= POLLIN | POLLRDNORM; //标识数据可获得
if(...)//可写
mask |= POLLOUT | POLLWRNORM; //标识数据可写入
...
return mask;
}