本文介绍进程控制,包括创建进程,执行程序和进程终止
1.进程标识符
每一个进程都有一个非负整数表示的唯一进程ID,pid。虽然pid是唯一的,但是可以重复使用,当进程结束的时候,
其pid就可以在次使用了。
系统中常有一些专用进程:
0:通常是调度进程,常称为交换进程,因为不使用磁盘中的任何程序,所以又称为系统进程。
1:通常是init进程
下面的函数返回进程标识符
#include <unistd.h>
pid_t getpid(void);
//返回调用进程的pid
pit_t getppid(void);
//返回调用进程的父进程pid
uid_t getuid(void);
//返回调用进程的实际用户id
uid_t geteuid(void);
//返回调用进程的有效用户id
gid_t getgid(void);
//返回调用进程的实际组id
gid_t getegid(void);
//返回调用进程的有效组id
2.fork函数
创建一个新进程
#include <unistd.h>
pid_t fork(void);
//子进程中返回0,父进程返回子进程pid,出错返回-1
子进程是父进程的副本,子进程获得父进程的数据空间,堆和栈的副本,父子之间不共享这些存储区部分
父子进程共享正文段,而正文段通常是只读的。也就是说对子进程进行操作,对父进程没有任何影响。
(现代的处理方法:使用到了写时复制的技术,存储区内容父子共享,并为只读,当父子进程中任一要去写的时候,
内核就为其修改的那个部分制作一个副本,通常是虚拟存储器系统的一页)
3.vfork函数
创建一个进程,但是与fork有区别
#include <unistd.h>
pid_t vfork(void);
//子进程中返回0,父进程返回子进程pid,出错返回-1
此时,子进程和父进程共享资源,当对子进程进行操作的时候,会影响到父进程。vfork之后通常接着exec。
vfork保证子进程先运行,他在调用了exec或者exit之后父进程才能被调度运行。
4.exit函数
在说明fork函数时,显而易见,子进程是在父进程内调用fork后生成的。子进程在终止的时候会将把自己的终止状态
返回给父进程。如果父进程在子进程终止之前终止,对于父进程的所有子进程他们的父进程都改为init进程。我们称
这些进程由init领养。其操作的大体过程如下:
在一个进程终止的时候,内核逐个检查所有活动进程,以判断它是否是正要终止进程的子进程。如果是,
则将该进程的父进程ID更改为1.这种处理方式保证了每个进程都有一个父进程。
如果子进程在父进程之前终止,内核就为每个终止子进程保存了一定量的信息,所以当终止进程的父进程调用wait
或waitpid时,可以得到这些信息。这些信息至少包括进程ID,进程的终止状态,以及该进程使用的CPU时间总量。
内核可以释放终止进程所使用的所有存储区,关闭其所有的打开文件。但是一个已经终止,而父进程尚未对其进行
善后处理的进程被称之为僵死进程。
当init的子进程终止的时候,init进程会自动调用wait函数取得其终止状态。防止了系统中有很多僵死进程。
5.wait和waitpid函数
当调用这两个函数的时候:
如果其所有子进程都还在运行,则阻塞;
如果一个子进程已经终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回;
如果没有任何子进程,则会出错.
#include <sys/wait.h>
pit_t wait(int *statloc);
pit_t waitpid(pid_t pid, int *statloc, int options);
在一个子进程终止前,wait使其调用者阻塞,而waitpid有一个选项,可以使调用者不阻塞。
waitpid并不等待在其调用之后的第一个终止子进程,它有若干个选项,可以控制它所等待的进程。
如果第一个子进程已经终止,并且是一个僵死的进程,wait立即返回并取得该子进程的状态,否则wait使其调用者阻塞
直到一个子进程终止。若调用者阻塞而且它有多个子进程,则当有一个子进程终止的时候,wait函数就立即返回。
statloc是一个整型指针,如果这个指针不为空,则存储子进程的状态
pid参数的作用如下:
pid == -1 等待任一子进程,此时,与wait等效
pid > 0 等待其进程ID与pid相等的子进程
pid == 0 等待其组ID等于调用进程组ID的任一子进程
pid < -1 等待其组ID等于pid绝对值的任一子进程
options参数使我们能进一步控制waitpid操作,此参数可以为0
常量 说明
WCONTINUED 若实现支持作业控制,那么由pid指定的任一子进程在暂停后已经继续,但其状态尚未报告,
则返回其状态
WNOHANG 若由pid指定的子进程并不是立即可用的,则waitpid不阻塞,此时其返回值为0
WUNRTACEO 若某实现支持作业控制,而由pid指定的任一子进程已处于暂停状态,并且其状态自暂停以来
还未报告过,则返回其状态。WIFSTOPPED宏确定返回值是否对应于一个暂停子进程
6.waitid函数
此函数类似与waitpid,但是提供了更多的灵活性
#include <sys/wait.h>
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
//成功返回0,出错返回-1
使用单独的参数表示要等待的子进程类型id的值与idtype的值有关,以下列出idtype的类型:
P_PID 等待一个特定的进程,id包含要等待进程的进程id
P_PGID 等待一个特定进程组的任一子进程,id包含等待进程组的进程组id
P_ALL 等待任意子进程,乎略id
options参数:
WCONTINUED 等待一个进程,它以前曾被暂停,此后有继续执行,但其状态未返回
WEXITED 等待已经退出的进程
WNOHANG 如无可用的子进程退出状态,立即返回而非阻塞
WNOWAIT 不破坏进程退出状态。该子进程退出状态后可由后续的wait,waitpid或waitid调用获取
WSTOPPED 等待一个进程,它已经暂停,但其状态尚未报告
7.竞争条件
当多个进程都企图对共享数据进行某种处理,而最后的结果由取决于进程运行顺序的时候,我们认为发生了竞争
8.exec函数
当进程调用exec函数的时候,进程执行的程序完全替换为新的程序,而新程序从其main函数开始执行。
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[]);
//出错返回-1,成功不返回
前4个函数将路径名作为参数,后两个函数将文件名作为参数,如果filename含有/,则表示路径名
否则就按照环境变量所指定的目录中搜索可执行文件
execlp和execvp的filename可以是shell脚本
其他参数为新程序的命令行参数
6个函数中,字母p表示该函数取filename为参数,字母l表示该函数取一个参数表,与字母v互斥,v表示该函数取一个envp[]数组,而不使用当前环境
9.更改用户ID和组ID
#include <unistd.h>
int setuid(uid_t uid);
int setgid(gid_t gid);
//成功返回0,出错返回-1
1.进程标识符
每一个进程都有一个非负整数表示的唯一进程ID,pid。虽然pid是唯一的,但是可以重复使用,当进程结束的时候,
其pid就可以在次使用了。
系统中常有一些专用进程:
0:通常是调度进程,常称为交换进程,因为不使用磁盘中的任何程序,所以又称为系统进程。
1:通常是init进程
下面的函数返回进程标识符
#include <unistd.h>
pid_t getpid(void);
//返回调用进程的pid
pit_t getppid(void);
//返回调用进程的父进程pid
uid_t getuid(void);
//返回调用进程的实际用户id
uid_t geteuid(void);
//返回调用进程的有效用户id
gid_t getgid(void);
//返回调用进程的实际组id
gid_t getegid(void);
//返回调用进程的有效组id
2.fork函数
创建一个新进程
#include <unistd.h>
pid_t fork(void);
//子进程中返回0,父进程返回子进程pid,出错返回-1
子进程是父进程的副本,子进程获得父进程的数据空间,堆和栈的副本,父子之间不共享这些存储区部分
父子进程共享正文段,而正文段通常是只读的。也就是说对子进程进行操作,对父进程没有任何影响。
(现代的处理方法:使用到了写时复制的技术,存储区内容父子共享,并为只读,当父子进程中任一要去写的时候,
内核就为其修改的那个部分制作一个副本,通常是虚拟存储器系统的一页)
3.vfork函数
创建一个进程,但是与fork有区别
#include <unistd.h>
pid_t vfork(void);
//子进程中返回0,父进程返回子进程pid,出错返回-1
此时,子进程和父进程共享资源,当对子进程进行操作的时候,会影响到父进程。vfork之后通常接着exec。
vfork保证子进程先运行,他在调用了exec或者exit之后父进程才能被调度运行。
4.exit函数
在说明fork函数时,显而易见,子进程是在父进程内调用fork后生成的。子进程在终止的时候会将把自己的终止状态
返回给父进程。如果父进程在子进程终止之前终止,对于父进程的所有子进程他们的父进程都改为init进程。我们称
这些进程由init领养。其操作的大体过程如下:
在一个进程终止的时候,内核逐个检查所有活动进程,以判断它是否是正要终止进程的子进程。如果是,
则将该进程的父进程ID更改为1.这种处理方式保证了每个进程都有一个父进程。
如果子进程在父进程之前终止,内核就为每个终止子进程保存了一定量的信息,所以当终止进程的父进程调用wait
或waitpid时,可以得到这些信息。这些信息至少包括进程ID,进程的终止状态,以及该进程使用的CPU时间总量。
内核可以释放终止进程所使用的所有存储区,关闭其所有的打开文件。但是一个已经终止,而父进程尚未对其进行
善后处理的进程被称之为僵死进程。
当init的子进程终止的时候,init进程会自动调用wait函数取得其终止状态。防止了系统中有很多僵死进程。
5.wait和waitpid函数
当调用这两个函数的时候:
如果其所有子进程都还在运行,则阻塞;
如果一个子进程已经终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回;
如果没有任何子进程,则会出错.
#include <sys/wait.h>
pit_t wait(int *statloc);
pit_t waitpid(pid_t pid, int *statloc, int options);
在一个子进程终止前,wait使其调用者阻塞,而waitpid有一个选项,可以使调用者不阻塞。
waitpid并不等待在其调用之后的第一个终止子进程,它有若干个选项,可以控制它所等待的进程。
如果第一个子进程已经终止,并且是一个僵死的进程,wait立即返回并取得该子进程的状态,否则wait使其调用者阻塞
直到一个子进程终止。若调用者阻塞而且它有多个子进程,则当有一个子进程终止的时候,wait函数就立即返回。
statloc是一个整型指针,如果这个指针不为空,则存储子进程的状态
pid参数的作用如下:
pid == -1 等待任一子进程,此时,与wait等效
pid > 0 等待其进程ID与pid相等的子进程
pid == 0 等待其组ID等于调用进程组ID的任一子进程
pid < -1 等待其组ID等于pid绝对值的任一子进程
options参数使我们能进一步控制waitpid操作,此参数可以为0
常量 说明
WCONTINUED 若实现支持作业控制,那么由pid指定的任一子进程在暂停后已经继续,但其状态尚未报告,
则返回其状态
WNOHANG 若由pid指定的子进程并不是立即可用的,则waitpid不阻塞,此时其返回值为0
WUNRTACEO 若某实现支持作业控制,而由pid指定的任一子进程已处于暂停状态,并且其状态自暂停以来
还未报告过,则返回其状态。WIFSTOPPED宏确定返回值是否对应于一个暂停子进程
6.waitid函数
此函数类似与waitpid,但是提供了更多的灵活性
#include <sys/wait.h>
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
//成功返回0,出错返回-1
使用单独的参数表示要等待的子进程类型id的值与idtype的值有关,以下列出idtype的类型:
P_PID 等待一个特定的进程,id包含要等待进程的进程id
P_PGID 等待一个特定进程组的任一子进程,id包含等待进程组的进程组id
P_ALL 等待任意子进程,乎略id
options参数:
WCONTINUED 等待一个进程,它以前曾被暂停,此后有继续执行,但其状态未返回
WEXITED 等待已经退出的进程
WNOHANG 如无可用的子进程退出状态,立即返回而非阻塞
WNOWAIT 不破坏进程退出状态。该子进程退出状态后可由后续的wait,waitpid或waitid调用获取
WSTOPPED 等待一个进程,它已经暂停,但其状态尚未报告
7.竞争条件
当多个进程都企图对共享数据进行某种处理,而最后的结果由取决于进程运行顺序的时候,我们认为发生了竞争
8.exec函数
当进程调用exec函数的时候,进程执行的程序完全替换为新的程序,而新程序从其main函数开始执行。
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[]);
//出错返回-1,成功不返回
前4个函数将路径名作为参数,后两个函数将文件名作为参数,如果filename含有/,则表示路径名
否则就按照环境变量所指定的目录中搜索可执行文件
execlp和execvp的filename可以是shell脚本
其他参数为新程序的命令行参数
6个函数中,字母p表示该函数取filename为参数,字母l表示该函数取一个参数表,与字母v互斥,v表示该函数取一个envp[]数组,而不使用当前环境
9.更改用户ID和组ID
#include <unistd.h>
int setuid(uid_t uid);
int setgid(gid_t gid);
//成功返回0,出错返回-1