原文来自静雅斋,转载请注明出处。javascript
因为前文中wait
和waitpid
函数有不少不灵活的地方,SUS标准规定了之外一个进程终止状态获取函数前端
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
int waitid(idtype_t, id_t, siginfo_t *, int) __DARWIN_ALIAS_C(waitid);复制代码
上面两个一个是原著实现,还有一个是苹果系统实现,可是笔者没有在函数手册中找到具体描述,因此这里就不介绍,各位直接看原著。java
除了上面的等待函数,还有两个从BSD时代延续下来的函数,也成为了事实意义上的Unix标准,算法
pid_t wait3(int *stat_loc, int options, struct rusage *rusage);
pid_t wait4(pid_t pid, int *stat_loc, int options, struct rusage *rusage);复制代码
其实从参数的个数和命名基本也能猜出这两个函数干吗用的了。不过仍是附上Unix系统手册shell
The wait4() call provides a more general interface for programs that need to wait for certain child processes, that need resource utilization statistics accumulated by child processes, or that require options. The other wait functions are implemented using wait4().
The waitpid() call is identical to wait4() with an rusage value of zero. The older wait3() call is the same as wait4() with a pid value of -1.复制代码
wait4函数提供更通用的接口,并且还包括了资源的统计数据,数组
struct rusage {
struct timeval ru_utime; /* user time used */
struct timeval ru_stime; /* system time used */
long ru_maxrss; /* max resident set size */
long ru_ixrss; /* integral shared text memory size */
long ru_idrss; /* integral unshared data size */
long ru_isrss; /* integral unshared stack size */
long ru_minflt; /* page reclaims */
long ru_majflt; /* page faults */
long ru_nswap; /* swaps */
long ru_inblock; /* block input operations */
long ru_oublock; /* block output operations */
long ru_msgsnd; /* messages sent */
long ru_msgrcv; /* messages received */
long ru_nsignals; /* signals received */
long ru_nvcsw; /* voluntary context switches */
long ru_nivcsw; /* involuntary context switches */
}复制代码
这两个函数的最后一个参数就是一个结构体,可以让咱们经过这个结构体来了解子进程的资源统计。网络
资源是有限的,因此操做系统的一个功能就是提供了资源的分配,竞争条件是操做系统原理概念中的资源竞争。例如,fork函数调用后有某种逻辑的正确执行须要依赖父进程和子进程运行前后,那么这就是一种竞争条件,在现代操做系统中,通常都是多种调度算法,因此咱们没法预料到哪一个进程先运行。
在原著中一个例程,须要第二个子进程在第一个子进程以前运行。就如同事件循环能立刻想到while (true)
同样,当碰到这种问题,第一反应就是轮询,while (getppid() != -1)
,很容易理解的代码,可是这样就浪费了CPU时间。
为了防止这种恶性的竞争和没必要要的轮询,Unix系统引入了信号机制,可是这里暂且先不讨论具体实现,等到后面章节再讨论。ide
exec函数族是很重要的一个学习概念,在学习fork函数的时候讲到过有两种用法,一种是网络服务的master-worker进程模型,一种是子进程执行程序,一般叫作spawn
,在Unix系统中,没有提供spawn相似的函数,可是将其拆分开来,fork
和exec
组合就是spawn
,当进程调用一种exec函数时,该进程的正文段、数据段、bss段和堆栈将彻底被替换,可是其余的进程属性不变。函数
int execl(const char *path, const char *arg0, ... /*, (char *)0 */);
int execle(const char *path, const char *arg0, ... /*, (char *)0, char *const envp[] */);
int execlp(const char *file, const char *arg0, ... /*, (char *)0 */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvP(const char *file, const char *search_path, char *const argv[]);复制代码
原著上讲有7个函数,可是实际上笔者在苹果系统中只找到6个函数族,而在CentOS系统上也只找到5个函数。实际上这些函数都是execve
函数的前端,最终都是execve
函数来完成实际工做。学习
int execve(const char *path, char *const argv[], char *const envp[]);复制代码
这是最终调用的函数,从这个函数中咱们能够看到,exec函数族想要运行须要三个参数,文件路径、传入的参数数组和环境变量。其余基本就是这个函数的简化版本或者变体。在前面的描述中咱们能够看到有两个函数不使用path做为参数,而是以file做为参数,当file参数为绝对路径时,则使用绝对路径的位置,不然在PATH环境变量的指示中搜索文件。从前面能够看到,只有一个函数execle
带有环境变量指针参数,若是没有带此参数的函数,都会复制现有的进程的环境变量,每一个系统基本都有不一样的实现,因此应当使用共有的函数,并结合使用条件编译,来作跨平台开发。
在前面的Unix文件章节,提到了FD_CLOEXEC
标志,进程的每一个文件描述符都有一个执行时关闭标志,若是设置了这个参数,则在exec执行时自动关闭该文件。默认状况下,exec后仍然保持描述符打开。
在前面也提到了,用户组ID能够分为实际用户族ID和有效用户组ID,实际用户组ID在exec后依旧是不变的,可是有效用户组ID变化与不然取决于执行程序的设置用户组ID,若是有,则有效用户组ID改成拥有者用户组ID,不然不变,这个很好理解,shell启动一个程序,实际用户组ID是确定不变的,由于要标识一个进程的使用者,可是用于权限检查的有效用户组ID则是取决于设置用户组ID,sudo命令也是相似。
在Unix系统中,进程拥有实际用户组ID和有效用户组ID,在权限检查的时候,检查的是有效用户组ID,在设计开发的时候,Unix的哲学就是最小特权,咱们常常须要修改进程的权限,因此这里也有了系统提供的函数。
int setuid(uid_t uid);
int setgid(gid_t gid);
int seteuid(uid_t euid);
int setegid(gid_t egid);复制代码
在讲解函数以前,先讲解一下进程的三种ID,实际用户组ID用于标识进程是谁拥有负责的,有效用户组ID则是用于权限检查的,保存的设置用户组ID则是用于恢复有效用户ID的,在早期Unix系统设计中,只存在一种用户组ID,它承担了全部的功能,后来,因为set-uid bit
的存在,就分离出了实际用户组ID和有效用户组ID,可是,在不少状况下,并非一直须要有效用户组ID等于拥有者用户组ID的,因此就有了保存设置用户组ID。保存设置用户组ID是最近一次setuid系统调用或是exec一个setuid程序的结果,用于恢复最先的有效用户组ID。
The setuid() function sets the real and effective user IDs and the saved set-user-ID of the current process to the specified value. The setuid() function is permitted if the effective user ID is that of the super user, or if the specified user ID is the same as the effective user ID. If not, but the specified user ID is the same as the real user ID, setuid() will set the effective user ID to the real user ID.
setuid函数为当前进程设置实际用户ID、有效用户ID和保存的set-user-ID为指定值。若是有效用户ID是超级用户,setuid函数则被放行,换言之,root权限是有特权的,或者说若是指定的有效用户ID等同现有的有效用户ID,也是能够放行的,若是不等同,可是指定的有效用户ID等同于实际用户ID,setuid函数将会将有效用户ID设置为实际用户ID。
对于内核维护的三种ID,有几点须要注意:
seteuid和setegid相似setuid和setgid,可是它们只更改有效用户组ID,root权限进程能够任意修改有效用户组ID,可是非特权用户只能将其设置为实际用户组ID或者保存设置用户组ID,
int setreuid(uid_t ruid, uid_t euid);
int setregid(gid_t rgid, gid_t egid);复制代码
这两个函数用于分别设置实际用组ID和有效用户组ID,固然,只有root权限才能任意更改,而非特权用户通常都是用于交换实际用户组ID和有效用户组ID。
其实一言以蔽之,上面的六个函数实际上都有root权限运行和非root权限运行两种形式,任何状况下,只有root权限才能更改实际用户组ID,而有效用户组ID表明的是权限检查,因此root权限进程能够下降自身权限,可是其余进程不行。
解释器文件实际上就是普通文件,只是权限位增长了执行权限,而且在头部以#!pathname[optional-arguments]
的形式代表了解释器。解释器文件通常用于脚本编写,没什么可说的,因此这里就省略了。
int system(const char *command);
The system() function hands the argument command to the command interpreter sh(1). The calling process waits for the shell to finish executing the command, ignoring SIGINT and SIGQUIT, and blocking SIGCHLD. If command is a NULL pointer, system() will return non-zero if the command interpreter sh(1) is available, and zero if it is not.复制代码
system函数将字符串传递给sh解释器,而且一直会等待shell结束执行,若是command参数是NULL指针,而且sh解释器存在,则返回非0,不然就是0,这主要用于判断sh存在与否。这里也没什么可讲,原著中都讲完了,须要注意的是,这是一个很是实用的函数。
进程会计不是任何标准规定的东西,因此各个Unix系统实现都将其按照本身的理解方式实现,因此在跨平台开发中就很是的麻烦,并且因为各个平台提供的查询命令都不一致,因此并无实际上的意义所在,这节能够忽略。
在实际的Unix系统中,uid和gid是标志一个用户的方式,也叫做凭据,可是用户不须要以数字标志的形式管理系统,因此就有了以英文形式提供的用户标识,系统也提供了对应的映射
char *getlogin(void);
int setlogin(const char *name);复制代码
登陆名在一个会话中是不改变的,即便是一些更改uid的程序,例如su命令,而setlogin则是设置登陆名,这个函数只能由root权限调用。
进程调度是由操做系统决定,开发者和用户不可得知系统会如何调度本身的进程,可是却能够设置进程的优先级。Unix系统也提供了相关接口用于修改和查询。固然,因为CPU也是一种资源,因此就如同其余的资源限制同样,只有root权限进程才能提升自身的优先级,其余进程只能下降自身优先级。
int nice(int incr);复制代码
nice函数用于得到设置自身的有限度,而且这是一个累加的值,因为这个函数局限性,系统还提供了另外一个函数
int getpriority(int which, id_t who);
int setpriority(int which, id_t who, int prio);复制代码
which参数取RIO_PROCESS, PRIO_PGRP, or PRIO_USER
,而who参数的解释取决于which参数,当who为0时,表示当前的进程、进程组或用户,而prio则是-20到20的值,默认为0,低优先级则有高CPU使用。
前面讲过Unix系统有三种时间:墙上时钟时间、用户CPU时间和系统CPU时间
clock_t times(struct tms *buffer);
struct tms {
clock_t tms_utime;
clock_t tms_stime;
clock_t tms_cutime;
clock_t tms_cstime;
};复制代码
上面是结构体和函数声明,结构体没有包含墙上时钟时间,可是函数返回了时钟时间,结构体按照顺序包含了用户CPU时间、系统CPU时间、包含子进程的用户CPU时间和包含子进程的系统CPU时间。