当前位置:首页 > 数码 > 内核调度器源码解析-Linux-从调度入口到挑选下一个进程 (内核调度器源码是什么)

内核调度器源码解析-Linux-从调度入口到挑选下一个进程 (内核调度器源码是什么)

admin6个月前 (05-10)数码62

在 Linux 内核中,调度器扮演着至关重要的角色,决定了哪个进程将获得 CPU 的执行时间。本文将深入剖析内核中调度器的代码实现,从入口函数开始,一步步分析如何选择下一个要执行的进程。让我们一同揭开这个内核之谜。

调度器入口

Linux 调度器入口函数定义在 kernel/sched/core.c 中:

asmlinkage__visible void __schedschedule(void) {
  // 获取当前任务结构体的指针
  struct task_struct tsk = current;
  // 将任务提交到调度工作队列中
  sched_submit_work(tsk);
  // 进入调度循环,直到没有需要被调度的任务
  do {
    // 禁用抢占
    preempt_disable();
    // 调用实际的调度函数 __schedule,并传入调度策略参数 SM_NONE
    __schedule(SM_NONE);
    // 启用抢占,但不进行重新调度
    sched_preempt_enable_no_resched();
  } while (need_resched());
  // 循环直到没有需要重新调度的任务
  // 更新工作队列中的任务状态
  sched_update_worker(tsk);
}
EXPORT_SYMBOL(schedule);
  

调度器的入口函数是 schedule ,首先获取当前任务结构体的指针,然后将任务提交到调度工作队列中,接着进入一个循环,该循环会禁用抢占,调用实际的调度函数 __schedule ,并在循环结束后启用抢占。循环会一直执行,直到没有需要重新调度的任务为止。最后,函数会更新工作队列中任务的状态。函数最后 export 导出 schedule 函数以供其他部分使用。

实际调度函数

这里, __schedule 函数负责实际的调度操作。它获取了当前任务结构体的指针( prev )、运行队列( rq )以及切换计数器( switch_count )。通过调用 pick_next_task 函数,它选择下一个要运行的进程( next )。最后,通过 context_switch 函数,它进行进程切换,将 CPU 控制权移交给下一个进程。

static void __sched__schedule(bool preempt) {
  struct task_struct prev, next;
  unsigned long switch_count;
  struct rq rq;
  prev = current;
  rq = this_rq();
  switch_count = &prev->nivcsw;
  // 获取下一个要运行的进程
  next = pick_next_task(rq);
  // 切换到下一个进程
  context_switch(rq, prev, next, switch_count);
  // 如果需要抢占,启用抢占
  if (preempt)
    need_resched();
}
  

具体如何挑选下一个需要运行的进程,就要扒开 pick_next_task 函数。

选择下一个进程

pick_next_task 函数负责选择下一个要运行的进程。它主要根据以下规则进行选择:

  1. 如果存在可运行的实时任务,则选择优先级最高的实时任务。
  2. 否则,如果存在可运行的一般进程,则选择优先级最高的可运行一般进程。
  3. 否则,如果存在可运行的空闲任务,则选择优先级最高的可运行空闲任务。
  4. 否则,返回 NULL

以下是 pick_next_task 函数的代码:

static inline struct task_struct __pick_next_task(struct rq rq,
                                                 struct task_struct prev,
                                                 struct rq_flags rf) {
  const struct sched_class class;
  struct task_struct p;

  // 优化:如果前一个任务是公平调度类中的任务,且运行队列中的任务数与 CFS 队列中的任务数相等,
  // 则可以直接选择下一个公平类任务,因为其他调度类的任务无法抢占 CPU。
  if (likely(!sched_class_above(prev->sched_class, &fr_sched_class) &&
      rq->nr_running == rq->cfs.nr_running)) {
    p = pick_next_task_fair(rq);
    if (p)
      return p;
  }

  // 遍历所有调度类,选择优先级最高的可运行进程
  for_each_class(class) {
    if ((p = class->pick_next_task(rq, prev, rf)))
      return p;
  }

  return NULL;
}
  

