(转)进程调度函数schedule()解读

原文

在linux系统中,单处理器也是多线程处理信号、事件等。这就需要一个核心算法来进行进程调度。这个算法就是CFS(Completely Fair Scheduler)。在 LInux Kernel Development 一书中用一句话总结CFS进程调度:

运行rbtree树中最左边叶子节点所代表的那个进程。

在一个自平衡二叉搜索树红黑树rbtree的树节点中,存储了下一个应该运行进程的数据。在这里我们看到了二叉搜索树的完美运用。具体可参见Introduction to Algorithms Page 174~182。

而进程调度的主要入口函数就是schedule()。它定义在文件kernel/sched.c中。

我们先看一个在等待队列中进行进程调度的例子:

DEFINE_WAIT(wait); //申明等待队列

    add_wait_queue(q,&wait); //把我们用的q队列加入到wait等待队列中
    while(!condition){ //当等待事件没有来临时
         prepare_to_wait(&q,&wait,TASK_INTERRUPTIBLE);
         //将q从TASK_RUNNING或者其他状态置为TASK_INTERRUPTIBLE不可运行的休眠状态。
         //同时接受信号&&事件来唤醒它
         if(signal_pending(current))  //如果有来自从处理器的信号
         { processingsignal();}//处理信号
         schedule(); //调用红黑树中的下一个进程
    }
    finish_wait(&q,&wait); //将进程设置为TASK_RUNNING并移出等待队列.

其实我们可以这么理解这段代码。现在有一个任务要等待事件到来才能运行,怎么实现呢?就是阻塞加查询。但是这样会使得这段代码独占整个操作系统。为了解决这个问题,就在阻塞查询之中加入了队列和进程调度schedule(),从而不耽误其它线程的执行。

再来看一看schedule()函数的结构:


schedule()函数结构

asmlinkage void __sched schedule(void)  ///定义通过堆栈传值
    {
    struct task_struct *prev, *next;
    unsigned long *switch_count;
    struct rq *rq;
    int cpu;

    /*At the end of this function, it will check if need_resched() return
    true, if that indeed happen, then goto here.*/
    need_resched:

    /*current process won‘t be preempted after call preemept_disable()*/
    preempt_disable(); //不让优先占有当前进程
    cpu = smp_processor_id();
    rq = cpu_rq(cpu);
    /* rcu_sched_qs ? */
    rcu_sched_qs(cpu);

    /* prev point to current task_struct */
    prev = rq->curr;

    /* get current task_struct‘s context switch count */
    switch_count = &prev->nivcsw;

    /* kernel_flag is "the big kernel lock".
     * This spinlock is taken and released recursively by lock_kernel()
     * and unlock_kernel(). It is transparently dropped and reacquired
     * over schedule(). It is used to protect legacy code that hasn‘t
     * been migrated to a proper locking design yet.
     * In task_struct, there is a member lock_depth, which is inited -1,
     * indicates that the current task have no kernel lock.
     * When lock_depth >=0 indicate that it own kernel lock.
     * During context switching, it is not permitted that the task
     * switched away remain own kernel lock , so in scedule(),it
     * call release_kernel_lock(), release kernel lock.
     */
    release_kernel_lock(prev);

    need_resched_nonpreemptible:

    schedule_debug(prev);

    if (sched_feat(HRTICK))
        hrtick_clear(rq);

    /* occupy current rq‘s lock */
    raw_spin_lock_irq(&rq->lock); //占有rq自旋锁

    /* update rq‘s clock,this function will call sched_clock_cpu() */
    update_rq_clock(rq);

    /* clear bit in task_struct‘s thread_struct‘s flag TIF_NEED_RESCHED.
     * In case that it will be rescheduled, because it prepare to give
     * up cpu.
     */
    clear_tsk_need_resched(prev);

    if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {
        if (unlikely(signal_pending_state(prev->state, prev)))
            prev->state = TASK_RUNNING;
        else
            deactivate_task(rq, prev, 1);
        switch_count = &prev->nvcsw;
    }

    /* For none-SMP, pre_schedule is NULL */
    pre_schedule(rq, prev);

    if (unlikely(!rq->nr_running))
        idle_balance(cpu, rq);

    put_prev_task(rq, prev);

    next = pick_next_task(rq);

    if (likely(prev != next)) {
        sched_info_switch(prev, next);
        perf_event_task_sched_out(prev, next);

        rq->nr_switches++;
        rq->curr = next;
        ++*switch_count;

        context_switch(rq, prev, next); /* unlocks the rq */
        /*
         * the context switch might have flipped the stack from under
         * us, hence refresh the local variables.
         */
        cpu = smp_processor_id();
        rq = cpu_rq(cpu);
    } else
      raw_spin_unlock_irq(&rq->lock);//current task still occupy cpu

    post_schedule(rq);

    if (unlikely(reacquire_kernel_lock(current) < 0)) {
        prev = rq->curr;
        switch_count = &prev->nivcsw;
        goto need_resched_nonpreemptible;
    }

    preempt_enable_no_resched();
    if (need_resched())
        goto need_resched;
    }
    EXPORT_SYMBOL(schedule);

