免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1193 | 回复: 0
打印 上一主题 下一主题

全面解析Linux内核的同步与互斥机制--同步篇(2) [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-09-14 19:18 |只看该作者 |倒序浏览
3      等待事件event
Linux 内核中最简单的休眠方式是称为 wait_event的宏(及其变种),它实现了休眠和进程等待的条件的检查。形式如下:
wait_event(queue, condition)/*不可中断休眠,不推荐*/
wait_event_interruptible(queue, condition)/*推荐,返回非零值意味着休眠被中断,且驱动应返回 -ERESTARTSYS*/
wait_event_timeout(queue, condition, timeout)
wait_event_interruptible_timeout(queue, condition, timeout)
/*有限的时间的休眠;若超时,则不管条件为何值返回0,*/

上述四个宏函数为内核对外的接口,其他的休眠函数应避免使用。因为宏并不是函数,参数所做的任何修改对调用环境仍然有效,所以queue都是“值传递”,在宏内部会调用底层函数,采用的指针传递。Linux内核中存在大部分这样的宏,其都在接口上都是值传递。

3.1   wait_event
认真地看简单休眠中的 wait_event(queue, condition) 和 wait_event_interruptible(queue, condition) 底层源码会发现,其实他们只是手工休眠中的函数的组合。因此在驱动程序中应避免使用手动休眠代码,而应该采用内核已经封装好的四个wait_event系列函数。

\include\linux\wait.h
#define  __wait_event(wq,condition)                \
     do {                           \
       DEFINE_WAIT(__wait);                       \
                                    \
for(;;) {                      \
          prepare_to_wait(&wq,&__wait,TASK_UNINTERRUPTIBLE);   \
/// 添加到等待队列中,同时更改进程状态;若已经加入则不会重复添加  
        if (condition)                        \
           break;                          \
       schedule();   //何时返回呢???                       \
     }                               \
     finish_wait(&wq,&__wait);                    \
    } while (0)
// “__”表示内部函数,默认为condition不满足,添加至等待队列,调度
注意prepare_to_wait和finish_wait的匹配关系

#define  wait_event(wq,condition)                    \
do {                                   \
    if(condition)                              \
       break;                               \
   __wait_event(wq,condition);                    \
}while (0)   
//对外的接口函数,需要判断condition,若假则等待;若真则直接退出
  --------------------------------------------------------------------------------------------------------------
等待系列函数架构设计:

3.2   wait_event_timeout
#define __wait_event_timeout(wq, condition, ret)                     \
do {                                                               \
        DEFINE_WAIT(__wait);                                       \
                                                                      \
        for (;;) {                                                  \
               prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);   \
               if (condition)                                            \
                       break;                                             \
               ret = schedule_timeout(ret);                             \
               if (!ret)                                            \
                       break;              //延时到,退出                              \
        }                                                            \
        finish_wait(&wq, &__wait);                                \
} while (0)

#define  wait_event_timeout(wq,condition,timeout)      \
({        \
      long   __ret=timeout;     \
    if( !(condition) )             \
      __wait_event_timeout( wq,condition,__ret);     \
    __ret;     \
})


3.3   wait_event_interruptible
#define __wait_event_interruptible(wq, condition, ret)                      \
do {                                                                \
        DEFINE_WAIT(__wait);                                       \
                                                                      \
        for (;;) {                                                  \
                prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE); \
                if (condition)                                            \
                       break;                                             \
                if (!signal_pending(current)) {                          \
                       schedule();                                      \
                       continue;                                 \
                }                                                     \
                ret = -ERESTARTSYS;                                   \
                break;                                                     \
        }                                                            \
        finish_wait(&wq, &__wait);                                    \
} while (0)

/**
* wait_event_interruptible - sleep until a condition gets true
* @wq: the waitqueue to wait on
* @condition: a C expression for the event to wait for
*
* The process is put to sleep (TASK_INTERRUPTIBLE) until the
* @condition evaluates to true or a signal is received.
* The @condition is checked each time the waitqueue @wq is woken up.
*
* wake_up() has to be called after changing any variable that could
* change the result of the wait condition.
*
* The function will return -ERESTARTSYS if it was interrupted by a
* signal and 0 if @condition evaluated to true.
*/
#define wait_event_interruptible(wq, condition)                        \
({                                                                   \
        int __ret = 0;                                                   \
        if (!(condition))                                        \
                __wait_event_interruptible(wq, condition, __ret);      \
        __ret;                                                             \
})

