您的位置:首页 > 运维架构 > Linux

阻塞与非阻塞I/O

2015-09-04 14:12 591 查看
一,阻塞与非阻塞

阻塞是指没有获得资源则挂起进程,直到获得资源为止。被挂起的进程进入休眠状态,被调度器的运行队列移走,直到等待条件被满足。
非阻塞是不能进行设备操作时不挂起,或放弃,或反复查询,直到可以进行操作为止。

驱动程序常需要这种能力:当应用程序进行read(),write()等系统调用时,若设备的资源不能获取,而用户又希望以阻塞的方式访问设备,驱动程序应该在设备驱动程序的xxx_read(),xxx_write()等操作中将进程阻直到资源可以获取,以后,应用程序的read(),write()等调用返回,整个过程仍然进行了正确的设备访问,用户并没有感知到;若用户以非阻塞的方式访问设备文件,则当设备资源不可获取时,设备驱动的xxx_read(),xxx_write()等操作应立即返回,read(),write()等系统调用也随即被访问。
阻塞不是低效率,如果设备驱动不阻塞, 用户想获取设备资源只能不断查询,消耗CPU资源,阻塞访问时,不能获取资源的进程将进入休眠,将CPU资源让给其他资源。
阻塞的进程会进入休眠状态,因此,必须确保有一个地方能唤醒休眠的进程。唤醒进程的地方最大可能发生在中断里面,因为硬件资源获得的同时往往伴随着一个中断
eg1:阻塞地读取串口一个字符

char buf;  
fd=open("/dev/ttyS1",O_RDWR);  
...  
res=read(fd,&buf,1);//当串口有输入时才返回  
if(res==1)  
    printf("%c\n",buf);  

eg2:非阻塞地读取串口一个字符

char buf;  
fd=open("/dev/ttyS1",O_RDWR|O_NONBLOCK);  //O_NONBLOCK设置为非阻塞方式
...  
while(read(fd,&buf,1)!=1);//串口上无输入也返回,所以要循环尝试读取串口  
printf("%c\n",buf);  

二,等待队列
在linux驱动程序中,可使用等待队列(wait queue)来实现阻塞进程的唤醒,以队列为基础数据结构,与进程调度机制紧密结合,用于实现内核中的异步事件通知机制,也可用于同步对系统资源的访问。
定义"等待队列头"

wait_queue_head_t my_queue;  

初始化"等待队列头"

init_waitqueue_head(&my_queue);  

宏名用于定义并初始化,相当于"快捷方式"

DECLARE_WAIT_QUEUE_HEAD (name);  

定义等待队列

DECLARE_WAITQUEUE(name,tsk);定义并初始化一个名为name的等待队列  

添加/移除等待队列

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(queue,condition);可被信号打断  
wait_event_timeout(queue,condition,timeout);阻塞等待的超时时间,时间到了,不论condition是否满足,都要返回  
wait_event_interruptible_timeout(queue,condition,timeout)  

唤醒队列

void wake_up(wait_queue_head_t *queue);  
void wake_up_interruptible(wait_queue_head_t *queue);  

上述操作唤醒以queue作为等待队列头的所有等待队列中所有属于该等待队列头的等待队列对应的进程。
wake_up()与wake_event()或者wait_event_timeout成对使用,

wake_up_intteruptible()与wait_event_intteruptible()或者wait_event_intteruptible_timeout()成对使用。


三,轮询操作

在用户程序中,select()和poll()也是与设备阻塞与非阻塞访问的,使用非阻塞I/O的应用程序常会使用select()和poll()系统调用查询是否可对设备进行无阻塞的访问。
select()和poll()系统调用最终会引发设备驱动中的poll()函数被执行。
应用程序中的轮询编程

int select(int numfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,  
    struct timeval *timeout);  

功能:测试指定的fd可读?可写?有异常条件待处理?

readfds,writefds,exceptfds是被select监视的读,写,异常处理的文件描述符集合。

numfds是需要检查的号码最高的文件描述符加1,timeout参数是一个指向struct timeval类型的指针,它可以使select()在等待timeout时间后若没有文件描述符准备好则返回。timeout为NULL,select将阻塞进程,直至某个文件满足要求。

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()函数:

unsigned int (*poll)(struct file *filp, struct poll_table *wait);  

主要进行两项工作:
1.对可能引起设备文件状态变化的等待队列调用poll_wait()函数,将对应的等待队列头添加到poll_table;
2.返回表示是否能对设备进行无阻塞读,写访问的位掩码。

用于向poll_table注册等待队列的poll_wait()函数的原型

void poll_wait(struct file *filp,wait_queue_head_t *queue,poll_table *wait);  

此函数用于将当前进程添加到wait参数指定的等待列表(poll_table)中。
驱动程序poll()返回设备资源的可获取状态,POLLIN(定义为0x0001) | POLLRDNORM 意味着设备可以无阻塞地读,POLLOUT(定义为0x0004) | POLLWRNORM 意味着可以无阻塞的写。
eg3: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(read_buffer_not _empty)//可读  
    {  
        mask|= POLLIN |POLLRDNORM;//数据可获得  
    }  
    if(write_buffer_not _full)//可写  
    {  
        mask|= POLLOUT|POLLWRNORM;//数据可写入  
    }  
    ...  
    return mask;  
}  

eg4:globalfifo设备驱动的poll()函数  

static unsigned int globalfifo_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->w_wait,wait);  
    //fifo非空  
    if(dev->current_len!=0)  
    {  
        mask|=POLLIN |POLLRDNORM;//标示数据可获得    
    }  
    //fifo非满  
    if(dev->current_len!=GLOBALFIFO_SIZE)  
    {  
        mask|=POLLOUT |POLLWRNORM;  
    }  
    up(&dev->sem);  
    return mask;  
}<

在测试程序中select()

main()

{

  int fd, num;

  char Buf[128]="memdev_poll test!";

  fd_set rfds,wfds;

 

  /*以非阻塞方式打开/dev/memdev0设备文件*/

  fd = open("/dev/memdev0", O_RDWR | O_NONBLOCK);

  if (fd !=  - 1)

  {

    while (1)

        {

              FD_ZERO(&rfds);

              FD_ZERO(&wfds);

              FD_SET(fd, &rfds);

              FD_SET(fd, &wfds);

              select(fd + 1, &rfds, &wfds, NULL, NULL);

              /*数据可获得*/

              if (FD_ISSET(fd, &rfds))

              {

                  printf("Poll monitor:can be read\n");

            lseek(fd, 0, SEEK_SET);

            read(fd, Buf, sizeof(Buf));

            printf("Read Buf: %s\n", Buf);

            sleep(1);

              }

              /*数据可写入*/

              if (FD_ISSET(fd, &wfds))

              {

                  printf("Poll monitor:can be written\n");

            lseek(fd, 0, SEEK_SET);

            write(fd, Buf, sizeof(Buf));

            printf("Write Buf: %s\n", Buf);

            sleep(1);

              }      

       }

  }

  else

  {

    printf("Device open failure\n");

  }

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Linux设备驱动 io