这篇内容可能会较多,慢慢来欣赏
这是以前分析的
http://blog.csdn.net/leesagacious/article/details/50493939
这是github上模拟linux kernel的等待队列,暂未实现
https://github.com/leesagacious/wait_queue
这个是在各个子系统中的具体实现
http://blog.csdn.net/leesagacious/article/details/51627608
进程需要休眠,是为了等待某些资源可用或者某些事件发生
比如 设备初始化完成、I/O操作完成、定时器到时、进程同步、中断到来等
linux kernel 使用 等待队列来将 process 与 它等待的资源、事件关联在一起.
不是常说吗,当你的能力还驾驭不了你的目标时,就应该沉下心来,历练………哈哈, process 也是这样,需要资源或者事件,那就在等待队列上休息吧
那么,问题来了,多个process在等待同一个事件发生、资源满足.如果满足了,唤醒是怎么一个场景?
下面有分析
一 :显然,队列上的每个Node表示一个处于睡眠状态的进程, 那么,是用什么来表示一个处于睡眠状态的进程?
是下面这个struct,
/*
等待队列上的一个元素,即 表示一个睡眠的进程
睡眠是暂时的,睡眠的时候还持有一个被唤醒的回调函数 func
不同睡眠状态的进程都可以在同一个队列上,这个与中断处理函数设计的不同,
在Interrupt中,不是共享中断且触发类型不同的isr,是不会让给你挂接到isr链表上的
中断限制的更严格点
*/
struct __wait_queue {
/*
这个flags很重要,涉及到一个被<<深入Linux内核架构>>称之为“惊群效应”的问题
下面会有分析
*/
unsigned int flags;
/*
0x01 很重要 !
*/
#define WQ_FLAG_EXCLUSIVE 0x01
/*
在初始化等待队列元素的时候 ,会指向睡眠进程的task_struct.
看!没有等待队列元素,别想挂入等待队列,这里的等待队列元素就
相当于要睡眠进程的一张单人床.
*/
void *private;
/*
自然是唤醒函数了,这个唤醒函数可以由用户自己指定,也可以使用系统默认的唤醒函数
这也是在初始化等待队列时,通过不同的函数来指定的.
那么,问了,如果是用户自定义的唤醒函数,被唤醒是一个什么样的流程和场景,别急,
下面有分析
*/
wait_queue_func_t func;
/*
等待队列也是一个普通的内核链表,双向循环链表
*/
struct list_head task_list;
};
include/linux/wait.h
唤醒的过程
简单的画了一个小流程框图,帮你理解
代码流程欣赏 :
/*
@x
等待队列头,你要唤醒的Process所在的等待队列的队列头
@TASK_NORMAL
#define TASK_NORMAL (TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE)
看上面的宏,可以知道 wake_up 能唤醒这两种状态的睡眠进程
比 TASK_ALL 少了两个
@nr_exclusive
唤醒几个标志位为WQ_FLAG_EXCLUSIVE的Process
这里是 1 ,只唤醒一个,即 找到了,就停止遍历,就brea了
如果你要唤醒多个,可以使用另外一个函数,wake_up_nr(),它指定了唤醒的个数
@key
如果找到了匹配的Process,那么就会回调它持有的唤醒函数,这个key是传递给唤醒函数的参数
看到了把,wake_up() 的参数就表明了它要干什么、它能干什么了
好,唤醒的条件都给你了,下面就是看__wake_up()怎么表演了,
下面关注一下 这些参数在底层函数的活动
*/
#define wake_up(x) __wake_up(x,TASK_NORMAL,1,NULL)
void __wake_up(wait_queue_head_t *q, unsigned int mode,int nr_exclusive, void *key)
{
/**
一看到这个未初始化的flags 下面一定要spin_lock_irqsave、spin_lock_irqrestore了
*/
unsigned long flags;
/*
锁 : 防止来自其他处理器的并发访问
禁用中断: 防止来自中断处理程序的并发访问
内核抢占呢?
加自旋锁了,就禁止内核抢占了,自旋锁期间,你还想发生内核抢占!!
为什么? 看下面
#define spin_lock_irqsave(lock, flags)
do {
raw_spin_lock_irqsave(spinlock_check(lock), flags);
} while (0)
↓
#define raw_spin_lock_irqsave(lock, flags)
do {
typecheck(unsigned long, flags);
_raw_spin_lock_irqsave(lock, flags);
} while (0)
↓
unsigned long __lockfunc _raw_spin_lock_irqsave(raw_spinlock_t *lock)
{
return __raw_spin_lock_irqsave(lock);
}
↓
static inline unsigned long __raw_spin_lock_irqsave(raw_spinlock_t *lock)
{
...
preempt_disable();
}
↓
#define preempt_disable()
do {
inc_preempt_count();
barrier();
} while (0)
↓ 接近真相了
#define inc_preempt_count() add_preempt_count(1)
↓
#define add_preempt_count(val) do { preempt_count() += (val); } while (0)
↓
#define preempt_count() (current_thread_info()->preempt_count)
隐藏的好深呀,这次我拿出来让它透透气、见见光,哈哈!
具体是 + 1 的操作.解锁的时候 -1,这里不再赘述了
看到了把,preempt_count计数器的值,决定了允不允许抢占,
这个preempt 是一个 int 类型,下面会分析它的具体位置代表的含义
我只想说,仅仅这个preempt的值这一点,可以一叶知秋看出调度可真够精彩的!! 还不止,还有调度类、周期性调度任务
cpu亲和力、虚拟运行时间、进程权重、唤醒抢占、优先级、cpu负载均衡.......... 一大波重量级的嘉宾正在赶来
哈哈!
*/
spin_lock_irqsave(&q->lock, flags);
/**
受保护的这个函数,它就是遍历上图的那个链表了
它执行的那个唤醒函数 将会是我们分析的重点
因为,里面包含了新唤醒的这个进程能否抢占到当前运行的进程的处理
*/
__wake_up_common(q, mode, nr_exclusive, 0, key);
spin_unlock_irqrestore(&q->lock, flags);
}
/**
这段代码太撩人!! 作者是哪位大神??
*/
static int
try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
{
unsigned long flags;
int cpu, success = 0;
}