Linux Kernel课第二次作业。

burningcodes  原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

1.什么是内核

计算机其实是由一堆硬件组装成的机器,计算机结构大致如下:

linux

Linux内核其实就是专门与硬件打交道的,它向下能与硬件交互,向上提供了系统调用接口,是连接应用和底层硬件必不可少的部分。我们平时使用的Linux操作系统其实是Linux内核加上上层应用的总称,不同的distribution基本上都只是上层应用不同罢了。

内核主要包括了以下几个功能部分:

  • 文件系统
  • 内存管理
  • 进程管理
  • 中断管理
  • 网络管理
  • 设备驱动等

这次实验主要针对其中的进程管理,用一个小型的时间片轮转调度程序来理解进程管理。

2.内核栈与用户栈

每个进程实际上都会有2个栈,一个用户栈一个内核栈。当运行的进程遇到系统调用或中断时就会将ebp和esp切换至当前进程的内核栈空间。

zhan

当进程发生中断或者系统调用时,会先将用户栈的ebp,esp等保存进内核栈,当执行完相应的内核代码后再重新恢复出用户栈的ebp,esp等,这样就完成了内核栈和用户栈的切换。

本次实验代码简化了内核栈和用户栈,在PCB中保存的既是内核栈也是用户栈,所以可以在后面看到执行my_process函数和执行时钟中断处理函数my_timer_handler都是使用当前进程PCB中的栈。

3.基于mykernel的时间片轮转调度

mykernel源码在github上:mykernel。核心代码在三个文件中,其主要功能如下:

  • mypcb.h : 定义PCB结构体。
  • mymain.c: 初始化各个PCB并启动0号进程。
  • myinterrupt.c:进程主动调度及时钟中断处理。

修改完代码之后执行命令:

由于执行速度过快,将my_process的执行时间调大,执行效果如下:

process

可以看到进程执行一段时间后主动进入my_schedule,让出CPU控制权, 而时间中断my_timer_handle将my_need_sched置1是进入调度的必要条件。

n个进程的PCB组成循环链表,按序被调度:

PCB1

PCB的具体结构见mypcb.h。PCB中不仅保存了该进程的进程号pid,进程状态还保存了进程堆栈。初始情况下,sp指向进程栈底,ip和task_entry都指向my_process函数。在每次进行进程调度时,都会将进程当前的sp和ip保存在PCB中,下次调度该进程时再将PCB中保存的sp和ip恢复。

mykernel0

为了简便处理,该时间片轮转调度程序采用的是主动调度。当进程被第一次调度时,从my_process这个函数开始执行,执行一段时间后进程进入主动调度程序,让出CPU控制权。当下一个进程是第一次被调度时,执行my_schedule中的else部分,初次被调度的进程和非初次被调度的进程的区别如下;

  • 下一条执行指令不同。 初次被调度的进程下一条指令为my_process函数的开始,而非初次被调度的进程下一条指令为pop %%ebp(my_schedule函数中)
  • 进程状态不同。初次被调度的进程PCB中state值为-1(unrunnable),而非初次被调度的进程PCB中state值为0(runnable)
  • ebp和esp不同。初次被调度的进程ebp和esp都指向PCB中的栈底(此时栈空),而非初次被调度的进程ebp和esp不同(栈中至少保存了原来的ebp)

理解上面所说的之后再来看看代码,核心部分是3段内联汇编:

首先是my_start_kernel中启动第一个进程的代码:

这段代码其实是有一点问题的,因为进程在开启之后不会退出,所以上面那句popl %%ebp永远不会执行到。前两句话目的是将ebp和esp都指向0号进程PCB中的栈底。3,4句则是跳转至进程0的起始地址。

接下来是进程调度时的关键代码:

其中$1f指的是标号1下面的那条语句即popl %%ebp。当下一个进程不是第一次执行时进入if块,是第一次执行时进入else块。该进程第二次被执行时起始地址是标号1下面的popl %%ebp。从函数执行的角度上看,当函数不是第一次执行时,是从my_schedule函数中恢复执行状态的:

method

这里要注意一点:prev是当前进程,next是下一个进程,但是由于PCB是由循环链表链接而成的,所以当前进程在某个时刻也会变成next进程,所以上面的代码可以被所有进程复用。

在微观的角度上看,每隔固定时间内核都会调度时间中断处理程序,在这个时间片调度程序中是my_timer_handle:

mykernel1

就如上图所示,即使当前时刻控制权在某一个进程,该进程的执行也不是连续的,而是“走走停停”,在固定的时间间隔“停”下来处理中断。每次执行my_timer_handle时都会将my_need_sched字段置1, 进程要让出CPU控制权时又会将my_need_sched字段置0,而只有当该字段为1时当前进程才能执行主动调度。

5.总结

通过这次实验加深了对内核中进程调度的理解。对于单核CPU而言,从用户的角度看,似乎可以同时执行多个应用程序,但从CPU执行的角度来看,每个时刻CPU都只能做一件事,因为每个进程的时间片小到无法被人区分,所以对用户而言,“似乎”能同时执行多个进程。

 

观看更多有关 的文章?

*

    Sip
    2015年3月14日

    耀神~~~上次Linux课认识信安两位大神 哈哈哈 (●’◡’●)

    觉得你图画的特棒特清晰。。想问下 使用了什么工具的吗?

      burningcodes
      2015年3月14日

      我在Linux下画的,用的是Dia

    Sip
    2015年3月14日

    。。。。怎么响应这么慢。

      burningcodes
      2015年3月14日

      不是响应慢,是我在后台要审核评论。。要不然会有一堆广告。。

    zhanghan
    2015年3月15日

    请问,那个prev和next指针不是保存在不同的任务栈中的么?my_schedule代码复用会影响这两个值么?如果任务从3调到0,那么进程调度代码段第17行的打印输出应该不是switch 3 to 0,而应该是switch 0 to 1吧?这样分析哪里有问题?

      burningcodes
      2015年3月16日

      你这个问题问的很好。他的代码的确看上去有点怪,不过你可以用objdump -S 看下反汇编的代码,那个prev和next是保存在两个寄存器里面的,所以从1切换到0的时候寄存器的值仍然是进程1中的,所以在执行的时候不会出问题。

+
跳转到评论