【Linux内核笔记】进程调度

11 篇文章 2 订阅
8 篇文章 0 订阅

进程调度原理

进程调度程序:分配有限处理器时间资源
抢占式多任务 preemptive multitasking
Linux 2.6之前O(1)调度器 使用静态时间片算法和针对每一处理器的运行队列
Linux 2.6 后 为了提高交互程序的调度性能,RSDL反转楼梯最后期限调度算法。 此刻称为 完全公平调度算法CFS

  • I/O消耗型: 常常处在提交或等待IO请求。可运行态,运行时间短
  • 处理器消耗型:执行代码,不停地在运行,调度策略倾向于降低调度频率而延长运行时间

调度算法要在响应时间短和高吞吐量之间找平衡

进程优先级

Linux倾向于IO消耗型
Linux 优先级范围:

  1. nice值:[-20,19] 默认为0 越小越优先 ps -ef
  2. 实时优先级: [0,99] 越高越优先 任何实时进程的优先级都高于普通进程 ps -eo state, pid, uid, ppid, rtprio, time, comm 在RTPRIO列“-”则不是实时进程 (Mac os 下是 pri)

time slice 时间片表示进程在被抢占前所能持续运行的时间
对于IO消耗型进程不需要长时间片,而处理器消耗型希望长。一般处理器默认10ms

  • Linux 的CFS调度器并不直接分配时间片,而是分配使用比给进程。使用比进一步受进程nice值影响。nice作为权重调整进程所使用的处理器时间使用比。高nice值,低权重,损失一小部分处理器使用比。
  • Linux抢占式系统,进程进入可运行态就被准许投入运行,是否将一个进程立刻投入运行(抢占当前进程)完全由进程优先级和是否有时间片决定。 Linux中抢占时机取决于新的可运行程序消耗了多少处理器使用比,若比当前进程小,则立刻投入运行。

Linux调度算法

Linux调度器以模块方式提供,使得不同类型的进程可以有针对性地选择调度算法,模块化结构称为调度器类。scheduler classes
允许多种不同的可动态添加的调度算法并存,调度属于自己范畴的进程。 基础调度器算法kernel/sched.c 按照优先级顺序遍历调度类,选择最高优先级可执行进程。

  • CFS是针对普通进程的调度类Linux称为SCHED_NORMAL。算法定义在kernel/shed_fair.c
Unix系统中的进程调度

进程优先级和时间片。 进程一旦启动就会有一个默认的时间片。更高优先级运行得更加频繁,赋予更多的时间片。Unix系统中时间片以nice值形式输出到用户空间。现实中会遇到如下问题

    1. nice值映射到时间片,nice单位值对应处理器的绝对时间。进程切换无法最优化
nice05
时间片100ms5ms
占用处理器时间100/1055/105105ms上下文切换一次
55
5ms5ms
1/21/210ms上下文切换两次

增加上下文切换次数
高nice值往往是后台进程且计算密集型。普通优先级的进程则更多是前台用户任务。与设计背道而驰

    1. 相对nice值,及时间片映射
nice01
时间片100ms95ms
占用处理器时间100/19595/195时间片差别不大
1819
10ms5ms
10/155/15前者比后者多两倍处理器时间

“把进程nice值减小”取得的效果极大的取决于其nice值的初始值
解决方式:可以把nice几何的增加,抵消比例问题。

    1. 时间片与系统定时器节拍。 整数倍10ms 1ms 的倍数
    1. 基于优先级的调度器为了优化交互任务而唤醒相关进程的问题。 会打破公平原则获得更多的处理器时间

实质性问题:分配的绝对的时间片引发的固定的切换频率

公平调度
  • CFS采用的方法是:完全摒弃时间片而是分配给进程一个处理器使用比重。
    允许每个进程运行一段时间、循环轮转、选择运行最少的进程作为下一个运行进程。nice值作为获得处理器运行比的权重。

  • CFS为完美多任务中的无限小调度周期的近似值设立了一个目标,这个目标称为目标延迟

越小的调度周期将带来越好的交互性,更接近完美的多任务。同时承担更高的切换代价和更差的系统总吞吐能力。

每个进程时间片底线称为最小粒度 1ms

nice05
时间片权重nice(0)1/3 nice(0)
目标延迟20ms
占用处理器时间15ms5ms
nice(10)nice(15)
nice(10)1/3 nice(10)
15ms5ms

Linux调度的实现

  • 时间记账
  • 进程选择
  • 调度器入口
  • 睡眠和唤醒
时间记账

多数Unix系统分配一个时间片给每一个进程,每次系统时钟节拍发生时,时间片会被减少一个节拍周期。当进程的时间片减到0,会被另一个未到0的可运行进程抢占。

  • 调度器实体结构 <linux/sched.h> struct_sched_entity 调度器实体结构作为一个名为se成员变量嵌在task_struct 内
  • 虚拟实时
    vruntime虚拟运行时间:花在运行上的时间和,该计算经过所有可运行进程总数加权。vruntime与节拍器不再相关。
    完美多任务:优先级相同的所有进程的虚拟时间相同,所有任务都将收到相等的处理器份额。
    CFS使用vruntime记录每一个程序到底运行了多长时间以及它还应该再运行多久。 此记账功能存放在kernel/shced_fair.c update_curr() update_curr()由系统定时器周期性调用,计算了当前进程的执行时间delta_exec。传给__update_curr(),它根据当前可运行进程总数对运行时间进行加权,最终将权重值与当前运行进程的vruntime相加。 vruntime可以准确测量给定进程运行时间,还可以知道下一个被运行的进程