3.4   wait_event_interruptible_timeout
#define   __wait_event_interruptible_timeout(wq,condition,ret)     \
do {                               \
       DEFINE_WAIT(__wait);                     \
                                   \
       for (;;) {                          \
           prepare_to_wait(&wq,&__wait,TASK_INTERRUPTIBLE);   \   
               if (condition)                   \
                   break;                     \
               if(!signal_pending(current)) {                 \
                   // 当前进程无信号需要处理
ret = schedule_timeout(ret);            \
                   if(!ret)                   \               
                      break; //时间片用完唤醒                     \
                   continue;                 \              .
               }                            \
               ret = _ERESTARTSYS;   //被信号唤醒                  \
               break;                            \
             }                            \
             finish_wait(&wq,&__wait);                   \
  } while (0)   

#define   wait_event_interruptible_timeout(wq,condition,timeout)    \
( {                                 \
    long__ret = timeout;                      \
    if(!(condition))                       \
        __wait_event_interruptible_timeout(wq,condition,__ret);  \
    __ret;                               \
})

    wait_event_interruptible_timeout()类架构:  

4      唤醒系列wake_up
4.1   wake_up 的API
惯例:用 wake_up 唤醒 wait_event;用 wake_up_interruptible 唤醒wait_event_interruptible。很少会需要调用wake_up_interruptible 之外的唤醒函数,但为完整起见,这里是整个集合:
wake_up(wait_queue_head_t *queue);
wake_up_interruptible(wait_queue_head_t *queue);
/*wake_up 唤醒队列中的每个非独占等待进程和一个独占等待进程。wake_up_interruptible 同样, 但它跳过处于不可中断休眠的进程。它们在返回之前, 使一个或多个进程被唤醒、被调度(如果它们被从一个原子上下文调用, 这就不会发生).*/
wake_up_nr(wait_queue_head_t *queue, int nr);
wake_up_interruptible_nr(wait_queue_head_t *queue, int nr);
/*这些函数类似 wake_up, 除了它们能够唤醒多达 nr 个独占等待者, 而不只是一个. 注意传递 0 被解释为请求所有的互斥等待者都被唤醒*/
wake_up_all(wait_queue_head_t *queue);
wake_up_interruptible_all(wait_queue_head_t *queue);
/*这种 wake_up 唤醒所有的进程, 不管它们是否进行独占等待(可中断的类型仍然跳过在做不可中断等待的进程)*/
wake_up_interruptible_sync(wait_queue_head_t *queue);
/*一个被唤醒的进程可能抢占当前进程, 并且在 wake_up 返回之前被调度到处理器。 但是, 如果你需要不要被调度出处理器时,可以使用 wake_up_interruptible 的"同步"变体. 这个函数最常用在调用者首先要完成剩下的少量工作,且不希望被调度出处理器时。*/

4.2   wake_up 的实现细节

