Linux进程阻塞的相关知识

1.如果驱动程序无法立即满足要求,该如何响应?

当数据不可用时,用户可能调用read;或者进程试图写入数据,但因为输出缓冲区已满,设备还未准备好接受数据。调用进程通常不会关心这类问题,程序员只会简单调用readwrite,然后等待必要的工作结束后返回调用。因此,在这种情况下,我们的驱动程序应该(默认)阻塞该进程,将其置入休眠状态直到请求可继续。

阻塞从字面上听起来似乎意味着低效率,实则不然,如果设备驱动不阻塞,则用户要获得设备资源要不停地查询,这反而无畏的耗费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 (res=read(fd,&buf,1)!=1);//当串口上没有数据也返回,所以要循环尝试读取串口

printf(“%c\n”,buf);

 

2.“休眠(sleep)”对进程来讲意味着什么?

当一个进程被置入休眠时,它会被标记为一种特殊状态并从调度器的运行队列中移走。直到某些情况下修改了这个状态,进程才会在任意CPU上调度,也即运行该进程。休眠中的进程会被搁置在一边,等待将来的某个事件发生。对Linux设备驱动程序来讲,让一个进程进入休眠状态很容易。但是,为了将进程以一种安全的方式进入休眠,我们需要牢记两天规则:

1)       永远不要在原子上下文中进入休眠

2)       当我们被唤醒时,我们永远无法知道休眠了多长时间,或者休眠期间都发生了什么事。

等待队列:就是一个进程链表,其中包含了等待某个特定事件的所有进程。Linux中,一个等待队列通过一个“等待队列头”来管理,等待队列头是一个类型为wait_queue_head_t的结构体。

DECLARE_WAIT_QUEUE_HEAD(name)//静态定义并初始化一个等待队列头

动态方法:

Wait_queue_head_t  my_queue;

Init_waitqueue_head(&my_queue);

3.简单休眠

当进程休眠时,它将期待某个条件会在未来成为真。当一个休眠的进程被唤醒时,它必须再次检查它所等待的条件的确为真。Linux内核中最简单的休眠方式是称为wait_event的宏(以及它的几个变种);在实现休眠的同时,它也检查进程等待的条件。

Wait_event(queue,condition);//非中断休眠,通常不用

Wait_event_interruptible(queue,conditon);//常用,可被信号中断

Wait_event_timeout(queue,condition,timeout);

Wait_event_interruptible_timeout(queue,condition,timeout);

其中,queue是等待队列头,condition是一个布尔表达式,上面的宏在休眠前后都要对该表达式求值;在条件为真之前,进程会休眠。

唤醒进程

Voidwake_up(wait_queue_head_t  *queue);//会唤醒等待在queue上的所有进程

Voidwake_up_interruptible(wait_queue_head_t *queue);//只会唤醒那些执行可中断休眠的进程。

在实践中,约定做法是在使用wait_event时使用wake_up,而在使用wait_event_interruptible时使用wake_up_interruptible

举例:任何试图从该设备上读取的进程均被置于休眠。只要某个进程向该设备写入,所有休眠的进程就会被唤醒。

StaticDECLARE_WAIT_QUEUE_HEAN(wq);

Static int flag=0;

Ssize_sleep_read(structfile *filp, char __user *buf,size_t count, loff_t *pos)

{

         printk(KERN_DEBUG “process %i  (%s) going tosleep\n”,current->pid,current->com);

         wait_event_interruptible(wq,flag!=0);

         flag=0;

         printk(KERN_DEBUG “awaken %i  (%s) going tosleep\n”,current->pid,current->com);

         return 0;

}

Ssize_sleep_write(structfile *filp, char __user *buf,size_t count, loff_t *pos)

{

         printk(KERN_DEBUG “process %i  (%s) going tosleep\n”,current->pid,current->com);

         flag=1;

         wake_up_interruptible(&wq);

         return count;

}

注意flag变量的使用,因为wait_event_interruptible要检查改变为真的条件,因此我们使用flag来构造这个条件。

4.轮询操作

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

使用非阻塞I/O的应用程序也经常使用pollselectepoll系统调用。Poll epollselect功能本质上是一样的:都允许进程决定是否可以对一个或多个打开的文件做非阻塞的读取或写入。这些调用也会阻塞进程,直到给定的文件描述符集合中的任何一个可读取或写入。因此,常常用于那些要使用多个输入或输出流而又不会阻塞于其中任何一个流的应用程序中。

所有三个系统调用都通过驱动程序的poll方法提供。原型如下:

Unsignedint  (*poll)(struct file *filp,poll_table*wait);//当用户空间程序在驱动程序关联的文件描述符上执行pollselect epoll系统调用时,该驱动程序方法将会被调用。第二个参数为轮询表指针。

该设备方法分为两步处理:

1)       对可能引起设备文件状态变化的等待队列调用pol_wait函数,将对应的等待队列头添加poll_table;

2)       返回表示是否能对设备进行无阻塞读、写访问的掩码。

void poll_wait(structfile *filp, wait_queue_head_t *queue, poll_talbe *wait);

该函数所做的工作就是将当前进程添加到wait参数指定的等待队列表(poll_table)中。

 

Poll select调用的目的是确定接下来的I/O操作是否会阻塞。Pollselect的更重要的用途是他们可以使应用程序同时等待多个数据流。

当用户应用程序调用了pollselectepoll函数时,内核会调用由该系统调用引用的全部文件的poll方法,并向它们传递同一个poll_table.poll_table结构是构成实际数据结构的一个简单封装。

总结:

阻塞与非阻塞访问是I/O操作的两种不同模式,前者在I/O操作暂时不可进行时会让进程睡眠。在设备驱动中阻塞I/O一般基于等待队列来实现,等待队列可用于同步驱动中事件发生的先后顺序。使用非阻塞I/O的应用程序也可借助轮询函数来查询设备是否能立即被访问,用户空间调用select()poll()接口,设备驱动提供poll()函数。设备驱动的poll本身不会阻塞,但是pollselect系统调用则会阻塞地等待文件描述符集合中的至少一个可访问或超时。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值