farwang 2014-12-05
进程vs程序
程序是一个包含可执行代码的文件,它放在磁盘等介质上。
当程序被操作系统装载到内存并分配给它一定资源后,此时可称为进程。
为方便操作系统管理,每个进程都会有一个唯一的非负整数编号。
程序是一个静态概念,进程是一个动态概念。
Linux进程描述
进程描述符:当进程产生时有Linux操作系统分配。
内存:用来存放进程要执行的代码和使用的数据。
文件描述符:进程运行时打开的文件。
认证信息:用户和组ID
进程执行环境:各种环境变量
资源安排:CPU时间
进程状态
Linux进程状态
运行状态:当进程正在被运行或者已经处于可调度状态。
可中断状态:进程正在等待一个信号或者资源。
不可中断状态:不可被信号唤醒,一般用于硬件初始化时。
暂停状态:当进程收到SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU信号后就会进入TASK_STOPPED状态。可向其发送SIGCONT信号让进程转换到可运行状态
僵尸状态:当进程已经运行结束,但其父进程还未查询其状态。
父子进程的终止
父子进程谁先终止时未定义的,如果父进程先于子进程终止。那么子进程会被init进程“领养”,子进程的父进程id变为0。
如果父进程不去查询子进程的状态,那么子进程一直会处于“僵尸”状态。这也是init进程存在的原因之一。
父进程可以通过wait和waitpid函数来显示的查询子进程。
父进程查询子进程状态
wait和waitpid可查询子进程的结束状态,如果子进程还处于运行状态,那么父进程则会阻塞。
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
wait函数只要任意一个子进程结束,就会返回该子进程ID,如果出错则返回-1。
status为子进程返回的状态,需要通过宏WIFEXITED和WEXITSTATUS最终获得返回状态。
exec函数
fork进程后,往往通过exec函数执行另一个程序。此时该子进程完全被替换为新程序。
exec用一个全新的程序替换当前进程的正文,数据,堆和栈。
执行外部命令:system
system执行外部一个命令或者程序,相当于fork, exec,waitpid。
如何创建一个进程
当计算机开机的时候,内核只建立了一个init进程。Linux kernel并不提供直接建立新进程的系统调用。
剩下的所有进程都是init进程通过fork机制建立的。新的进程要通过老的进程复制自身得到,这就是fork。
fork是一个系统调用。
进程存活于内存中。每个进程都在内存中分配有属于自己的一片空间 (address space)。
当进程fork的时候,Linux在内存中开辟出一片新的内存空间给新的进程,并将老的进程空间中的内容复制到新的空间中,此后两个进程同时运行。
老进程成为新进程的父进程,而相应的,新进程就是老的进程的子进程。
一个进程除了有一个PID之外,还会有一个PPID(parent PID)来存储的父进程PID。如果我们循着PPID不断向上追溯的话,总会发现其源头是init进程。
所以说,所有的进程也构成一个以init为根的树状结构。
如下,我们查询当前shell下的进程:
root@admin:~# ps -o pid,ppid,cmd
PID PPID CMD
16935 3101 sudo -i
16939 16935 -bash
23774 16939 ps -o pid,ppid,cmd
我们可以看到,第二个进程bash是第一个进程sudo的子进程,而第三个进程ps是第二个进程的子进程。
还可以用$pstree命令来显示整个进程树。
fork通常作为一个函数被调用。这个函数会有两次返回,将子进程的PID返回给父进程,0返回给子进程。
实际上,子进程总可以查询自己的PPID来知道自己的父进程是谁,这样,一对父进程和子进程就可以随时查询对方。
通常在调用fork函数之后,程序会设计一个if选择结构。
当PID等于0时,说明该进程为子进程,那么让它执行某些指令,比如说使用exec库函数(library function)读取另一个程序文件,并在当前的进程空间执行 (这实际上是我们使用fork的一大目的: 为某一程序创建进程);
而当PID为一个正整数时,说明为父进程,则执行另外一些指令。
由此,就可以在子进程建立之后,让它执行与父进程不同的功能。
子进程的终结(termination)
当子进程终结时,它会通知父进程,并清空自己所占据的内存,并在kernel里留下自己的退出信息(exit code,如果顺利运行,为0;如果有错误或异常状况,为>0的整数)。
在这个信息里,会解释该进程为什么退出。父进程在得知子进程终结时,有责任对该子进程使用wait系统调用。
这个wait函数能从kernel中取出子进程的退出信息,并清空该信息在kernel中所占据的空间。
但是,如果父进程早于子进程终结,子进程就会成为一个孤儿(orphand)进程。
孤儿进程会被过继给init进程,init进程也就成了该进程的父进程。init进程负责该子进程终结时调用wait函数。
当然,一个糟糕的程序也完全可能造成子进程的退出信息滞留在kernel中的状况(父进程不对子进程调用wait函数)
这样的情况下,子进程成为僵尸(zombie)进程。当大量僵尸进程积累时,内存空间会被挤占。