一. 阻塞操作

阻塞操作是指在执行折本操作时,若不能获得自愿,则挂起进程直到满足可操作性的条件后在进行操作。被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。

假设recvfrom函数是一个系统调用:
在这里插入图片描述
阻塞不是低效率,如果设备驱动不阻塞,用户想获取设备资源只能不断查询,消耗CPU资源,阻塞访问时,不能获取资源的进程将进入休眠,将CPU资源让给其他资源。
因为阻塞的进程会进入休眠状态,因此,必须确保有一个地方能够唤醒休眠的进程。最大可能发生在中断函数里。

二. 等待队列

在Linux驱动程序中,可以使用等待队列(wait queue)来实现阻塞进程的唤醒。
Linux内核的等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制,也可以用来同步对系统资源的访问。
在这里插入图片描述
等待队列有两种数据结构:等待队列头 (wait_queue_head_t)和等待队列项(wait_queue_t)。
等待队列头和等待队列项中都包含一个list_head类型的域作为”连接件”。它通过一个双链表和把等待task的头,和等待的进程列表链接起来。

struct __wait_queue_head {
      spinlock_t lock;                    /* 保护等待队列的原子锁 (自旋锁),在对task_list与操作的过程中,使用该锁实现对等待队列的互斥访问*/
      struct list_head task_list;          /* 等待队列,双向循环链表,存放等待的进程 */
};

struct __wait_queue {
	 unsigned int flags;	/* 其中flags域指明该等待的进程是互斥进程还是非互斥进程。其中0是非互斥进程,WQ_FLAG_EXCLUSIVE(0×01)是互斥进程 */
	 void *private;        /* 通常指向当前任务控制块 */
	 wait_queue_func_t func;             
	 struct list_head task_list;   /* 挂入wait_queue_head的挂载点 */
};
typedef struct __wait_queue wait_queue_t;

等待队列的使用分为以下两部分:
(1)为使当前进程在一个等待队列中睡眠,需要调用wait_event(或某个等价函数),此后,进程进入睡眠,将控制权交给调度器。
(2)相对应的,是当数据到达后,必须调用wake_up函数(或某个等价函数)来唤醒等待队列中睡眠的进程

  1. 定义等待队列头
    wait_queue_head_t my_queue;  
  1. 初始化等待队列头
	init_waitqueue_head(&my_queue);
	-->__init_waitqueue_head((q), #q, &__key)
		--> INIT_LIST_HEAD(&q->task_list);
			--> static inline void INIT_LIST_HEAD(struct list_head *list)	//将双循环链表的指针指向队列头部
				{
					list->next = list;
					list->prev = list;
				}

定义并初始化等待队列头

DECLARE_WAIT_QUEUE_HEAD(name)
-->wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALZER(name)
	-->#define __WAIT_QUEUE_HEAD_INITIALZER(name){
		...
		.task_list = {&(name).task_list, &(name).task_list} ==> INIT_LIST_HEAD(struct list_head *list)
		}
  1. 定义等待队列
    静态定义:
DECLARE_WAITQUEUE(name, tsk)
-->#define DECLARE_WAITQUEUE(name, tsk)
	wait_queue_t name = __WAITQUEUE_INITIALZER(name, tsk)
	-->#define __WAITQUEUE_INITIALZER(name, tsk){
		.private 	= current,
		.func 		= default_wake_function==> try_to_wake_up
		.task_list	= {NULL, NULL}
	}

静态定义并初始化等待队列(即2+3):

DEFINE_WAIT(name)

动态声明和初始化一个等待队列

struct wait_queue_t myqueue;
init_waitqueue_entry(&myqueue, tsk);
-->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;
	}
  1. 添加/移除等待队列
//设置等待的进程为非互斥进程,并将其添加进等待队列头(q)的队头中。
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);
    -->list_add(&new->task_list, &head->task_list);
    		-->void __list_add(struct list_head *new, struct list_head *prev, struct list_head next)
    			{
    				next->prev = new;
    				new->next = next;
    				new->prev = prev;
    				prev->next = new;
    			}
    					
    spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(add_wait_queue);

//下面函数也和add_wait_queue()函数功能基本一样,只不过它是将等待的进程(wait)设置为互斥进程。
void add_wait_queue_exclusive(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_tail(q, wait);
	-->list_add_tail(&new->task_list, &head->task_list);
		-->__list_add(new, head->prv, head);

    spin_unlock_irqrestore(&q->lock, flags);
    }
    EXPORT_SYMBOL(add_wait_queue_exclusive);
//在等待的资源或事件满足时,进程被唤醒,使用该函数被从等待头中删除。
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
     unsigned long flags; 
     spin_lock_irqsave(&q->lock, flags);
     
     __remove_wait_queue(q, wait);
	-->list_del(&old->task_list);
		-->static inline void list_del(struct liset_head *entry)
			{
				__list_del(entry->prev, entry->next);
				-->static inline void __list_del(struct list_head *prev, struct list_head *next)
					{
						next->prev = prev;
						prev->next = next;
					}
					
				entry->next = LIST_POISON1; ==>0x00100100
				entry->prev = LIST_POISON2; ==>0x00200200
			}

     spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(remove_wait_queue);
  1. 等待事件
