当每次时钟节拍到来时,即我们提到过的timer_interrupt会调用do_timer_interrupt_hook,从而调用do_timer和update_process_times函数,update_process_times则就是用来更新进程使用到的一些跟时间相关的字段,其罪重要的是调用scheduler_tick()更新时间片剩余节拍数:

void scheduler_tick(void)
{unsigned long long now = sched_clock();struct task_struct *p = current;int cpu = smp_processor_id();struct rq *rq = cpu_rq(cpu);update_cpu_clock(p, rq, now);rq->timestamp_last_tick = now;if (p == rq->idle) {if (wake_priority_sleeper(rq))goto out;rebalance_tick(cpu, rq, SCHED_IDLE);return;}/* Task might have expired already, but not scheduled off yet */if (p->array != rq->active) {set_tsk_need_resched(p);goto out;}spin_lock(&rq->lock);if (rt_task(p)) {if ((p->policy == SCHED_RR) && !--p->time_slice) {p->time_slice = task_timeslice(p);p->first_time_slice = 0;set_tsk_need_resched(p);/* put it at the end of the queue: */requeue_task(p, rq->active);}goto out_unlock;}if (!--p->time_slice) {dequeue_task(p, rq->active);set_tsk_need_resched(p);p->prio = effective_prio(p);p->time_slice = task_timeslice(p);p->first_time_slice = 0;if (!rq->expired_timestamp)rq->expired_timestamp = jiffies;if (!TASK_INTERACTIVE(p) || expired_starving(rq)) {enqueue_task(p, rq->expired);if (p->static_prio < rq->best_expired_prio)rq->best_expired_prio = p->static_prio;} elseenqueue_task(p, rq->active);} else {if (TASK_INTERACTIVE(p) && !((task_timeslice(p) -p->time_slice) % TIMESLICE_GRANULARITY(p)) &&(p->time_slice >= TIMESLICE_GRANULARITY(p)) &&(p->array == rq->active)) {requeue_task(p, rq->active);set_tsk_need_resched(p);}}
out_unlock:spin_unlock(&rq->lock);
out:rebalance_tick(cpu, rq, NOT_IDLE);
}

scheduler_tick()负责执行以下步骤:

1.    把转换为纳秒的TSC的当前值,也就是读取处理器中,存放记录着当前距离开机后时间戳寄存器的值,存入本地运行队列的timestamp_last_tick字段。我们在相关博文中提到过了,CPU中有个TSC硬件机制,当系统的时钟节拍为1GHz时,那么时间戳每纳秒增加1次。这个时间戳是从函数sched_clock()获得的。

2.    检查当前进程是否是本地CPU的swapper进程(通过p == rq->idle语句判断),如果是,执行下面的子步骤:

a)    如果本地运行队列除了swapper进程外,还包括另外一个可运行的进程,就设置当前进程的TIF_NEED_RESCHED字段,以强迫进行重新调度。就像我们稍后在讲schedule函数博文所看到的,如果内核支持超线程技术,那么,只要一个逻辑CPU运行队列中的所有进程都有比另一个逻辑CPU(两个逻辑CPU对应同一个物理CPU)上已经在执行的进程有低得多的优先级,前一个逻辑CPU就可能空闲,即使它的运行队列中有可运行的进程。
b)    跳转到第7步(没有必要更新swapper进程的时间片计数器)

3.    检查current->array是否指向本地运行队列的活动链表(通过p->array != rq->active语句判断)。如果不是,说明该进程已经过期,已经处于rq->expired链表中,但还没有被替换,则设置他的TIF_NEED_RESCHED字段,以强迫进行重新调度并跳转到第7步。

4.    获得this_rq()->lock自旋锁。

5.    递减当前进程的时间片计数器,并检查是否已经用完时间片(!--p->time_slice)。由于进程的调度类型不同(rt_task(p)),函数所执行的这一步操作也有很大的差别,我们马上将会讨论它。

6.    释放this_rq()->lock自旋锁。

7.    调用rebalance_tick()函数,该函数应该保证不同CPU的运行队列包含数量基本相同的可运行进程,以后博文还会详细谈到。

更新实时进程的时间片:

如果当前进程是先进先出(FIFO)的实时进程,即在代码中,不进入最后两个if条件断,则函数scheduler_tick()什么都不做。实际上在这种情况下,current所表示的当前进程想占用CPU多久就占用多久,而且不可能比其他优先级低或其他优先级相等的进程所抢占,因此,维持当前进程的最新时间片计数器是没有意义的。

前面讲过,基于时间片轮转的实时进程(rt_task(p) && (p->policy == SCHED_RR))不能更新其动态优先级,而只能修改其时间片。那么,如果current表示基于时间片轮转的实时进程(通过(p->policy == SCHED_RR) && !--p->time_slice语句判断),scheduler_tick()就递减它的时间片计数器并检查时间片是否被用完:

 if (current->policy == SCHED_RR && !--current->time_slice) {current->time_slice = task_timeslice(current);current->first_time_slice = 0;set_tsk_need_resched(current);requeue_task(p, rq->active);}

如果函数确定时间片确实用完了,就执行一系列操作以达到抢占当前进程的目的,如果必要的话,就尽快抢占。

第一步操作包括调用task_timeslice()来重填进程的时间片计数器,查看“进程调度的数据结构和优先级 ”博文。该函数检查进程的静态优先级,并根据前面“进程的优先级”公式返回相应的基本时间片。此外,current的first_time_slice字段被清零:该标志被fork系统调用例程中的copy_process()设置,并在进程的第一个时间片刚用完时立刻清零。

第二步,scheduler_tick()函数调用函数set_tsk_need_resched()设置进程的TIF_NEED_RESCHED标志。该标志强制调用schedule()函数,以便current指向的进程能被另外一个有相同优先级或更高优先级的实时进程所取代。

scheduler_tick()的最后一步操作包括把进程描述符移到与当前进程优先级相应的运行队列活动链表的尾部(requeue_task函数本质上执行的是list_add_tail(&current->run_list,this_rq( )->active->queue+current->prio);)。把current指向的进程放到链表的尾部,可以保证每个优先级与它相同的可运行实时进程获得CPU时间片以前,它不会再次被选择来执行。这是基于时间片轮转的调度策略。进程描述符的移动是通过两个步骤完成的:先调用list_del()把进程从运行队列的活动链表中删除,然后调用list_add_tail()把进程重新插入到同一个活动链表的尾部。

更新普通进程的时间片:

如果当前进程是普通进程(第四个首层if),函数scheduler_tick()执行下列操作:

1.    递减时间片计数器(current->time_slice)。
2.    检查时间片计数器。如果时间片用完,函数执行下列操作

a)    调用dequeue_task()从可运行进程的this_rq()->active集合中删除current指向的进程。
b)    调用set_tsk_need_resched( )设置TIF_NEED_RESCHED标志。
c)    更新current指向的进程的动态优先级:current->prio = effective_prio(current);。函数effective_prio()读current的static_prio和sleep_avg字段,并根据前一博文的公式计算出进程的动态优先级。
d)    重填进程的时间片:
    current->time_slice = task_timeslice(current);//前面代码,请仔细琢磨
    current->first_time_slice = 0;
e)    如果本地运行队列数据结构中的expired_timestamp字段等于0(即过期进程集合为空),就把当前时钟节拍值赋给expired_timestamp:
    if (!this_rq( )->expired_timestamp)
        this_rq( )->expired_timestamp = jiffies;