\kernel \sched.c
/*
* The core wakeup function.  Non-exclusive wakeups (nr_exclusive == 0) just
* wake everything up.  If it's an exclusive wakeup (nr_exclusive == small +ve
* number) then we wake all the non-exclusive tasks and one exclusive task.
*
* There are circumstances in which we can try to wake a task which has already
* started to run but is not in state TASK_RUNNING.  try_to_wake_up() returns
* zero in this (rare) case, and we handle it by continuing to scan the queue.
*/
static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
                            int nr_exclusive, int sync, void *key)
{
        struct list_head *tmp, *next;

        list_for_each_safe(tmp, next, &q->task_list) {
                wait_queue_t *curr = list_entry(tmp, wait_queue_t, task_list);
                unsigned flags = curr->flags;

                if (curr->func(curr, mode, sync, key) &&
                               (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
                       break;
        }
}
// 循环结束的条件包括list_for_each_safe本身、排他性(flags & WQ_FLAG_EXCLUSIVE)以及唤醒非0个进程,!--nr_exclusive非常巧妙,当传入0时,!--nr_exclusive的结果总是0,if条件不可能成立,就无法break,即表示唤醒所有的进程。对于非WQ_FLAG_EXCLUSIVE进程,由于(flags & WQ_FLAG_EXCLUSIVE)为0后,就不计算!--nr_exclusive,因此这个过程可以唤醒所有的非WQ_FLAG_EXCLUSIVE进程。但遇到WQ_FLAG_EXCLUSEVE之后的任意进程无法唤醒。
最终哪个进程运行是由schedule决定的。

/**
* __wake_up - wake up threads blocked on a waitqueue.
* @q: the waitqueue
* @mode: which threads
* @nr_exclusive: how many wake-one or wake-many threads to wake up
* @key: is directly passed to the wakeup function
*/
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);
//通用的wakeup,不可重入的,需要__wake_up对之进行封装
        spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(__wake_up);
根据int nr_exclusive值唤醒对应的进程,只是更改了进程的状态,具体何时运行由schedule决定。
并没有将唤醒的进程从等待队列中删除,只有当schedule获得cpu时才从等待队列中删除。

\include\linux\wait.h
void FASTCALL(__wake_up(wait_queue_head_t *q, unsigned int mode, int nr, void *key));
extern void FASTCALL(__wake_up_locked(wait_queue_head_t *q, unsigned int mode));
extern void FASTCALL(__wake_up_sync(wait_queue_head_t *q, unsigned int mode, int nr));

#define wake_up(x)                 __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1, NULL)
#define wake_up_nr(x, nr)           __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, nr, NULL)
#define wake_up_all(x)                   __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 0, NULL)
#define wake_up_interruptible(x)     __wake_up(x, TASK_INTERRUPTIBLE, 1, 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)
各种wakeup通过宏定义的形式本质上就是一个函数__wake_up,但对外的接口不一样,这样对外的意义明确,相当于采用了默认参数,而不是在各个wakeup内部调用函数,省掉了函数调用的开销。在实现代码复用的同时保证了对外的明确接口,值得借鉴。

#define     wake_up_locked(x)          __wake_up_locked((x), TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE)
#define wake_up_interruptible_sync(x)   __wake_up_sync((x),TASK_INTERRUPTIBLE, 1)

5      独占等待和高级休眠
5.1   独占等待
当一个进程调用 wake_up 在等待队列上,所有的在这个队列上等待的进程被置为可运行的。 这在许多情况下是正确的做法。但有时,可能只有一个被唤醒的进程将成功获得需要的资源,而其余的将再次休眠。这时如果等待队列中的进程数目大,这可能严重降低系统性能。为此,内核开发者增加了一个“独占等待”选项。它与一个正常的睡眠有 2 个重要的不同:
²      当等待队列入口设置了 WQ_FLAG_EXCLUSEVE 标志,它被添加到等待队列的尾部;否则,添加到头部。因为唤醒一个WQ_FLAG_EXCLUSEVE标志的进程后就不再唤醒其他任意类型的进程。添加在尾部可以保证FIFO。
²      当 wake_up 被在一个等待队列上调用, 它在唤醒第一个有 WQ_FLAG_EXCLUSIVE 标志的进程后停止唤醒。但内核仍然会唤醒所有的非独占等待进程,因为所有的非WQ_FLAG_EXCLUSIVE进程在队头。

采用独占等待要满足以下条件:
²      希望对资源进行有效竞争;
²      当资源可用时,唤醒一个进程就足够来完全消耗资源;
²      所有使用该资源的进程都应采用统一的独占等待规则。

使一个进程进入独占等待,可调用:
void prepare_to_wait_exclusive(wait_queue_head_t *queue, wait_queue_t *wait, int state);

注意:无法使用通用的wait_event 和它的变体宏函数来进行独占等待。
此时需要使用休眠的高级特性,利用等待队列的prepare_to_wait_exclusive和finish_wait接口函数手动编写相关代码,

5.2   高级休眠的基本步骤:
(1)分配和初始化一个 wait_queue_t 结构, 随后将其添加到正确的等待队列。
(2)设置进程状态,标记为休眠。TASK_RUNNING 意思是进程能够运行。有 2 个状态指示一个进程是在睡眠: TASK_INTERRUPTIBLE 和 TASK_UNTINTERRUPTIBLE。2.6 内核的驱动代码通常不需要直接操作进程状态。但如果需要这样做使用的代码是:
void set_current_state(int new_state);
在老的代码中, 你常常见到如此的东西:current->state = TASK_INTERRUPTIBLE; 但是象这样直接改变 current 是不推荐的,当数据结构改变时这样的代码将会失效。通过改变 current 状态,只改变了调度器对待进程的方式,但进程还未让出处理器。

(3)最后一步是放弃处理器。 但必须先检查进入休眠的条件。如果不做检查会引入竞态: 如果决定休眠后,在做上述准备工作到真正调用schedule时,若等待的条件变为真,不对条件重新进行判断,则你可能错过唤醒且长时间休眠。因此典型的代码下:
if (!condition)
    schedule();
在调用schedule前,应对条件再次进行检查。

(4)更改进程状态并将进程从等待队列中删除。
如果代码是从 schedule 返回,则进程肯定处于TASK_RUNNING 状态。 如果不需睡眠而跳过对 schedule 的调用,必须将任务状态重置为 TASK_RUNNING。无论是否调用过schedule,都需要从等待队列中去除这个进程,否则它可能被多次唤醒。

5.3   手工休眠的具体函数执行流
特殊睡眠要求程序员手动处理所有上面的步骤. 它是一个繁琐的过程, 包含相当多的易出错的样板式的代码. 程序员如果愿意还是可能用那种方式手动睡眠。
(1)创建和初始化一个等待队列。常由宏定义完成:
DEFINE_WAIT(my_wait);
/*name 是等待队列入口项的名字. 也可以用2步来做:*/
wait_queue_t my_wait;
init_wait(&my_wait);
/*常用的做法是放一个 DEFINE_WAIT 在循环的顶部,来实现休眠。*/

(2)添加等待队列入口到队列,并设置进程状态:
void prepare_to_wait(wait_queue_head_t *queue, wait_queue_t *wait, int state);
/*queue 和 wait 分别地是等待队列头和进程入口。state 是进程的新状态:TASK_INTERRUPTIBLE(可中断休眠,推荐)或TASK_UNINTERRUPTIBLE(不可中断休眠,不推荐)。*/
prepare_to_wait_exclusive
(3)在检查确认仍然需要休眠之后调用 schedule
schedule();

(4)schedule 返回,重新判断等待条件,若为真则退出,否则继续schedule

(5)条件满足退出后,确保状态为running,同时将进程从等待队列中删除。
void finish_wait(wait_queue_head_t *queue, wait_queue_t *wait);

5.4   wait_event_interruptible_exclusive
为了避免手工编写上述复杂代码,内核提供了最常见的interruptible类型排他性等待函数。而对于非排他性的可以直接利用等待事件event系列函数。

#define __wait_event_interruptible_exclusive(wq, condition, ret)      \
do {                                                                \
        DEFINE_WAIT(__wait);                                       \
                                                                      \
        for (;;) {                                                  \
                prepare_to_wait_exclusive(&wq, &__wait,                     \
                                       TASK_INTERRUPTIBLE);            \
                if (condition)                                            \
                       break;                                             \
                if (!signal_pending(current)) {                          \
                       schedule();                                      \
                       continue;                                 \
                }                                                     \
                ret = -ERESTARTSYS;                                  \
                break;                                                     \
        }                                                            \
        finish_wait(&wq, &__wait);                                    \
} while (0)

#define wait_event_interruptible_exclusive(wq, condition)        \
({                                                                   \
        int __ret = 0;                                                   \
        if (!(condition))                                        \
                __wait_event_interruptible_exclusive(wq, condition, __ret);\
        __ret;                                                             \
})

6      Completion
completion是一种轻量级的机制,它允许一个线程告诉另一个线程某个工作已经完成。代码必须包含。使用的代码如下:
DECLARE_COMPLETION(my_completion);/* 创建completion(声明+初始化) */
struct completion my_completion;/* 动态声明completion 结构体*/
static inline void init_completion(&my_completion);/*动态初始化completion*/
void wait_for_completion(struct completion *c);/* 等待completion */
void complete(struct completion *c);/*唤醒一个等待completion的线程*/
void complete_all(struct completion *c);/*唤醒所有等待completion的线程*/
/*如果未使用completion_all,completion可重复使用;否则必须使用以下函数重新初始化completion*/
INIT_COMPLETION(struct completion c);/*快速重新初始化completion*/

completion的典型应用是模块退出时的内核线程终止。在这种模式,某些驱动程序的内部工作有一个内核线程在while(1)循环中完成。当内核准备清除该模块时,exit函数会告诉该线程退出并等待completion。为此内核包含了用于这种线程的一个特殊函数:
void complete_and_exit(struct completion *c, long retval);
               
               
               

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u3/93262/showart_2052336.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP