linux中的等待队列
最近看epoll 和 select 都涉及到一个东西叫做设备等待队列,等待队列是如何工作的,内核是怎么管理的?看这篇文章
- 问题:进程是如何组织起来的?
我们知道,进程是有很多种状态的:include/linux/sched.h
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define __TASK_STOPPED 4
#define __TASK_TRACED 8
/* in tsk->exit_state */
#define EXIT_ZOMBIE 16
#define EXIT_DEAD 32
等等。
那么,对于不同状态的进程,内核是如何来管理的呢?- 就绪队列:状态为TASK_RUNNING的进程组成的列表;
- 处于TASK_STOPPED、EXIT_ZOMBIE或者EXIT_DEAD状态的进程是不需要连接进特定链表的。因为对于这些状态的进程而言,父进程只会通过 PID或者子进程链表来进行访问。
- 而处于TASK_INTERRUPTIBLE、TASK_UNINTERRUPTIBLE状态的进程分为很多种类型,其每个进程对应一 种特定事件。在这种情况下,进程的状态信息是不能提供足够的信息去快速的检索所需进程,因此有必要介绍一些其他的链表组织结构。比如等待队列。
- 等待队列:
在内核里面,等待队列是有很多用处的,尤其是在中断处理、进程同步、定时等场合。我们主要讨论其在进程同步中的应用。
有时候,一个进程可能要等待一些事件的发生,如磁盘操作结束、一些系统资源的释放等等。个人理理解:等待队列就是暂时存放等待某些事件发生的进程的集合。如果一个进程要等待一个事件发生,那么该进程便将自身放入相应的等待队列中进入睡眠,而放弃控制权,直到等待事件发生后才会被内核唤醒。 - 等待队列的结构:
- 等待队列是以双循环链表的形式实现的,而且队列中的成分(等待队列项)包含了指向进程描述符task的指针。
- 等待队列头:include/linux/wait.h
每一个等待队列是有一个等待队列头,等待队列头是一个wait_queue_head_t的数据结构:
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
成员说明:
lock:因为等待队列是不允许多个进程同时进行访问的,以防产生不可预料的结果,因此在此结构中定义了"自旋锁"以实现访问间的同步。
task_list:用于实现双向链表形式。
struct list_head {
struct list_head *next, *prev;
}; - 等待队列项:include/linux/wait.h
struct __wait_queue {
unsigned int flags;
struct task_struct *task;(2.6.25.5中是void *private;)
wait_queue_func_t func;
struct list_head task_list;
};
typedef struct __wait_queue wait_queue_t;等待队列中的每一个成分:等待队列项,代表着一个正在等待特定事件发生的睡眠进程。
成员解释:
task:存放着睡眠进程状态描述符的地址;
task_list:用于将进程链接进等待相同事件发生的进程链表中(等待队列)。
flag:
互斥进程(exclusive processes)和非互斥进程:我们来考虑一下,如果等待的事件发生了、变为真的了,那么是不是要唤醒等待该事件的所有进程(某个等待队列中)呢?
总是唤醒所有等待该事件的进程并不一定是合适的。比如考虑这样一种情况:如果队列中的多个进程等待的资源是要互斥访问的,一定时间内只允许一个进程去访问的话,这时候,只需要唤醒一个进程就可以了,其他进程继续睡眠。如果唤醒所有的进程,最终也只有一个进程获得该资源,其他进程让需返回睡眠。因此,等待队列中的睡眠进程可被划分为互斥、非互斥进程。
互斥进程:等待的资源是互斥访问的;互斥进程由内核有选择的唤醒,等待队列项的flag字段为1;
非互斥进程:等待的资源是可多进程同时访问的。非互斥进程在事件发生时,总是被内核唤醒,等待队列元素的flag字段为0。
func:
指定等待队列中的睡眠进程如何被唤醒。
- 等待队列的创建:include/linux/wait.h
DECLARE_WAITQUEUE()
init_waitqueue_head()
可以用DECLARE_WAIT_QUEUE_HEAD(name)宏定义一个新的等待队列,该宏静态地声明和初始化名为name的等待队列头变量。 init_waitqueue_head()函数用于初始化已动态分配的wait queue head变量。
等待队列可以通过 DECLARE_WAITQUEUE()静态创建,也可以用 init_waitqueue_head()动态创建。进程把自己放入等待队列中并设置成不可执行状态。 例:
The init_waitqueue_entry(q, p) function initializes a wait_queue_t structure q as follows:q->flags = 0;
q->task = p;
q->func = default_wake_function;
非互斥进程p(flags = 0)将被default_wake_function函数唤醒,而default_wake_function唤醒函数是try_to_wake_up( )的包装而已。DEFINE_WAIT:
可以用宏DEFINE_WAIT声明一个新的wait_queue_t变量(等待队列项),并且对其进行初始化:
#define DEFINE_WAIT(name) \
wait_queue_t name = { \
.private = current, \
.func = autoremove_wake_function, \
.task_list = LIST_HEAD_INIT((name).task_list), \
} - 等待队列的添加和删除:
- add_wait_queue( ):kernel/wait.c
add_wait_queue_exclusive( )
add_wait_queue()函数把一个非互斥进程插入等待队列链表的第一个位置;
在wait.c中:
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;wait->flags &= ~WQ_FLAG_EXCLUSIVE;
spin_lock_irqsave(&q->lock, flags);
__add_wait_queue(q, wait);
spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(add_wait_queue);
内嵌内核函数__add_wait_queue(),并且使用了所机制对该操作进行互斥保护。
add_wait_queue_exclusive( )函数把一个互斥进程插入等待队列链表的最后一个位置; - remove_wait_queue( ):
remove_wait_queue( )函数从等待队列链表中删除一个进程; - waitqueue_active( ):
waitqueue_active( )函数检查一个给定的等待队列是否为空。
- add_wait_queue( ):kernel/wait.c
- 等待队列的使用:睡眠和唤醒:/kernel/sched.c
该组函数使用任务管理中公用形式的等待队列。
希望等待一个特定事件的进程能调用下列函数中的任一个:- 睡眠操作:思想是更改当前进程(CURRENT)的任务状态,并要求重新调度,因为这时这个进程的状态已经改变,不再在调度表的就绪队列中,因此无法再获得执行机会,进入"睡眠"状态,直至被"唤醒"(wake_up()),即其任务状态重新被修改回就绪态。
常用的睡眠操作有interruptible_sleep_on和sleep_on,两个函数类似,是把调用进程加入到特定的等待队列中,只不过前者将进程的状态从就绪态 (TASK_RUNNING)设置为TASK_INTERRUPTIBLE,允许通过发送signal唤醒它(即可中断的睡眠状态);而后者将进程的状态 设置为TASK_UNINTERRUPTIBLE,在这种状态下,不接收任何singal。在当前进程上操作的sleep_on()函数:
void sleep_on(wait_queue_head_t *wq)
{
wait_queue_t wait;
/* 构造当前进程对应的等待队列项 */
init_waitqueue_entry(&wait, current); //wait.h中
/* 将当前进程的状态从TASK_RUNNING改为TASK_UNINTERRUPTIBLE */
current->state = TASK_UNINTERRUPTIBLE;
/* 将等待队列项添加到指定链表中 */
wq_write_lock_irqsave(&q->lock,flags);
__add_wait_queue(q, &wait);
wq_write_unlock(&q->lock);/* 进程重新调度,放弃执行权 */
schedule( );/* 本进程被唤醒,重新获得执行权,首要之事是将等待队列项从链表中删除 */
wq_write_lock_irq(&q->lock);
__remove_wait_queue(q, &wait);
wq_write_unlock_irqrestore(&q->lock,flags);
/* 至此,等待过程结束,本进程可以正常执行下面的逻辑 */
}
该函数把当前进程的状态设置为TASK_UNINTERRUPTIBLE,并把它插入到特定的等待队列。然后,它调用调度程序,而调度程序重新开始另一个进程的执行。当睡眠进程被唤醒时,调度程序重新开始执行sleep_on()函数,把该进程队列中删除。 - interruptible_sleep_on():
interruptible_sleep_on()与sleep_on()函数基本上是一样的,但是interruptible_sleep_on()把 当前进程的状态设置为TASK_INTERRUPTIBLE而不是TASK_UNINTERRUPTIBLE,因此,接受一个信号就可以唤醒当前进程; - sleep_on_timeout()interruptible_sleep_on_timeout()于上述两个函数类似,只是他们还允许调用者定义一个时间间隔使得进程被内核唤醒;但是,在这两个函数中调用的是schedule_timeout()来代替schedule()。
- prepare_to_wait()、prepare_to_wait_exclusive()、finish_wait():在wait.c中:
是在linux2.6中介绍的,将当前进程放入等待队列的另一种方式。作用同sleep_on()。 - 对应的唤醒操作包括wake_up_interruptible和wake_up。wake_up函数不仅可以唤醒状态为 TASK_UNINTERRUPTIBLE的进程,而且可以唤醒状态为TASK_INTERRUPTIBLE的进程。 wake_up_interruptible只负责唤醒状态为TASK_INTERRUPTIBLE的进程。这两个宏的定义如下:
#define wake_up(x) __wake_up((x),TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1)
#define wake_up_interruptible(x) __wake_up((x),TASK_INTERRUPTIBLE, 1)
__wake_up函数主要是获取队列操作的锁,具体工作是调用__wake_up_common完成的。
void __wake_up(wait_queue_head_t *q, unsigned int mode, int nr)
{
if (q) {
unsigned long flags;
wq_read_lock_irqsave(&q->lock, flags);
__wake_up_common(q, mode, nr, 0);
wq_read_unlock_irqrestore(&q->lock, flags);
}
}
参数q表示要操作的等待队列,mode表示要唤醒任务的状态,如TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBLE等。nr_exclusive是要唤醒的互斥进程数目,在这之前遇到的非互斥进程将被无条件唤醒。sync表示???
{
struct list_head *tmp;
struct task_struct *p;
CHECK_MAGIC_WQHEAD(q);
WQ_CHECK_LIST_HEAD(&q->task_list);
/* 遍历等待队列 */
list_for_each(tmp,&q->task_list) {
unsigned int state;
/* 获得当前等待队列项 */
wait_queue_t *curr = list_entry(tmp, wait_queue_t, task_list);
CHECK_MAGIC(curr->__magic);
/* 获得对应的进程 */
p = curr->task;
state = p->state;
/* 如果我们需要处理这种状态的进程 */
if (state & mode) {
WQ_NOTE_WAKER(curr);
if (try_to_wake_up(p, sync) && (curr->flags&WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
break;
}
}
}其他的还有wake_up_nr, wake_up_all, wake_up_interruptible_nr, wake_up_interruptible_all, wake_up_interruptible_sync, wake_up_locked.
- 睡眠操作:思想是更改当前进程(CURRENT)的任务状态,并要求重新调度,因为这时这个进程的状态已经改变,不再在调度表的就绪队列中,因此无法再获得执行机会,进入"睡眠"状态,直至被"唤醒"(wake_up()),即其任务状态重新被修改回就绪态。
参考文章:
http://hi.baidu.com/abigbigman/blog/item/a0a1fb54f2faa85cd009065f.html
http://linux.chinaunix.net/techdoc/system/2008/03/08/982296.shtml
linux中的等待队列的更多相关文章
- Linux源码-等待队列注释
等待队列 Linux中了等待队列的毒,代码中充斥着等待队列.不信你翻翻代码. 等待队列的唤醒我们这里叫激活.免得和线程唤醒混淆. 数据结构 头结点wait_queue_head_t的结构 struct ...
- Linux中等待队列的实现
1. 等待队列数据结构 等待队列由双向链表实现,其元素包括指向进程描述符的指针.每个等待队列都有一个等待队列头(wait queue head),等待队列头是一个类型为wait_quequ ...
- linux中的阻塞机制及等待队列
阻塞与非阻塞是设备访问的两种方式.驱动程序需要提供阻塞(等待队列,中断)和非阻塞方式(轮询,异步通知)访问设备.在写阻塞与非阻塞的驱动程序时,经常用到等待队列. 一.阻塞与非阻塞 阻塞调用是没有获得资 ...
- linux中的阻塞机制及等待队列【转】
转自:http://www.cnblogs.com/gdk-0078/p/5172941.html 阻塞与非阻塞是设备访问的两种方式.驱动程序需要提供阻塞(等待队列,中断)和非阻塞方式(轮询,异步通知 ...
- Linux中信号量处理
参考文章: http://blog.csdn.net/qinxiongxu/article/details/7830537/ 信号量一. 什么是信号量信号量的使用主要是用来保护共享资源,使得资源在一个 ...
- 查看linux中的TCP连接数【转】
转自:http://blog.csdn.net/he_jian1/article/details/40787269 查看linux中的TCP连接数 本文章已收录于: 计算机网络知识库 分类: ...
- 深入理解Java AIO(三)—— Linux中的AIO实现
我们调用的Java AIO底层也是要调用OS的AIO实现,而OS主要也就Windows和Linux这两大类,当然还有Solaris和mac这些小众的. 在 Windows 操作系统中,提供了一个叫做 ...
- linux中c多线程同步方法
https://blog.csdn.net/jkx01whg/article/details/78119189 Linux下提供了多种方式来处理线程同步,最常用的是互斥锁.条件变量和信号量. 一.互斥 ...
- Linux Wait Queue 等待队列
一.引言 linux 内核的等待队列和进程调度息息相关,进程在某些情况下必须等待某些事件的发生,例如:等待一个磁盘操作的终止,等待释放系统资源,或等待指定的时间间隔. 等待队列实现了在事件上的条件等待 ...
随机推荐
- bzoj 3732 Network(最短路+倍增 | LCT)
[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=3732 [题意] 给定一个无向图,处理若干询问:uv路径上最长的边最小是多少? [思路一 ...
- C++ 我想这样用(二)
话接上篇,从纯C环境转C++环境需要注意些什么呢? 没错,虽然C++曾号称兼容C,而且很多人甚至觉得C就是C++子集,但是c脑残粉一定知道,两者有很大的不同! 下面这些要点是比较突出的,后期我再补充其 ...
- Fast Intro To Java Programming (2)
Java局部变量 局部变量声明在方法.构造方法或者语句块中: 局部变量在方法.构造方法.或者语句块被执行的时候创建,当它们执行完成后,变量将会被销毁: 访问修饰符不能用于局部变量: 局部变量只在声明它 ...
- SQL入门
# SQL入门 数据库表 一个数据库(database)通常包含一个或多个表(table). 每一个表都有一个名字标识. 表单包含数据的记录(行). 一些重要的SQL命令(常用的吧) 命令 说明 ...
- 第三百零四天 how can I 坚持
我以为我遇到了,却是痴心妄想啊.哪有那么好的事.其实也无所谓,淡定,却又有点不淡定了. 洗澡睡觉吧,明天还要上班呢. 应该摆脱这种状态. 什么都不想,放空.
- maven 控制台乱码
在pom.xml加一条配置 <project> …… <properties> <argLine>-Dfile.encoding=UTF-8</argLine ...
- 【转】UIBezierPath精讲
http://www.henishuo.com/uibezierpath-draw/ 基础知识 使用UIBezierPath可以创建基于矢量的路径,此类是Core Graphics框架关于路径的封装. ...
- DB2日期和时间函数汇总
上一篇提到过在DB2中,可以通过SYSIBM.SYSDUMMY1.SYSIBM.DUAL获取寄存器中的值,也可以通过VALUES关键字获取寄存器中的值.则在这篇中,我们直接用VALUES关键字来看看这 ...
- poj1459
初涉网络流.改日再写一些概念性的介绍. ek算法可作为模板使用. #include <iostream> #include <queue> using namespace st ...
- c# 实现IComparable、IComparer接口、Comparer类的详解
在默认情况下,对象的Equals(object o)方法(基类Object提供),是比较两个对象变量是否引用同一对象.我们要必须我自己的对象,必须自己定义对象比较方式.IComparable和ICom ...