f)    把当前进程插入活动进程集合或过期进程集合:
    if (!TASK_INTERACTIVE(current) || expired_starving(rq) {//如果当前进程不是交互进程,或者运行队列上有饥饿进程存在
        enqueue_task(current, this_rq( )->expired);
        if (current->static_prio < this_rq( )->best_expired_prio)
            this_rq( )->best_expired_prio = current->static_prio;
    } else
        enqueue_task(current, this_rq( )->active);
如果用前面列出的公式(3)识别出进程是一个交互式进程,TASK_INTERACTIVE宏就产生1。函数expired_starving检查运行队列中的第一个过期进程的等待时间是否已经超过1000个时钟节拍乘以运行队列中的可运行进程数加1,如果是,函数返回1。如果当前进程的静态优先级大于一个过期进程的静态优先级,函数也也返回1。

3.    否则,即时间片没有用完(current->time_slice不等于0),检查当前进程的剩余时间片是否太长:
    if (TASK_INTERACTIVE(p) && !((task_timeslice(p) -
         p->time_slice) % TIMESLICE_GRANULARITY(p)) &&
        (p->time_slice >= TIMESLICE_GRANULARITY(p)) &&
        (p->array == rq->active)) {
    list_del(&current->run_list);
    list_add_tail(&current->run_list,
                  this_rq( )->active->queue+current->prio);
    set_tsk_need_resched(p);
}
宏TIMESLICE_GRANULARITY产生两个数的乘积给当前进程的bonus,其中一个数为系统中的CPU数量,另一个为成比例的常量。基本上,具有高静态优先级的交互式进程,其时间片被分成大小为TIMESLICE_GRANULARITY的几个片段,以使这些进程不会独占CPU。

好啦,重要的scheduler_tick函数就介绍到这里,如果看不懂就回头去看看相关的数据结构。下一讲里,我们将通过介绍try_to_wake_up函数,给大家详细讲解进程是如何由其他状态进入到运行状态的。

scheduler_tick函数相关推荐

  1. linux 2.6内核进程调度,Linux2.6内核进程调度系列--scheduler_tick()函数2.更新实时进程的时间片,...

    Linux2.6内核进程调度系列--scheduler_tick()函数2.更新实时进程的时间片, RT /** * 递减当前进程的时间片计数器,并检查是否已经用完时间片. * 由于进程的调度类型不同 ...

  2. linux内核进程调度—scheduler_tick函数解析

    本博,我们详细分解每次定时器中断调用的最重要的更新时间片的函数 -- scheduler_tick函数 当每次时钟节拍到来时,即我们提到过的timer_interrupt会调用do_timer_int ...

  3. scheduler_tick函数详解

    scheduler_tick是调度器中的一个核心重要的函数,它叫做周期调度器,驱动调度器运行的机制之一. event_handler()-->tick_handle_periodic()-> ...

  4. schedule()函数(重点)

    好了,前面的准备工作都做完了,我们就进入进程调度的主体程序--schedule()函数. 函数schedule()实现调度程序.它的任务是从运行队列的链表rq中找到一个进程,并随后将CPU分配给这个进 ...

  5. recalc_task_prio函数

    函数recalc_task_prio更新进程p的平均睡眠时间和动态优先级,更重要的是,他还通过平均睡眠时间判断进程是否是交互进程.它接收进程描述符的指针p和由函数sched_clock()计算出当前时 ...

  6. Linux2.6内核--抢占

    [摘要]本文首先介绍非抢占式内核(Non-Preemptive Kernel)和可抢占式内核(Preemptive Kernel)的区别.接着分析Linux下有两种抢占:用户态抢占(User Pree ...

  7. Linux 调度器发展简述

    引言 进程调度是操作系统的核心功能.调度器只是是调度过程中的一部分,进程调度是非常复杂的过程,需要多个系统协同工作完成.本文所关注的仅为调度器,它的主要工作是在所有 RUNNING 进程中选择最合适的 ...

  8. 【Linux 内核】CFS 调度器 ④ ( 调度子系统组件模块 | 主调度器、周期性调度器 | 调度器类 )

    文章目录 一.调度子系统组件模块 二.主调度器.周期性调度器 三.调度器类 一.调度子系统组件模块 调度器 需要对 被调度的进程 进行 排序 和 调度管理 , 进程管理过程需要 调度器 的 组件模块 ...

  9. kernel笔记——进程调度

    调度器完成以下任务: 时钟中断(或类似的定时器)时间内刷新进程的时间片,设置进程调度标志 系统调用返回或中断完成时检查调度标志 schedule函数 内核代码中完成进程调度的函数为schedule() ...

最新文章

  1. Windows 各种计时函数总结
  2. Unknown property 'mybatis-plus' yml文件报错
  3. windows NT的意义和各个版本
  4. RANSAC与 最小二乘(LS, Least Squares)拟合直线的效果比较
  5. AAAIT学院JDK15新特性JAVA15版本
  6. 【渝粤题库】国家开放大学2021春1443卫生信息与文献检索题目
  7. 性能优化CPU、内存、磁盘I/O、网络性能相关命令
  8. mysql DCL数据控制语言
  9. 是逻辑运算符 java_跟我学java编程—Java逻辑运算符
  10. 用计算机画画内容,【经验】怎么用电脑绘画?
  11. Thymeleaf模板引擎使用详解
  12. UMLChina公众号文章精选(20220126更新精选)
  13. 怎样批量修改图片像素大小?
  14. Codeforces Round #554 (Div. 2) A. Neko Finds Grapes
  15. Html5 jquery视频播放插件Video.js
  16. 二叉树算法--数据结构课程设计
  17. 集散控制系统是集计算机技术,集散控制系统概述
  18. [转帖]隔行扫描与逐行扫描视频有什么区别
  19. C++实现扫雷(最简单版)
  20. 云计算笔记10day、11day

热门文章

  1. JQ选择器操作多个元素
  2. rem适配布局制作苏宁移动端首页
  3. C语言--菜鸟基本概念梳理
  4. 校招面试过程中几个需要注意的地方
  5. 【项目管理案例】第三期:如何应对项目中的风险
  6. Java操作系统进程调度算法——先来先服务(FCFS)算法
  7. 胡正的网页 知道星际译王不?不知道的话就进来看看吧
  8. Error response from daemon: error while removing network: network macvlan1 id 432b2be6bba68f376ffcfb
  9. 碰瓷 MongoDB?MangoDB 正式改名为 FerretDB;谷歌和高通将在神经网络方面进行合作;PyCharm 2021.3 发布 | 开源日报
  10. layui登录php,Thinkphp6 + layui 实现后台登录(验证码刷新)