在实际操作中,Linux 内核使用了一个层次化的调度器模型,其中每个调度类负责管理其自己的任务队列。例如,公平调度器管理着可运行的一般进程,实时调度器管理着可运行的实时任务,空闲调度器管理着可运行的空闲任务。

以上就是 Linux 内核调度器的代码实现的简要概述。这是一个复杂且重要的子系统,在保证系统性能和公平性方面发挥着至关重要的作用。


深入解析Linux系统下的进程切换

Linux内核下进程切换Linux切换并没有使用X86CPU的切换方法,Linux切换的实质就是cr3切换(内存空间切换,在switch_mm函数中)+ 寄存器切换(包括EIP,ESP等,均在switch_to函数中)。 这里我们讲述下switch_to主流程:1、 在switch_mm函数中将new_task-pgd设置到cr3寄存器中,实现页表切换,由于每个进程3-4G的页表映射机制完全一样(从内核页表中直接复制过来的),故这里虽然切换了pgd,但是并无影响,只是在任务回到用户空 间中时,才会发生变化,因为每个任务在0-3G中的页表映射都是各自独立的;2、 压入esi edi ebp到cur_task堆栈中;3、 将esp寄存器中的值保存到cur__中,也就是将cur_task切换时的堆栈指针保存起来;4、 将new__中的值设置到esp寄存器中,这里的new__中的值就是new_task上一次被换出时的堆栈指针,现在被恢复了,2和3结合实现了从cur_task到new_task的堆栈切换;5、 将1f地址设置到cur__中,当下次cur_task恢复运行时,将会从1f处开始运行,下面阐述了这种原理;6、 将new__压入到new_task的堆栈中,这里new__的值就是1f,因为从4中可知,new_task上一次被换出时,其也是和现在的cur_task类似,1f地址被设置到new__中;7、 随后CPU跳转到__switch_to函数中开始执行,注意这里使用的是jmp,不是call,call会pusheip,而jmp不会,由于__switch_to是函数,当CPU执行完该函数后,最后一条指令必然为iret,该指令会popeip,从5中可以知道,此时new_task堆栈中的镜像为[......., esi,edi,ebp,eip(1f)],故popeip将值eip(1f)设置到eip寄存器中,这样当iret执行完毕后,CPU将从eip处继续执行,也就是从1f处继续执行;8、 此时已经在new_task的执行环境中了,pop ebp, pop edi, popesi,回到schedule函数中,当返回用户空间中时,由于new_task用户空间的eip,ss,esp等均被从new_task的堆栈中弹出到对应寄存器中,从而new_task得以顺利执行。 Linux 前后台进程切换当你用shell启动一个程序时,往往他是在前台工作的。 例如经常用PUTTY连接到远程服务器执行脚本的时候,如果本地网络中断后,这个时候前台进程就结束了,比较的懊恼,必须重新执行。 因此有必要进行前后台进程的切换。 例如直接在终端里输入firefox,那么会打开firefox,但当你关闭此终端或者ctrl+c强制终止时,firefox也随机关闭了。 你可以在执行时后面加一个,这样就在后台工作了。 Shell支持作用控制,有以下命令:(1). command 让进程在后台运行(2). jobs –l 查看后台运行的进程(3). fg %n 让后台运行的进程n到前台来(4). bg %n 让进程n到后台去;PS:n为jobs查看到的进程编号。 1、执行命令切换至后台在Linux终端运行命令的时候,在命令末尾加上符号,就可以让程序在后台运行代码如下:root@Ubuntu$ ./tcpserv012、切换正在运行的程序到后台如果程序正在前台运行,可以使用Ctrl+z 选项把程序暂停,然后用 bg %[number]命令把这个程序放到后台运行,这个步骤分为3步,如下:2.1暂停程序运行CTRL+Zctrl + z跟系统任务有关的,ctrl + z可以将一个正在前台执行的命令放到后台,并且暂停。 代码如下:[Oracle@linuxidc ~]$ sh [1]+Stopped 2.2查看暂停的程序察看jobs使用jobs或ps命令可以察看正在执行的jobs。 代码如下:[oracle@linuxidc ~]$ jobs -l[1]+ 4524Stopped 命令执行的结果,+表示是一个当前的作业,减号表是是当前作业之后的一个作业。 jobs -l选项可显示所有任务的PID,jobs的状态可以是running, stopped,Terminated2.3切换程序至后台bg将一个在后台暂停的命令,变成继续执行如果后台中有多个命令,可以用bg %jobnumber将选中的命令调出.代码如下:[oracle@linuxidc ~]$ bg %1[oracle@linuxidc ~]$ jobs -l[1]+ 4524Running 2.4切换程序至前台也可以用 fg %[number]指令把一个程序掉到前台运行代码如下:[oracle@linuxidc ~]$ fg %1./tcpserv012.5终止后台程序也可以直接终止后台运行的程序,使用 kill 命令代码如下:[oracle@linuxidc ~]$ kill %1但是如果任务被终止了(kill),shell 从当前的shell环境已知的列表中删除任务的进程标识;也就是说,jobs命令显示的是当前shell环境中所起的后台正在运行或者被挂起的任务信息。

Linux系统的进程调度

Linux进程调度

1.调度方式

Linux系统的调度方式基本上采用“ 抢占式优先级 ”方式,当进程在用户模式下运行时,不管它是否自愿,核心在一定条件下(如该进程的时间片用完或等待I/O)可以暂时中止其运行,而调度其他进程运行。一旦进程切换到内核模式下运行时,就不受以上限制,而一直运行下去,仅在重新回到用户模式之前才会发生进程调度。

Linux系统中的调度基本上继承了UNIX系统的 以优先级为基础 的调度。也就是说,核心为系统中每个进程计算出一个优先级,该优先级反映了一个进程获得CPU使用权的资格,即高优先级的进程优先得到运行。核心从进程就绪队列中挑选一个优先级最高的进程,为其分配一个CPU时间片,令其投入运行。在运行过程中,当前进程的优先级随时间递减,这样就实现了“负反馈”作用,即经过一段时间之后,原来级别较低的进程就相对“提升”了级别,从而有机会得到运行。当所有进程的优先级都变为0(最低)时,就重新计算一次所有进程的优先级。

2.调度策略

Linux系统针对不同类别的进程提供了3种不同的调度策略,即SCHED_FIFO、SCHED_RR及SCHED_OTHER。其中,SCHED_FIFO适合于 短实时进程 ,它们对时间性要求比较强,而每次运行所需的时间比较短。一旦这种进程被调度且开始运行,就一直运行到自愿让出CPU或被优先级更高的进程抢占其执行权为止。

SCHED_RR对应“时间片轮转法”,适合于每次运行需要 较长时间的实时进程 。一个运行进程分配一个时间片(200 ms),当时间片用完后,CPU被另外进程抢占,而该进程被送回相同优先级队列的末尾,核心动态调整用户态进程的优先级。这样,一个进程从创建到完成任务后终止,需要经历多次反馈循环。当进程再次被调度运行时,它就从上次断点处开始继续执行。

SCHED_OTHER是传统的UNIX调度策略,适合于交互式的 分时进程 。这类进程的优先级取决于两个因素:一个是进程剩余时间配额,如果进程用完了配给的时间,则相应优先级降到0;另一个是进程的优先数nice,这是从UNIX系统沿袭下来的方法,优先数越小,其优先级越高。nice的取值范围是-20 19。用户可以利用nice命令设定进程的nice值。但一般用户只能设定正值,从而主动降低其优先级;只有特权用户才能把nice的值设置为负数。进程的优先级就是以上二者之和。

后台命令对应后台进程(又称后台作业)。后台进程的优先级低于任何交互(前台)进程的优先级。所以,只有当系统中当前不存在可运行的交互进程时,才调度后台进程运行。后台进程往往按批处理方式调度运行。

3.调度时机

核心进行进程调度的时机有以下5种情况:

(1)当前进程调用系统调用nanosleep( )或者pause( ),使自己进入睡眠状态,主动让出一段时间的CPU的使用权。

(2)进程终止,永久地放弃对CPU的使用。

(3)在时钟中断处理程序执行过程中,发现当前进程连续运行的时间过长。