进程选择
  • 挑选下一个任务: CFS选择vruntime最小的任务,用红黑树rbtree存储。运行rbtree树种最左叶子节点所代表的那个进程。kernel/sched_fair.c __pick_next_entity() 此函数不主要负责查找,最左叶子节点已存。 若无可运行进程,CFS选择idle任务运行。
  • 向树中加入进程: 发生在进程变为可运行态或fork() 调用第一次创建进程时。 enqueue_entity() 更新运行时间和其他统计数据。它调用__enqueue_entity() 插入数据。其中leftmost是记录最左节点的更新,新节点有一次右移leftmost=0 否则为1 ,更新缓存rb_leftmost指向被插入的进程。
  • 向树中删除进程: 发生在不可运行态或终止态 dequeue_entity() 调用__dequeue_entity()实操。若删除最左节点则rb_next()遍历找下一个节点,更新。
调度器入口

shedule() kernel/sched.c 选择哪个进程可运行,何时运行。 调用优先级最高的调度类,该调度类有自己的可运行队列,调度类选择下一个运行进程。schedule() - pick_next_task()(返回下一个可运行进程的指针) - _pick_next_entity() 选择高优先级的调度类 - 高优先级的进程

睡眠和唤醒

休眠:加入等待队列 add_wait_queue() -> 标记TASK_INTERRUPTBLE/ UNINTERRUPTIBLE -> 移除可执行rbtree deactivate_task()-> prepare_to_wait() -> schedule()其他进程
唤醒: 收到信号 -> 设置TASK_RUNNING -> finish_wait() -> 加入可执行rbtree activate_task() -> schedule() -> remove_wait-queue()

  • 等待队列: 简单链表,内核中用wake_queue_head_t 代表。 由DECLARE_WAITQUEUE()静态创建、init_wait_queue_head()动态创建
    休眠时,while循环等待条件发生,
  1. 定义宏DEFINE_WAIT()创建一个等待队列的项
  2. add_wait_queue() 加入队列。事件发生,wake_up()队列
  3. prepare_to_wait() 变更状态 TASK_UNITERRUPTIBLE/ TASK_INTERRUPTBLE
  4. TASK_INTERRUPTIBLE 收到信号伪唤醒,检查信号
  5. 唤醒。再次检查条件为真,是则退出等待循环,否则schedule()
  6. TASK_RUNNING finish_wait()出列

抢占和上下文切换

上下文切换context_switch() kernel/sched.c -> schedule()
context_switch()->switch_mm() <asm/mmu_context.h> 将虚拟内存从上一个进程映射到新进程 -> switch_to() <asm/system.h> 从上一个进程的处理器状态切换到新进程处理器状态。保存、恢复栈信息和寄存器信息以及体系结构状态信息,以每个进程为对象进行管理和保存。
进程应被抢占时 scheduler_tick()设置need_resched标志
优先级高的进程可执行时 try_to_wake_up()设置need_reched标志
返回用户空间、中断返回时 内核也会检查need_resched
每当有进程可执行(或者状态改变)时都有函数检查need_resched标志

用户抢占
  • 从系统调用返回用户空间时
  • 从中断处理程序返回用户空间时
    返回路径在entry.S 汇编语言实现。 文件包含内核入口以及内核退出部分代码
内核抢占

不具备抢占的内核各任务以协作方式调度,内核代码一直执行到完成(返回用户空间)或明显的阻塞为止;
抢占式内核只要重新调度是安全的,内核就可以任何时间抢占
只要没有持有锁,内核就可以抢占
在进程的thread_info中引入preempt_count计数器初始为0 持有一个锁+1 释放锁-1
need_resched=1 preempt_count=0 可抢占
need_resched=1 preempt_count>0 不可抢占
当时preempt_count释放锁=0时 检查need_reched

  • 中断处理程序正在执行,且返回内核空间之前
  • 内核代码再次具备可抢占性时
  • 内核显式调用schedule()
  • 内核任务阻塞 也会调用schedule()

实时调度策略

kernel/sched_rt.c

  • SCHED_FIFO 无时间片,只受优先级控制
  • SCHED_RR 有时间片的FIFO 优先级高的时间片耗尽再运行下一个
    以上两种的静态优先级,不互相抢占 [0 , MAX_RT_PRIO -1 ] [0,99]
    SCHED_NORMAL 普通非实时 nice[MAX_RT_PRIO, MAX_RT_PRIO+40) [100,139]对应[-20 , 19]

与调度有关的系统调用

与调度策略和优先级相关的系统调用

sched_setscheduler()
sched_getscheduler()改写task_struct的policy和rt_priority
sched_setparam() 实时优先级
sched_getparam() sched_param结构体的rt_priority
sched_get_priority_max()
sched_get_priority_min() 返回给定调度策略最大最小优先级 实时调度策略的优先级[1, MAX_USER_RT_PRIO -1]
对于普通进程nice()给静态优先级增加一个给定的量。超级用户可以使其负值。nice()调用set_user_nice()设置进程的task_struct的static_prio和prio

与处理器绑定有关的系统调度

processor affinity 亲和性 使进程尽量在一个处理器上运行,也可强制指定task_struct 的cpus_allowed 位掩码标志。每位对应一个系统可用的处理器。默认所有位都被设置。 sched_setaffinity()设置不同的一个或几个位组合的位掩码。sched_getaffinity()返回cpus_allowed位掩码

  • 线程在刚创建时,继承父进程的掩码,使用父进程的处理器。当处理器绑定关系改变时“移植线程”任务推到合法的处理器。
放弃处理器时间

sched_yield() 活动队列 -> 过期队列(一段时间内不会再被运行)
实时进程不会过期,-> 优先级队列最后面
内核yield() 用户空间sched_yield()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值