操作系统是如何工作的(Linux)

----------------------操作系统是如何工作的----------------------


       上个实验研究了Linux系统对内存的管理。其中函数的调用通过栈机制完美地执行。那么,不禁要问,操作系统又是如何工作的呢?我们都知道,其实一个程序段在运行时就是一个进程。那么,我们一起来研究一下进程是如何调度的。

 

       该实验就是实现一个简单的单时间片轮转的微型操作系统,从而理解操作系统是如何对进程进行调度。

 

下面开始实验!

1使用终端,输入

              cd LinuxKernel/linux-3.9.4

              qemu -kernel arch/x86/boot/bzImage

       启动mylinux 

 


      屏幕上不断地输出>>>>>my_timer_handlerhere <<<<<和my_start_kernel here,这实际上已经形成了一个小型的进程相互切换的过程,我们一起来对它分析一下。(其中,qemu是一款可单独运行的模拟器,它非常灵活且可移植)


       转到mykernel文件夹下,查看其源代码。

       主要的代码段就两个:mymain.c和myinterrupt.c。 



       mymain.c就是一个死循环,一直不断地打印my_start_kernel here。中间加上if判断只是因为,cpu处理一次循环的速度太快,它只每100000次循环打印一次,以便我们观察。

 


       myinterrupt.c是一个被时间周期调用的函数,也可以看成是一个死循环的功能,一直打印>>>>>my_timer_handler here <<<<<。但与一般的死循环不同是,一般的死循环只是在做循环,而这个是周期性的调用函数。

 


       因此,mymain.c中的my_start_kernel只调用了一次,而myinterrupt.c中的my_timer_handler一直被调用。这其实就相当于,进程的初始化与进程的调度。

 

2接下来,我们要完成自己的操作系统

       大家都说这个实验很难,所以为了便于理解,在这里先不要看源代码,我们再仔细思考设计一下我们的系统。

 

       要做进程的调度,即切换进程,那么首先要先知道是要从哪个进程切换到哪个进程。很显然,我们会把正在工作的进程叫为活跃进程,而要切换的进程叫为就绪进程。那么,我们就需要一个标志位来记录进程的状态,而进程的识别显然是用进程号pid来实现的。其次,调度进程就必然涉及现场的保存与恢复,这样就还需记录进程当前的运行的信息。这样对于一个进程,我们要记录这么多的信息,那么最好的方法是利用结构体。因此,我们需要一个头文件。不妨,我们就命名这个头文件叫mypcb.h。

有了这些,当然接下来要使一个进程开始运行,那么,就需要对进程进行初始化。刚才我们知道,mymain.c主要完成初始化工作,那么我们还可以继续利用这个文件,只要有所改动,使之对目前的结构体进行初始化即可。

显然接下来,被时间周期调用的myinterrupt.c也应随之改动。完成这些之后,还有一个问题,进行调度,显然要有调度算法,这该在何处实现呢?显然,就在myinterrupt.c里加入一个函数来控制打断进程的周期和顺序即可,就命名为my_schedule吧。

 

      好的,这样思路就很清晰了:

    (1)      写一个记录状态信息的结构体头文件mypcb.h

    (2)      对应修改mymain.c来进行初始化

    (3)      对应修改myinterrupt.c来进行打断

    (4)      在中增加my_schedule函数来进行调度的管理

 


       要做的工作清楚了,最大的技术难点在哪呢?显然是对现场的保存与恢复。这时我们要重新理解上一个实验《计算机是如何工作的》,cpu识别一个函数,就是依靠ebp、esp来确定函数的栈空间,然后利用eip来找到执行指令的位置。所以cpu对整个内存的识别,就是来靠读取寄存器的信息来确定的,而进程实质就是一个在运行的函数段,所以进程的现场也就是各个寄存器的值。这样就非常清晰了,只要将寄存器的值压栈,就可以放心调度另一个进程了,再将压栈的值出栈还给各寄存器,就又回到了原进程的运行,看起来就像什么都没发生过一样。这就是现场的保存与恢复。

 

       明确了这一系列的问题,我们再来看源代码,这样就容易多了。

       mypcb.h中结构体是typedef struct PCB,再加上其他的函数声明而已。

       结构体PCB的内容是

               pid:进程号

               state:进程状态(就绪态为-1,运行态为0)

               stack:进程的栈

               thread:线程信息

               task_entry:进程入口

               next:下一跳,用于找到下一个PCB对象进程

 


       mymain.c用于系统启动后对进程初始化并启动,然后创建其他进程的PCB。利用全局变量my_need_sched来标识是否要进行调度,然后交给my_schedule进行时间轮的管理。



       myinterrupt.c里my_timer_handler 函数被周期性地调用,然后my_schedule函数完成管理,即进程切换工作。切换的方式我们之前已经说过,就是将当前的一些寄存器的值保存起来与读取的操作,利用汇编代码实现。这里只需要保存ebp、esp、eip即可,ebp+esp可以确定栈桢,然后eip即是返回地址,也就是指令正运行的位置,用于之后回调。

 


       现在,所有的工作都完成了,我们来编译运行一下我们的系统!



      可以看到,2进程正切换3进程。结果非常符合我们的预期。


总结

       通过这个实验,我们可以清晰的看到,进程的调度的过程,也就明白了操作系统的工作机制。这样,只要时间周期足够的短,人类无法识别,我们在使用的时候就感觉若干任务进程好像在同时运行一样。而且也更为深入的理解了由ebp、esp确定的栈桢的意义。现在的操作系统都是在这个基础上实现的。

 

-----------------------------END--------------------------------

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

------------------------------------------------------------------

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值