在面对驱动程序无法立即满足请求的情况下,我们需要考虑如何进行响应。通常情况下,调用进程并不会关心驱动程序的状态,所以我们需要在驱动程序中进行相应的处理。一种常见的做法是阻塞该进程的请求。
阻塞I/O是指在执行设备操作时,如果无法获得所需的资源,就会挂起当前进程,直到满足可操作的条件后再执行操作。被挂起的进程进入睡眠状态,并从调度器的运行队列中被移除,直到等待的条件满足。相反,非阻塞I/O在无法进行设备操作时,不会挂起进程,而是选择放弃或者持续轮询,直到可以进行操作为止。
“
等待队列是处理阻塞I/O的经典机制。
”
点击下载“电脑DLL修复工具”;
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); } ... }
登录后复制
以上就是Linux驱动中阻塞IO进程的处理机制的详细内容,更多请关注小闻网其它相关文章!
评论(0)