《Linux内核设计与实现》读书笔记—进程调度

本文介绍Linux操作系统中的进程调度原理,包括抢占式多任务模式、CFS公平调度器的工作机制、实时调度策略以及与调度相关的系统调用等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

进程调度

  1. 进程调度程序决定将哪个进程投入运行,何时运行以及运行多长时间。进程调度程序可看做在可运行态进城之间分配有限的处理器时间资源的内核子系统。
  2. 多任务系统可以划分为两类:非抢占式和抢占式。Linux提供了抢占式的多任务模式,在此模式下,由调度程序来决定什么时候停止一个进程的运行,以便其他进程能够得到执行机会,这个强制的挂起动作就叫做抢占。
  3. 当今众多现代操作系统对程序运行都采用了动态时间片计算的方式,并且引入了可配置的计算策略。不过Linux独一无二的"公平"调度程序本身并没有采取时间片来达到公平调度。

 

调度策略

  1. 进程可以被分为I/O消耗型和处理器消耗型。前者指进程的大部分时间用来提交I/O请求,这样的进程经常处于可运行状态,但调度时间较短。后者指进程的大部分时间用在执行代码上,这样的进程应该减少调度频域,延长其运行时间。
  2. 调度策略通常要在两个矛盾的目标中间寻找平衡:进程响应迅速(响应时间短)和最大系统利用率(高吞吐量)
  3. Linux才用了两种不同的优先级范围。
  • 第一种用nice值,它的范围是从-20到+19,默认值为0;越大的nice值意味着越低得的优先级。相比高nice值的进程,低nice值的进程可以获得更多的处理器时间。在Linux系统中,nice值代表时间片的比例。
  • 第二种是实时优先级,其值是可配置的。默认范围是0~99。与nice值意义相反,越高的数值代表进程优先级越高。任何实时进程的优先级都高于普通进程,也就是说实时优先级和nice值处于互不相交的两个范畴
  1. Linux的CFS调度器并没有直接分配时间片到进程,而是将处理器的使用比划分给了进程。这样一来,进程所获得的处理器时间其实是和系统负载密切相关的。这个比例进一步还会受到进程nice值的影响,nice值作为权重将调整进程所使用的处理器时间使用比。
  2. 在多数操作系统中,是否要将一个进程立刻投入允许(将占其他进程),是完全由该进程的优先级和是否有时间片决定的。而在Linux中使用新的CFS调度器,其抢占时机取决于新的可运行程序消耗了多少处理器使用比。如果消耗的使用比比当前进程小,则新进程立刻投入运行,抢占当前进程。否则推迟运行。

 

调度器算法

  1. Linux调度器是以模块方式提供的,这样做的目的是允许不同类型的进程可以有针对性地选择调度算法,这种模块化的结构被称为调度器类。
  2. 每个调度器都有一个优先级,基础调度器会按照优先级顺序遍历每一个调度器类,拥有一个可执行进程的最高优先级的调度器类胜出
  3. CFS是一个针对普通进程的调度器类,它的设计思路是为了解决“给每个进程分配 一个绝对的时间片会引发固定的切换频率,给公平性造成了很大的变数”这个问题。因此CFS给每个进程分配一个处理器使用比重。CFS确保了进程调度中能有恒定的公平性。

 

CFS调度算法

  1. CFS的出发点基于一个简单的理念:进程调度的效果应如同系统具备一个理想中的完美多任务处理器。在这种系统中,每个进程将能获得1/n的处理器时间,n指的是可运行进程的数量。同时我们可以调度给进程无限小的时间周期,所以在任何可测量周期内,我们给与n个进程统同样的运行时间。
  2. nice值对时间片的作用不是算数加权,而是几何加权。任何进程所获得处理器时间是由它自己和其他所有可运行进程nice值的相对差之决定的。
  3. CFS使用结构体struct Sched_entity来追踪进程,运行时间记账。struct sched_entity在struct task_struct中已作为成员变量已se命名保存
  4. CFS使用sched_entity结构体中的vruntime变量来记录一个程序到底运行了多长时间,以及它还要再运行多久。vruntime的计算是经过了所有可运行进程总数的标准化,以ns为单位,所以vruntime和定时器节拍不再相关。
  5. CFS的核心在于尽可能的保证每个进程的vruntime相等,因此CFS总是选择具有最小vruntime的任务。
  6. CFS使用红黑树来组织可运行进程队列,并利用其迅速找到最小vruntime值的进程。当进程变为可运行状态或是通过fork()调用第一次创建进程时,CFS将进程添加到红黑树中。当进程堵塞或者终止时,CFS从红黑树中删除进程。

 

抢占和上下文切换

  1. 上下文切换主要由函数context_switch()函数负责处理。它完成了两项基本的工作:switch_mm()函数负责把虚拟内存从上一个进程映射切换到新进程中。switch_to()负责从上一个进程的处理器状态切换到新进程的处理状态。
  2. 内核通过need_resched标志来表明是否需要重新执行一次调度,该标志对内核来讲,意味着有其他进程应当被运行了,需要尽快调用调度程序。
  3. 用户抢占发生在内核返回用户空间的时候,如果need_resched标志被设置,则调用schedule()。用户抢占发生于一下两种情况:从系统调用返回用户空间;从中断处理程序返回用户空间。
  4. 与其他操作系统不同,Linux完整的支持内核抢占。只要重新调度是安全的,内核就可以在任何时间抢占正在执行的任务。那么什么时候重新调度才是安全的呢?只要没有持有锁,内核就可以进行抢占。内核抢占发生在:中断处理程序正在执行,返回内核空间之间;内核代码再一次具有可抢占性的时候;内核的任务显式地调用了schedule();内核中的任务阻塞。

 

实时调度策略

  1. Linux提供了两种实时调度策略:SCHED_FIFO和SCHED_RR。而普通的、非实时的调度策略是SCHED_NORMAL。这些实时策略不被CFS管理,而是被一个实时调度器管理。
  2. 实施进程比任何优先级的普通进程优先级更高,SCHED_FIFO和SCHED_RR相比,主要的区别在于SCHED_RR有时间片,当时间片耗尽时,同一优先级的其他实时进程被轮流调度。而同优先级的两个SCHED_FIFO进程,它们轮流调度,知道一方调完或者主动让出处理器时才会退出。

 

调度相关的系统调用

  1. sched_setscheduler()和sched_getscheduler()分别用于设置和获取进程的调度策略和实时优先级。
  2. sched_setparam()和sched_getparam()分别用于设置和获取进程的实时优先级。
  3. sched_get_priority_min()和sched_get_priority_max()分别用于获取给定调度策略的最大和最小优先级。
  4. nice()函数可以讲给定进程的静态优先级增加一个给定的量。
  5. sched_setaffinity()和sched_getaffinity()分别用来设置和获取进程的处理器亲和力,即指定进程在某些特定的处理器上执行。
  6. sched_yield()使进程可以显式地将处理器让出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值