schedule()函数的目的在于用另一个进程替换当前正在运行的进程。因此,这个函数的主要结果就是设置一个名为next的变量,以便它指向所选中的 代替current的进程的描述符。如果在系统中没有可运行进程的优先级大于current的优先级,那么,结果是next与current一致,没有进程切换发生。


References

[1].UNDERSTANDING THE LINUX KERNEL. Page 276

[2].Linux Kernel Development. Page 52

[3].http://hi.baidu.com/zengzhaonong/item/20d9e8207b04cb8f6e2cc323

时间: 2024-11-01 16:17:30

(转)进程调度函数schedule()解读的相关文章

进程调度函数schedule()分析

1.功能简述: 最主要作用就是 从就绪进程中选择一个优先级最高的进程来代替当前进程运行. 2.代码分析 schedule();      struct task_struct *tsk = current; //current是当前进程 sched_submit_work(tsk);  //避免死锁 __schedule();//这就是调度的主函数了 static void __sched __schedule(void) { struct task_struct *prev, *next; u

linux进程调度函数浅析(基于3.16-rc4)

众所周知,进程调度使用schedule()函数来完成,下面我们从分析该函数开始,代码如下(kernel/sched/core.c): 1 asmlinkage __visible void __sched schedule(void) 2 { 3 struct task_struct *tsk = current; 4 5 sched_submit_work(tsk); 6 __schedule(); 7 } 8 EXPORT_SYMBOL(schedule); 第3行获取当前进程描述符指针,存

进程调度函数scheduler_tick()的触发原理:周期PERIODIC定时器

参考文章: https://www.jb51.net/article/133579.htm https://blog.csdn.net/flaoter/article/details/77509553 https://www.cnblogs.com/arnoldlu/p/7078204.html 中时间子系统相关系列blog,讲的比较详细. 主要文件所在目录: kernel/msm-4.9/kernel/time/tick-common.c.tick-dchrf.c.timer.c.hrtime

thinkphp 主函数库解读

thinkphp核心库解读: thinkphp common common.php[Think 基础函数库] ------------------------------------------------------------------------------------------------- 功能列表: 1.// 记录和统计时间(微秒) G($start,$end='',$dec=4) 2.// 设置和获取统计数据 N($key, $step=0) 3.字符串命名风格转换 parse

关于PHP中usort()函数的解读

最近学习遇到自定义数组排序函数usort()有些不了解,搜了很多地方都没有很好的解释,自己研究好久,发来与和我一样的初学者共享- bool usort ( array &$array , callable $cmp_function ) 函数为对数组进行自己自定义排序,排序规则由 $cmp_function 定义.返回值为ture 或者false. 现在先对简单的一个函数进行分析: 1 <?php 2 function re($a,$b){ 3 return ($a<$b)?1:-1;

Java中系统时间的获取_currentTimeMillis()函数应用解读

快速解读 System.currentTimeMillis()+time*1000) 的含义 一.时间的单位转换 1秒=1000毫秒(ms) 1毫秒=1/1,000秒(s)1秒=1,000,000 微秒(μs) 1微秒=1/1,000,000秒(s)1秒=1,000,000,000 纳秒(ns) 1纳秒=1/1,000,000,000秒(s)1秒=1,000,000,000,000 皮秒(ps) 1皮秒=1/1,000,000,000,000秒(s) 1分钟=60秒 1小时=60分钟=3600秒

cvTrimWeights函数详细解读

cvTrimWeights的源码在opencv的cvboost.cpp文件之中,具体内容和部分注释如下所 /* *cvTrimWeights *作用:对小于一定阈值的权重剔除,因为权重较小的样本对训练结果影响很小,剔除后,这样在训练样本时可以缩短训练时间 */ CV_BOOST_IMPL CvMat* cvTrimWeights( CvMat* weights,//训练样本的权重矩阵  CvMat* idx, //训练样本的索引序列矩阵 float factor ) //剔除小权重的样本后剩余样

理解进程调度时机跟踪分析进程调度与进程切换的过程

李洋 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 这一次实验是针对linux系统中进程调度时机得深入理解. Linux 调度器将进程分为三类: 1. 交互式进程 2. 批处理进程 3. 实时进程 根据进程的不同分类 Linux 采用不同的调度策略.对于实时进程,采用 FIFO 或者 Round Robin 的调度策略.对于普通进程,则需要区分交互式和批处理式的不同.传统 Linux 

深入分析linux调度机制

一.说明 本文以linux-2.4.10 为例主要分析Linux 进程调度模块中的schedule 函数及其相关的函数.另外相关的前提知识也会说明.默认系统平台是自己的i386 架构的pc. 二.前提知识 在进行schedule 分析之前有必要简单说明一下系统启动过程,内存分配使用等.这样才能自然过渡到schedule 模块. 首先是Linux各个功能模块之间的依赖关系: 可见进程调度是整个内核的核心.但这部分,我想说明的是,我的pc是怎样把操作系统从硬盘装载到内存中,并启动进程调度模块的.然后