每个进程都有一个非负整型表示的唯一进程 ID。
有关进程 ID 的一些获取函数
#include
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
#include
pid_t fork(void);
//返回值: 子进程返回 0,父进程返回子进程ID;若出错,返回-1
由 fork 创建的新进程被称为子进程。fork 函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是 0,而父进程的返回值则是新建子进程的进程 ID。
子进程和父进程继续执行 fork 调用之后的指令。子进程是父进程的一个副本。例如,子进程获得父进程数据空间、堆和栈的副本。注意:这是子进程拥有的副本。父进程和子进程并不共享这些存储空间。父进程和子进程共享正文段。
由于在 fork 之后经常跟着 exec,所以很多实现并不执行一个父进程数据段、栈和堆的完全副本。而是采用写时复制技术(Copy On Write,COW)。这些区域由父进程和子进程共享,而且内核将它们的访问权限改变为只读。如果父进程和子进程中的任意一个试图修改这些区域,则内核只为修改区域的那块内存制作一个副本,通常是虚拟存储系统中的一页。
fork 失败的两个主要原因:
fork 有以下两种用法:
当一个进程正常或异常终止时,内核就向其父进程发送 SIGCHLD 信号。因为子进程终止是个异步事件(可能在父进程运行的任何时候发生),所以这种信号也是内核想父进程发的异步通知。父进程可以选择忽略该信号,或者提供一个该信号发生时即被调用执行的函数。对于这种信号的系统默认动作是忽略它。
当调用 wait 和 waitpid 的进程可能发生:
#include
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc,int options);
//返回值:若成功,返回进程ID,若出错,返回0或-1
这两个函数的区别如下:
这两个函数的参数 statloc 是一个整型指针。如果 statloc 不是一个空指针,则终止进程的终止状态就存放在它所指向的单元内。如果不关心终止状态,则可将该参数指定为空指针。
对于 waitpid 函数中 pid 参数的作用解释如下:
waitpid 函数返回终止子进程的进程 ID,并将该子进程的终止状态存放在由 statloc 指向的存储单元中。
options 参数使我们能进一步控制 waitpid 的操作。此参数或者是 0,或者是下列常量按位或运算的结果:
waitpid 函数提供了 wait 函数没有提供的 3 个功能:
内核为每个终止子进程保存了一定量的信息,所以当终止进程的父进程调用 wait 或 waitpid 时,可以得到这些信息。这些信息至少包括进程 ID、该进程的终止状态以及该进程使用的 CPU 时间总量。内核可以释放终止进程所使用的所有存储区,关闭其所有打开文件。
一个以及终止、但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息、释放它仍占用的资源)的进程别称为僵死进程。
当进程调用一种 exec 函数时,该进程执行的程序完全替换为新程序,而新程序则从其 main 函数开始执行。因为调用 exec 并不创建新进程,所有前后的进程 ID 并未改变。exec 只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段。
有七种不同的 exec 函数可供使用,它们通常被称为 exec 函数,我们可以使用这 7 个函数中的任一个。
#include
int execl(const char *pathname, const char *agr0, ... /* (char *)0 */);
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, ... /* (char *)0, char *const envp[] */);
int execce(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 argc[]);
int fexecve(int fd, char *const argv[], char *const encp[]);
//返回值:若出错,返回 -1;若成功,不返回
这些函数之间的第一个区别时前 4 个函数取路径名作为参数,后两个函数则取文件名作为参数,最后一个函数取文件描述符作为参数。
当指定 filename 作为参数时:
第二个区别与参数表的传递有关(l 表示列表 list, v 表示适量 vector)。函数 execl、execlp 和 execle 要求将新程序的每个命令行参数都说明为一个单独的参数。这种参数表以空指针结尾。对于另外 4 个函数(execv、execvp、execve 和 fexecve),则应先构造一个指向各参数的指针数组,然后将该数组地址作为这 4 个函数的参数。
最后一个区别与想向程序传递环境表相关。以 e 结尾的 3 个函数(execle、execve 和 fexecve)可以传递一个指向环境字符串指针数组的指针。其他 4 个函数则使用调用进程中的 environ 变量为新程序复制现有的环境。
#inlcude
int setuid(uid_t uid);
int setgid(gid_t fid);
//返回值:若成功,返回0;若出错,返回-1
更改用户 ID 的规则(关于用户 ID 我们所说明的一切都适用于组 ID)
关于内核所维护的 3 个用户 ID,还要注意以下几点:
#include
int system(const char *cmdstring);
如果 cmdstring 是一个空指针,则仅当命名处理程序可用时,system 返回非 0 值,这一特征可用确定在一个给定的操作系统上是否支持 system 函数。
因为 system 在其实现中调用了 fork、exec 和 waitpid,因此有 3 种返回值: