您的位置:首页 > 其它

操作系统原理——系统进程简析

2015-08-18 14:29 211 查看
1、程序(program)是这样一系列指令的所构成的集合,程序大多数时候被存储为可执行的文件。进程是程序的一个具体实现,是执行程序的过程。同一个程序可以执行多次,每次都可以在内存中开辟独立的空间来装载,从而产生多个进程。不同的进程还可以拥有各自独立的IO接口。
使用ps命令来查询正在运行的进程,比如ps -eo pid,comm,cmd,执行结果:
(-e表示列出全部进程,-o pid,ppid,comm,cmd表示我们需要PID,COMMAND,CMD信息)



PID(processIDentity)是一个16位的整数,每一个进程都有一个唯一的PID来作为自身标识。进程当中的pid号的分配是从0—32767之间的,其中0—299的进程号是分配给demo(守护进程)的,剩下的pid号是分配给普通进程的。第二列的ppid是本进程的父进程号。第三列的COMMAND是这个进程的简称。第四列的CMD是进程所对应的程序以及运行时所带的参数。
所有的进程也构成一个以init为根的树状结构,用pstree命令可以显示整个进程树的结构(这里不再展示)。
当子进程终结时,它会通知父进程,并清空自己所占据的内存,并在kernel里留下自己的退出信息。父进程在得知子进程终结时,有责任对该子进程使用wait系统调用。这个wait函数能进行内存清空处理。但是,如果父进程早于子进程终结,子进程就会成为一个孤儿(orphand)进程。孤儿进程会被过继给init进程,init进程也就成了该进程的父进程。init进程负责该子进程终结时调用wait函数。
当然,一个糟糕的程序也完全可能造成子进程的退出信息滞留在kernel中的状况(父进程不对子进程调用wait函数),这样的情况下,子进程成为僵尸(zombie)进程。当大量僵尸进程积累时,内存空间会被挤占。
进程的创建、销毁、执行等详细过程在此不做讨论,在学习unix c编程后,在代码编写的过程中会有深入的了解
在Linux中,线程是一种特殊的进程,多个线程之间可以共享内存空间和IO接口。(进程之间内存是独有的,不会共享)

2、进程的基本状态转换(三态和五态)
三态模型:在多道程序系统中,进程在处理器上交替运行,状态也不断地发生变化。进程一般有3种基本状态:运行、就绪和阻塞。



(1)运行:当一个进程在处理机上运行时(占据cpu),则称该进程处于运行状态。处于此状态的进程的数目小于等于处理器的数目——如果是单核处理器,则当前只有一个进程处于运行态;如果是多核(例如n核),则有m(0<m<n+1)个进程处于运行态。
特别注意:多核处理器可能在同一时间执行一个进程(可以认为是多行代码一块执行,每个核执行一条)
(2)就绪:当一个进程获得了除处理机以外的一切所需资源,一旦得到处理机即可运行,则称此进程处于就绪状态。
(3)阻塞:也称为等待或睡眠状态,一个进程正在等待某一事件发生(例如请求I/O而等待I/O完成等)而暂时停止运行,这时即使把处理机分配给进程也无法运行,故称该进程处于阻塞状态。

五态模型:对于一个实际的系统,进程的状态及其转换更为复杂。引入新建态终止态构成了进程的五态模型。



新建态:进程正在创建过程中,还不能运行的状态。创建进程时分为两个阶段,第一个阶段为新建态,为一个新进程创建必要的管理信息,第二个阶段让该进程进入就绪状态。
终止态:进程已结束运行,回收除进程控制块之外的其他资源,并让其他进程从进程控制块中收集有关信息。类似的,进程的终止分为两个阶段,第一个阶段等待操作系统进行善后处理,第二个阶段释放主存。
由于进程的不断创建,内存资源已不能满足所有进程运行的要求(例如:进程总资源有5GB,内存大小只有4GB)。这时,就必须将某些进程挂起,放到磁盘对换区,暂时不参加调度。
所以就绪态可以分为两种————活跃就绪、静止就绪
活跃就绪:是指进程在主存并且可被调度的状态。
静止就绪(挂起就绪):是指进程被对换到磁盘区时的就绪状态,是不能被直接调度的状态,只有当主存中没有活跃就绪态进程,或者这个就绪态进程具有更高的优先级,系统将把挂起就绪态进程调回主存并转换为活跃就绪。



在linux系统中标识进程的5种状态码:

TASK_RUNNIN 运行态
TASK_INTERRUPTIBLE 可中断等待状态(在等待接受到信号执行)
TASK_UNINTERRUPTIBLE 不可中断等待状态(硬件资源无法满足,需等待中断,满足条件)
TASK_ZOMBIE 僵死状态(进程已终止, 但进程描述符存在, 直到父进程调用wait4()系统调用后释放)
TASK_STOPPED 中止状态

3、linux每个进程对应一个task_struct类型结构。该结构包含进程相关的所有信息。linux进程通过list_head组织双向链表,每一个结点就是一个进程描述符。进程描述符里面包含了进程所有的信息:进程所打开的文件、进程的地址空间、挂起信号、进程状态和其他更多的信息。以下是进程描述符的部分定义:

shruct task_struct

{

unsigned long state; //进程的状态,在2.6.23已经有9个状态

unsigned long policy; //描述进程调度策略.判断是实时进程还是非实时进程

struct task_struct *parent; //组织进程的层次关系,指向父进程

struct list_head tasks; //通过list_head组织成双向链表

pid_t pid; //每个进程唯一的标号

......

};