(4)当唤醒一个睡眠进程时,发现被唤醒的进程比当前进程更有资格运行。

(5)一个进程通过执行系统调用来改变调度策略或者降低自身的优先级(如nice命令),从而引起立即调度。

4.调度算法

进程调度的算法应该比较简单,以便减少频繁调度时的系统开销。Linux执行进程调度时,首先查找所有在就绪队列中的进程,从中选出优先级最高且在内存的一个进程。如果队列中有实时进程,那么实时进程将优先运行。如果最需要运行的进程不是当前进程,那么当前进程就被挂起,并且保存它的现场—— 所涉及的一切机器状态,包括程序计数器和CPU寄存器等,然后为选中的进程恢复运行现场。

(二)Linux常用调度命令

· nohup命令

nohup命令的功能是以忽略挂起和退出的方式执行指定的命令。其命令格式是:

nohup command [arguments]

其中,command是所要执行的命令,arguments是指定命令的参数。

nohup命令告诉系统,command所代表的命令在执行过程中不受任何结束运行的信号(hangup和quit)的影响。例如,

$ nohup find / -name -print>f1 &

find命令在后台运行。在用户注销后,它会继续运行:从根目录开始,查找名字是的文件,结果被定向到文件f1中。

如果用户没有对输出进行重定向,则输出被附加到当前目录的文件中。如果用户在当前目录中不具备写权限,则输出被定向到$HOME/ 中。

· at命令

at命令允许指定命令执行的时间。at命令的常用形式是:

at time command

$ at 15:00 Oct 20

回车后进入接收方式,接着键入以下命令:

mail -s Happy Birthday! liuzheny

按下D键,屏幕显示:

job .a at Wed Oct 20 15:00:00 CST 1999

表明建立了一个作业,其作业ID号是.a,运行作业的时间是1999年10月20日下午3:00,给liuzheny发一条标题为“Happy Birthday!”(生日快乐)的空白邮件。

内核调度器源码是什么

利用 at -l 可以列出当前at队列中所有的作业。

利用 at -r 可以删除指定的作业。这些作业以前由at或batch命令调度。例如,

将删除作业ID号是.a的作业。其一般使用形式是:

at -r job_id

注意,结尾是.a的作业ID号,表示这个作业是由at命令提交的;结尾是.b的作业ID号,表示这个作业是由batch命令提交的。

· batch命令

batch命令不带任何参数,它提交的作业的优先级比at命令提交的作业的优先级低。batch无法指定作业运行的时间。实际运行时间要看系统中已经提交的作业数量。如果系统中优先级较高的作业比较多,那么,batch提交的作业则需要等待;如果系统空闲,则运行batch提交的作业。例如,

回车后进入接收方式,接着键入命令:

find / -name -print

按下D。退出接收方式,屏幕显示:

job .b at Thu Nov 18 14:30:00 CST 1999

表示find命令被batch作为一个作业提交给系统,作业ID号是.b。如果系统当前空闲,这个作业被立即执行,其结果同样作为邮件发送给用户。

· jobs命令

jobs命令用来显示当前shell下正在运行哪些作业(即后台作业)。例如:

[2] + Running tar tv3 *&

[1] - Running find / -name README -print > logfile &

其中,第一列方括号中的数字表示作业序号,它是由当前运行的shell分配的,而不是由操作系统统一分配的。在当前shell环境下,第一个后台作业的作业号为1,第二个作业的作业号为2,等等。

第二列中的“ ”号表示相应作业的优先级比“-”号对应作业的优先级高。

第三列表明作业状态,是否为运行、中断、等待输入或停止等。

最后列出的是创建当前这个作业所对应的命令行。

利用 jobs -l 形式,可以在作业号后显示出相应进程的PID。如果想只显示相应进程的PID,不显示其它信息,则使用 jobs -p 形式。

· fg命令

fg命令把指定的后台作业移到前台。其使用格式是:

其中,参数job是一个或多个进程的PID,或者是命令名称或者作业号(前面要带有一个“%”号)。例如:

[2] + Running tar tv3 *&

[1] - Running find / -name README -print > logfile&

find / -name README -print > logfile

