ImageVerifierCode 换一换
格式:DOCX , 页数:28 ,大小:109.31KB ,
资源ID:28172263      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/28172263.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(LINUX进程调度机制及其堆栈切换分析精.docx)为本站会员(b****8)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

LINUX进程调度机制及其堆栈切换分析精.docx

1、LINUX进程调度机制及其堆栈切换分析精LINUX2.4.18进程调度机制及其堆栈切换分析张华强2004-081 LINUX 调度机制概述LINUX 的进程调度机制的设计主要是从下面三个方面来考虑的。调度的时机, LINUX 的进程调度分为主动调度和被动的调度。其中主动的调度在内核 空间通过调用 schedule (函数来启动一次调度。在用户空间则是通过调用延时、睡眠、退 出等主动放弃运行权的系统调用接口进入内核空间并最终通过调用 schedule (函数来是启 动一次调度。而被动的调度则发生每次处理中断或异常后从系统空间切换到用户空间的前 夕,或者经过其他的系统调用返回到用户空间的前夕。调度

2、的策略,为了适应各种不同的应用, LINUX 在从 UNIX 上继承下来的基于优先级 的调度策略的基础上实现了三种调度策略:SCHED_FIFO、 SCHED_RR和 SCHED_OTHER。 其中的 SCHE_FIFO和 SCHED_RR都是实时调度的策略,他们在系统中被赋予了至少 1000的优先权值。 二者不同的地方在于对待同等优先权值的进程的处理方式上的差别。 当系统中 有多个同等优先级的 SCHED_FIFO进程就绪时,按照先入先出的原则只有当前进程运行到 自动放弃 CPU 的权限时才会让同等优先级的另一个进程调度起来运行。 而对于 SCHED_RR的进程, 则会在每次时钟中断到来时检

3、查先前进程的时间片是否用完, 如果用完则剥夺当前 进程的 CPU 使用权调度出同等优先级的另一个 SCHED_RR进程运行。 SCHED_OTHER则 为传统的调度策略,主要用于交互式应用。调度的方式:LINUX 内核的调度采用有条件的可剥夺方式, 当进程在用户空间运行时, 一旦有必要, 例如发生了外部中断或者进程的时间片到时, 内核都可以剥夺当前进程的运行 权而调度其他的进程运行, 因而说这种调度方式是可剥夺的, 但当进入内核态时, 即使内核 知道应该调度了, 但这种调度并不会立即发生, 只有当前进程回到用户空间的前夕这种调度 才会发生。根据上面的分析可以得出以下几点结论:1、 LINUX

4、的进程调度与中断是密不可分的(即使是系统调用也是通过软件中断 INT 0x80来实现的 。2、由于不论是主动的调度还是被动的调度最终都是通过在内核空间中调用 schedule 函 数来实现的,因此 LINUX 的进程切换一定是在系统空间中完成的,在下面的分析中还可以 看到这个过程实际上就是在 schedule 函数中完成的。2 LINUX2.4.18调度时的堆栈切换分析基于以上两点我们假设系统中目前有两个进程,进程 1和进程 2,进程 1正在运行。下 面我们就分别分析在被动调度(中断发生了和主动调度(调用 nanosleep 时系统的堆栈 切换情况和进程的调度实现。2.1 被动调度时的堆栈切换

5、分析首先我们分析被动调度的情况。 假设进程 1正在用户空间运行的过程中来了一个外设的 中断请求。根据 CPU 对中断的响应规则, CPU 会在进程的当前指令执行完毕后相应中断。 CPU 响应中断的过程如下描述。 由于在 LINUX 中只使用了 CPU 的 0和 3这两个运行级别, 因此在以下的讨论中涉及到 CPU 的运行级别时只考虑这两个运行级。首先 CPU 从中断控制器中取得中断向量,然后根据该向量重 IDT 中取出一个初始化好 的中断门并试图穿过该中断门进入相应得中断服务程序中。在穿过中断门的过程中,根据 INTEL IA-32体系结构中设计的硬件机制, CPU 将作如下工作:首先 CPU

6、 将其当前运行级 别与中断门中要求的运行级别进行比较,如果发现 CPU 的当前运行级别低于中断门的描述 符运行级别(即 CPL=3,中断门的 DPL=0时, CPU 首先会将由 TR 寄存器选择的 TSS 段中 的 SS0和 ESP0(运行级别 0的堆栈 装入 CPU 的 SS 和 ESP 中,从后面的分析可知, SS0和 ESP0一定对应的是当前运行任务的系统堆栈指针。通过这样就完成了用户空间堆栈到系统 空间堆栈的切换。堆栈切换完成之后, CPU 紧接着就将当前进程的用户空间堆栈的 SS 和 ESP 值压入新的堆栈中,然后在将当前的 EFLAGS 值和 CS 、 EIP 值,这些就对应当前任

7、务 被中断时的 CPU 标志寄存器值和中断处理完成后返回当前进程的地址。 如果发现 CPU 的当 前运行级和描述符的运行级一致则不进行堆栈切换而直接将 EFLAGS 、 CS 、 EIP 的值压入堆 栈中。 不管上述哪种情况, 穿过中断门后的堆栈都会是当前任务的系统空间堆栈。 由于中断 发生在用户空间,因此中断发生时的 CPL=3,因而必然会发生堆栈切换。因此进入中断服 务入口处时系统堆栈情况如图 1所示。 图 1 进入中断服务程序入口处的系统堆栈从后面的分析可知由于 CPU 每次使用内核堆栈时对其所作的操作都是均衡的,因此每 次从用户空间堆栈切换到系统空间堆栈时,该堆栈都是空的。穿过中断门后

8、系统进入中断服务程序的入口。在 LINUX 系统中所有中断服务程序的总 入口都是由 gcc 预生成的,具有如下形式:asmlinkage void IRQ0xYY_interrupt(;_asm_(n“ IRQ_0xYY_interrupt:ntpushl $#nr-256nt jmp common_interrupt;上述代码中的 YY 代表外部中断 0-254的 16进制值 (其中系统调用 0x80除外, 它是由 系 统 另 外 单 独 初 始 化 的 。 根 据 上 述 代 码 可 以 看 出 所 有 的 外 部 中 断 都 是 统 一 进 入 common_interrup的地方进行处

9、理的。同样该段由 gcc 预处理出来的代码如下:asmlinkage void call_do_IRQ(void;_asm_(n _ALIGN_STRncommon_interrupt:ntSA VE_ALLcall_do_IRQ:ntcall do_IRQ ntjmp ret_from_intrn;上述代 码的主 要作 用就是 通过 SA VE_ALL宏将 CPU 的 当前 寄存器 内容 入栈。 SA VE_ALL宏的定义如下:#define SAVE_ALL cldnt pushl %esnt pushl %dsnt pushl %eaxnt pushl %ebpnt pushl %edi

10、nt pushl %esint pushl %edxnt pushl %ecxnt pushl %ebxnt movl $ STR(_KERNEL_DS ,%edxnt movl %edx,%dsnt movl %edx,%esnt保护完 CPU 的寄存器,就通过一条 CALL 指令调用 do_IRQ进入真正的中断服务程序 中。这样 CPU 在进入中断服务程序 do_IRQ时系统的堆栈情况如图 2所示:图 2 进入中断服务程序 do_IR时的中断 从图 2可知,当系统处理完中断从 do_IRQ(函数返回时将跳转到 ret_from_intr处开 始执行代码。 (由于系统设计使得在中断服务过程中

11、不会调用 schedule(函数产生任务切换。 因而中断服务结束后一定会返回到图 2中的返回点 。图 3 中断返回后的堆栈其中的 ret_from_intr处的代码如下:ret_from_intr:GET_CURRENT(%ebxret_from_exception:movl EFLAGS(%esp,%eax # mix EFLAGS and CSmovb CS(%esp,%altestl $(VM_MASK | 3,%eax # return to VM86 mode or non-supervisor?jne ret_from_sys_calljmp restore_all在上述代码中,系

12、统首先取出当前进程(进程 1的 task_struct结构指针放入 ebx 寄存 器中。 然后在判断中断是发生在用户空间还是系统空间, 如果发生在系统空间, 则直接跳转 到 restore_all处中断返回。由于本例中中断发生在进程 1的用户空间中,因而程序跳转到 ret_from_syscall处执行,该段代码如下:ret_from_sys_call:cli # need_resched and signals atomic testcmpl $0,need_resched(%ebxjne reschedulecmpl $0,sigpending(%ebxjne signal_return

13、restore_all:RESTORE_ALL到达 ret_from_syscall后, 系统将判断当前进程 (进程 1 的 need_resched字段值是否为 零,如果非零则调用 schedule(函数进行任务切换。在这里我们假设在中断服务程序中唤醒 了进程 2,则从中断服务程序 do_IRQ返回时进程 1的 need_resched字段值即为 1。因而系 统将跳转到 reschedule 处运行,该段代码如下:reschedule:call SYMBOL_NAME(schedule # testjmp ret_from_sys_call由此可见系统在此处将直接调用 schedule (函

14、数进行进程切换。 Schedule (函数的 实现入下:asmlinkage void schedule(voidstruct schedule_data * sched_data;struct task_struct *prev, *next, *p;struct list_head *tmp;int this_cpu, c;if (!current-active_mm BUG(;need_resched_back:prev = current;this_cpu = prev-processor;if (unlikely(in_interrupt( printk(Scheduling in

15、interruptn;BUG(;sched_data = & aligned_datathis_cpu.schedule_data;if (unlikely(prev-policy = SCHED_RRif (!prev-counter prev-counter = NICE_TO_TICKS(prev-nice;move_last_runqueue(prev;switch (prev-state case TASK_INTERRUPTIBLE:if (signal_pending(prev prev-state = TASK_RUNNING;break;default:del_from_ru

16、nqueue(prev;case TASK_RUNNING:;prev-need_resched = 0;repeat_schedule:next = idle_task(this_cpu;list_for_each(tmp, &runqueue_head p = list_entry(tmp, struct task_struct, run_list;if (can_schedule(p, this_cpu int weight = goodness(p, this_cpu, prev-active_mm;if (weight cc = weight, next = p;/* Do we n

17、eed to re-calculate counters? */if (unlikely(!c struct task_struct *p;spin_unlock_irq(&runqueue_lock;read_lock(&tasklist_lock;for_each_task(pp-counter = (p-counter 1 + NICE_TO_TICKS(p-nice; read_unlock(&tasklist_lock;spin_lock_irq(&runqueue_lock;goto repeat_schedule;sched_data-curr = next;task_set_c

18、pu(next, this_cpu;if (unlikely(prev = next /* We wont go through the normal tail, so do this by hand */ prev-policy &= SCHED_YIELD;goto same_process;kstat.context_swtch+;/* there are 3 processes which are affected by a context switch:* prev = . = (last = next* Its the much more previous prev that is

19、 on nexts stack,* but prev is set to (the just run last process by switch_to(.* This might sound slightly confusing but makes tons of sense.*/prepare_to_switch(;struct mm_struct *mm = next-mm;struct mm_struct *oldmm = prev-active_mm;if (next-active_mm BUG(;next-active_mm = oldmm;atomic_inc(&oldmm-mm

20、_count;enter_lazy_tlb(oldmm, next, this_cpu; else if (next-active_mm != mm BUG(;switch_mm(oldmm, mm, next, this_cpu;if (!prev-mm prev-active_mm = NULL;mmdrop(oldmm;/* This just switches the register state and the* stack.*/switch_to(prev, next, prev;_schedule_tail(prev;same_process:reacquire_kernel_l

21、ock(current;if (current-need_reschedgoto need_resched_back;return;上述代码摘自 LINUX2.4.18内核源代码,其中我们忽略了预 SMP (对称多处理器有 关的内容。 进入 schedule 函数后先取出当前进程的 task_struct指针, 此处我们主要关注进程 的切换,因此我们省去对 mm 的分析过程,这样程序就运行到 need_resched_back处,由于 在中断服务程序中不会发生对 schehule(的调用, 因此运行到此处时 unlikely(in_interrupt(返 回真值,程序继续往下运行。接下来系统就

22、判断当前进程的调度策略是否是 SCHED_RR的 方式,如果是则应将其移动到运行队列尾,这样才能保证系统组同等优先级的 SCHED_RR进程得到轮转调度,如果系统发现当前进程的运行状态不是 TASK_RUNNING状态或者在 TASK_INTERRUPTIBLE状态下收到信号这两种情况之一就要将当前进程从运行队列中删 除。 做完上述工作后, 以下就即将进行进程切换的工作, 为了表明对当前进程的切换请求已 经响应, 系统将当前进程 (进程 1 的 need_resched的字段置为 0。 接着进程运行 repeat_schedule处开始进行切换调度。切换前系统首先遍历运行队列并依次调用 goo

23、dness(函数计算进程的 权值并从中取出权值最大者作为将要运行的进程。goodness(计算的权值与进程的调度策略有关, 当进程的调度策略为 SCHED_YIELD时 直接将其权值置为-1返回, 如果为 SCHED_OTHER时则以进程的 counter 值返回, 该 counter 值反映了普通进程占用 CPU 的时间,它会在每个 CPU 时间片减 1,因此对于普通进程即使优先级很高, 但随着进程的运行, 其权值也会逐渐减少从而使其他普通进程得到调度的机会, 体现了一种公平调度的原则。对于 SCHED_RR和 SCHED_FIFO的实时调度进程则直接将 其优先级加上 1000作为权值返回。

24、因此在系统中实时进程是优先得到调度的。从上面的计算原则可见,当系统中没有实时进程时,计算出的最高权值有可能为零, 此时就需要重新设置运行队列中所有进程的 counter 值,设置的原则就是将进程的当前 counter 值承 2加上有进程 nice 值通过 NICE_TO_TICK宏转换过来的一个值。 然后在重新回 到 repeat_reschedule处选出将要切换的进程。在本例中假设通过上述的步骤选出了进程 2作为将要运行的进程。接着 schedule(函数 就开始真正进行进程切换的工作了, 首先是进行两个进程的内存页面的切换, 这部分内容是 内存管理的内容我们可以不用深究。切换完内存后,

25、schedule(函数调用 switch_to(实现 进程的运行切换。 switch_to(函数宏的定义如下:#define switch_to(prev,next,last do asm volatile(pushl %esint pushl %edint pushl %ebpnt movl %esp,%0nt /* save ESP */ movl %3,%espnt /* restore ESP */ movl $1f,%1nt /* save EIP */ pushl %4nt /* restore EIP */ jmp _switch_ton 1:t popl %ebpnt popl

26、 %edint popl %esint :=m (prev-thread.esp,=m (prev-thread.eip, =b (last :m (next-thread.esp,m (next-thread.eip, a (prev, d (next, b (prev; while (0在本例中我们传给 switch_to的 prev 和 last 都是当前进程 (进程 1 的任务控制块指针, next 为将要运行的进程 (进程 2的指针 。 从上述代码中我们可以看到进入 switch_to后首先 用三个入栈语句将进程 1的 esi,edi 和 ebp 入栈。然后将当前 CPU 的堆栈寄存

27、器的内容也就 是当前进程(进程 1的系统堆栈指针保存到当前进程的 thread_struct结构的 esp 中,在此 处没有保存堆栈段寄存器 SS ,原因我们将在后面分析。保存当前的 ESP 之后,就通过将将 要运行的进程 (进程 2 的 thread_struct结构的 esp 赋给 CPU 的 ESP 寄存器而将当前进程 (进 程 1的系统堆栈切换到进程 2的系统堆栈中,前面我们已经知道在 LINUX 下所有的进程 调度最终都是通过在系统空间中调用 schedule 函数来实现的, 因此, 所有进程的系统堆栈的 切入和切出都应该发生在 switch_to的堆栈切换的地方,分析这点上堆栈切换

28、情况是分析整 个调度的关键。为此我们将堆栈切换前后的系统堆栈情况分析如下。切换前的系统堆栈为当前正在运行的进程(进程 1的系统堆栈,内容入图 4所示。图 4 堆栈切换前夕当前进程的系统堆栈情况我们假设进程 2上一次也是在中断的情况下从系统中被切换出去的,则通过堆栈切换 后当前的堆栈就应该为进程 2的系统堆栈, 它应该具有和图 3一样的形式。 唯一不同的是栈 中的相关寄存器和变量的内容是为近程 2保存的。堆栈切换后 switch_to接着往下运行,通 过一条赋值语句首先将当前进程(进程 1的返回地址(指向 switch_to中标号为 1处保 存到其任务控制块的 thread_struct结构的

29、eip 字段中,当任务再次被调度时将从这个地方开 始运行。接着将将要运行的进程(进程 2的任务控制块中的 trhead_struct结构中先前保存 的 eip 值压入进程 2的系统堆栈中。 然后通过 jmp _switch_to(跳转到 _switch_to(函数处执 行。进入该函数后进程 2的系统空间堆栈在图 3 的基础上形成了如图 5所示的情况。 图 5 进入 _switch_to(时的系统栈由于进程 2先前切换出去时的流程和进程 1一致,因此在此处压入堆栈中的返回地址 也应该指向 switch_to中的标号为 1处。此处只压入 EIP 是因为在调 _switch_to函数用了一 条近调用

30、的 CALL 指令,因而从 _switch_to函数中遇到 RET 指令返回时只需要将当前栈中 的内容 POP 到 EIP 中即可 (该工作由 CPU 完成 。 由此可见当从 _switch_to(中返回时系统 已经切换到进程 2上运行。由此可见此处通过巧妙地利用一条 jmp 指令跳转到 _switch_to函数中执行, 再利用编译器为该函数加入的返回指令的作用实现了进程运行代码的切换。 因 此 switch_to中的 jmp _switch_to语句为整个 LINUX 进程调度的进程切换点。 在 _switch_to还为进程切换做了以下的辅助工作:在 _swith_to(中做的主要工作是将 tss 段中的 ss0和 esp0置为将要运行的进程 (进程 2 的 ss0和 esp0的,这要才能保证进程 2从用户空间通过系统调用或者其他方式进入系统空 间时 CPU 能够正确地将堆栈从进程 2的用户堆栈切换到其系统堆栈中。 LINUX 正是利用这 种方式达到了用一个 tss 段为系统中所有的进程服务的目的。从 _switch_to(返回后进程 2即开始运行, 根据前面的堆栈情况, 进程 2应该从 schedule(函数中标号为 1的地方开始运行,标号为 1的地方开始的是三条 POP 指令因此进程 2在这 个地方首先将其在上一次堆栈切换前

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1