8.1 阻塞与非阻塞I/O
阻塞操作 : 是指在执行设备操作时,若不能获得资源,则挂起进程直到满足操作条件后再进行操作。
- 被挂起的进程进入休眠, 被从调度器移走,直到条件满足。
非阻塞操作:在不能进行设备操作时,并不挂起,它或者放弃,或者不停地查询,直到可以进行操作。
- 非阻塞应用程序通常使用select系统调用查询是否可以对设备进行无阻塞的访问最终会引发设备驱动中poll函数执行。
- 阻塞与非阻塞例程:
//阻塞例程
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);//O_NONBLOCK 非阻塞标识
.....
while(read(fd,&buf,1)!=1);//串口上没有输入则返回,所以循环读取
printf("%c/n",buf);
8.1.1 等待队列
- 定义“等待队列头”
wait_queue_head_t my_queue;
- 初始化“等待队列头”
init_wait_queue_head(&my_queue);
- 定义并初始化等待队列头的快捷方式(宏)
DECLARE_WAIT_QUEUE_HEAD(name)
- 定义等待队列
- DECLARE_WAITQUEUE():该宏用于定义并初始化一个名为name的等待队列
**DECLARE_WAITQUEUE(name, tsk)**
- 添加/移除等待队列
- add_wait_queue():将等待队列wait添加到等待队列头q指向的等待队列链表中
- remove_wait_queue():将等待队列wait从附属的等待队列头q指向的等待队列链表中移除
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);
- 等待事件
- wait_event(queue, condition):queue作为等待队列头的等待队列被唤醒,而且condition必须满足,否则阻塞。不能被信号打断
- wait_event_interruptible():可以被信号打断
- wait_event_timeout():加上_timeout后意味着阻塞等待的超时时间,以jiffy为单位。在timeout到达时不论condition是否满足,均返回
wait_event(queue, condition);
wait_event_interruptible(queue, condition) ;
wait_event_timeout(queue, condition) ;
wait_event_interruptible_timeout(queue, condition)
- 唤醒队列
- 唤醒以queue作为等待队列头的所有等待队列中所有属于该等待队列头的等待队列对应的进程
- wake_up()应与wait_event()或wait_event_timeout()成对使用
- wake_up()可唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE的进程
- wake_up_intterruptible()则应与wait_event_interruptible()或wait_envent_interruptible_timeout()成对使用
- wake_up_interruptible()只能唤醒处于TASK_INTERRUPTIBLE的进程
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);
- 在等待队列上睡眠
- sleep_on():将目前进程的状态设置成TASK_UNINTERRUPTIBLE,并定义一个等待队列,之后把它附属到等待队列头q,直到资源可获得,q引导的等待队列被唤醒。
- interruptible_seep_on():将目前进程的状态设置成TASK_INTERRUPTIBLE,并定义一个等待队列,之后把它附属到等待队列头q,直到资源可获得,q引导的等待队列被唤醒或者进程收到信号。
sleep_on(wait_queue_head_t *q);
interruptible_seep_on(wait_queue_head_t *q);
- 例程:设备驱动中使用等待队列
static ssize_t xxx_write(struct file *file, const char *buffer, size_t count, loff_t *ppos)
{
...
DECLARE_WAITQUEUE(wait, current);
add_wait_queue(&xxx_wait, &wait);
/*等待设备缓冲区可写*/
do {
avail = device_writable(...);
if(avail < 0) {
if(file->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
goto out;
}
__set_current_state)(TASK_INTERRUPTIBLE);
schedule();
if(signal_pending(current)){
ret = -ERESTARTSYS;
goto out;
}
}
} while(avail < 0);
/*写设备缓存区*/
device_write(...)
out:
remove_wait_queue(&xxx_wait, &wait);
set_current_state(TASK_RUNNING);
return ret;
}
8.2 轮询操作
8.2.1 轮询的概念与作用
- 非阻塞I/O的应用程序通常使用select()和poll()系统调用查询是否可对设备进行无阻塞的访问。
- select()和poll()系统调最终会引发设备中的poll()函数执行(xxx_poll()).
8.2.2 应用程序中的轮询编程
- select()系统调用:
- readfds、writefds、exceptfds分别是被select()监视的读、写和异常处理的文件描述符集合
- numfds的值是需要检查的号码最高的文件描述符加1
- timeout参数是一个指向struct timeval类型的指针,它可以使select()在等待timeout时间后若没有文件描述符准备好则返回
int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
- timeout参数是一个指向struct timeval类型的指针,它可以使select()在等待timeout时间后若仍然没有文件描述符准备好则超时返回
struct timeval
{
int tv_sec; // 秒
int tv_usec; // 微秒
}
- 设置、清除、判断文件描述符集合:
FD_ZERO(fd_set *set) // 清除一个文件描述符集
FD_SET(int fd, fd_set *set) // 将一个文件描述符加入文件描述符集中
FD_CLR(int fd, fd_set *set) // 将一个文件描述符从文件描述符集中清除
FD_ISSET(int fd, fd_set *set) // 判断文件描述符是否被置位
- 例程:驱动函数中的poll()函数典型模板
#inlcude------
main()
{
int fd,num;
char rd_ch[BUFFER_LEN];
fd_set rfds,wfds; //读写文件描述符集
//以非阻塞方式打开/dev/globalfifo设备文件
fd=open("/dev/globalfifo",O_RDWR|O_NONBLOCK);
if(fd != -1)
{
//FIFO 清零
if(ioctl(fd,FIFO_CLEAR,0) < 0)
{
printf("ioctl cmd failed /n");
}
while(1)
{
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_SET(fd,&rfds);
FD_SET(fd,&wfds);
select(fd+1,&rfds,&wfds,null,null);
}
}
}
8.2.3 设备驱动中的轮询编程
- poll()函数:
- 参数
- file:file结构指针
- poll_table:轮训表指针
- 作用
- 对可能引起设备文件状态变化的等待队列调用poll_wait()函数,将对应的等待队列头添加到poll_table
- 返回表示是否能对设备进行无阻塞读、写访问的掩码
- 返回
- poll()函数应该返回设备资源的可获取状态,即POLLIN、POLLOUT、POLLPRI、POLLERR、POLLNVAL等宏的位“或”结果。
- 参数
unsigned int (*poll)(struct file *flip, struct poll_table *wait);
- poll_wait()函数
- 用于向poll_table注册等待队列,该函数不会引起阻塞
- 工作是把当前进程添加到wait参数指定的等待列表(poll_table)中
void poll_wait(struct file *flip, wait_queue_head_t *queue, poll_table *wait);
- 用户空间调用select()和poll()接口,设备驱动提供poll()函数
- 设备驱动的poll()本身不会阻塞
- poll()和select()系统调用则会阻塞地等待文件描述符集合中的至少一个可访问或超时。
- 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;
}
- 支持轮询操作的globalfifo驱动
static unsigned int gloablfif0_poll(struct file *filp,poll_table *wait)
{
unsigned int mask = 0;
struct globalfifo_dev *dev = filp->private_data;
down(&dev->sem);
poll_wait(filp,&dev->r_wait , wait) ;
poll_wait(filp,&dev->r_wait , wait) ;
if(dev->current_len != 0)
{
mask |= POLLIN | POLLRDNORM;
}
if(dev->current_len != GLOBALFIFO_SIZE)
{
mask |= POLLOUT | POLLWRNORM;
}
up(&dev->sem);
return mask;
}