分类: LINUX
2008-08-05 10:32:07
等待队列操作分析:
在linux驱动程序中可以用等待队列(wiat queue)来实现阻塞的唤醒
(1)定义等待队列头
等待队列头结构体的定义:
struct __wait_queue_head {
spinlock_t lock; //自旋锁变量,用于在对等待队列头
//指向的等待队列链表进行操作时锁上,查看哪有对lock的操作?
struct list_head task_list; // 指向等待队列的list_head
};
typedef struct __wait_queue_head wait_queue_head_t;
#define DECLARE_WAIT_QUEUE_HEAD(name) \ \\声明一个待队列头对象 name:
wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
#define __WAIT_QUEUE_HEAD_INITIALIZER(name) { \\\待队列头的初始化:
.lock = __SPIN_LOCK_UNLOCKED(name.lock), \
.task_list = { &(name).task_list, &(name).task_list } }
将lock赋为unlocked,将等待队列头指向的等待队列链表指向name,从而将等待队列头和
等待队列连起来;
(2)等待队列中存放的是在执行设备操作时不能获得资源而挂起的进程
定义等待对列:
struct __wait_queue {
unsigned int flags; //prepare_to_wait()里有对flags的操作,查看以得出其含义
#define WQ_FLAG_EXCLUSIVE 0x01 //一个常数,在prepare_to_wait()用于修改flags的值
wait_queue_func_t func; //唤醒阻塞任务的函数
struct list_head task_list; // 阻塞任务链表
};
typedef struct __wait_queue wait_queue_t;
#define DECLARE_WAITQUEUE(name, tsk) \\\声明一个等待队列并初始化为name
wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
#define __WAITQUEUE_INITIALIZER(name, tsk) { \ \\等待对列初始化:
.private = tsk, \
.func = default_wake_function, \
.task_list = { NULL, NULL } }
下列两个函数用于对特定的成员进行赋值(当传入不同类型的参数时);
static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)
{
q->flags = 0;
q->private = p; //私有数据指针
q->func = default_wake_function; //使用默认的唤醒函数
}
static inline void init_waitqueue_func_entry(wait_queue_t *q,
wait_queue_func_t func)
{
q->flags = 0;
q->private = NULL;
q->func = func; // 自定义的唤醒函数
}
(3)对等待队列进行操作
static inline int waitqueue_active(wait_queue_head_t *q)
{
return !list_empty(&q->task_list);
}
判断等待对列头是否为空,当一个进程访问设备而得不到资源时就会被放入等待队列头指
向的等待队列中,当该它是第一个被阻塞的进程,(等待队列头是一开始就有的还
是有了第一个被阻塞的进程后才创建的?)若此时等待队列头还是空的,要先创建(见上面)
然后再插入新的等待队列 。
对等待队列的链表操作
static inline void __add_wait_queue(wait_queue_head_t *head,\
wait_queue_t *new) /
{
list_add(&new->task_list, &head->task_list);
}
/增加一个等待队列new到等待
队列头head指向的等待队列链表中;
static inline void __add_wait_queue_tail(wait_queue_head_t *head, //
wait_queue_t *new)
{
list_add_tail(&new->task_list, &head->task_list);
}
增加一个等待队列到表尾
static inline void __remove_wait_queue \
(wait_queue_head_t *head,wait_queue_t *old)
{
list_del(&old->task_list);
}
删除一个等待队列(4)等待事件当等待队列加入到链表中以后,就要等待特定的condition来 唤醒它;
#define __wait_event(wq, condition) \\\wq:在等待事件的等待队列;condition:等待的条件
do { \
DEFINE_WAIT(__wait); \定义并初始化一个wait_queue_t结构
\
for (;;) { \
prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); \
if (condition) \ //看wait_queue:wq要等的condition是否满足
break; \
schedule(); \//condition不成立,放弃cpu重新调度一个task
} \
finish_wait(&wq, &__wait); \
} while (0)
等待condition在成立,否则进程睡眠(TASK_UNINTERRUPTIBLE);condition满足
后等待结束,跳出循环(后面调用wake_up(x)进行唤醒)当任何能改变等待条件
的值的变量发生改变时,要调用wake_up();
wait_event(wq, condition) 在__wait_event()的基础上多了一次查询(每次被唤醒的时候)
#define __wait_event_timeout(wq, condition, ret) \
当condition满足或ret使用完了时进程被唤醒; 返回值为:return timeout < 0 ? 0 : timeout
timeout是一个jiffies类型的变量,当时间用完了,函数返回0,当等待的条件成立了,
timeout还未用完,则将最后的jiffies保留下来。类似的操作还有:
#define __wait_event_interruptible_timeout(wq, condition, ret)
\可中断,有超时时间的__wait_event(),当timeout长的时间完了后,函数返回0;当时间未完,
函数被信号中断则返回-ERESTARTSYS;如果timeout isn't out,保留jiffies最后的值; #define wait_event_interruptible_timeout(wq, condition, timeout) \
多了一次查询
#define __wait_event_interruptible_exclusive(wq, condition, ret) \ #define wait_event_interruptible_exclusive(wq, condition) \ 这几个函数都有用到prepare_to_wait()下面分析一下这个函数; prepare_to_wait() 函数
void fastcall
prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)
{
unsigned long flags;
wait->flags &= ~WQ_FLAG_EXCLUSIVE; //弄清楚这一行是什么意思;
spin_lock_irqsave(&q->lock, flags); //获得自旋锁并保存EFLAGS的值
if (list_empty(&wait->task_list)) //判断是等待队列是否为空:空时返回1;函数为:
//static inline int list_empty(const struct list_head *head)
__add_wait_queue(q, wait); //{ return head->next == head;}
/* 插入等待队列中(为何空时插入,非空时不行?)
* don't alter the task state if this is just going to
* queue an async wait queue callback
*/
if (is_sync_wait(wait))
set_current_state(state); //因为非阻塞进程访问不到设备时并不挂起,所以不改状态
//set_current_state()->set_mb()->mb() :强制顺序执行(更改状态)
//函数mb()内存栅头文件中定义的
spin_unlock_irqrestore(&q->lock, flags);//解锁将EFLAGS的值读回
}
prepare_to_wait()的作用是将等待队列插入等待队列链表中,并更改等待队列的状态为state;
inux将进程状态描述为如下五种:
TASK_RUNNING:可运行状态。处于该状态的进程可以被调度执行而成为当前进程。
TASK_INTERRUPTIBLE:可中断的睡眠状态。处于该状态的进程在所需资源有效时被唤醒,也可以通过信号或定时中断唤醒。
TASK_UNINTERRUPTIBLE:不可中断的睡眠状态。处于该状态的进程仅当所需资源有效时被唤醒。
TASK_ZOMBIE:僵尸状态。表示进程结束且已释放资源,但其task_struct仍未释放。
TASK_STOPPED:暂停状态。处于该状态的进程通过其他进程的信号才能被唤醒。
唤醒:
#define wake_up(x) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1, NULL)
void fastcall __wake_up(wait_queue_head_t *q, unsigned int mode,
int nr_exclusive, void *key)
{
unsigned long flags;
spin_lock_irqsave(&q->lock, flags);
__wake_up_common(q, mode, nr_exclusive, 0, key);
spin_unlock_irqrestore(&q->lock, flags);
}
Wake_up()唤醒等待队列中的进程,其参数含义:
q:等待队列;
mode:要唤醒的进程
nr_exclusive:要唤醒的进程数;
key:is directly passed to the wakeup function
niutao.linux2008-08-05 19:18:11
”prepare_to_wait()不是设置flag值,而是和sleep_on()功能一样,把当前进程加入等待队列“ 你说错了。prepare_to_wait的基本功能是把当前进程加入等待队列头中,并改变进程的状态。而sleep_on()是先设置进程的状态为TASK_UNINTERRUPTIBLE,在将进程添加进等待队列中,并进入睡眠,等到等待的资源可用时被唤醒并将其从等待队列中删除。 如此说来sleep_on()和wait_event()的功能到是有些相似。
kylinux2008-08-05 16:44:57
flag: 互斥进程(exclusive processes)和非互斥进程: 其中在__wake_up()有个参数nr,判断唤醒的进程数。 0:代表唤醒所有进程 1:代表唤醒一个 nr:当然还有其他 总是唤醒所有等待该事件的进程并不一定是合适的。比如考虑这样一种情况:如果队列中的多个进程等待的资源是要互斥访问的,一定时间内只允许一个进程去访问的话,这时候,只需要唤醒一个进程就可以了,其他进程继续睡眠。如果唤醒所有的进程,最终也只有一个进程获得该资源,其他进程让需返回睡眠。 因此,等待队列中的睡眠进程可被划分为互斥、非互斥进程。 互斥进程:等待的资源是互斥访问的;互斥进程由内核有选择的唤醒,等待队列项的flag字段为1; 非互斥进程:等待的资源是可多进程同时访问的。非互斥进程在事件发生时,总是被内核唤醒,等待队列元素的flag字段为0。 prepare_to_wait()不是设置flag值,而是和sleep_on()功能一样,把当前进程加入等待队列