Linux的多任务编程-进程
Linux的多任务编程-进程
进程的概念进程是指一个具有独立功能的程序在某个数据集合上的一次动态执行过程,它是系统进行资源分配和调度的基本单元.一次任务的运行可以并发激活多个进程,这些进程相互合作来完成该任务的一个最终目标.
进程的特性:并发性,动态性,交互性,独立性,异步性.
进程的种类:交互式进程,批处理进程,实时进程.
进程和程序是有本质区别的:程序是静态的一段代码,是一些保存在非易失性存储器的指令的有序集合,没有任何执行的概念;而进程是一个动态的概念,它是程序执行的过程,包括了动态创建,调度和消亡的整个过程,它是程序执行和资源管理的最小单位.
进程状态:运行状态,可中断的阻塞状态,不可中断的阻塞状态,可终止的阻塞状态,暂停状态,跟踪状态,僵尸状态,僵尸撤销状态.
进程状态转换关系:
进程是构成Linux系统应用的一块基石,它代表了一个Linux系统上的绝大部分活动,不管你是系统程序员,应用程序员,还是系统管理员,弄明白Linux的进程管理将使你"一切尽在掌握".
一个正在运行的程序(或者叫进程),是由程序代码,数据,变量(占用着系统内存),打开的文件(文件描述符)和一个环境组成.通常,Linux系统会让进程共享代码和系统库,所以在任何时刻内存里都只有代码的一份拷贝.例如,不管有多少进程在调用printf()函数,内存里只需要有一份它的代码就够了.
每个进程都会分配到一个独一无二的数字编号,我们称之为"进程标识码"(Process identifier,PID),它这是一个正整数,取值范围从2到32768.当一个进程被启动的时候,它会分配到一个未使用的编号数字做为自己的PID.虽然该编号是唯一的,但是当一个进程终止后,其PID就可以再次使用了.根据系统具体实现的不同,大多数的系统则会将所有可有的PID轮过一圈后,再考虑使用之前释放出的PID.
Linux内核通过惟一的进程标识符PID来标识每个进程.PID存放在进程描述符的pid字段中.在Linux中获得当前进程的进程号(PID)和父进程号(PPID)的系统调用函数分别为getpid()和getppid().
进程的数据结构表示进程的数据结构是struct task_struct.task_struct结构是进程实体的核心,Linux内核通过对该结构的相关操作来控制进程,task_struct结构是一个进程存在的唯一标志,也就是通常说的进程控制块(PCB,Process Control Block).
Linux将所有task_struct结构的指针存储在task数组中,数组的大小就是系统能容纳的进程数目,默认为512.
与其他的操作系统有所不同,为了实现创建进程的开销尽可能低,在Linux中"创建一个新的进程"与"在一个进程中运行一个给定的操作"是有所区别的.不过这样的区别在概念上并不十分重要,而是通过这样的观点设计出的Linux内核具有了很好的多进程性能,这样的设计思想是值得我们去学习的.一个现有的进程可以调用fork()函数创建一个新的进程.
fork()函数用于从已存在的进程中创建一个新进程.新进程称为子进程,而原进程称为父进程.使用fork()函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间,包括进程上下文、代码段、进程堆栈、内存信息、打开的文件描述符、信号控制设定、进程优先级、进程组号、当前工作目录、根目录、资源限制和控制终端等,而子进程所独有的只有它的进程号、资源使用和计时器等.fork函数的原型和返回值如下:
注:unistd.h 是 C 和 C++ 程序设计语言中提供对 POSIX 操作系统 API 的访问功能的头文件的名称.该头文件由 POSIX.1 标准(单一UNIX规范的基础)提出,故所有遵循该标准的操作系统和编译器均应提供该头文件(如 Unix 的所有官方版本,包括 Mac OS X,Linux 等).对于类 Unix 系统,unistd.h 中所定义的接口通常都是大量针对系统调用的封装,如 fork,pipe 以及各种 I/O 原语(read,write,close 等等).
fork()函数的使用很简单,下面通过一个简单的例子来进一步学习.
#include <unistd.h>#include <stdio.h>int main(){ printf("Start exec() \n"); execlp("ps", "ps", "-ax", 0); printf("Done.\n"); exit(0);}
exec函数族使用区别:
查找方式
当进程接收到某些信号时;或是调用abort()函数,它产生SIGABRT信号.这是前一种的特例.
这便是进程异常终止的两种方式。一个进程正常退出后传递了一个退出状态给系统,如return语句和exit()等函数.退出值是一个8位值,通常为一个int型的值.通常退出状态0表示正常退出,任何非0的退出状态表示出现了某种错误.
exit()和_exit()
exit()和_exit()函数都是用来终止进程的.当程序执行到exit()或_exit()时,进程会无条件地停止剩下的所有操作,清除包括各种数据结构,并终止本进程的运行.
exit()和_exit()的区别
前面我们已经多次用到了wait()和waitpid(),这两个函数的原型是:
wait()函数是用于使父进程(也就是调用wait()的进程)阻塞,直到一个子进程结束或者该进程接到了一个指定的信号为止.如果该父进程没有子进程或者他的子进程已经结束,则wait()就会立即返回。
waitpid()的作用和wait()一样,但它并不一定要等待第一个终止的子进程,它还有若干选项,如可提供一个非阻塞版本的wait()功能,也能支持作业控制
下面有几个宏可判别结束情况:
WIFEXITED(status)如果子进程正常结束则为非0 值.
WEXITSTATUS(status)取得子进程exit()返回的结束代码,一般会先用WIFEXITED来判断是否正常结束才能使用此宏.
WIFSIGNALED(status)如果子进程是因为信号而结束则此宏值为真.
WTERMSIG(status) 取得子进程因信号而中止的信号代码,一般会先用WIFSIGNALED来判断后才使用此宏.
WIFSTOPPED(status) 如果子进程处于暂停执行情况则此宏值为真.一般只有使用WUNTRACED时才会有此情况.
WSTOPSIG(status) 取得引发子进程暂停的信号代码,一般会先用WIFSTOPPED来判断后才使用此宏.
waitpid函数可以提供wait函数所没有的三个特性:
创建子进程是十分容易的,但你必须密切注意子进程的执行情况.当一个子进程结束运行的时候,它与其父进程之间的关联还会保持到父进程也正常地结束运行或者父进程调用了wait()才告终止.因此,进程表中代表子进程的数据项是不会立刻释放的,虽然不再活跃了,可子进程还停留在系统里,因为它的退出码还需要保存起来以备父进程中后续的wait()调用使用.它将成为一个Zombie 进程("僵尸进程").