内核随记(三)--同步(2)【转】

简介: 转自:http://blog.csdn.net/tommy_wxie/article/details/7425668 版权声明:本文为博主原创文章,未经博主允许不得转载。 2.2、睡眠与唤醒 在操作系统中,睡眠和唤醒原语实际上是操作系统的基本原语,也是实现同步的一种方式,而且它还是实现信号量的基础。

转自:http://blog.csdn.net/tommy_wxie/article/details/7425668

版权声明:本文为博主原创文章,未经博主允许不得转载。
2.2、睡眠与唤醒
在操作系统中,睡眠和唤醒原语实际上是操作系统的基本原语,也是实现同步的一种方式,而且它还是实现信号量的基础。当进程请求的资源(如内存、文件等)不能得到满足时,就会主动放弃CPU,进入等待状态(可中断等待或者不可中断等待)。当资源满足时,就会由别的进程唤醒,从而投入运行。
2.2.1、等待队列
等待队列表示一组睡眠的进程,这些进程正在等待特定的事件发生(或者说条件为真),比如,等待足够的内存。等待队列是一个双链表,每个队列都有一个队列头,其定义如下:
[html] view plain copy print?
//include/linux/wait.h  
//等待队列头  
struct __wait_queue_head {  
      // 自旋锁  
    spinlock_t lock;  
    struct list_head task_list;  
};  
typedef struct __wait_queue_head wait_queue_head_t;  

等待队列链表中的元素类型为:
[html] view plain copy print?
typedef struct __wait_queue wait_queue_t;  
//唤醒函数指针  
typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int sync, void *key);  
//默认的唤醒函数  
int default_wake_function(wait_queue_t *wait, unsigned mode, int sync, void *key);  
  
struct __wait_queue {  
    /*取值为WQ_FLAG_EXCLUSIVE(=1)表示互斥进程,由内核有选择的唤醒.为0时表示非互斥进程,由内核在  
    **事件发生时唤醒所有等待进程.  
    **/  
    unsigned int flags;  
#define WQ_FLAG_EXCLUSIVE    0x01  
    //等待的任务描述符  
    struct task_struct * task;  
  
    //唤醒函数,默认为default_wake_function  
    wait_queue_func_t func;  
    struct list_head task_list;  
};  

其典型的结构如下:

等待队列头的初始化:
DECLARE_WAIT_QUEUE_HEAD(name);
其定义如下:
[html] view plain copy print?
//incude/linux/wait.h  
#define __WAIT_QUEUE_HEAD_INITIALIZER(name) {                \  
    .lock        = SPIN_LOCK_UNLOCKED,                \  
    .task_list    = { &(name).task_list, &(name).task_list } }  
  
//初始化等待队列头  
#define DECLARE_WAIT_QUEUE_HEAD(name) \  
    wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)  

或者如下:
wait_queue_head_t my_queue; 
init_waitqueue_head(&my_queue);

等待队列元素初始化:
//linux/wait.h
//wait_queue_t初始化
static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)
{
    q->flags = 0;
    q->task = p;
    q->func = default_wake_function;
}
2.2.2、等待事件(Waiting on the Event)
内核提供的等待接口包括wait_event(), wait_event_ interruptible(), 和wait_event_interruptible_timeout()。此外sleep_on(), sleep_on_timeout(), 和interruptible_sleep_on()在2.6中仍然支持,但已经过时。这些接口的基本实现如下:

具体代码如下:
[html] view plain copy print?
//linux/wait.h  
#define wait_event(wq, condition)                     \  
do {                                    \  
    if (condition)    //条件发生                         \  
        break;                            \  
    __wait_event(wq, condition);                    \  
} while (0)  
  
#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)  
  
//kernel/wait.c  
void fastcall  
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);  //将等待任务加入等待队列  
    /*  
     * don't alter the task state if this is just going to  
     * queue an async wait queue callback  
     */  
    if (is_sync_wait(wait))  
        set_current_state(state);  //设置任务当前的状态  
    //释放自旋锁,并恢复处理器状态  
    spin_unlock_irqrestore(&q->lock, flags);  
}  
  
//等待完成之后,应该设置任务的状态为运行状态,并从等待队列中删除  
void fastcall finish_wait(wait_queue_head_t *q, wait_queue_t *wait)  
{  
    unsigned long flags;  
  
    __set_current_state(TASK_RUNNING); //设置为运行状态  
      
    if (!list_empty_careful(&wait->task_list)) {  
        spin_lock_irqsave(&q->lock, flags);  
        list_del_init(&wait->task_list);    //从等待队列中删除  
        spin_unlock_irqrestore(&q->lock, flags);  
    }  
}  

2.2.3、唤醒(Waking Up)
接口如下:
[html] view plain copy print?
//include/inux/wait.h  
#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)  
#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)  

具体实现:
[html] view plain copy print?
//kernel/sched.c  
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);  
    spin_unlock_irqrestore(&q->lock, flags);  
}  
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;  
        unsigned flags;  
        curr = list_entry(tmp, wait_queue_t, task_list);  
        flags = curr->flags;  
        //调用相应的唤醒函数, 唤醒第1个有WQ_FLAG_EXCLUSIVE标志的进程后停止  
        if (curr->func(curr, mode, sync, key) &&  
            (flags & WQ_FLAG_EXCLUSIVE) &&  
            !--nr_exclusive)  
            break;  
    }  
}  
//默认的唤醒函数  
int default_wake_function(wait_queue_t *curr, unsigned mode, int sync, void *key)  
{  
    task_t *p = curr->task;  
    return try_to_wake_up(p, mode, sync);  
}  

try_to_wake_up是唤醒原语中核心部分,其具体代码如下:
[html] view plain copy print?
/*p被唤醒的进程描述符.  
**state被唤醒的进程的状态掩码  
**sync禁止被唤醒的进程抢占本地CPU正在运行的进程  
*/  
static int try_to_wake_up(task_t * p, unsigned int state, int sync)  
{  
    int cpu, this_cpu, success = 0;  
    unsigned long flags;  
    long old_state;  
    runqueue_t *rq;  
#ifdef CONFIG_SMP  
    unsigned long load, this_load;  
    struct sched_domain *sd;  
    int new_cpu;  
#endif  
    //关闭中断,并获取最后执行该进程的CPU(可能不同于本地CPU)的运行队列的锁  
    rq = task_rq_lock(p, &flags);  
    schedstat_inc(rq, ttwu_cnt);  
    old_state = p->state;  
    if (!(old_state & state))  
        goto out;  
  
    if (p->array)  
        goto out_running;  
      
    //最后执行该任务的CPU  
    cpu = task_cpu(p);  
    //本地CPU  
    this_cpu = smp_processor_id();  
      
/*对于多CPU系统,检查要被唤醒的进程是否应该从最近执行该进程的CPU的运行队列,  
**转移到另外一个CPU的运行队列.  
*/  
#ifdef CONFIG_SMP  
    if (unlikely(task_running(rq, p)))  
        goto out_activate;  
  
    new_cpu = cpu;  
  
    if (cpu == this_cpu || unlikely(!cpu_isset(this_cpu, p->cpus_allowed)))  
        goto out_set_cpu;  
  
    load = source_load(cpu);  
    this_load = target_load(this_cpu);  
  
    /*  
     * If sync wakeup then subtract the (maximum possible) effect of  
     * the currently running task from the load of the current CPU:  
     */  
    if (sync)  
        this_load -= SCHED_LOAD_SCALE;  
  
    /* Don't pull the task off an idle CPU to a busy one */  
    if (load < SCHED_LOAD_SCALE/2 && this_load > SCHED_LOAD_SCALE/2)  
        goto out_set_cpu;  
  
    new_cpu = this_cpu; /* Wake to this CPU if we can */  
  
    /*  
     * Scan domains for affine wakeup and passive balancing  
     * possibilities.  
     */  
    for_each_domain(this_cpu, sd) {  
        unsigned int imbalance;  
        /*  
         * Start passive balancing when half the imbalance_pct  
         * limit is reached.  
         */  
        imbalance = sd->imbalance_pct + (sd->imbalance_pct - 100) / 2;  
  
        if ((sd->flags & SD_WAKE_AFFINE) &&  
                !task_hot(p, rq->timestamp_last_tick, sd)) {  
            /*  
             * This domain has SD_WAKE_AFFINE and p is cache cold  
             * in this domain.  
             */  
            if (cpu_isset(cpu, sd->span)) {  
                schedstat_inc(sd, ttwu_wake_affine);  
                goto out_set_cpu;  
            }  
        } else if ((sd->flags & SD_WAKE_BALANCE) &&  
                imbalance*this_load <= 100*load) {  
            /*  
             * This domain has SD_WAKE_BALANCE and there is  
             * an imbalance.  
             */  
            if (cpu_isset(cpu, sd->span)) {  
                schedstat_inc(sd, ttwu_wake_balance);  
                goto out_set_cpu;  
            }  
        }  
    }  
  
    new_cpu = cpu; /* Could not wake to this_cpu. Wake to cpu instead */  
out_set_cpu:  
    schedstat_inc(rq, ttwu_attempts);  
    new_cpu = wake_idle(new_cpu, p);  
    if (new_cpu != cpu && cpu_isset(new_cpu, p->cpus_allowed)) {  
        schedstat_inc(rq, ttwu_moved);  
        set_task_cpu(p, new_cpu);  
        task_rq_unlock(rq, &flags);  
        /* might preempt at this point */  
        rq = task_rq_lock(p, &flags);  
        old_state = p->state;  
        if (!(old_state & state))  
            goto out;  
        if (p->array)  
            goto out_running;  
  
        this_cpu = smp_processor_id();  
        cpu = task_cpu(p);  
    }  
  
out_activate:  
#endif /* CONFIG_SMP */  
    if (old_state == TASK_UNINTERRUPTIBLE) {  
        rq->nr_uninterruptible--;  
        /*  
         * Tasks on involuntary sleep don't earn  
         * sleep_avg beyond just interactive state.  
         */  
        p->activated = -1;  
    }  
  
    /*  
     * Sync wakeups (i.e. those types of wakeups where the waker  
     * has indicated that it will leave the CPU in short order)  
     * don't trigger a preemption, if the woken up task will run on  
     * this cpu. (in this case the 'I will reschedule' promise of  
     * the waker guarantees that the freshly woken up task is going  
     * to be considered on this CPU.)  
     */  
     //将进程p加入目标CPU的可运行队列  
    activate_task(p, rq, cpu == this_cpu);  
    /*如果没有设置sync标志(表示允许抢占),且目标CPU不是本地CPU,则检查p是否比rq运行队列中当前进程的动态优先级高.  
    **即(p)->prio < (rq)->curr->prio,如果是,则调用resched_task()抢占rq->curr。  
    */  
    if (!sync || cpu != this_cpu) {  
        if (TASK_PREEMPTS_CURR(p, rq))  
            /*在单CPU中,仅仅设置TIF_NEED_RESCHED标志.多CPU系统中,则检查相应标志,并使目标CPU重新调度  
            */  
            resched_task(rq->curr);  
    }  
    success = 1;  
  
out_running:  
    //设置进程的状态  
    p->state = TASK_RUNNING;  
out:  
    //释放rq的锁,并打开本地中断  
    task_rq_unlock(rq, &flags);  
  
    return success;  
}  
  
#ifdef CONFIG_SMP  
//多CPU系统  
static void resched_task(task_t *p)  
{  
    int need_resched, nrpolling;  
  
    BUG_ON(!spin_is_locked(&task_rq(p)->lock));  
  
    /* minimise the chance of sending an interrupt to poll_idle() */  
    nrpolling = test_tsk_thread_flag(p,TIF_POLLING_NRFLAG);  
    need_resched = test_and_set_tsk_thread_flag(p,TIF_NEED_RESCHED);  
    nrpolling |= test_tsk_thread_flag(p,TIF_POLLING_NRFLAG);  
      
    if (!need_resched && !nrpolling && (task_cpu(p) != smp_processor_id()))  
        //产生IPI,强制目标CPU重新调度  
        smp_send_reschedule(task_cpu(p));  
}  
#else  
//单CPU系统  
static inline void resched_task(task_t *p)  
{  
    set_tsk_need_resched(p);  
}  
#endif  

2.2.4、互斥等待
当调用wake_up唤醒等待队列时,等待队列上的所有进程都转置为可运行。在一些情况下,这种做法是正确的,比如等待某个特定的事件。但是在另外一些情况,可以提前知道只有一个被唤醒的进程能够成功的获取资源,比如等待临界区资源,其它的进程将再次睡眠。如果等待队列中的进程数量太大,将会严重影响系统性能,这就是所谓的thundering herd行为。为此,内核引入互斥等待,它与非互斥等待的区别如下:
(1) 当一个等待队列入口有 WQ_FLAG_EXCLUSEVE 标志置位, 它被添加到等待队列的尾部. 没有这个标志的入口项, 相反, 添加到开始。
(2) 当 wake_up 被在一个等待队列上调用, 它在唤醒第一个有 WQ_FLAG_EXCLUSIVE 标志的进程后停止。
这样,进行互斥等待的进程一次只唤醒一个。使一个进程进入互斥等待是调用prepare_to_wait_exclusive完成的。
[html] view plain copy print?
//kernel/wait.c  
void fastcall  
prepare_to_wait_exclusive(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_tail(q, wait);  
    /*  
     * don't alter the task state if this is just going to  
      * queue an async wait queue callback  
     */  
    if (is_sync_wait(wait))  
        set_current_state(state);  
    spin_unlock_irqrestore(&q->lock, flags);  
}  

 

【作者】 张昺华
【新浪微博】 张昺华--sky
【twitter】 @sky2030_
【facebook】 张昺华 zhangbinghua
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
目录
相关文章
|
7天前
|
存储 JavaScript 前端开发
JavaScript基础
本节讲解JavaScript基础核心知识:涵盖值类型与引用类型区别、typeof检测类型及局限性、===与==差异及应用场景、内置函数与对象、原型链五规则、属性查找机制、instanceof原理,以及this指向和箭头函数中this的绑定时机。重点突出类型判断、原型继承与this机制,助力深入理解JS面向对象机制。(238字)
|
6天前
|
云安全 人工智能 安全
阿里云2026云上安全健康体检正式开启
新年启程,来为云上环境做一次“深度体检”
1621 6
|
2天前
|
消息中间件 人工智能 Kubernetes
阿里云云原生应用平台岗位急招,加入我们,打造 AI 最强基础设施
云原生应用平台作为中国最大云计算公司的基石,现全面转向 AI,打造 AI 时代最强基础设施。寻找热爱技术、具备工程极致追求的架构师、极客与算法专家,共同重构计算、定义未来。杭州、北京、深圳、上海热招中,让我们一起在云端,重构 AI 的未来。
|
3天前
|
存储 人工智能 自然语言处理
OpenSpec技术规范+实例应用
OpenSpec 是面向 AI 智能体的轻量级规范驱动开发框架,通过“提案-审查-实施-归档”工作流,解决 AI 编程中的需求偏移与不可预测性问题。它以机器可读的规范为“单一真相源”,将模糊提示转化为可落地的工程实践,助力开发者高效构建稳定、可审计的生产级系统,实现从“凭感觉聊天”到“按规范开发”的跃迁。
572 11
|
8天前
|
安全 数据可视化 网络安全
安全无小事|阿里云先知众测,为企业筑牢防线
专为企业打造的漏洞信息收集平台
1333 2
|
7天前
|
缓存 算法 关系型数据库
深入浅出分布式 ID 生成方案:从原理到业界主流实现
本文深入探讨分布式ID的生成原理与主流解决方案,解析百度UidGenerator、滴滴TinyID及美团Leaf的核心设计,涵盖Snowflake算法、号段模式与双Buffer优化,助你掌握高并发下全局唯一ID的实现精髓。
366 160
|
7天前
|
人工智能 自然语言处理 API
n8n:流程自动化、智能化利器
流程自动化助你在重复的业务流程中节省时间,可通过自然语言直接创建工作流啦。
451 6
n8n:流程自动化、智能化利器
|
9天前
|
人工智能 API 开发工具
Skills比MCP更重要?更省钱的多!Python大佬这观点老金测了一周终于懂了
加我进AI学习群,公众号右下角“联系方式”。文末有老金开源知识库·全免费。本文详解Claude Skills为何比MCP更轻量高效:极简配置、按需加载、省90% token,适合多数场景。MCP仍适用于复杂集成,但日常任务首选Skills。推荐先用SKILL.md解决,再考虑协议。附实测对比与配置建议,助你提升效率,节省精力。关注老金,一起玩转AI工具。
|
16天前
|
机器学习/深度学习 安全 API
MAI-UI 开源:通用 GUI 智能体基座登顶 SOTA!
MAI-UI是通义实验室推出的全尺寸GUI智能体基座模型,原生集成用户交互、MCP工具调用与端云协同能力。支持跨App操作、模糊语义理解与主动提问澄清,通过大规模在线强化学习实现复杂任务自动化,在出行、办公等高频场景中表现卓越,已登顶ScreenSpot-Pro、MobileWorld等多项SOTA评测。
1622 7
|
5天前
|
IDE 开发工具 C语言
【2026最新】VS2026下载安装使用保姆级教程(附安装包+图文步骤)
Visual Studio 2026是微软推出的最新Windows专属IDE,启动更快、内存占用更低,支持C++、Python等开发。推荐免费的Community版,安装简便,适合初学者与个人开发者使用。
596 10