上面说到的每一个进程状态都有一个链表,来把同一状态的进程串起来。看一下list_head:
struct list_head {
structlist_head *next, *prev;
};
这是双向链表,被放到结构中。通过结构task_struct 定义,可以得到list_head在task_struct 的偏移量,在遍历的时候,通过list_head的地址就可以得到这个节点的task_struct的首地址,然后就可以访问整个task_struct结构。
进程状态的改变(五态之间的转换)的过程如下——将进程从当前状态链表中取出,改变进程状态,把改变后的进程插入当前状态链表中。
4、进程的同步与互斥
进程同步是进程之间直接的制约关系,是为完成某种任务而建立的两个或多个进程,进程需要在某些位置上协做工作次序而等待、传递信息所产生的制约关系。进程间的直接制约关系来源于他们之间的合作。
进程互斥是进程之间的间接制约关系。当一个进程进入临界区使用临界资源时,另一个进程必须等待。只有当使用临界资源的进程退出临界区后,这个进程才会解除阻塞状态。
打个比喻:抄别人作业必须先等待别人做完之后,自己才能把作业抄完,这就是合作。如果借了一份作业,却被别人拿走先去抄了,必须等着拿走的人抄完自己才能抄,这就是互斥。而作业就是临界资源,它只能供一个人占用。
解决问题方案————锁机制、信号量
锁机制——在进入临界区之前获得锁(保证没其他进程在临界区,也禁止后面进程进入临界区),在对临界区资源执行完成之后,释放锁,此时其他进程才能获得锁进入临界区访问。(锁可以被看为一个bool值)
信号量——P和V操作分表示占有和释放,P操作首先减少信号量(减一),表示有一个进程将占用或等待资源,然后检测S是否小于0,如果小于0则阻塞,如果大于0则占有资源进行执行。执行V操作时,S的值加1,若结果不大于0则释放一个因执行P(S)而等待的进程.
(1)完成互斥控制
P(信号量S)
......
临界区(访问临界资源的代码段)
......
V(信号量S)
(2)完成同步操作
最简单的同步形式是:进程A在另一个进程B到达L2以前,不该不该前进到超过L1,这样就可以使用以下程序表示:
进程A 进程B
...... ......
L1:P(信号量S) L2:V(信号量S)
...... ......
对于信号量而言,理解思想即可!信号量在linux与java中都有出现,但在linux中的使用却复杂的多,需要自己编写代码调试才能深入理解,恍然大悟。Java的信号量相对很容易理解(已经抽象化了),依靠底层代码支持实现的。
5、情景模式分析
生产者_消费者(一个生产者,一个消费者,公用一个缓冲区)
可以作以下比喻:将一个生产者比喻为一个生产厂家,如牛奶厂家,而一个消费者,比喻是学生,而一个缓冲区则比喻成一间商店。牛奶生产厂家生产一盒牛奶,把它放在商店进行销售,而学生则可以从那里买到这盒牛奶。只有当厂家把牛奶放在商店里面后,学生才可以从商店里买到牛奶。
解题如下:
定义两个同步信号量:
empty——表示缓冲区是否为空,初值为n。(缓冲区即商店,n代表商店存储最大容量)
full——表示缓冲区中是否为满,初值为0。
mutex——表示当前只有一个进程在缓冲区。
生产者进程
while(TRUE){
生产一个产品;
P(empty);
P(mutex);
产品送往Buffer;
V(mutex);
V(full);
}
消费者进程
while(TRUE){
P(full);
P(mutex);
从Buffer取出一个产品;
V(mutex);
V(empty);
消费该产品;
对于常见的情景问题,分析模型之后,保证逻辑不出错误,按照逻辑编写代码即可!!!
6、进程间通信

环境变量/文件描述符、命令行参数、管道、共享内存(动态数据交换)、信号、消息队列
分别对这些方式进行分析:
——环境变量/文件描述符——
当创建一个子进程时,它接受了父进程许多资源的拷贝。子进程接受了父进程的文本、堆栈以及数据片断的拷贝,也接受了父进程的环境数据以及所有文件描述符的拷贝。之后,他们之间便成为完全独立的两个进程。这种通信是单向的、一次性的通信,严格意义上甚至并不算进程间通信。
——命令行参数——
通过命令行参数(command-line argument)可以完成另一种单向、一次性的进程间通信。类似于上面描述的传递,父进程通过shell命令创建子进程,并把shell命令的参数传递到子进程
——管道——
管道(Pipe)可以关联进程间通信(双向通信)。管道是一种数据结构,像一个序列化文件一样访问。它形成了两个进程间的一种通信渠道。管道结构通过使用文本和读写方式来访问。如果进程A希望通过管道发送数据给进程B,那么,进程A向管道写入数据,进程B读取管道获得信息。管道可以在程序的整个执行期间使用,在进程间发送和接收数据。
——共享内存——
共享内存也可以实现进程间的通信。进程需要有可以被其他进程浏览的内存块,希望访问这个内存块的其他进程请求对它的访问,或由创建它的进程授予访问内存块的权限。共享内存被映射到使用它的每个进程的地址空间。所以,它看起来像是另一个在进程内声明的变量。当一个进程写共享内存,所有的进程都立即知道写入的内容,而且可以访问。
进程间共享内存的关系与函数间全局变量的关系相似。程序中的所有函数都可以使用全局变量的值。同样,共享内存块可以被正在执行的所有进程访问。
——信号——
对中断机制的一种模仿。进程有某种事件发生(例如程序异常),进程发送信号,相应的处理进程得到信号进行处理,处理完之后返回原进程继续从中断处执行。
——消息队列——
消息队列提供了从一个进程向另一个进程发送一个数据块的方法。每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。
7、线程
线程被认为是进程的子程序执行,一个进程可以有多个线程,且他们共享进程的资源(线程本身只有很少的资源)

最后说明——简单的对进程分析很难深入的理解进程机制,只有通过unix下c编程,通过编写相关进程代码,进行测试与分析,才能更加深入的理解
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: