一、什么是进程
操作系统作为硬件的使用层,提供使用硬件资源的能力,进程作为操作系统使用层,提供使用操作系统抽象出的资源层的能力。
进程:是指计算机中已运行的程序。进程本身不是基本的运行单位,而是线程的容器。程序本身只是指令、数据及其组织形式的描述,进程才是程序(那些指令和数据)的真正运行实例。
进程的四要素
1、有一段程序代其执行;
2、有进程专用的系统堆栈空间。
3、在内核有task_struct数据结构;
4、进程有独立的存储空间,拥有专有的用户空间;
如果具备前面三条的话而缺少第四条,就被称为”线程“。
如果完全没有用户空间,就成为”内核线程“;如果共享用户空间则称为”用户线程“。
操作系统第一个创建的进程是init_task系统调用创建的,此后进程和线程都是如果细胞分裂那样通过系统调用复制出来,我们称为fork或者clone,但是内核为每个进程分配task_struct的时候,实际上就是分配了两个连续的物理页,这两个页面的底部作为进程的task_struct结构,而且在这个结构上面,就用作进程系统空间堆栈,那么数据结构task_struct大约在1k的样子,进程的系统空间、堆栈的大小大约在7k左右,不能扩展,可以理解为静态确定的。
那么在进程创建的时候,我们到底创建了什么?其实它就是一个数据结构task_struct,这个结构记录了进程运行的各种资源,比如打开文件描述符,或者挂起信号量的标识,或者内核内部的数据,又或者处理器状态等等。这些我们都可以通过PS命令把进程的资源相关信息展示出来。这个进程创建之后如何表示呢?以及用户如何能够快速索引到对应进程的task_struct呢?内核中是通过一个循环双向链表来组织task_struct ,并且通过一个唯一的pid来标识每个进程。
进程创建被分为两个单独的函数,一个是fork,它拷贝当前的进程创建一个子进程,第二个是exec,这个函数是专门负责读入可执行文件,并且将其纳入到地址空间开始运行。但是要注意这里有个写时拷贝的概念,写时拷贝是推迟或者免除copy数据的技术,就是父进程和子进程会共享同一份拷贝,只要在需要写入的时候数据才会被复制。这是因为如果创建子进程的时候把所有资源复制一份效率太低,因为拷贝的数据有可能根本不共享或者是新进程打算执行一个新的映射那么所有拷贝的内存都会失效。
系统调用是操作系统提供给进程与操作系统进行交互的一种接口,这种接口就称为系统调用,它提供的作用: 与进程受限的硬件访问、创建新的进程、与已经有的进程进行通信,还有就是申请操作系统其它资源能力等。为什么需要系统调用?因为人与人之间没有信任可言...你怎么知道进程会按照你的想法使用操作系统呢??所以系统调用提供了一种硬件得抽象接口,使得进程不需要关心硬盘类型等一些底层信息。作为操作系统得底层接口,操作系统可以基于权限、用户的类型和其它的一些规则对访问进行一些限制,系统提供的系统调用接口有很多,操作系统抽象出对应功能给对应函数,至于这些函数怎么用,不是操作系统关心的问题。系统调用怎么做,每一个系统调用都附一个系统调用号,通过这个系统调用号可以关联到系统调用,系统调用表就是记录所有已经注过的系统调用号的列表,这样的话我们就可以通过进程进行操作里面的一些相关信息。刚才我们所讲的所有基础知识是有关于进程的,这些知识是在操作系统原理里面大学都讲过,在这里我只是带着大家复习一下。下面的才是本文的正题。
二、进程生命周期
Linux操作系统属于多任务操作系统,系统中的每个进程能够分时复用CPU时间片,通过有效的进程调度策略实现多任务并行执行。而进程在被CPU调度运行,等待CPU资源分配以及等待外部事件时会属于不同的状态。进程之间的状态关系:
运行:该进程此刻正在执行。
等待:进程能够运行,但没有得到许可,因为CPU分配给另一个进程。调度器可以在下一次任务切换时选择该进程。
这个图略带喜感,是当初我一个老师画的,所以给它放大一点,如同一个❤一样。
这个图是进程状态之间的切换,这里需要注意一点,运行的进程CPU可以将它调度到等待状态, 进程本身也可以因为等待外部资源而进入到睡眠状态。但是!处于等待外部资源状态的进程不能直接从睡眠状态进入到运行状态,而必须先过渡到等待状态之后,等待cpu调度运行。这个图里面其实还少一个特殊的进程状态,即僵尸状态,顾名思义,僵尸状态是指进程已经不可能再运行了,因为进程资源比如与内存与外设的连接都已经释放了,它无法也绝不会再次运行,但是,进程表当中,仍然有对应的表项在里面。
另外,提到进程状态还可以从另一个角度来阐述,这两种状态即用户状态和核心状态,他们的设计角度反应了所有现代CPU两种不同执行状态的策略,其中核心态有无限的权利,用户态受到各种限制。具体而言:通常进程是都处于用户状态,只能访问自身的数据,无法干扰系统中的其他应用程序,甚至也不会注意自身之外的其他存在等等。有的时候进程尝试访问某一些内存区,是建立在封闭隔离罩的前提,维持着系统当中已有的各个进程,防止他们与系统部分相互感染。
这就是我们要掌握和理解的进程的生命周期以及进程的状态之间的转换。
四、进程优先级
并非所有进程都具有相同的重要性。除了大多数我们所熟悉的进程优先级之外,进程还有不同的关键度类别,以满足不同需求。首先进行比较粗糙的划分,进程可以分为实时进程和非实时进程。实时进程优先级(0-99)都比普通 进程的优先级(100-139)高。当系统中有实时进程运行时,普通进程几乎无法分到赶时间片(只能分到5%的CPU时间)。
通过时间片分配CPU时间
五、进程系统调用
讨论fork和exec系列系统调用的实现。通常这些调用不是由应用程序直接发出的,而是通过一个中间层调用,即负责与内核通信的C标准库。从用户状态切换到核心态的方法,依不同的体系结构而各有不同。
1、进程复制
clone_flags:子进程终止的时候它会被发给父进程信号的号码。
stack_start:用户状态栈的起始地址。
stack_size:栈的大小。一般设为0。
parent_tidptr:专门用来指向用户空间地址的两个指针,指向父进程。
child_tidptr:指向子进程。
- 周期性地将修改的内存页与页来源块设备同步(例如,使用mmap的文件映射)。
- 如果内存页很少使用,则写入交换区。
- 管理延时动作(deferredaction)。实现文件系统的事务日志。