免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 6602 | 回复: 8
打印 上一主题 下一主题

[FreeBSD] FreeBSD6.0中调度机制分析(一) [复制链接]

论坛徽章:
2
亥猪
日期:2014-03-19 16:36:35午马
日期:2014-11-23 23:48:46
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2006-04-10 10:37 |只看该作者 |倒序浏览
                                            FreeBSD6.0中cpu_switch调度机制分析

writtent by: gvim @ chinaunix /bsd

release under BSD license

在这一系列的小文里,我将从我自己的学习分析入手,尽量为大家展示一张清晰的进程相关机制与实现。后面的小文初步计划包括中断,时钟,系统调用框架,KSE,调度策略,等等有关进程的部分。有些部分已经有朋友实现了分析说明,我会在他们分析的基础上添加,修改,更新,使得这些东西可以形成一张体系网络。我不是专职作家或教育家,所以时间可能会比较长。当然,我不是神仙,我也会犯错。

作为开篇,我先详细分析调度机制。操作系统的心脏是调度器,并且这个心跳的脉搏是时钟。调度可以分为两个部分,一部分属于平台无关的调度策略,策略在[2]、[3]、[4]中已经有十分详尽的介绍;另一部分属于平 台相关的调度机制,也就是本篇小文偏向的重点。时钟将在以后的小文中予以介绍(这部分[2]写得十分简略)。

既然调度机制是平台相关的,那为什么还需要分析它?我个人认为,首先自低向上的分析也是一种学习方式;只学习调度策略而不分析调度机制,对于调度器这个行为体来讲,是不完整的;另外,调度机制是与计算机体系紧密结合的,是软硬件之间交互的方式之一;最后,知道了实际的调度机制之后,我们可以在上层替换/修改调度策略,实现我们自己的调度器。分析的代码忽略SMP的相关内容,一些剖析(profiling)内容和其他与调度切换本身关系不大的相关代码(从前面的行号可以看出来省略部分,需要完整的话可以在下面的链接找到)。下面的代码都摘自 http://fxr.watson.org/fxr/source/i386/i386/swtch.s?v=RELENG60,因此保留行符便于查对。如果对汇编不熟悉的话,跳过相应代码而直接看解释应该没有太大问题。

本文的写法与[6]的任务切换部分的详细扩展。[6]中其余部分可以参见风雨兄的翻译【与CPU架构相关部分】之【推荐一篇关于FreeBSD内核如何在保护模式下管理IA32处理器的文章】

感谢雨丝风片@chinaunix/bsd和congli@chinaunix/bsd的审查、鼓励与支持

首先找到调度器调度机制的代码入口,该入口如下,在i386/i386/swtch.s中。

  1. 95 ENTRY(cpu_switch)
复制代码

宏ENTRY在i386/include/asm.h中定义如下:

  1. #define ENTRY(x) _ENTRY(x)
复制代码

在相同的文件中我们可以看到如下的定义:

  1. #define CNAME(csym)  csym
  2. #define _START_ENTRY .text; .p2align 2,0x90
  3. #define _ENTRY(x) _START_ENTRY; \
  4.              .globl CNAME(x); .type CNAME(x),@function; CNAME(x):
复制代码

这样ENTRY(cpu_switch)可以做如下替换

  1. .text; .p2align 2,0x90; \
  2.            .globl cpu_switch; .type cpu_switch,@function; cpu_switch:
复制代码

以上伪指令可以查看info gas中相关解释。

这段伪汇编代码的意思是告诉连接器,这部分数据是程序代码,.p2align 2,0x90; 告诉连接器这些指令以32bit也就是4字节长度(power(2,2))对齐,对齐时填充部分填入数值0x90,这个数值是NOP指令的机器代码。

.globl cpu_switch;声明cpu_switch是一个全局范围可见的符号。连接器可以使用这个符号。
.type cpu_switch,@function;声明这个符号是一个函数类型的符号。
cpu_switch: 标志符,告诉连接器从这里开始是代码部分的开始。

在正文段的开始,我们首先看看cpu_switch之前的代码注释。

