加入收藏 | 设为首页 | 会员中心 | 我要投稿 东莞站长网 (https://www.0769zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 服务器 > 搭建环境 > Linux > 正文

Linux 2.4调度系统分析--转

发布时间:2021-01-24 14:04:24 所属栏目:Linux 来源:网络整理
导读:简介: ?本文详尽地分析了Linux 2.4内核中调度系统的工作原理,特别是i386体系结构下SMP系统的调度表现。通过对2.4调度系统实现原理及其细节的分析,文章在文末指出了2.4调度系统在功能上、实时性上以及多处理机系统表现上存在的不足,为后继的2.6系统的分析

以runqueue_head为表头的链表记录了所有处于就绪态的进程(当前正在运行的进程也在其中,但idle_task除外),调度器总是从中选取最适合调度的进程投入运行。

从一个进程的上下文切换到另一个进程的上下文,因为其发生频率很高,所以通常都是调度器效率高低的关键。在Linux中,这一功能是以一段经典的汇编代码实现的,此处就着力描述这段代码。

这段名为switch_to()的代码段在schedule()过程中调用,以一个宏实现:

thread.esp中 "movl %3,%%espnt" 从next->thread.esp恢复esp "movl $1f,%1nt" 在prev->thread.eip中保存"1:"的跳转地址, 当prev被再次切换到的时候将从那里开始执行 "pushl %4nt" 在栈上保存next->thread.eip,__switch_to()返回时将转到那里执行, 即进入next进程的上下文 "jmp __switch_ton" 跳转到__switch_to(),进一步处理(见下) "1:t" "popl %%ebpnt" "popl %%edint" "popl %%esint" 先恢复上次被切换走时保存的寄存器值, 再从switch_to()中返回。 :"=m" (prev->thread.esp),%0 "=m" (prev->thread.eip),%1 "=b" (last) ebx, 因为进程切换后,恢复的栈上的prev信息不是刚被切换走的进程描述符, 因此此处使用ebx寄存器传递该值给prev :"m" (next->thread.esp),%3 "m" (next->thread.eip),%4 "a" (prev),"d" (next),eax,edx "b" (prev)); ebx } while (0) 进程切换过程可以分成两个阶段,上面这段汇编代码可以看作第一阶段,它保存一些关键的寄存器,并在栈上设置好跳转到新进程的地址。第二阶段在switch_to()中启动,实现在__switch_to()函数中,主要用于保存和更新不是非常关键的一些寄存器(以及IO操作许可权映射表ioperm)的值:

  • unlazy_fpu(),如果老进程在task_struct的flags中设置了PF_USEDFPU位,表明它使用了FPU,unlazy_fpu()就会将FPU内容保存在task_struct::thread中;
  • 用新进程的esp0(task_struct::thread中)更新init_tss中相应位置的esp0;
  • 在老进程的task_struct::thread中保存当前的fs和gs寄存器,然后从新进程的task_struct::thread中恢复fs和gs寄存器;
  • 从新进程的task_struct::thread中恢复六个调试寄存器的值;
  • 用next中的ioperm更新init_tss中的相应内容

switch_to()函数正常返回,栈上的返回地址是新进程的task_struct::thread::eip,即新进程上一次被挂起时设置的继续运行的位置(上一次执行switch_to()时的标号"1:"位置)。至此转入新进程的上下文中运行。

在以前的Linux内核中,进程的切换使用的是far jmp指令,2.4采用如上所示的手控跳转,所做的动作以及所用的时间均与far jmp差不多,但更利于优化和控制。

Linux schedule()函数将遍历就绪队列中的所有进程,调用goodness()函数计算每一个进程的权值weight,从中选择权值最大的进程投入运行。

进程调度权值的计算分为实时进程和非实时进程两类,对于非实时进程(SCHED_OTHER),影响权值的因素主要有以下几个:

1. 进程当前时间片内所剩的tick数,即task_struct的counter值,相当于counter越大的进程获得CPU的机会也越大,因为counter的初值与(-nice)相关,因此这一因素一方面代表了进程的优先级,另一方面也代表了进程的"欠运行程度";(weight = p->counter;)

2. 进程上次运行的CPU是否就是当前CPU,如果是,则权值增加一个常量,表示优先考虑不迁移CPU的调度,因为此时Cache信息还有效;(weight += PROC_CHANGE_PENALTY;)

3. 此次切换是否需要切换内存,如果不需要(或者是同一进程的两个线程间的切换,或者是没有mm属性的核心线程),则权值加1,表示(稍微)优先考虑不切换内存的进程;(weight += 1;)

4. 进程的用户可见的优先级nice,nice越小则权值越大。(Linux中的nice值在-20到+19之间选择,缺省值为0,nice()系统调用可以用来修改优先级。)(weight += 20 - p->nice;) 对于实时进程(SCHED_FIFO、SCHED_RR),权值大小仅由该进程的rt_priority值决定(weight = 1000 + p->rt_priority;),1000的基准量使得实时进程的权值比所有非实时进程都要大,因此只要就绪队列中存在实时进程,调度器都将优先满足它的运行需要。

如果权值相同,则选择就绪队列中位于前列的进程投入运行。

除了以上标准值以外,goodness()还可能返回-1,表示该进程设置了SCHED_YIELD位,此时,仅当不存在其他就绪进程时才会选择它。

如果遍历所有就绪进程后,weight值为0,表示当前时间片已经结束了,此时将重新计算所有进程(不仅仅是就绪进程)的counter值,再重新进行就绪进程选择(详见"调度器工作流程")。

Linux的调度器主要实现在schedule()函数中。

schedule()函数的基本流程可以概括为四步:

1). 清理当前运行中的进程

2). 选择下一个投入运行的进程

3). 设置新进程的运行环境

4). 执行进程上下文切换

5). 后期整理

其中包含了一些锁操作:就绪队列锁runquque_lock,全局核心锁kernel_flag,全局中断锁global_irq_lock,进程列表锁tasklist_lock。下面先从锁操作开始描述调度器的工作过程。

(编辑:东莞站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!