#define wait_event(wq, condition)                  
do {                                   
        if (condition)                         
        	break;                         
        
        __wait_event(wq, condition);               
        -->(void) __wait_event(wq, condition, TASK_UNINTERRUPTIBLE, 0, 0, schedule()) 
        	-->for(;;)
        		{
        			if(condition)
        				break;
        			prepare_to_wait_event(wait_queue_head_t *q, wait_queue_head_t *wait, int state); /* 主要是将进程状态改变为state */
        			cmd; /* 执行schedule()==>进程调度 */
        		}   
} while (0)

wait_event(queue,condition);等待以queue为等待队列头等待队列被唤醒,condition必须满足,否则阻塞  
wait_event_interruptible(queue,condition);可被信号打断  
wait_event_timeout(queue,condition,timeout);阻塞等待的超时时间,时间到了,不论condition是否满足,都要返回  
wait_event_interruptible_timeout(queue,condition,timeout)
  1. 唤醒队列
/*
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);
    -->try_to_wake_up()	/*  最后调用到这个函数唤醒进程 */
    
    spin_unlock_irqrestore(&q->lock, flags);
}
    EXPORT_SYMBOL(__wake_up);
//唤醒等待队列.可唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERUPTIBLE状态的进程,和wait_event/wait_event_timeout成对使用

#define wake_up_interruptible(x)    __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
//和wake_up()唯一的区别是它只能唤醒TASK_INTERRUPTIBLE状态的进程.,与wait_event_interruptible
wait_event_interruptible_timeout
wait_event_interruptible_exclusive成对使用

#define wake_up_all(x)          __wake_up(x, TASK_NORMAL, 0, NULL) 
#define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)
#define wake_up_interruptible_all(x)    __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)
//这些也基本都和wake_up/wake_up_interruptible一样

wait_event()宏:

在等待会列中睡眠直到condition为真。在等待的期间,进程会被置为TASK_UNINTERRUPTIBLE进入睡眠,直到condition变量变为真。每次进程被唤醒的时候都会检查condition的值.

wait_event_interruptible()函数:

和wait_event()的区别是调用该宏在等待的过程中当前进程会被设置为TASK_INTERRUPTIBLE状态.在每次被唤醒的时候,首先检查 condition是否为真,如果为真则返回,否则检查如果进程是被信号唤醒,会返回-ERESTARTSYS错误码.如果是condition为真,则 返回0.

wait_event_timeout()宏:

也与wait_event()类似.不过如果所给的睡眠时间为负数则立即返回.如果在睡眠期间被唤醒,且condition为真则返回剩余的睡眠时间,否则继续睡眠直到到达或超过给定的睡眠时间,然后返回0.

wait_event_interruptible_timeout()宏

与wait_event_timeout()类似,不过如果在睡眠期间被信号打断则返回ERESTARTSYS错误码.

wait_event_interruptible_exclusive()宏

同样和wait_event_interruptible()一样,不过该睡眠的进程是一个互斥进程.
7. 在等待队列上睡眠

void __sched sleep_on(wait_queue_head_t *q)
{  
	sleep_on_common(q, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}

static long __sched
sleep_on_common(wait_queue_head_t *q, int state, long timeout)
{
        unsigned long flags;
        wait_queue_t wait;
        init_waitqueue_entry(&wait, current);
        __set_current_state(state);
        spin_lock_irqsave(&q->lock, flags);
        __add_wait_queue(q, &wait);
        spin_unlock(&q->lock);
        timeout = schedule_timeout(timeout);
        spin_lock_irq(&q->lock);
        __remove_wait_queue(q, &wait);
        spin_unlock_irqrestore(&q->lock, flags); 
        return timeout;
}

该函数的作用是定义一个等待队列(wait),并将当前进程添加到等待队列中(wait),然后将当前进程的状态置为 TASK_UNINTERRUPTIBLE,并将等待队列(wait)添加到等待队列头(q)中。之后就被挂起直到资源可以获取,才被从等待队列头(q) 中唤醒,从等待队列头中移出。在被挂起等待资源期间,该进程不能被信号唤醒。
大多数设备驱动不调用,而是直接进行进程状态的改变和切换,如

static ssize_t xxx_write(struct file *file, const char *buffer, size_t count, loff_t *ppos)
{
	...
	DECLARE_WAITQUEUE(wait, current);//定义等待队列
	add_wait_queue(&xxx_wait, &wait);//添加等待队列

	ret = count;
	
	//等待设备缓冲区可写
	do{
		avail = device_writeable(...);
		if(avail < 0)
			__set_current_state(TASK_INTERRUPTIBLE);

		if(avail < 0)
		{
			if(file->f_flags & O_NONBLOCK) //非阻塞
			{
				if(!ret)
					ret = - EAGAIN;
				goto out;
			}
			schedule();//调度其他进程执行
			if(signal_pending(current))//如果是因为信号唤醒
			{
				if(!ret)
					ret = - ERESTARTSYS;
				goto out;
			}
		}
	}while(avail < 0);

	//写设备缓冲区
	device_write(...);
out:
	remove_wait_queue(&xxx_wait, &wait);//将等待队列移出等待队列头
	set_current_state(TASK_RUNNING);//设置进程状态为TASK_RUNNING
}
Logo

更多推荐