注意!在FreeBSD 6.0 release 中man cpu_switch得到的是1996年的解释。(多谢风雨的开路性探索,使得我知道man手册跟不上步骤)

  1. 86 /*
  2. 87  * cpu_switch(old, new)
  3. 88  *
  4. 89  * Save the current thread state, then select the next thread to run
  5. 90  * and load its state.
  6. 91  * 0(%esp) = ret
  7. 92  * 4(%esp) = oldtd
  8. 93  * 8(%esp) = newtd
  9. 94  */
复制代码

这部分注释告诉我们,cpu_switch取得两个参数,分别为old和new。这两个参数都是指向结构thread的指针(可以在sched_switch()[kern/sched_4bsd.c]等上层函数中找到对cpu_switch的调用)。函数调用栈桢的栈顶指针esp存放的是ret地址也就是返回地址(关于函数调用栈桢的相关概念可以参考[5])。下面紧接着的是切换之前的old线程结构指针,也就是cpu_switch的第一个参数,由于IA32上堆栈的生长方向和内存地址增长方向相反,因此这里是4(%esp)也就是%esp+4的意思,通过这样的语句我们可以得到old指针所指地址。4表示IA32上一个指针长度为4字节=32bit(不是吗?J)。old之后是new,也需要越过4字节,所以相对于esp栈顶来说是4+4=8。

为了将处理器的执行切换到new线程,我们必须首先保留old的线程上下文,以便当调度器在一次选择这个线程的时候可以恢复运行。所以,我们首先通过thread结构[sys/proc.h]的old指针取得old线程。

  1. 97         /* Switch to new thread.  First, save context. */
  2. 98         movl    4(%esp),%ecx     /* 现在ecx中存放的是old线程的thread指针*/
复制代码

TD_PCB宏在i386/i386/genassym.c中定义为ASSYM(TD_PCB, offsetof(struct thread, td_pcb)),意义是:定义TD_PCB这个符号,它的值是thread结构中成员变量td_pcb的偏移量。其中的一系列宏替换我就不在这里罗嗦了。最后的实际宏行为体现在i386/compile/xxx/assym.s中的这一句:#define TD_PCB 0xfc,可见td_pcb成员在thread结构中是位于从0计数开始的0xfc字节处。td_pcb是一个pcb结构[i386/include/pcb.h]类型的实例变量。

  1. 105         movl    TD_PCB(%ecx),%edx  /* 取得old线程的进程控制块, 地址放在edx中*/
复制代码

具体的保存工作在下面进行,第107行的(%esp)取得91行说明的ret地址,这个地址也就是调用cpu_switch之后的那句指令的地址。在汇编层面来说,就是call cpu_switch之后的指令地址。以前面说的上层函数sched_switch()为例,(%esp)放的是cpu_switch之后的紧接着的语句地址。

PCB_EIP等宏和上面105行出现的TD_PCB的解释相同,不再罗嗦。注意这里edx存放的是pcb结构的指针。首先保存IA32对外接口的几个(通用)寄存器:eip,ebx,esp,ebp,esi,edi,gs段描述符,和机器状态字。

  1. 107         movl    (%esp),%eax                     /* Hardware registers */
  2. 108         movl    %eax,PCB_EIP(%edx)     /* 正如上面解释的,IP指向返回地址 */
  3. 109         movl    %ebx,PCB_EBX(%edx)     /* 保存当前线程的ebx */
  4. 110         movl    %esp,PCB_ESP(%edx)     /* 这里没有保存eax,它现在保存的是
  5.                                                         old 的EIP */
  6. 111         movl    %ebp,PCB_EBP(%edx)     /* 也没有保存ecx,它现在保存的是
  7.                                                        old thread */
  8. 112         movl    %esi,PCB_ESI(%edx)     /* 也没有保存edx,它现在保存的是
  9.                                                           old pcb */
  10. 113         movl    %edi,PCB_EDI(%edx)
  11. 114         movl    %gs,PCB_GS(%edx)
复制代码

由于IA32指令集中没有直接取得处理器状态字的指令,所以我们需要间接使用堆栈及堆栈操作指令获得处理器状态字。

  1. 115         pushfl                          /* PSL ,取得当前状态字,并保存在pcb结构中*/
  2. 116         popl    PCB_PSL(%edx)           /* 的pcb_psl变量中。
  3.                                                  psl:process status long */
