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)来挂起,和唤醒自身进程。
先介绍下内核等待队列的机制:
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)因为是双向循环链表,将prev和next指向自身头节点。
list->next = list;
list->prev = list;
等待队列
linux中等待队列的实现思想:
DECLARE_WAITQUEUE(name, tsk)初始化一个等待队列。一般的用法是
DECLARE_WAITQUEUE(wait, current);
(1)等待队列名称为wait,
(2 )等待队列中的任务指针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),然后进行调度,直到等到资源满足,再将进程设定为运行态,继续当前的进程操作。
采用双向链表来组织睡眠的进程,可以方便在唤醒所有的进程,在条件满足的时候。
转载于:https://blog.51cto.com/jackwang702/1213888