- 一、进程创建
- 1.1、创建进程
- 1.2、获取进程号
- 1.3、让子进程执行新任务
- 二、进程退出和等待
- 2.1、进程退出
- 2.2、僵尸进程和进程等待
#include#include pid_t fork(void); pid_t vfork(void);
两者执行成功返回大于等于0的值,失败返回 -1
返回大于0的值(子进程的pid)表示在父进程中
返回0表示在子进程中
并发异步执行
后面的函数若无说明,则头文件和fork()一样
- fork()
子进程会复制父进程的所有资源,采用写时复制
父子进程内存空间相互独立
父子进程执行顺序不确定,由cpu调度决定
若父进程先结束,则子进程会成为孤儿进程,被init进程收养 - vfork()
子进程共享父进程的内存空间(全局变量,堆栈),并先于父进程执行
创建进程系统开销更小
执行顺序固定容易发生死锁,
共享内存空间容易造成进程间同步错误
pid_t getpid(void); pid_t getppid(void);
- getpid
获取当前进程的进程号
成功返回调用进程的进程号,失败返回 -1- getppid
获取父进程的进程号
成功返回父进程号,失败返回 -1
在linux中 父进程的父进程是shell ,可以通过以下命令查看shell的进程号
$ ps -aux | grep bash1.3、让子进程执行新任务
由于子进程的代码是从父进程拷贝来的,所以一般情况下所做工作与父进程一样
可以用execve函数在进程中运行另一个程序
int execvp(const char *file, char *const argv[]);
参数含义
file: 待运行的程序名
argv[] : 运行时的参数
(可以有多个,以NULL结尾,第一个参数一般同file)返回值
成功 无返回
失败 -1
1.3.1 测试 execvp
-
先写个hello程序
#include
#include int main(int argc,char* argv[]) { printf("n******************************n"); if(argc < 2){ printf("Too few parameters !n"); } else if(argc == 2){ printf("Program name = "%s"n",argv[0]); printf("The parameter is "%s"n",argv[1]); printf("pid = %d ppid = %dn",getpid(),getppid()); } else{ printf("Too many parameters !n"); } printf("******************************nn"); return 0; } $ gcc -o hello hello.c
-
execvp程序
#include
#include int main() { pid_t pid; pid = fork(); if(pid<0){ perror("fork"); } else if(pid == 0){ //子进程 printf("nin child process,pid = %dn",getpid()); char *file = "./hello"; char *argv[] = {file,"hello word",NULL}; printf("use execvpn"); int ret = execvp(file,argv); printf("ret = %dn",ret); } else{ //父进程 sleep(1); printf("in parent process,pid = %dn",getpid()); } return 0; } 子进程先结束
父进程先结束 (调整sleep函数)
-
说明
1、execvp调用并没有生成新进程
2、一旦调用该函数,进程本身就结束了(子进程最后一段没有打印)
3、调用该函数的进程只会保留进程ID,但对系统来说还是同一个进程
4、exec类函数还有5个 他们都是调用 execve 这个系统调用来实现的
5、在父进程结束后的子进程成为孤儿进程 会被init进程接收,init进程pid为 1
2.1.1、_exit()
void _exit(int status);
- status的值
0: 正常退出,非0:异常退出- 函数功能
1、关闭进程打开的所有文件描述符、目录描述符
2、清除进程使用的内存空间
3、将该进程的 ppid 设置为init 进程的pid
4、向父进程发送SIGCHLD信号
5、如果父进程调用wait或waitpid来等待子进程结束,则唤醒父进程,取得终止进程的status
6、结束进程- 不会刷新IO缓存
return 将控制权交给主调函数
exit、_exit 将控制权交给系统
2.1.2、exit , on_exit
#includevoid exit(int status); int on_exit(void (*function)(int , void *), void *arg);
exit
用法和_exit类似 但处理机制不一样 _exit只是exit要调用函数中的一个
会刷新IO缓存 (调用fflush)on_exit
注册一个进程正常终止前的处理函数 对_exit不生效
返回值:成功 0,失败 非0
function:表示要注册的函数 arg:表示要传入的指针,可以为NULL
-
简单测试
#include
#include #include char fun(int status, void *arg){ printf("status = %d,arg = %sn",status,(char*)arg); return '0'; } int main() { on_exit((void *)fun,(void *)"hello"); on_exit((void *)fun,NULL); printf("on_exit testn"); exit(123); } -
说明
1、 如果注释掉exit(),status会等于 0
2、将exit换为_exit on_exit会失败
3、推测注册方式为压栈
2.2.1、僵尸进程
-
含义
子进程比父进程后终止:子进程为孤儿进程 ,这进程没马
子进程比父进程先终止:子进程为僵尸进程 -
查看
使用以下命令查看僵尸进程 STAT 中 Z+表示僵尸进程
$ ps -u | grep Z
-
处理
1、父进程调用wait或waitpid函数(调用会阻塞自己),回收
2、父进程将信号处理函数设为SIG_IGN 让内核去处理子进程
3、fork两次,结束一级子进程,令二级子进程成为孤儿进程,让init去清理(不好操作)
2.2.2、进程等待函数
#include#include pid_t wait(int *wstatus); pid_t waitpid(pid_t pid, int *wstatus, int options);
- 参数说明
wstatus:用于存放子进程的终止状态, 可以为NULL
pid:子进程的进程号 若为 -1 表示等待任 一 子进程
options:包括 WNOHANG、WUNTRACED、WCONTINUED (waitpid)- 返回值
大于0 :子进程的进程号
等于0:不阻塞 (waitpid)
-1 出错
-
测试
#include
#include #include #include int main() { int status1 = -2; int status2 = -2; pid_t pid; pid = fork(); if(pid<0){ perror("fork"); } else if(pid == 0){ //子进程 1 printf("in child1 process : pid = %d ppid = %dn",getpid(),getppid()); _exit(0);//正常退出 } else { //父进程 pid = fork(); if(pid<0){ perror("fork"); } else if(pid == 0){ //子进程 2 printf("in child2 process : pid = %d ppid = %dn",getpid(),getppid()); _exit(-1);//错误退出 } else{ //父进程 printf("in parent process,pid = %dn",getpid()); printf("cpid = %d t", wait(&status1)); printf("status1 = %dn", status1); sleep(1);//让子进程先结束 printf("cpid = %d t",wait(&status2)); //printf("cpid = %d t",waitpid(-1,&status2,0)); printf("status2 = %dn",status2); } } return 0; } -
说明
- wait(&wstatus) 相当于 waitpid(-1, &wstatus, 0)
- 一个wait()只等待一个子进程结束
- 若子进程未结束,则阻塞自己,直到有进程退出
- 若子进程已经结束,调用wait依然可以回收
部分笔记来源:mooc