复制代码

这里我还没有分析switchout,应该是kse概念内的东西,先放到这里不解释,以后再补充。

  1. 117         /* Check to see if we need to call a switchout function. */
  2. 118         movl    PCB_SWITCHOUT(%edx),%eax
  3. 119         cmpl    $0, %eax
  4. 120         je      1f
  5. 121         call    *%eax
复制代码

调试寄存器,需要保存就保存,不需要就跳过这段代码,DR(debug register)可以参看[7]。不了解这里问题也不大。

  1. 122 1:
  2. 123         /* Test if debug registers should be saved. */
  3. 124         testl   $PCB_DBREGS,PCB_FLAGS(%edx)
  4. 125         jz      1f                              /* no, skip over */
  5. 126         movl    %dr7,%eax                       /* yes, do the save */
  6. 127         movl    %eax,PCB_DR7(%edx)
  7. 128         andl    $0x0000fc00, %eax               /* disable all watchpoints */
  8. 129         movl    %eax,%dr7
  9. 130         movl    %dr6,%eax
  10. 131         movl    %eax,PCB_DR6(%edx)
  11. 132         movl    %dr3,%eax
  12. 133         movl    %eax,PCB_DR3(%edx)
  13. 134         movl    %dr2,%eax
  14. 135         movl    %eax,PCB_DR2(%edx)
  15. 136         movl    %dr1,%eax
  16. 137         movl    %eax,PCB_DR1(%edx)
  17. 138         movl    %dr0,%eax
  18. 139         movl    %eax,PCB_DR0(%edx)
  19. 140 1:
复制代码

看看有没有使用287/387[i386/isa/npx.c]协处理器,当然,如果使用了我们也需要保存它的状态。

  1. 142 #ifdef DEV_NPX
  2. 143         /* have we used fp, and need a save? */
  3. 144         cmpl    %ecx,PCPU(FPCURTHREAD)
  4. 145         jne     1f
  5. 146         addl    $PCB_SAVEFPU,%edx   /* h/w bugs make saving complicated */
  6. 147         pushl   %edx
  7. 148         call    npxsave                         /* do it in a big C function */
  8. 149         popl    %eax
  9. 150 1:
  10. 151 #endif
复制代码

与一个进程/线程相关的,除了上面需要保存的通用寄存器的各种状态之外,还有它的页表映射状态。这个页表的基地址是保存在CR3寄存器里的。现在的工作是保存old线程的地址映射关系,载入new的。因此,虽然old线程寄存器的各种状态我们已经保存好了,但是我们还必须改变old线程中的虚拟地址映射关系。这部分和虚拟内存有关,详细的分析可以参见风雨的文章及congli等众兄弟的实验和解答【内核相关】之【内存管理】

  1. 153         /* Save is done.  Now fire up new thread. Leave old vmspace. */
  2. 154         movl    %ecx,%edi         /* ecx还记得吗?从107行到这里一直没有变化 */
  3.                                                               /* 到这里仍然代表old线程thread结构指针 */
  4.                                                               /* 但是从现在开始edi代表old线程了 */
  5.                                                               /* edi我们在113行保存过,所以现在可以使用 */
  6. 155         movl    8(%esp),%ecx             /* New thread ,ecx现在代表new线程 */
  7. 160         movl    TD_PCB(%ecx),%edx       /* 获得new线程的pcb地址 */
  8. 161         movl    PCPU(CPUID), %esi       /* 展开之后是 movl %fs:PC_CPUID, %esi */
复制代码

有关CR3的作用和使用,参见[7]。大致来说,CR3存放的是页表目录的基地址。整个使用过程如下:

[7]:图3-12

