• Linux0.11内核--进程调度分析之1.初始化


    【版权所有,转载请注明出处。出处:http://www.cnblogs.com/joey-hua/p/5596746.html

    首先看main.c里的初始化函数main函数里面有个函数是对进程调度的初始化,sched_init()函数,次函数在sched.c中实现:

    // 调度程序的初始化子程序。
    void sched_init (void)
    {
      int i;
      struct desc_struct *p;	// 描述符表结构指针。
    
      if (sizeof (struct sigaction) != 16)	// sigaction 是存放有关信号状态的结构。
        panic ("Struct sigaction MUST be 16 bytes");
      // 设置初始任务(任务0)的任务状态段描述符和局部数据表描述符(include/asm/system.h,65)。
      set_tss_desc (gdt + FIRST_TSS_ENTRY, &(init_task.task.tss));
      set_ldt_desc (gdt + FIRST_LDT_ENTRY, &(init_task.task.ldt));
      // 清任务数组和描述符表项(注意i=1 开始,所以初始任务的描述符还在)。
      p = gdt + 2 + FIRST_TSS_ENTRY;
      for (i = 1; i < NR_TASKS; i++)
        {
          task[i] = NULL;
          p->a = p->b = 0;
          p++;
          p->a = p->b = 0;
          p++;
        }
      /* Clear NT, so that we won't have troubles with that later on */
      /* 清除标志寄存器中的位NT,这样以后就不会有麻烦 */
      // NT 标志用于控制程序的递归调用(Nested Task)。当NT 置位时,那么当前中断任务执行
      // iret 指令时就会引起任务切换。NT 指出TSS 中的back_link 字段是否有效。
      __asm__ ("pushfl ; andl $0xffffbfff,(%esp) ; popfl");	// 复位NT 标志。
      ltr (0);			// 将任务0 的TSS 加载到任务寄存器tr。
      lldt (0);			// 将局部描述符表加载到局部描述符表寄存器。
      // 注意!!是将GDT 中相应LDT 描述符的选择符加载到ldtr。只明确加载这一次,以后新任务
      // LDT 的加载,是CPU 根据TSS 中的LDT 项自动加载。
      // 下面代码用于初始化8253 定时器。
      outb_p (0x36, 0x43);		/* binary, mode 3, LSB/MSB, ch 0 */
      outb_p (LATCH & 0xff, 0x40);	/* LSB */// 定时值低字节。
      outb (LATCH >> 8, 0x40);	/* MSB */// 定时值高字节。
      // 设置时钟中断处理程序句柄(设置时钟中断门)。
      set_intr_gate (0x20, &timer_interrupt);
      // 修改中断控制器屏蔽码,允许时钟中断。
      outb (inb_p (0x21) & ~0x01, 0x21);
      // 设置系统调用中断门。
      set_system_gate (0x80, &system_call);
    }
    

    首先初始化任务0的TTS,FIRST_TSS_ENTRY为4,表示在描述符表的索引是4。因为gdt是desc_struct类型为8个字节,刚好是一个描述符的长度,所以这里的gdt+4可以理解为gdt[4]。刚好对应的是TSS0。

    描述符表的内容如下:

    0-没有用nul,1-代码段cs,2-数据段ds,3-系统段syscall,4-任务状态段TSS0,5-局部表LTD0,6-任务状态段TSS1,等。

    //// 在全局表中设置任务状态段/局部表描述符。
    // 参数:n - 在全局表中描述符项n 所对应的地址;addr - 状态段/局部表所在内存的基地址。
    // type - 描述符中的标志类型字节。
    // %0 - eax(地址addr);%1 - (描述符项n 的地址);%2 - (描述符项n 的地址偏移2 处);
    // %3 - (描述符项n 的地址偏移4 处);%4 - (描述符项n 的地址偏移5 处);
    // %5 - (描述符项n 的地址偏移6 处);%6 - (描述符项n 的地址偏移7 处);
    #define _set_tssldt_desc(n,addr,type) 
    __asm__ ( "movw $104,%1
    	" 	// 将TSS 长度放入描述符长度域(第0-1 字节)。
    "movw %%ax,%2
    	" 		// 将基地址的低字放入描述符第2-3 字节。
      "rorl $16,%%eax
    	" 	// 将基地址高字移入ax 中。
      "movb %%al,%3
    	" 		// 将基地址高字中低字节移入描述符第4 字节。
      "movb $" type ",%4
    	" 	// 将标志类型字节移入描述符的第5 字节。
      "movb $0x00,%5
    	" 		// 描述符的第6 字节置0。
      "movb %%ah,%6
    	" 		// 将基地址高字中高字节移入描述符第7 字节。
      "rorl $16,%%eax" 		// eax 清零。
      ::"a" (addr), "m" (*(n)), "m" (*(n + 2)), "m" (*(n + 4)),
      "m" (*(n + 5)), "m" (*(n + 6)), "m" (*(n + 7)))
    //// 在全局表中设置任务状态段描述符。
    // n - 是该描述符的指针(向量);addr - 是描述符中的基地址值。任务状态段描述符的类型是0x89。
    #define set_tss_desc(n,addr) _set_tssldt_desc(((char *) (n)),addr, "0x89")
    //// 在全局表中设置局部表描述符。
    // n - 是该描述符的指针(向量);addr - 是描述符中的基地址值。局部表描述符的类型是0x82。
    #define set_ldt_desc(n,addr) _set_tssldt_desc(((char *) (n)),addr, "0x82")
    

    因为TSS最小尺寸是104字节,所以第一句是把长度104赋值给TTS0描述符的第0-1字节,描述符的格式如 描述符格式 ;第二句是把ax也就是addr也就是任务联合的第一个任务的tss地址赋值给*(n+2)处,因为是movw字,所以也就是描述符的第2-3字节处。接下来填充第4字节,然后把类型type填充到第5字节,最后把剩余的字节填充。

    初始化任务0的ldt的方法也是类似,好了,这里初始化完成任务0的TSS和LDT。

    sched_init接下来是清空除了任务0的所有任务的数组和对应的描述符,这个好理解。

    下面是加载任务0的TSS到任务寄存器tr,加载ldt到局部描述符表寄存器ldtr,sched.h:

    /*
    * 寻找第1 个TSS 在全局表中的入口。0-没有用nul,1-代码段cs,2-数据段ds,3-系统段syscall
    * 4-任务状态段TSS0,5-局部表LTD0,6-任务状态段TSS1,等。见head.s
    */
    // 全局表中第1 个任务状态段(TSS)描述符的选择符索引号。
    #define FIRST_TSS_ENTRY 4
    // 全局表中第1 个局部描述符表(LDT)描述符的选择符索引号。
    #define FIRST_LDT_ENTRY (FIRST_TSS_ENTRY+1)
    // 宏定义,计算在全局表中第n 个任务的TSS 描述符的索引号(选择符)。
    #define _TSS(n) ((((unsigned long) n)<<4)+(FIRST_TSS_ENTRY<<3))
    // 宏定义,计算在全局表中第n 个任务的LDT 描述符的索引号。
    #define _LDT(n) ((((unsigned long) n)<<4)+(FIRST_LDT_ENTRY<<3))
    // 宏定义,加载第n 个任务的任务寄存器tr。
    #define ltr(n) __asm__( "ltr %%ax":: "a" (_TSS(n)))
    // 宏定义,加载第n 个任务的局部描述符表寄存器ldtr。
    #define lldt(n) __asm__( "lldt %%ax":: "a" (_LDT(n)))
    

    LDTR局部描述符寄存器:16位,高13为存放LDT在GDT中的索引值。

    所以FIRST_LDT_ENTRY要左移3位,(((unsigned long) n)<<4)不太好理解,因为先要去掉左移的3位,所以实际值是n<<1,也就是2n。最终的值相当于FIRST_LDT_ENTRY+2n。这样就好理解了,因为每个任务都有两个描述符项。

    这里要注意:只明确加载这一次,以后新任务LDT 的加载,是CPU 根据TSS 中的LDT 项自动加载。

    接下来是初始化定时器,没什么好说的。

    接下来两句最关键了,进程调度的引发的诱因就是在下面初始化的:

      // 设置时钟中断处理程序句柄(设置时钟中断门)。
      set_intr_gate (0x20, &timer_interrupt);
      // 修改中断控制器屏蔽码,允许时钟中断。
      outb (inb_p (0x21) & ~0x01, 0x21);
    

    第一句在系统调用机制分析中有讲到,是设置中断门的,所以这里就是把system_call.s中的函数timer_interrupt和中断号0x20关联起来,下面一句代码参考 时钟中断 开启了时钟中断也就是0x20号中断,也就是说时钟每滴答(10ms)一下就会调用timer_interrupt函数。

    到这里,进程调度的初始化就结束了。

  • 相关阅读:
    让Flask-admin支持markdown编辑器
    单例模式
    【Python】关于如何判断一个list是否为空的思考
    【Python】抽象工厂模式
    【Python篇】工厂模式
    【Python】直接赋值,深拷贝和浅拷贝
    【Python】可变对象和不可变对象
    【Python】__name__ 是什么?
    【Python】any() 或者 or
    [Python] list vs tupple
  • 原文地址:https://www.cnblogs.com/joey-hua/p/5596746.html
Copyright © 2020-2023  润新知