HelloOs总结之进程管理(上)

HelloOs总结之进程管理(上)

  • 五、进程管理
    • 5.1 进程
      • 5.1.1 进程描述
      • 5.1.2 进程调度

五、进程管理

   站在CPU的角度上说,它是不认识线程啊、进程啊、程序啊啥的。它所能感知到的就是一条条指令,按照指令的规则往下执行,这也就是执行流(或者叫做指令流)。而线程和进程的概念是在操作系统上才有的,按照我的理解,操作系统按照用户的要求把一个个独立的执行流封装起来,然后按照一定的规则在CPU上进行调度,这个封装的过程也就是进程。或者换一种说法,操作系统为了更方便的进行“执行流”的管理和调度,把这个执行流的实体(也就是程序映像,或者简单可以理解成程序的代码部分和数据部分)以及对其进行描述和存储的元数据(也就是操作系统中的PCB)抽象成一个一个概念,即进程。
   线程的概念出现在进程之后,是为了加快任务的执行速度,“同时”地(这种同时,其实是伪同时,也就是并发)运行一些执行流,而在进程的基础之上添加的功能。换句话说,线程的机制就是使得进程具有多个执行流,让每个执行流可以单独的被调度到CPU之上运行,从而加快执行速度。
   当然,上面说的线程指的是单进程多线程,如果一个进程内部没有多条执行流,那么其就是单进程单线程了,在HelloOs中只实现了这种最基本的形式。
   关于线程、进程、执行流等这些概念,在《真象还原》线程一章节描述的相当详细,想深入了解的可以查阅。


 

5.1 进程

   对于HelloOs来说,其只支持了单进程单线程模式,所以这一节所述的进程其实本质上和线程区别不是很大(进程拥有地址空间,线程必须使用进程的地址空间,这里的地址空间可以简单理解为页表),因此下文中如果不做说明,则线程和进程不做区别。
   同时,对于基本的进程理论部分的知识,如进程的状态、进程的切换、基本的调度算法等等下文也不再赘述,具体的可以参照相关操作系统书籍。


 

5.1.1 进程描述

   就像在内存管理中需要使用元数据来管理具体的内存区域一样,对进程的管理也需要一些元数据。这些元数据抽象在一起就是PCB—进程的身份证。在HelloOs中用task_struct 结构体来描述一个进程,如下图5.1所示。

HelloOs总结之进程管理(上)_第1张图片

  • 图5.1 进程PCB示意图

   以上结构体主要包含的信息可以大致分为以下几个部分:
   (1)、进程描述信息:pid、name等。这部分主要用来描述进程的姓名、pid号等,主要其标识作用。在一些大型系统中还包括gid等等
   (2)、进程控制和管理信息:status、priority、ticks、elapsed_ticks等。进程控制和管理信息主要用于管理和调度进程。对于HelloOs来说,其基本的调度方式为时间片轮转(RR调度),但是上层可以使用priority这个属性给进程分配时间片,从某种程度上来说,也算是用作优先级了。(对于Linux等其他操作系统,其proority优先级并不是这样使用的,它越小代表的优先级越高)
   (3)、进程资源分配信息:ttBase 等。 这部分目前就包括一个进程的页表,也就是ttBase所指向的空间。对于一些实际的操作系统来说,其应该还包括进程的打开文件描述符、鼠标、键盘等资源。
   (4)、进程的上下文信息:self_kstack(这个self_kstack本质上是指向一个thread_stack的结构体)等。这个self_kstack作为进程的上下文信息进行保存的,它指向一个结构体,如下图5.2所示。

HelloOs总结之进程管理(上)_第2张图片

  • 图5.2 进程的上下文结构体

   这个结构体主要是对应了s3c2410的寄存器部分,用于在切换进程结构的时候保存进程的上下文信息。这里要多说一点,每个进程都有自己的栈空间(函数调用时需要保存局部变量、返回的函数地址等),这个栈空间首地址是用thread_stack中的sp来描述的,在创造进程时,HelloOs会申请一块栈空间,然后由sp来指向这片区域。

   (5)、其他:HelloOs的PCB中还有几个属性,其中general_tag和all_list_tag用来串起其他的进程。这里简单说明一下(在辅助功能实现一章节还有有叙述), HelloOs是通过把不同的进程串成不同的链式队列进行管理的。这里的“串”,本质上也就是双向链表,不过指向的不是整个pcb,而是pcb中的这个xxx_tag成员。
还有一个stack_magic,是一串事先定义好的数字,用来检测PCB的溢出。

   整个PCB结构体示意如下图5.3所示。

HelloOs总结之进程管理(上)_第3张图片

  • 图5.3 PCB结构体示意图

   好了,今天就到这儿吧。今天是周六,我要去休息一下(^O^)。


 

