在面对驱动程序无法立即满足请求的情况下,我们需要考虑如何进行响应。通常情况下,调用进程并不会关心驱动程序的状态,所以我们需要在驱动程序中进行相应的处理。一种常见的做法是阻塞该进程的请求。
阻塞I/O是指在执行设备操作时,如果无法获得所需的资源,就会挂起当前进程,直到满足可操作的条件后再执行操作。被挂起的进程进入睡眠状态,并从调度器的运行队列中被移除,直到等待的条件满足。相反,非阻塞I/O在无法进行设备操作时,不会挂起进程,而是选择放弃或者持续轮询,直到可以进行操作为止。
“
等待队列是处理阻塞I/O的经典机制。
”
1. 驱动中阻塞I/O处理流程
概况来讲,阻塞I/O的处理流程包括4部分内容。首先初始化等待队列链表,该链表中存放需要阻塞的进程。然后初始化一个等待队列,并将当前需要阻塞的进程加入到等待队列链表中。再通过设置进程可中断状并将阻塞进程睡眠。最后,当某些条件满足亦即资源可用时,唤醒等待队列中的进程。
下图描述了阻塞I/O的处理流程:
2. 初始化等待队列链表
在进行初始化等待队列链表之前,我们首先需要定义一个wait_queue_head_t
的变量。例如在RK3399
的ISP驱动中数据结构struct rkisp1_stream
包含了wait_queue_head_t done;
。通过调用调用init_waitqueue_head(&stream->done);
进行初始化操作。
void rkisp1_stream_init(struct rkisp1_device *dev, u32 id)
{
struct rkisp1_stream *stream = &dev->stream[id];
memset(stream, 0, sizeof(*stream));
stream->id = id;
stream->ispdev = dev;
INIT_LIST_HEAD(&stream->buf_queue);
init_waitqueue_head(&stream->done);
spin_lock_init(&stream->vbq_lock);
...
}
wait_queue_head_t
变量的原型是__wait_queue_head
,如下所示:
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
init_waitqueue_head()
真正执行的函数是__init_waitqueue_head()
,它的函数定义如下:
void __init_waitqueue_head(wait_queue_head_t *q, const char *name, struct lock_class_key *key)
{
spin_lock_init(&q->lock);
lockdep_set_class_and_name(&q->lock, key, name);
INIT_LIST_HEAD(&q->task_list);
}
3. 等待队列处理
调用DECLARE_WAITQUEUE(wait, current)
将当前进程初始化为等待队列。注意,这里的等待队列和等待队列链表头可不是一个东东。
#define DECLARE_WAITQUEUE(name, tsk) \
wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
等待队列的定义如下:
struct __wait_queue {
unsigned int flags;
void *private;
wait_queue_func_t func;
struct list_head task_list;
};
等待队列和等待队列链表头是通过add_wait_queue()
结合到一起的。
init_waitqueue_head(&delay_wait);
add_wait_queue(&delay_wait, &wait);
以上代码是将等待队列进程加入到等待队列链表中:
static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)
{
list_add(&new->task_list, &head->task_list);
}
4. 阻塞进程处理
阻塞进程处理包括两部分内容,首先设置进程的睡眠状态,包括TASK_INTERRUPTIBLE
和TASK_UNINTERRUPTIBLE
两种。前者用于可中断睡眠,后者用于不可中断睡眠。然后,将当前进程退出调度器让出CPU的使用权。
set_current_state(TASK_INTERRUPTIBLE);
schedule();
5. 唤醒处理
唤醒处理通常位于中断处理函数或某些动作成功执行之后,特定条件满足时,唤醒通过阻塞队列睡眠的进程。例如:
void bitmap_endwrite(struct bitmap *bitmap, sector_t offset, unsigned long sectors,
int success, int behind)
{
if (!bitmap)
return;
if (behind) {
if (atomic_dec_and_test(&bitmap->behind_writes))
wake_up(&bitmap->behind_wait);
pr_debug("dec write-behind count %d/%lu\n",
atomic_read(&bitmap->behind_writes),
bitmap->mddev->bitmap_info.max_write_behind);
}
...
}
以上就是良许教程网为各位朋友分享的Linu系统相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多干货等着你 !