1.阻塞

阻塞操作,是在执行设备操作时,如果不能获得资源,就挂起进程。直到资源能够获得,再对设备进行访问。

比如read系统调用,默认是阻塞操作

Char buf;

Int fd = open(“/dev/ttyS1”,O_RDWR);

Res = read(fd,&buf,1);

If(res == 1)

Printf(“%c\n”,buf);

如果要改为非阻塞操作,则在open时设定非阻塞的标志位:

Int fd = open(“/dev/ttyS1”,O_RDWR | O_NONBLOCK);

While(read(fd,&buf,1)==1)

{

printf(“%c”,buf); 

}

 

2. 等待队列

在阻塞操作的实现代码中,需要使用等待队列(wait queue)来挂起,和唤醒自身进程。

先介绍下内核等待队列的机制:

173537780.jpg

Linux的阻塞的进程会形成一个队列,本质是一个双向链表。

 

等待队列头:

struct __wait_queue_head {

spinlock_t lock; //等待队列一次只能容许一个进程访问

struct list_head task_list;

};

typedef struct __wait_queue_head wait_queue_head_t;

__init_waitqueue_head(wait_queue_head_t *q, struct lock_class_key *key)

初始化等待队列头,包括

1)初始化spinlock_t lock,设定为unlock状态。

2)因为是双向循环链表,将prevnext指向自身头节点。

list->next = list;

list->prev = list;

 

等待队列

linux中等待队列的实现思想:

DECLARE_WAITQUEUE(name, tsk)初始化一个等待队列。一般的用法是

DECLARE_WAITQUEUE(wait, current);

1)等待队列名称为wait, 

)等待队列中的任务指针private指针指向当前的进程。

等待队列的结构:

struct __wait_queue {

unsigned int flags;

#define WQ_FLAG_EXCLUSIVE0x01

void *private; //指向task进程控制块的指针

wait_queue_func_t func;//函数指针,用来指示该进程何时被唤醒

struct list_head task_list; //等待队列头

};

typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int flags, void *key);

#define __WAITQUEUE_INITIALIZER(name, tsk) {\

.private= tsk,\

.func= default_wake_function,\

.task_list= { NULL, NULL } }

进程控制块:

struct task_struct {

volatile long state;/* -1 unrunnable, 0 runnable, >0 stopped */

void *stack;

atomic_t usage;

unsigned int flags;/* per process flags, defined below */

unsigned int ptrace;

int lock_depth;/* BKL lock depth */

int prio, static_prio, normal_prio; //优先级

pid_t pid;

pid_t tgid;

}

add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)

将等待队列wait添加到等待队列头所属的等待队列链表中去

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);

}

1)等待队列头的解锁和恢复锁

2)将等待队列添加到等待队列头所属于的链表中

等待事件,等待某个条件满足被唤醒

#definewait_event_interruptible(wq, condition)\

({\

int __ret = 0;\

if (!(condition))\

__wait_event_interruptible(wq, condition, __ret);\

__ret;\

})

#define __wait_event_interruptible_timeout(wq, condition, ret)\

do {\

DEFINE_WAIT(__wait);\

\

for (;;) {\

prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);//改变当前的进程状态为TASK_INTERRUPTIBLE

if (condition)//如果条件满足,退出等待\

break;\

if (!signal_pending(current)) {\

ret = schedule_timeout(ret);//如果超时,退出等待\

if (!ret)\

break;\

continue;\

}\

ret = -ERESTARTSYS;\

break;\

}\

finish_wait(&wq, &__wait);//设置进程状态为TASK——RUNNING __set_current_state(TASK_RUNNING);\

} while (0)

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);

if (list_empty(&wait->task_list))

__add_wait_queue(q, wait);

set_current_state(state);

spin_unlock_irqrestore(&q->lock, flags);

}

signal_pending(struct task_struct *p)检查当前的进程是否有信号处理。返回不为0表示有信号需要处理。

static inline int signal_pending(struct task_struct *p)

{

return unlikely(test_tsk_thread_flag(p,TIF_SIGPENDING));

}

wake_up_interruptible(x)

唤醒所有该等待队列头中所有等待队列中对应的进程

void __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);

}

本质上对等待队列的操作,就是要对进程的状态进行切换。如果不能获取某个资源,就将状态设定为睡眠态(INTERRUTIBLE或是TASK_UNINTERRUPTIBLE),然后进行调度,直到等到资源满足,再将进程设定为运行态,继续当前的进程操作。

采用双向链表来组织睡眠的进程,可以方便在唤醒所有的进程,在条件满足的时候。