以4k页面为例,这里简单描述一下转换过程,熟悉得朋友可以跳过。首先,页目录Page Directory的基地址存放在CR3里面。线形地址的高[31:22]位作为对该目录的索引(为叙述方便,简写为IdxD: index directory),取得一个目录项也就是图中左边Directory Entry。Page Directory有pow(2,10)=1024项,也就是说,页目录可以索引1024个页表。如果我们以C语言的形式来表达可以写成(CR3)[IdxD]。这个目录项也是一个基地址,它索引一个页表:Page Table。页表也有pow(2,10)=1024项。这些项由线形地址的[21:12](称之为IdxT: index Table)索引。每一个项可以表示一个4K(pow(2,12),见下)大小的页面,用C语言数组方式表达为((CR3)[IdxD])[IdxT]。最后,线形地址低12位,也就是[11:0]作为页表的索引取得实际的物理地址,由表示的位数可以看出来,每张页表是4K。同样,C语言表示为(((CR3)[IdxD])[IdxT])[IdxP]。形象上说(或许不严谨),地址的变换是一个三维数组的遍历过程。最后,我们可以看见,总的地址寻址空间是1024*1024*4096=4G字节。That’s right。

回到我们的cpu_switch中来说,可以看见,变换了CR3,也就变换了虚拟地址的映射关系。

  1. 163         /* switch address space */
  2. 164         movl    PCB_CR3(%edx),%eax      /* edx表示160行取得的new线程的pcb地址,
  3.                                                 我们取出来new线程的CR3寄存器,放到eax里面
  4.                                               */
  5. 168         cmpl    %eax,IdlePTD                    /* Kernel address space? */
  6.                                                                    /* IdlePTD 在i386/i386/locore.S中定义 */
  7. 170         je      sw1                                    /* 检查线程是否在内核态使用内核地址空间 */
  8. 171         movl    %cr3,%ebx                       /* The same address space? */
  9. 172         cmpl    %ebx,%eax
  10. 173         je      sw1                                    /* 如原代码注释,检查是否处于相同地址空间 */
复制代码


[ 本帖最后由 gvim 于 2006-4-10 13:14 编辑 ]

论坛徽章:
2
亥猪
日期:2014-03-19 16:36:35午马
日期:2014-11-23 23:48:46
2 [报告]
发表于 2006-04-10 10:38 |只看该作者
常识告诉我们,线程既可以在内核空间挂起(传统Unix在内核空间时不能被打断,但是不要忘了我们现在的FreeBSD6.0里),也可以在用户空间挂起,所以存在如下的情况。

因为内核地址空间对所有进程是共享的,所以在内核空间时不需要转换CR3寄存器。

因为如果线程使用相同地址空间(废话,线程的存在就是为了共享地址空间嘛,也就是说这里检查的是old和new两个线程是否属于两个不同进程),页目录基址也不需要变换,我们自然也不需要操作CR3寄存器。

但是现在检测到线程既不处于内核空间,也不使用相同的地址空间,所以需要做下面的工作。

否则直接跳过174-181行。

  1. 174         movl    %eax,%cr3     /* new address space */
  2.                                                  /* 我们开始使用164行得到的new线程的CR3
  3.                                                  开始使用新的CR3,因此从现在开始地址映射关系改变。
  4.                                  改变后不会影响下面的操作吗?因为下面还要访问内存阿?!
  5.                                  不要忘了,我们在内核里,所有线程共享内核空间,下面的存储器
  6.                                  访问,虽然CR3变了,但是从虚拟地址经过上述的三维数组遍历之后
  7.                                 仍然访问的相同的地址。概念上和C语言的指针alias很相似。
  8.                                 [debug me]
  9.                                               */
  10. 176         /* Release bit from old pmap->pm_active */
  11. 177         movl    PCPU(CURPMAP), %ebx      /* %fs:PC_CURPMAP  = %fs:0x3c */
  12. 181         btrl    %esi, PM_ACTIVE(%ebx)           /* clear old */
  13.                                                     /* esi在161行赋值 */
  14.                                                     /* 清除活动位标志,由于old线程分配的虚存
  15.                                     * 在new线程活动期间不再需要,标志为不活动
  16.                                     * 可以让换页守护进程在存储空间不足时将之
  17.                                     * 交换出主存
  18.                                     */
复制代码