5.1.2 进程调度

   对于进程调度算法来说,HelloOs采用的是最简单的时间片轮转加上优先级的调度算法。其算法过程说起来也很简单:就是给每个进程分配一个时间片(大小由priority 属性决定),然后开启定时器中断,每次定时器中断到期时,检查当前进程的时间片是否结束,如果结束则调入下一个进程,如果还未结束,则其时间片滴答数(ticks)减一,继续运行。

   就像Linux创始人Torvalds所说的talk is cheap,show me the code。一定深入到技术的实现细节,很多在talk阶段注意不到的问题都出现了。
   (1)、如何找到下一个进程?
   (2)、在切换上下文的时候,具体如何保存上文的环境?如何恢复下文的环境?
   (3)、从中断中切换上下文(因为时间片到期而切换)和从别的进程中切换(因为阻塞而切换)相同吗?
   ……

   是不是看起来有点蒙蒙的?
   以下就上面三个问题谈谈HelloOs的实现。
   (1)、在HelloOs中是如何找到下一个进程的?
   其实这个寻找的过程相对来说还算比较简单,在HelloOs中,保存了thread_ready_list 和thread_all_list这两个全局队列,前者串起了所有处于ready状态的进程,后者串起了所有的进程(其实还有一个阻塞的队列,那是信号量结构体中的队列,用于串起所有阻塞在当前信号量的进程)。所谓找到下一个进程,其实就是从就绪队列中弹出第一个进程。
   在HelloOs中这个过程主要是下图5.4这两条语句实现的。

在这里插入图片描述

  • 图 5.4 弹出下一个进程

   (2)、HelloOs中,进程切换时是如何保存、恢复上下文的?
   在HelloOs中主要的切换上下文是利用SwitchContext这个函数实现的(从中断状态切换上下文)。这是一个汇编函数,其函数原型是
   void SwitchContext(struct task_struct* cur, struct task_struct* next);
   其具体实现如下图5.5所示。

HelloOs总结之进程管理(上)_第4张图片

  • 图5.5 切换上下文代码示意图

   这个函数虽然代码没有多少行,但理解起来还是很不容易的。为了不使大家有一种身在迷雾般的感觉,我尽量从一个高的层次上描述,尽量不涉及到太多底层细节。

   在进行上下文切换的时候,最重要的两个步骤就是保存当前的运行环境(context)和恢复下一个进程的运行环境。这个过程你可能会有以下疑问:

  • 可这些进程的运行环境信息包括哪些呢?
       为了降低复杂度,HelloOs这里是把所有的寄存器都作为进程的环境信息保存了下来。

  • 那这些进程的运行环境具体保存在哪呢?
       如进程描述一节中所述,进程的上下文切换的环境信息是保存在其PCB中的中断栈中的,也就是图5.2所示的结构体中。调用这个SwitchContext()函数之时,会将当前进程PCB的中断栈指针和下一个进程的中断栈指针当做参数传入这个函数中(在这个函数中也就是r0和r1寄存器所保存的内容,详细的涉及到C语言函数和汇编函数的传递规则,这里就不多说了)。如下图5.6所示。

在这里插入图片描述

  • 图5.6 SwitchContext函数调用示意图

   这里要稍微注意一点,传入的参数是self_kstack 的指针,是为了将修改后的中断栈指针再给保存下来。
   这里还需要注意一点,self_kstack的栈指针不是固定的,它只有在进程第一次运行的时候保存在pcb的4K空间中(如图5.3所示),其余情况下,它都是和其他进程一起保存在svc模式下的栈中(即它们共享这个栈空间)。

  • 进程保存运行环境具体的过程是怎样的呢?
       简单来说,就是按照中断栈的格式,生成一个中断栈,然后最后生成的栈指针保存在self_kstack中。如下图5.7所示,保存的时候从上一个进程的svc_sp保存,恢复的时候从下一个进程的svc_sp中恢复。

HelloOs总结之进程管理(上)_第5张图片

  • 图 5.7 中断栈示意图

  • 代码中涉及到的模式切换时怎么回事?
       如中断一节中所述,实际的中断处理函数是在svc模式下执行的,所以进入此函数时,cpu正处于svc模式下,使用的是svc模式下的栈空间。就目前HelloOs的设计来说,各个进程共享svc模式的栈,但是各个进程独立运行过程的栈是不一样的(在这儿可以简单认为其处于sys模式),所以所以上一个进程在保存上下文的时候需要从sys模式中提取进程实际所用到的栈(当然还有lr寄存器)。 整个cpu模式变化如下图5.8所示。

HelloOs总结之进程管理(上)_第6张图片

  • 图5.8 进程切换的模式变化

    我们要做的就是从上一个进程的SYS模式恢复到下一个进程的SYS模式。

   (3)、HelloOs中,从中断中切换上下文(因为时间片到期而切换)和从别的进程中切换(因为阻塞而切换)相同吗?
   从某种程度上说,虽然这两个过程本质上是一样的,都是从一个进程切换到另一个进程。但是实现起来是不一样的。因为前者切换时是出于中断上下文中,而后者是出于进程上下文中(直接从上一个进程的环境切换到下一个中)。 HelloOs中使用SwitchContext和SwitchContextFromThread来实现这两个过程。 当然,虽然实现的具体细节不同,但是大体上步骤是相似的,这里就不多介绍SwitchContextFromThread的实现了。

好了,进程调度这个部分算是差不多总结完了。如果你第一次看到这儿,没有蒙蒙的感觉的话,那么我觉得你应该可以去检查一下你的大脑(太牛逼了,())。 这个部分涉及到细节部分确实有很多很难理解的地方(HelloOs系统可能将近一大半的时间都花在了这个部分),看不懂的话,可以多看两遍,或者参考一下《真象还原》和《一步一步…》,如果还是理解不了的话,也没关系,忽略掉一些细节,从一个较高的层次理解进程调度也是可以的,以后有机会涉及到底层,可以在详细研究。
好了,吃饭时间到。(耳边响起一句话:“干活不太行,吃饭第一名. ̄□ ̄||”)

你可能感兴趣的:(HelloOs总结)