1 阻塞和非阻塞
阻塞调用:调用结果返回之前,当前线程被挂起,函数只有在得到结果之后才会返回
非阻塞调用:在不能立即得到结果之前,该函数不会阻塞当前进程,而会立即返回
2 等待队列
阻塞进程可以使用等待队列来实现
等待队列的基本数据结构是一个双向链表,存储睡眠的进程
2.1 等待队列的实现
等待队列定义:
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
(1)lock
功能:对task_list起保护作用
当要向task_list链表中加入或删除元素时,内核内部会锁定lock锁,当修改完成后,会释放lock锁
(2)task_list
双向链表,存放等待的进程
2.2 等待队列的作用
(1)定义和初始化等待队列头
定义:
struct wait_queue_head_t wait;
初始化定义:
#define DECLARE_WAIT_QUEUE_HEAD(name) \
wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
(2)定义等待队列
#define DECLARE_WAITQUEUE(name, tsk) \
wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
(3)添加和移除等待队列
// 将等待队列元素wait添加到等待队列头q所指向的等待队列链表中
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
// 将队列元素wait从等待队列头q所指向的等待队列链表中移除
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
(4)等待事件
#define wait_event(wq, condition)
#define wait_event_timeout(wq, condition, ret)
#define wait_event_interruptible(wq, condition, ret)
#define wait_event_interruptible_timeout(wq, condition, ret)
- wait_event:在等待队列中睡眠直到condition为真。等待期间,进程被置为TASK_UNINTERRUPTIBLE,进入睡眠,直到condition为真。
- wait_event_timeout:与wait_event类似,但如果睡眠时间为负数,则立即返回。如果在睡眠期间被唤醒,且condition为真,则返回剩余睡眠时间,否则继续睡眠直到到达给定时间,返回0。
- wait_event_interruptible:调用该宏在等待过程中当前进程会被设置成TASK_INTERRUPTIBLE。每次被唤醒时,查询condition是否为真,如果为真返回0,否则如果是被信号唤醒,返回-ERESTARTSYS。
- wait_event_interruptible_timeout:与wait_event_interruptible类似,如果在睡眠时被信号打断,则返回-ERESTARTSYS。
(5)唤醒等待队列
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
- wake_up:可唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE状态的进程,与wait_event、wait_event_timeout成对使用。
- wake_up_interruptible:只能唤醒TASK_INTERRUPTIBLE状态的进程。可以唤醒wait_event_interruptible、wait_event_interruptible_timeout睡眠的进程。
3 同步机制实验
3.1 同步机制设计
进程同步机制设计首先需要一个等待队列,所有等待一个事件完成的进程都挂在这个等待队列中:
struct CustomEvent{
int eventNum; // 事件号
wait_queue_head_t *p; // 系统等待队列首指针
struct CustomEvent *next; // 队列链指针
};
为了实现实验的意图,设计两个指针分别表示事件头部和尾部:
CustomEvent *lpevent_head = NULL; // 链头指针
CustomEvent *lpevent_end = NULL; // 链尾指针
每个事件由一个链表组成,每个链表中包含了等待这个事件的等待队列:
FindEventNum()从一个事件链表中找到某一个事件对应的链表:
// 参数1:事件序号 参数2:返回事件的前一个事件
CustomEvent *FindEventNum(int eventNum, CustomEvent **prev)
{
CustomEvent *tmp = lpevent_head; // 事件链表的头部
*prev = NULL;
while(tmp) // 找到所要事件的结构体指针
{
if(tmp->eventNum == eventNum) { // tmp所指的事件号是否与eventNum相同
return tmp; // 找到返回
}
*prev = tmp;
tmp=tmp->next;
}
return NULL;
}
系统调用函数sys_CustomEvent_open():新分配一个事件,并返回新分配事件的事件号。
// 建立一个新的事件 eventNum:事件号
asmlinkage int sys_CustomEvent_open(int eventNum)
{
// 定义两个事件指针
CustomEvent *new;
CustomEvent *prev;
if(eventNum) { // 判断事件号是否为0
if(!FindEventNum(eventNum, &prev)) { // 根据事件号查找事件
return -1;
} else {
return eventNum;
}
} else { // 重新创建一个事件
new = (CustomEvent *)kmalloc(sizeof(CustomEvent), GFP_KERNEL); // 分配一个新事件
new—>p = (wait_queue_head_t *)kmalloc(sizeof(wait_queue_head_t), // 分配该事件的等待队列
GFP_KERNEL);
new->next = NULL;
new—>p->task_list.next = &new—>p—>task_list;
new—>p->task_list.prev = &new—>p—>task_list;
if(!lpevent_head) { // 如果没有事件链表头 将新分配的事件赋给事件链表头
new->eventNum = 2;
lpevent_head = lpevent_end = new;
return new->eventNum;
} else { // 已有事件链表头 将新分配的事件连接到链表中
new->eventNum = lpevent_end->eventNum + 2;
lpevent_end—>next = new;
1pevent_end = new;
}
return new->eventNum;
}
return 0;
}
将进程阻塞到一个事件的系统调用函数,直到等待事件被唤醒时,事件才退出。
// 实现等待队列系统调用
asmlinkage int sys_CustomEvent_wait(int eventNum)
{
// 定义两个事件指针
CustomEvent *tmp;
CustomEvent *prev = NULL;
if((tmp = FindEventNum(eventNum, &prev)) != NULL) { // 查找事件结构体
DEFINE_WAIT(wait); // 定义并初始化一个等待队列wait_queue_head
prepare_to_wait(tmp->p, &wait, TASK_INTERRUPTIBLE); // 将当前进程放入等待队列中
schedule(); // 重新调度
finish_wait(tmp->p, &wait); // 进程被唤醒 从阻塞队列中退出
return eventNum;
}
return -1;
}
唤醒等待特定事件函数:
// 唤醒等待事件
asmlinkage int sys_CustomEvent_signal(int eventNum)
{
// 定义两个事件指针
CustomEvent *tmp = NULL;
CustomEvent *prev = NULL;
if(!(tmp = FindEventNum(eventNum, &prev))) {
return 0; // 没有发现事件
}
wake_up(tmp->p); // 唤醒队列上所有进程
return 1;
}
关闭事件函数:先唤醒事件上的等待队列,然后清除事件占用空间
// 唤醒等待事件
asmlinkage int sys_CustomEvent_close(int eventNum)
{
// 定义两个事件指针
CustomEvent *prev= NULL;
CustomEvent *releaseItem= NULL;
if(releaseItem = tmp = FindEventNum(eventNum, &prev)) { // 找到关闭事件
if(releaseItem == lpevent_end) { // 链表中最后一个事件
lpevent_end = prev;
} else if(releaseItem == lpevent_head) { // 链表中第一个事件
lpevent_head = lpevent_head->next;
} else {
prev->next = releaseItem->next;
}
sys_CustomEvent_signal(eventNum); // 唤醒需要关闭的事件
if(releaseNum) {
kfree(releaseNum);
}
return releaseItem;
}
return 0;
}