显然,我们弄死了一个,就要复活一个。我们下面需要设置new线程的虚拟页面为活动页面。

  1. 183         /* Set bit in new pmap->pm_active */
  2. 184         movl    TD_PROC(%ecx),%eax              /* newproc,取得new线程的进程结构 */
  3. 185         movl    P_VMSPACE(%eax), %ebx           /* 这是一个vmspace结构的指针 */
  4. 186         addl    $VM_PMAP, %ebx                  /* 现在ebx代表new线程中pmap结构的首
  5.                                                    地址vm/vm_map.h
  6.                                                                          */
  7. 187         movl    %ebx, PCPU(CURPMAP)
  8. 191         btsl    %esi, PM_ACTIVE(%ebx)           /* set new */
复制代码

如下面194-197行的代码注释那样,我们现在已经切换了new线程的地址空间。

  1. 193 sw1:
  2. 194         /*
  3. 195          * At this point, we've switched address spaces and are ready
  4. 196          * to load up the rest of the next context.
  5. 197          */
复制代码

pcb扩展,意思是每个进程有自己的TSS,反正都是TSS,因此是不是它自己的并不影响对线程切换行为的理解。差别不过是自己的TSS自己用,公用的TSS大家用。TSS: Task-State segment [7]:第6章。

注意FreeBSD中并没有使用Intel硬件提供的任务切换机制(将在下面解释为什么还要出现TSS),所以暂时忽略这段代码的分析。

  1. 198         cmpl    $0, PCB_EXT(%edx)               /* has pcb extension? */
  2. 199         je      1f                              /* If not, use the default */
  3. 200         btsl    %esi, private_tss               /* mark use of private tss */
  4. 201         movl    PCB_EXT(%edx), %edi             /* new tss descriptor */
  5. 202         jmp     2f                              /* Load it up */

  6. 204 1:      /*
  7. 205          * Use the common default TSS instead of our own.
  8. 206          * Set our stack pointer into the TSS, it's set to just
  9. 207          * below the PCB.  In C, common_tss.tss_esp0 = &pcb - 16;
  10. 208          */
  11. 209         leal    -16(%edx), %ebx                 /* leave space for vm86 */
  12. 210         movl    %ebx, PCPU(COMMON_TSS) + TSS_ESP0
  13. 211
  14. 212         /*
  15. 213          * Test this CPU's  bit in the bitmap to see if this
  16. 214          * CPU was using a private TSS.
  17. 215          */
  18. 216         btrl    %esi, private_tss               /* Already using the common? */
  19. 217         jae     3f                              /* if so, skip reloading */
  20. 218         PCPU_ADDR(COMMON_TSSD, %edi)
复制代码

我们可以看见227行的ltr装载task register的操作。但是,FreeBSD是不依赖intel的硬件机制来实施任务切换。因此,上面保留的问题在这里回答。要回答这个问题,需要看看tss的大致结构:

[7]:图6-1


里面的各项域都是在使用intel硬件提供的任务切换时自动保存的。由于不使用TSS进行任务切换,我们忽略大部分域,只关心三个域(Stack Priv Level 0),( Stack Priv Level 1),( Stack Priv Level 2),其时还有第四个域I/Omap,我还不熟悉,就不讲述I/O map的东西,希望有人可以补充下。

这三个域代表了处理器在0-2三个ring级别执行时的堆栈段及栈顶(SSx:ESPx,x代表ring级别)。还有一个ring3的堆栈,那自然在用户级大家经常看见的esp,ss就可以表示了。所以这里只需要记录四个ring级别中的三个。

由于80x86的处理器下降ring权限时,比如ring3(用户级)下降到0,或1,或2,(或者从ring2下降到0或1,等)处理器自动从TSS数据结构里面得相应ring级别找出堆栈。并在相应的级别中操作堆栈。为什么?因为我们不可以让低优先的东西使用高优先级堆栈,堆栈需要保护。所以我们可以看见下面的图示:

[7]:图5-4