注意,显示的命令行末尾没有“&”符号。下面命令能产生同样的效果:

这样,find命令对应的进程就在前台执行。当后台只有一个作业时,键入不带参数的fg命令,就能使相应进程移到前台。当有两个或更多的后台作业时,键入不带参数的fg,就把最后进入后台的进程首先移到前台。

· bg命令

bg命令可以把前台进程换到后台执行。其使用格式是:

其中,job是一个或多个进程的PID、命令名称或者作业号,在参数前要带“%”号。例如,在cc(C编译命令)命令执行过程中,按下Z键,使这个作业挂起。然后键入以下命令:

该挂起的作业在后台重新开始执行。

免责声明:本文转载或采集自网络,版权归原作者所有。本网站刊发此文旨在传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及版权、内容等问题,请联系本网,我们将在第一时间删除。同时,本网站不对所刊发内容的准确性、真实性、完整性、及时性、原创性等进行保证,请读者仅作参考,并请自行核实相关内容。对于因使用或依赖本文内容所产生的任何直接或间接损失,本网站不承担任何责任。

标签: Linux

“内核调度器源码解析-Linux-从调度入口到挑选下一个进程 (内核调度器源码是什么)” 的相关文章

Linux-中创建文件的八种方法 (linux系统)

Linux-中创建文件的八种方法 (linux系统)

在 Linux 系统中,一切皆文件,高效创建文件的技能至关重要,可以节省大量工作时间。 方法 1:重定向符号 (>) 重定向符号用于创建一个 0KB 的空文件,或重定向命令的输出...

系统中内存管理和优化指南-有效处理内存问题-Linux (系统内存管理)

系统中内存管理和优化指南-有效处理内存问题-Linux (系统内存管理)

本文将详细介绍内存管理和优化的几个高级技巧,帮助系统管理员和开发人员更好地优化 Linux 系统的内存使用情况,提高系统性能和稳定性。 1. 页面置换策略 Linux 使用页面置换算法...

经常使用-grep-awk把握日志监控-中日志剖析神器-ail-Linux-和 (经常使用公共卫生间引起尿路感染)

经常使用-grep-awk把握日志监控-中日志剖析神器-ail-Linux-和 (经常使用公共卫生间引起尿路感染)

一个日志查问剖析的例子 名目颁布上线后,接上去须要做什么?开Party,庆贺名目上线。但是Party开到一半,服务运转出了点疑问。指导要你马上考查要素并给出适合的处置方法。名目才刚上线,日志...

Linux日常使用最常见的指令 (linux日志命令)

Linux日常使用最常见的指令 (linux日志命令)

历史 Linux 系统创建于 1991 年,由芬兰大学生 Linus Torvalds 开发。它是一个开源操作系统,这意味着它的源代码可以免费使用和修改。由于其高度的可定制性和免费性,Lin...

嵌入式Linux系统开发 (嵌入式linux)

嵌入式Linux系统开发 (嵌入式linux)

什么是嵌入式系统 嵌入式系统是一种以微处理器为核心的、专门针对某一特定应用设计的计算机系统。它具有体积小、功耗低、成本低、稳定性高、抗干扰能力强等特点。 嵌入式系统操作 嵌入式系...

如何管理数据存储-深入剖析Linux中的磁盘和文件系统 (如何管理数据库团队)

如何管理数据存储-深入剖析Linux中的磁盘和文件系统 (如何管理数据库团队)

简介 在Linux系统中,一切皆文件的概念意味着所有的资源,包括普通文件、目录以及设备文件等,都以文件的形式存在。这种统一的文件系统管理方式使得Linux系统具有高度的灵活性和可扩展性。本文将深...

Linux子系统概览

Linux子系统概览

Linux操作系统是一个模块化的系统,由多个子系统组成。这些子系统协同工作,使Linux能够执行各种任务。了解Linux的子系统有助于更好地理解整个操作系统的运作机制。 内核子系统...

新特性和改进-Linux-6.9 (新特性和改进的区别)

新特性和改进-Linux-6.9 (新特性和改进的区别)

Linus Torvalds has announced the release of Linux 6.9-rc1, the first Release Candidate (RC) for th...