1 等待队列实现原理
1.1 功能介绍
进程有多种状态,当进程做好准备后,它就处于就绪状态(TASK_RUNNING),放入运
行队列,等待内核调度器来调度。当然,同一时刻可能有多个进程进入就绪状态,但是却可
能只有 1 个 CPU 是空闲的,所以最后能不能在 CPU 上运行,还要取决于优先级等多种因素。
当进程进行外部设备的 IO 等待操作时,由于外部设备的操作速度一般是非常慢的,所以进
程会从就绪状态变为等待状态(休眠),进入等待队列,把 CPU 让给其它进程。直到 IO 操
作完成,内核“唤醒”等待的进程,于是进程再度从等待状态变为就绪状态。
在用户态,进程进行 IO 操作时,可以有多种处理方式,如阻塞式 IO,非阻塞式 IO,
多路复用(select/poll/epoll),AIO(aio_read/aio_write)等等。这些操作在内核态都要
用到等待队列。
1.2 相关的结构体
typedef struct __wait_queue wait_queue_t;
struct __wait_queue {
unsigned int flags;
#define WQ_FLAG_EXCLUSIVE 0x01
struct task_struct * task;
wait_queue_func_t func;
struct list_head task_list;
};
这个是等待队列的节点,其中 task 表示等待队列节点对应的进程。func 表示等待队
列的回调函数,在进程被唤醒。在很多等待队列里,这个 func 函数指针默认为空函数。但
是,在 select/poll/epoll 函数中,这个 func 函数指针不为空,并且扮演着重要的角色。
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
这个是等待队列的头部。其中 task_list 里有指向下一个节点的指针。为了保证对等
待队列的操作是原子的,还需要一个自旋锁 lock。
这里需要提一下内核队列中被广泛使用的结构体 struct list_head。
struct list_head {