图上半部是同级别的入栈操作,下面是跨级别的入栈。可以看出来,在ring下降(也就是优先级升高)的时候,很多东西都是记录在高优先级的堆栈中。(虽然这里是用的中断处理的图,但是抛开中断或是任务切换的表象,我们解释的是不同ring级别堆栈的使用。当然,从低优先级到高优先级的访问,也只有中断,异常等几种方式可以做到)。由于什么信息都已经记载,从高优先级退出时返回是很容易的。正是由于这个自动操作的存在,使得我们虽然不使用TSS这个东西,还是必须填充它,随任务切换时切换。

  1. 219 2:
  2. 220         /* Move correct tss descriptor into GDT slot, then reload tr. */
  3. 221         movl    PCPU(TSS_GDT), %ebx             /* entry in GDT */
  4. 222         movl    0(%edi), %eax
  5. 223         movl    4(%edi), %esi
  6. 224         movl    %eax, 0(%ebx)
  7. 225         movl    %esi, 4(%ebx)
  8. 226         movl    $GPROC0_SEL*8, %esi             /* GSEL(entry, SEL_KPL) */
  9. 227         ltr     %si
  10. 228 3:
  11. 229
  12. 230         /* Copy the %fs and %gs selectors into this pcpu gdt */
  13. 231         leal    PCB_FSD(%edx), %esi
  14. 232         movl    PCPU(FSGS_GDT), %edi
  15. 233         movl    0(%esi), %eax           /* %fs selector */
  16. 234         movl    4(%esi), %ebx
  17. 235         movl    %eax, 0(%edi)
  18. 236         movl    %ebx, 4(%edi)
  19. 237         movl    8(%esi), %eax           /* %gs selector, comes straight after */
  20. 238         movl    12(%esi), %ebx
  21. 239         movl    %eax, 8(%edi)
  22. 240         movl    %ebx, 12(%edi)
复制代码

恢复new线程的寄存器。是107-114行的逆过程

  1. 242         /* Restore context. */
  2. 243         movl    PCB_EBX(%edx),%ebx
  3. 244         movl    PCB_ESP(%edx),%esp
  4. 245         movl    PCB_EBP(%edx),%ebp
  5. 246         movl    PCB_ESI(%edx),%esi
  6. 247         movl    PCB_EDI(%edx),%edi
  7. 248         movl    PCB_EIP(%edx),%eax
  8. 249         movl    %eax,(%esp)
  9. 250         pushl   PCB_PSL(%edx)
  10. 251         popfl                         /* 恢复了很多寄存器,包括处理器状态字EFLAGS */
复制代码

记录当前处理器的当前进程和当前线程。恩,平铺直叙的表达手法。

  1. 253         movl    %edx, PCPU(CURPCB)
  2. 254         movl    %ecx, PCPU(CURTHREAD)           /* into next thread */
复制代码

设置new线程的LDT描述符表,和GDT差不多,差别在于一个是公用的,一个是私有的。270行之下的USER_LDT在NetBSD中用来模拟windows,呵呵,我猜想FreeBSD也差不多吧,晕,猜想。。。。汗一个先。

  1. 256         /*
  2. 257          * Determine the LDT to use and load it if is the default one and
  3. 258          * that is not the current one.
  4. 259          */
  5. 260         movl    TD_PROC(%ecx),%eax
  6. 261         cmpl    $0,P_MD+MD_LDT(%eax)
  7. 262         jnz     1f
  8. 263         movl    _default_ldt,%eax
  9. 264         cmpl    PCPU(CURRENTLDT),%eax
  10. 265         je      2f
  11. 266         lldt    _default_ldt
  12. 267         movl    %eax,PCPU(CURRENTLDT)
  13. 268         jmp     2f
  14. 269 1:
  15. 270         /* Load the LDT when it is not the default one. */
  16. 271         pushl   %edx                            /* Preserve pointer to pcb. */
  17. 272         addl    $P_MD,%eax                      /* Pointer to mdproc is arg. */
  18. 273         pushl   %eax
  19. 274         call    set_user_ldt
  20. 275         addl    $4,%esp
  21. 276         popl    %edx
  22. 277 2:
  23. 278
  24. 279         /* This must be done after loading the user LDT. */
  25. 280         .globl  cpu_switch_load_gs
  26. 281 cpu_switch_load_gs:
  27. 282         movl    PCB_GS(%edx),%gs
复制代码

