以下内容全部摘自UNIX环境高级编程(第3版)
进程标识
除了进程ID,每个进程还有一些其他标识符。下列函数返回这些标识符。
#include <unistd.h>
pid_t getpid(void);
返回值:调用进程的进程ID
pid_t getppid(void);
返回值:调用进程的父进程ID
uid_t getuid(void);
返回值:调用进程的实际用户ID
uid_t geteuid(void);
返回值:调用进程的有效用户ID
gid_t getgid(void);
返回值:调用进程的实际组ID
gid_t getegid(void);
返回值:调用进程的有效组ID
函数fork
一个现有的进程可以调用fork函数创建一个新进程。
#include <unistd.h>
pid_t fork(void);
返回值:子进程返回0,父进程返回子进程ID;若出错,返回-1
函数vfork
vfork
和fork
之间的一个区别是:vfork
保证子进程先运行,在它调用exec
或exit
之后父进程才可能别调度运行,
当子进程调用这两个函数的任意一个时,父进程会恢复运行。
(如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。)
调用exit
会先执行一些清理处理,_exit
和_Exit
立即进入内核。
函数wait和waitpid
当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD
信号。因为子进程终止是个异步事件(这可以在父进程运行的任何时候发生),
所以这种信号也是内核向父进程发的异步通知。父进程可以选择忽略该信号,或者提供一个该信号发生时即被调用执行的函数(信号处理程序)。
对于这种信号的系统默认动作是忽略它。
现在需要知道的是调用wait
或waitpid
的进程可能会发生什么。
* 如果其所有子进程都还在运行,则阻塞。
* 如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回。
* 如果它没有任何子进程,则立即出错返回。
如果进程由于接收到SIGCHLD
信号而调用wait
,我们期望wait
会立即返回。但是如果在随机时间点调用wait
,则进程可能会阻塞。
#include <sys/wait.h>
pid_t wait(int *staloc);
pid_t waitpid(pid_t pid, int *statloc, int options);
两个函数返回值:若成功,返回进程ID;若出错,返回0或-1
这两个函数的参数statloc
是一个整型指针。如果statloc
不是一个空指针,则终止进程的终止状态就存放在它所指向的单元内。
如果不关心终止状态,则可将该参数指定为空指针。
对于waitpid
函数中pid
参数的作用解释如下
pid == -1 等待任一子进程。此种情况下,waitpid
与wait
等效
pid > 0 等待进程ID与pid
相等的子进程
pid == 0 等待组ID等于调用进程组ID的任一子进程。
pid < -1 等待组ID等于pid
绝对值的任一子进程
waitpid
函数返回终止子进程的进程ID,并将该子进程的终止状态存放在由staloc
指向的存储单元中。
对于wait
,其唯一的出错是调用进程没有子进程(函数调用被一个信号中断时,也可能返回另一种出错。)
但是对于waitpid
,如果指定的进程或进程组不存在,或者参数pid
指定的进程不是调用进程的子进程,都可能出错。
函数waitid
#include <sys/wait.h>
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
返回值:若成功,返回0;若出错,返回-1
该函数支持的idtype
如下图
options
参数是下图中各标志的按位或运算。这些标志指示调用者关注哪些状态变化。
WNOHANG : Return immediately instead of blocking if there is no child exit status available.
WCONTINUED
、WEXITED
或WSTOPPED
这3个常量之一必须在options
参数中指定。
infop
参数是指向siginfo
结构的指针。该结构包含了造成子进程状态改变有关信号的详细信息。
函数wait3和wait4
允许内核返回由终止进程及其所有子进程使用的资源概况。
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
pid_t wait3(int *statloc, int options, struct rusage *rusage);
pid_t wait4(pid_t pid, int *statloc, int options, struct rusage *rusage);
两个函数返回值:若成功,返回进程ID;若出错,返回-1
资源统计信息包括用户CPU时间总量、系统CPU时间总量、缺页次数、接收到信号的次数等。有关细节参阅
getrusage(2)手册页。
函数exec
#include <unistd.h>
int execl(const char *pathname, const char *arg0, ... /* (char *)0 */ );
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, ... /* (char *)0, char *const envp[] */ );
int execve(const char *pathname, char *const argv[], char *const envp[] );
int execlp(const char *filename, const char *arg0, ... /* (char *)0 */ );
int execvp(const char *filename, char *const argv[]);
int fexecve(int fd, char *const argv[], char *const envp[]);
7个函数返回值:若出错,返回-1;若成功,不返回
char arg0, char *arg1, …, char *argn, (char )0
这种语法显示地说明了最后一个命令行参数之后跟了一个空指针。如果用常量0来表示一个空指针,则必须将它强制转换为一个指针;
否则它将被解释为整型参数。如果一个整形数的长度与char *的长度不同,那么exec函数的实际参数将出错。
更改用户ID和更改组ID
可以用setuid
函数设置实际用户ID和有效用户ID。与此类似,可以用setgid
函数设置实际组ID和有效组ID
#include <unistd.h>
int setuid(uid_t uid);
int setgid(gid_t gid);
两个函数返回值:若成功,返回0;若出错,返回-1
关于谁能更改ID有若干规则。现在先考虑更改用户ID的规则(关于用户ID我们所说明的一切都适用组ID)
* 若进程具有超级用户特权,则setuid
函数将实际用户ID、有效用户ID以及保存的设置用户ID(saved set-user-ID)设置为uid
* 若进程没有超级用户特权,但是uid
等于实际用户ID或保存的设置用户ID,则setuid
只将有效用户ID设置为uid
。不更改
实际用户ID和保存的设置用户ID
* 如果上面两个条件都不满足,则errno
设置为EPERM
,并返回-1
函数setreuid和setregid
功能是交换实际用户ID和有效用户ID的值
#include <unistd.h>
int setreuid(uid_t ruid, uid_t euid);
int setregid(gid_t rgid, gid_t egid);
两个函数返回值:若成功,返回0;若出错,返回-1
如若其中任一参数的值为-1,则表示相应的ID应当保持不变
函数seteuid和setegid
类似于setuid
和setgid
,但只更改有效用户ID和有效组ID
#include <unistd.h>
int seteuid(uid_t uid);
int setegid(gid_t gid);
两个函数返回值:若成功,返回0;若出错,返回-1
函数system
#include <stdlib.h>
int system(const char *cmdstring);
返回值:(见下)
因为system
在其实现中调用了fork
、exec
和waitpid
,因此有3种返回值。
1. fork
失败或者waitpid
返回除EINTR
之外的出错,则system
返回-1,并且设置errno
以指示错误类型
2. 如果exec
失败(表示不能执行shell),则其返回值如同shell执行了exit(127)
一样。
3. 否则所有3个函数(fork
、exec
和waitpid
)都成功,那么system
的返回值是shell的终止状态,其格式已在waitpid
中说明。