等待队列操作分析:
在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