恢复调试寄存器。

  1. 284         /* Test if debug registers should be restored. */
  2. 285         testl   $PCB_DBREGS,PCB_FLAGS(%edx)
  3. 286         jz      1f
  4. 287
  5. 288         /*
  6. 289          * Restore debug registers.  The special code for dr7 is to
  7. 290          * preserve the current values of its reserved bits.
  8. 291          */
  9. 292         movl    PCB_DR6(%edx),%eax
  10. 293         movl    %eax,%dr6
  11. 294         movl    PCB_DR3(%edx),%eax
  12. 295         movl    %eax,%dr3
  13. 296         movl    PCB_DR2(%edx),%eax
  14. 297         movl    %eax,%dr2
  15. 298         movl    PCB_DR1(%edx),%eax
  16. 299         movl    %eax,%dr1
  17. 300         movl    PCB_DR0(%edx),%eax
  18. 301         movl    %eax,%dr0
  19. 302         movl    %dr7,%eax
  20. 303         andl    $0x0000fc00,%eax
  21. 304         movl    PCB_DR7(%edx),%ecx
  22. 305         andl    $~0x0000fc00,%ecx
  23. 306         orl     %ecx,%eax
  24. 307         movl    %eax,%dr7
复制代码

还记得249 movl %eax,(%esp)这句吗?我们在堆栈返回地址填充了new线程的eip,ret就返回到new线程上一次(因为它那个时候被剥夺了执行权)call cpu_switch之下的一条语句去执行。

  1. 308 1:
  2. 309         ret
  3. 310
复制代码

Now, a new world, enjoy.

小结:

可以看出,FreeBSD的调度机制与策略分隔的十分明显,数据流参数全部包括在thread结构变量old,new中,也就是说,如果我们在上层替换掉调度策略的话,就可能提供一种新的调度器。

参考

[1] sys/i386/i386/swtch.s
[2] FreeBSD操作系统设计与实现
[3] ULE: A Modern Scheduler for FreeBSD
[4] Operation System concept(the six edition)
[5] Computer Systems: A Programmer's Perspective (CS:APP)
[6] A guide to how the FreeBSD kernel manages the IA32 processors in Protected Mode
[7] IA-32 Intel® ArchitectureSoftware Developer’s Manual


[ 本帖最后由 gvim 于 2006-4-10 10:52 编辑 ]

论坛徽章:
0
3 [报告]
发表于 2006-04-10 10:54 |只看该作者
通俗易懂,阁下文章写得一级棒,只看完了前面一点,先支持一下再接着看

论坛徽章:
2
亥猪
日期:2014-03-19 16:36:35午马
日期:2014-11-23 23:48:46
4 [报告]
发表于 2006-04-10 10:56 |只看该作者
论坛里排版出来很难看。这篇文章我也放在了cu的blog里,效果比较好。

论坛徽章:
2
亥猪
日期:2014-03-19 16:36:35午马
日期:2014-11-23 23:48:46
5 [报告]
发表于 2006-04-10 10:59 |只看该作者
原帖由 colddawn 于 2006-4-10 10:54 发表
通俗易懂,阁下文章写得一级棒,只看完了前面一点,先支持一下再接着看


呵呵,谢谢夸奖,还请你们多提意见。

论坛徽章:
1
寅虎
日期:2013-09-29 23:15:15
6 [报告]
发表于 2006-04-10 12:38 |只看该作者
学习ing^_^

论坛徽章:
0
7 [报告]
发表于 2006-04-10 13:08 |只看该作者
条分缕析,图文并茂,俺就喜欢看这样的文章!期待着后续部分的发布,大家以后拼成一个“BSD内核源代码分析”,

论坛徽章:
2
亥猪
日期:2014-03-19 16:36:35午马
日期:2014-11-23 23:48:46
8 [报告]
发表于 2006-04-10 13:21 |只看该作者
呵呵,上面的牛牛们,看了可是要提意见的啊 

[大家以后拼成一个“BSD内核源代码分析”]  ->   that's a great idea. We are the gentle BSDer. oye

论坛徽章:
0
9 [报告]
发表于 2007-08-25 16:01 |只看该作者
这个是调度机制的分析吗,上下文切换过程的分析吧
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP