linux设备驱动(8)阻塞型IO

阻塞型IO

       当驱动程序无法立即满足请求,该如何响应?如当我们想要写入的时候,设备对应的缓冲区已满,或者是当我们想要读的时候当前缓冲区是空的。为了提高CPU的效率,我们的驱动程序应该阻塞等待该进程,将其置于休眠状态直到请求可继续。

       休眠(sleep)对于进程来讲意味着什么?当一个进程被置入休眠时,他会被标记为一种特殊状态并从调度器的运行队列中移走,直到某些情况下修改了这个状态,进程才会在任意CPU上调度,也就是运行该进程。休眠中的进程会被搁置到一边,等待将来某个事件的发生。
       在linux设备驱动中,将一个进程置于休眠状态很简单,但是怎么才能安全的将进程置于休眠状态呢?需要注意以下三点:

  1. 永远不要在原子上下文中进入休眠
    原子上下文:在执行多个步骤时,不能有任何的并发访问,即将要休眠的程序不能拥有自旋锁、seqlock或者RCU锁时休眠。如果我们已经禁止了中断也不能休眠。但是在拥有信号量的时候休眠是合法的,但是必须仔细检查拥有信号量时休眠的代码。如果代码在拥有信号量时休眠,任何其他等待该信号量的线程也会休眠,因此任何拥有信号量而休眠的代码必须很短,并且还要确保拥有信号量并不会阻塞最终会唤醒我们的那个进程。
  2. 当进程从休眠状态被唤醒之后,应该重新检查进程所等待的条件(因为可能有多个多个进程都在等待这同一个条件)
  3. 我们要确保其他的代码会在其他地方唤醒我们,否则不能休眠。完成唤醒任务的代码必须能够找到我们的进程,之后才能唤醒休眠的进程。为了确保唤醒发生,需要整体理解我们的代码,并清楚地知道对每个休眠而言那些事件序列会结束休眠。

       鉴于以上的三个点,我们需要维护一个称为等待队列的数据结构,在这个结构中包含了等待某个特定事件的所有进程。
       在linux中,一个等待队列通过一个等待队列头来管理,等待队列头是一个类型为wait_queue_head_t的结构体,定义在<linux/wait.h>中,如下所示:

struct __wait_queue_head {
	/**
	 * 由于等待队列可能由中断处理程序和内核函数修改,所以必须对双向链表进行保护,以免对其进行同时访问。
	 * 其同步是由lock自旋锁达到的。
	 */
	spinlock_t lock;
	/**
	 * 等待进程链表的头。
	 */
	struct list_head task_list;
};

typedef struct __wait_queue_head wait_queue_head_t;



我们可以通过两种方法来初始化这个wait_queue_head_t结构体

  • 静态方法
    DECLARE_WAIT_QUEUE_HEAD(name);
/**
 * 该宏定义一个新等待队列的头,它静态的声明了一个叫name的等待队列的头变量并对该变量的lock和task_list
 * 字段进行初始化
 */
#define DECLARE_WAIT_QUEUE_HEAD(name) \
	wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
	
#define __WAIT_QUEUE_HEAD_INITIALIZER(name) {				\
	.lock		= SPIN_LOCK_UNLOCKED,				\
	.task_list	= { &(name).task_list, &(name).task_list } }

  • 动态方法
    wait_queue_head_t my_queue;
    init_waitqueue_head(&my_queue);
/**
 * 用来初始化动态分配的等待队列的头变量
 */
static inline void init_waitqueue_head(wait_queue_head_t *q)
{
	q->lock = SPIN_LOCK_UNLOCKED;
	INIT_LIST_HEAD(&q->task_list);
}

/**
 * 创建一个新的链表。是新链表头的占位符,并且是一个哑元素。
 * 同时初始化prev和next字段,让它们指向list_name变量本身。
 */
#define INIT_LIST_HEAD(ptr) do { \
	(ptr)->next = (ptr); (ptr)->prev = (ptr); \
} while (0)

linux中的休眠函数

以下函数可以实现休眠并且可以不断检查条件,直到条件为真停止休眠。

wait_event(queue, condition);
wait_event_interruptible(queue, condition);
wait_event_timeout(queue, condition, timeout);
wait_event_interruptible_timeout(queue, condition, timeout);

queue:等待队列头
condition:任意一个布尔表达式,在条件为假的时候,进程会一直休眠(休眠过程中该条件可能会被求值多次,因此对该表达式求值不能带来副作用)
timeout:jiffies的个数,一个tick的时间长取决于内核的CONFIG_HZ的大小。比如CONFIG_HZ=200,则一个jiffies对应5ms时间。
       第一个函数是不可以被中断的,而第二个是可以被信号中断的,第三和第四个函数在第一和第二个函数的基础上增加了timeout参数,如果给定时间到达时,这两个宏都会返回0值,而无论condition如何求值。

linux中的唤醒函数

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

       wake_up会唤醒等待在给定queue上的所有进程,而wake_up_interruptible只会唤醒那些执行可中断休眠的进程。在实践中,约定做法是在使用wait_event时使用wake_up,而在使用wait_event_interruptible时使用wake_up_interruptible。

文件IO模型-阻塞(休眠)
       阻塞:当进程在读取外部设备的资源(数据),资源没有准备好,进程就会进入休眠状态 linux应用中,大部分接口都是阻塞,例如scanf(); read(); write(); accept();

       实现步骤
       1) 将当前进程加入等待队列头中
              add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
       2) 将当前进程状态设置为TASK_INTERRUPTIBLE,可以响应信号。
              set_current_state(TASK_INTERRUPTIBLE);
       3) 让出调度-休眠
              schedule(void);
       一个更加智能的接口是(可以实现上面三个的功能)
              wait_event_interrupt(wq, condition);

       非阻塞:在读写的时候,如果没有数据,立即返回,并且返回一个出错码(用的比较少,因为比较消耗资源)
       open(“/dev/key0”, O_RDWR | O_NOBLOCK);驱动中需要去区分,当前模式是阻塞还是非阻塞。

阻塞和非阻塞型操作

有时调用进程会告诉我们他不想阻塞,而不管其IO是否可以继续,显式的非阻塞IO由filp->f_flags中的O_NOBLOCK标志决定的。他可以在打开时指定。O_NDELAY标志是O_NONBLOCK的另一个名字,是为了保持和system V代码的兼容性而设计的。这个标志在默认情况下是被清除的,因为等待数据的进程一般只是休眠,在执行阻塞型操作的情况下,应该实现下面的动作:
1)如果一个进程调用了read但是还没有数据可读,此进程必须阻塞,数据到达时进程被唤醒,并把数据返回给调用者。即使数据数据少于count参数指定的数据也是这样
2)如果一个进程调用了write但缓冲区没有空间,此进程必须阻塞,而且必须休眠在与读取进程不同的等待队列上,当向硬件设备写入一些数据,从而腾出了部分输出缓冲区后,进程即将被唤醒,write调用成功,即使缓冲区中可能没有所要求的的count字节的空间而只是写入了部分数据,也是如此。
3)在驱动程序中实现输出缓冲区可以提高性能,这得益于减少了上下文的切换和用户级/内核级转换的次数。

如果指定了O_NOBLOCK 标志,read和write的行为就会有所不同,如果在数据没有就绪时调用read或是在缓冲区没有空间时调用write,则该调用简单地返回-EAGAIN。
非阻塞型操作会立即返回,使得应用程序可以查询数据,在处理非阻塞型文件时,应用程序调用stdio函数必须非常小心,因为很容易把一个非阻塞返回错误认为是EOF,所以必须检查errno
只有read,write和open文件操作会受到非阻塞标志的影响。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值