1.简介
进程控制所需要熟练掌握的函数不多,只有fork、exec系列、_exit、wait和waitpid。
2.进程标识
每个进程都有一个唯一的非负整数PID,常常作为标识符的一部分以保证唯一性。
系统中有一些专用进程,其中常见的专用进程PID与说明如下:
- 0:调度进程,也称之为交换进程。是内核的一部分,不执行磁盘上的程序,因此称之为系统进程。
- 1:init:新版本中的文件为/sbin/init,负责在自举内核后启动unix提供—读取和系统有关的初始化文件,并将系统引导到一个状态。init进程也作为所有孤儿进程的父进程。
常用的API有:
#include <unistd.h>
pid_t getpid(void); // 获取调用进程的PID
pid_t getppid(void); // 获取调用进程的父PID
1.fork
fork可以用于创建一个子进程,该子进程获取父进程的数据空间、堆和栈的内存数据结构副本,且该副本与父进程的内存数据结构保持隔离。
fork在父进程中调用会返回创建的子进程的PID,而子进程则会返回0,因此若需要让子进程做某些事情,常见的代码如下:
pid_t pid = 0;
if((pid = fork()) < 0){
err_sys("fork error.");
}else if(pid == 0){
// do something
}else{
// do nothing
}
fork之后,子进程和父进程继续执行fork之后的指令。但执行顺序是不确定的,取决于内核的调度。
由于子进程会复制父进程的内存数据结构副本,这也包括了父进程打开的文件描述符表,因此子进程共享父进程的打开文件。同时,由于文件标中还记录了文件的偏移量,因此对打开文件的偏移量也是一致的。
2.fork的原理
fork在被当前进程调用的时候,内核为该进程分配一个新的唯一PID,并为其创建当前进程的mm_struct、区域结构和页表的原样副本,本质是使用了内存映射的共享对象。
当fork在新进程返回中,新进程的虚拟内存由于内存映射,也拥有了和父进程相同的虚拟内存。当它们对其中的内存进行写操作时,就会触发写时复制,从而保持了私有地址空间。
3._exit
进程有五种正常终止,即执行return、exit或pthread_exit等;也有三种异常终止,即调用abort,接收到某些信号或最后一个线程对“取消”作出响应。
无论如何终止,进程最后都会执行内核的同一段代码,该代码会为进程关闭所有打开的文件描述符,释放它所使用的存储器等。
我们希望有一种方法可以通知父进程它的子进程是如何终止的,方式是要么为终止函数(exit、_exit、_Exit)作为返回参数传递,要么在异常终止时内核产生一个终止状态,这都可以被父进程的wait或waitpid捕获。捕获的信息是一个数据结构,其至少包括
- PID
- 终止状态
- 使用的CPU谁见总量
如果父进程在子进程之前终止,则该进程将会被PID=1的init进程收养。如果一个进程被init收养,则该进程终止的时候,init就会调用wait去获取该终止状态。
如果没有父进程对该数据结构进行捕获会怎么样?一个已经终止,但是父进程没有对其进行善后处理即获取信息,释放资源的进程被称之为僵尸进程。
僵尸进程产生的原因是父进程没有对其进行善后,因此只要找到该僵尸进程的父进程,并杀掉其父进程就可以让init收养该进程并释放其资源了。找到其父进程的指令为:
ps -ef | grep defunct_process_pid
4.wait和waitpid
进程中止后,内核会对其父进程发送SIGCHLD信号。父进程可以忽略或为该信号提供一个调用函数。
#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pit_t pid,int *statloc,int options);
- 若所有子进程都在运行,则阻塞
- 若一个子进程已终止,取得返回状态并立刻返回
- 没有任何子进程,则立刻出错返回-1
函数的返回值是已终止子进程的pid,其返回状态将存放在*statloc指针指向的单元中。若不关心返回状态,可以指定该参数为空指针。
检查返回状态的宏如图所示。
waitpid通过选项提供了wait函数以外的额外功能:
- 可以指定特定的进程
- waitpid提供了一个非阻塞版本。有时只是希望获取子进程的状态,并不想阻塞。
- 支持作业控制。
5.exec
exec并不是用于获取父进程的一个副本,而是执行另一个程序。进程调用exec的时候,该进程的程序将会完全替换成新的程序,并从新程序的main函数开始执行。
由于exec并不是创建新程序,而是执行新程序,因此调用前后的PID不会改变,且会继承如PPID、用户组、进程组ID等属性。
和exec相关的函数有七个:
前四个函数取路径名作为参数,后两个取文件名,最后一个取文件描述符。其中,当指定filename为参数的时候
- 如果filename有/,则看作路径名。
- 否则就按照PATH环境变量,在指定的目录中寻找可执行文件。
最后,七个函数中仅仅有execve是系统调用,其他的函数都是对execve的间接调用。
这七个函数的参数表如下。
6.execve函数原理
execve的本质就是对进程的内存数据结构进行重加载,其加载并运行可执行文件需要以下的步骤
- 删除已经存在的用户区域
- 映射私有区域:为新程序的mm_struct如代码、数据、bss和栈进行创建。
- 映射共享区域:与其共享对象或目标链接,如动态链接标准库等。如标准C库libc.so,先通过动态链接并映射到虚拟内存空间的共享区域中。
- 设置程序计数器:设置PC,使其指向main函数等函数入口。
7.其他有趣的API
setuid和setgid用于更改用户id和组id。
system函数用于在程序中执行一个命令字符串。
nice函数用于更改进程的调度优先级。