进程控制
Table of Contents
1 fork与vfork函数
UNIX提供了两个函数用于创建新进程
#include <unistd.h> pid_t fork(); /* 子进程中返回0,父进程中返回子进程ID,出错返回-1 */ pid_t vfork(); /* 子进程中返回0,父进程中返回子进程ID,出错返回-1 */
1.1 fork出来的子进程有几点要注意:
- 子进程与父进程分别持有相同的文件描述符,这些文件描述符共用核心中的同一个文件项
- 父进程设置的文件锁不会被子进程继承
- 子进程的未处理的闹钟(alarm)被清除
- 子进程的未处理信号集设置为空集
- 当子进程终止时,内核会向父进程发送SIGCHLD信号,但对该信号,系统的默认动作是忽略它.
- 子进程会继承父进程的信号处理方式. 因为子进程在开始时复制了父进程的存储映像,随意信号捕捉函数的地址在子进程中是有意义的
1.2 fork与vfork的区别在于
- 在子进程调用exec或exit之前,vfork并不会将父进程的地址空间复制到子进程中,即 子进程在父进程的空间中运行,因此子进程运行的结果会影响到父进程!
- vfork保证子进程先运行,在它调用exec或exit之后,父进程才可能被调度运行.
2 wait和waitpid函数
#include <sys/wait.h> /* 若成功则返回进程ID,0,若出错则返回-1 */ pid_t wait(int* status); pid_t waitpid(pid_t pid,int* status,int options);
- 如果所有子进程还在运行,则阻塞
- 如果一个子进程已终止,则父进程获取其终止状态并返回子进程pid
- 如果父进程没有任何子进程,则 报错返回
- waitpid可以通过设置选项,使得调用者不阻塞
2.1 参数status说明
- 参数status可以为空指针,表示不关心终止状态
- 在<sys/wait.h>中有定义四个宏来获取进程终止的原因
WIFXITED(status)
子进程是否正常终止.
若为真,则可以通过宏WEXITSTATUS(status)来获取退出的状态码
WIFSIGNALED(status)
子进程是否因收到一个不可捕获的信号而异常退出.
若为真,可以通过WTERMSIG(status)获取信号编号
某些系统可以通过WCOREDUMP(status)判断是否产生core文件
WIFSTOPPED(status)
子进程是否处于stop状态
若为真,则可以通过WSTOPSIG(status)来获取使子进程暂停的信号编号
WIFCONTINUED(status)
子进程是否处于continue状态
2.2 参数pid的说明
- pid == -1,等待任一子进程,此时waitpid与wait等效
- pid > 0,等待进程id与pid相等的子进程
- pid == 0,等待组ID等于调用进程组ID的任一子进程
- pid < -1,等待其组ID等于pid绝对值的任一子进程
2.3 参数optioins说明
通过设置options参数,waitpid不一定等到子进程退出来能获取子进程的状态. 也能在子进程处于stop或continue状态时返回子进程的状态.
WNOHANG
waitpid不阻塞
WUNTRACED
若子进程处于stop状态,且该stop状态尚未报告时,返回其状态
WCONTINUED
若子进程处于continue状态,且该continue状态尚未报告时,返回其状态.
3 waitid函数
waitid可以指定要监控的子进程的哪些状态变化
#include <sys/wait.h> int waitid(idtype_t idtype,id_t id,siginfo_t* infop,int options);
- 参数idtype_t指定了要等待的子进程的类型
- P_PID
- 等待一个特定的进程
- P_PGID
- 等待一个特定进程组中的任一子进程
- P_ALL
- 等待任一子进程,此时忽略参数id的值
- 参数id的作用则跟idtype的值相关
- 参数options指定关注子进程的哪些状态变化
- WNOHANG
- 非阻塞等待
- WNOWAIT
- 不破坏子进程的退出状态,该状态可由后续wait,waitid或waitpid调用获取
- WSTOPPED
- 等待一个尚未报告的STOP状态的子进程
- WCONTINUED
- 等待一个尚未报告的CONTINUE状态的子进程
- WEXITED
- 等待已退出的子进程
4 exec系列函数
- 由于exec一个新程序后,信号处理函数的地址已经失效了,因此exec函数将原先设置为要捕获的信号都更改为它们的默认动作
4.1 exec系列函数的区分
- 字母p表示该函数可以为不带目录的文件名,则会从PATH环境变量中搜索可执行文件
- 字母l表示该函数的参数要一个一个的在函数签名中列出来,最后以一个(char*)0结尾表示参数终结
- 字母v表示该函数取一个argv[]数组作为传递給新进程的参数
- 字母e表示该函数接收一个envp[]数组,可以分配不同于当前环境的新环境
4.2 exec后的新进程与原进程的关系
- 新进程保持
- 原pid和ppid
- 原实际用户id和实际组id
- 附加组ID
- 进程组ID
- 会话ID
- 控制终端
- alarm尚存留的事件
- 当前工作目录
- 根目录
- 文件模式创建屏蔽字
- 文件锁
- 进程信号屏蔽
- 未处理信号
- 资源限制
- tms_utime,tms_stime,tms_cutime及tms_cstime
- 是否关闭原进程打开的文件与该文件描述符的close-on-exec标志有关.
- 若设置了该标志,则指向exec时会关闭该描述符
- 若没设置该标准,则保持描述符打开
- 除非特地用fcntl设置了该标志,否则系统的默认为关闭该标志
- exec时 明确会关闭打开的目录流(opendir)
4.3 当exec调用一个带`#! 解析器程序 可选参数项'的脚本文件时:
- exec实际执行的并不是该脚本文件,而是脚本文件第一行`#! 解析器程序 可选参数项'中的 解析器程序
exec传递给该解析器程序的参数顺序为,解析器地址,可选参数,脚本文件地址,除argv0外的由exec函数传入的其他参数.
/* /tmp/testinterp的内容为: #! /usr/bin/echo.exe args: */ #include <unistd.h> int main() { execl("/tmp/testinterp","testinterp","myarg1","MY ARG2",(char*) 0); }
args: /tmp/testinterp myarg1 MY ARG2
5 进程会计
大多数UNIX系统都提供了一个选项以进行进程会计(process accounting)处理. 启用该选项后,每当进程结束时,内核就写一个会计记录.
会计记录是在fork时产生而不是exec时产生 但exec会改变响应记录中的命令名,而且AFORK标志也会被清除.
会计记录一般为二进制格式的,且结构各个系统实现的都不一样. 一般可以在<sys/acct.h>中查到struct acct
root用户可以使用`accton 命令行'来对`命令行'开启进程会计选项.
6 进程时间
任何进程都可以通过调用times函数获得自己的运行时间,用户态CPU时间和内核态CPU时间.
#include <sys/times.h> /* 若成功返回进程相对上一次运行times函数的运行时间,单位为时钟滴答数,若出错返回-1 */ clock_t times(struct tms* buf); struct tms{ clock_t tms_utime; /* user CPU time */ clock_t tms_stime; /* system CPU time */ clock_t tms_cutime; /* user CPU tim, terminated children */ clock_t tms_cstime; /* system CPU time, terminated children */ }