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