写时拷贝
父子进程代码共享,数据独有;当任意一方试图写入,便以写时拷贝的方式拷贝一份副本
(1) 代码运行完毕,结果正确
(2) 代码运行完毕,结果不正确
(3) 代码异常终止
void _exit(int status);
该函数为系统调用函数,谁调用谁退出
void exit(int status);
头文件:#include
status:是一个整型的参数,可以利用这个参数传递进程结束时的状态。一般来说,0表示正常结束;其他的数值表示出现了错误,进程非正常结束。在实际编程时,父进程可以利用wait 系统调用接收子进程的返回值,从而针对不同的情况进行不同的处理。
该函数为库函数,谁调用谁退出
exit函数的内部封装了_exit函数
从图中可以看出,_exit 函数的作用是:直接使进程停止运行,清除其使用的内存空间,并清除其在内核的各种数据结构;
exit 函数则在这些基础上做了一些小动作,在执行退出之前还加了若干道工序。exit() 函数与 _exit() 函数的最大区别在于exit()函数在调用exit系统调用前要检查文件的打开情况,把文件缓冲区中的内容写回文件,也就是图中的“清理I/O缓冲”。
exit函数在退出进程的时候要比_exit函数多做两件事
执行用户自定义的清理函数
刷新缓冲区
父子进程是抢占式运行,可能是父进程先运行,也可能是子进程先运行。
子进程中运行_exit(0)并未将"This is the content in the buffer _exit"打印出来,而父进程中运行的exit(0)将"This is the content in the buffer exit"打印出来了。说明exit(0)会在终止进程前,将缓冲I/O内容清理掉,所以即使printf里面没有 \n 也会被打印出来,而_exit(0)是直接终止进程,并未将缓冲I/O内容清理掉,所以不会被打印出来。
终止一个前台进程(前台进程:是当前正在使用的程序,后台进程:是在当前没有使用的但是也在运行的进程)
atexit函数
int atexit (void (*function)(void));
头文件:#include
返回类型是整型,参数为函数指针类型,接收一个函数的地址,函数的返回值为void,参数也是void
atexit函数是注册了一个函数mycallback,注册并不是调用。
当main()函数结束之后,才会调用刚刚注册的mycallback函数。
回调函数:1.注册回调函数 (上面的mycallback就是回调函数)
2.调用回调函数
(1) main函数的return返回
(2) fflush:强制刷新
(3) exit函数
(4) 关于"\n"刷新缓冲区
父进程调用进程等待的方法,等待子进程退出,回收子进程的退出资源,防止子进程变成僵尸进程
pid_t wait(int *status);
头文件:#include
函数功能是:父进程一旦调用了 wait 就立即阻塞自己,由 wait 自动分析当前进程的某个子进程是否已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait 就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
参数: 输出型参数:将wait函数内部计算的结果通过status返回给调用者
输入型参数:调用者给被调用函数的传参
参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉毫不在意,只想把这个僵尸进程消灭掉,我们就可以设定这个参数为NULL,就像下面这样:pid = wait(NULL);
例如:swap(int *a,int *b); 输入型输出型参数
编码的时候,小小的代码规范:
输入型参数:给引用
输入输出型参数,输出型参数:给指针
返回值:如果执行成功则返回子进程的pid, 如果有错误发生则返回-1。失败原因存于errno 中。
注:
wait()要与fork()配套出现,如果在使用fork()之前调用wait(),wait()的返回值则为-1,正常情况下wait()的返回值为子进程的PID。
判断是否有退出信号
int status ==》 wait(&status)
status & 0x7F ==0:正常退出
status & 0x7F > 0:异常退出
0x7F:0111 1111
判断coredump标志位:
(status >> 7) & 0x1
判断退出码:
(status >> 8) & 0xFF
(1) 创建子进程
(2) 模拟产生一个僵尸进程
目前子进程是一个僵尸进程
(3) 父进程调用wait函数,再看是否产生僵尸进程(wait函数要包含
不会产生僵尸进程
(4) 阻塞
牵扯到一个阻塞的问题,如果父进程调用wait函数,当子进程没有退出之前,wait函数是不会返回的。调用wait函数后,wait函数会压在父进程的栈针上面。
第一种情况:先执行子进程,打印,再执行父进程,wait等到,返回
第二种情况:先执行父进程,调用wait函数,产生阻塞,会等待子进程,然后打印,返回。子进程并不会变成孤儿进程,因为父进程的wait函数等到了子进程。
子进程不退出在等待的这1秒钟,从父进程的堆栈可以看出,父进程一直在调用wait函数,并没有退出。
pid_t waitpid(pid_t pid, int *status, int options)
如果在调用waitpid()函数时,当指定等待的子进程已经停止运行或结束了,则waitpid()会立即返回;但是如果子进程还没有停止运行或结束,则调用waitpid()函数的父进程则会被阻塞,暂停运行。
作用同于wait,但可指定pid进程清理,可以不阻塞。
参数pid:pid<-1,等待进程组识别码为pid 绝对值的任何子进程
pid = -1,等待任意一个子进程。与wait等效
pid = 0,等待和当前调用waitpid一个组的所有子进程
pid > 0,等待一个进程ID与pid相等的子进程
status:同wait
options: 0:阻塞模式
WNOHANG:非阻塞模式,非阻塞模式需要搭配循环使用
返回值:成功:返回清理掉的子进程ID
失败:-1(无子进程)
注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程需要用到循环
替换当前进程的代码段和数据段为新的程序,并且更新堆栈
注意:当前进程程序替换完毕之后,进程就在执行新的程序,但是进程的进程号不变
头文件:#include
int execl(const char* path,const cahr* arg, …)
path:待要替换的可执行程序,需要指定该程序在哪一个路径下
arg:给可执行程序传递的命令行参数
(1) 第一个参数必须是可执行程序本身
例如:ls -al (使用which ls命令,可以找到ls的路径)
execl(“/usr/bin/ls”, ”/usr/bin/ls”, “-al”,NULL);
(2) 可变列表也是填充给可执行程序的参数,需要以NULL结尾
返回值:如果替换成功,则没有返回值,因为替换成功后,就执行其他的程序了。
如果替换失败,则返回-1
int execlp(const char* file, const char* arg, …);
file:待要替换的可执行程序,可以不给路径(但是这个待要替换的可执行程序一定能在PATH环境变量中可以搜索到)
int execle(const char* path, const char* arg, … , char* const envp[ ]);
envp[ ]:表示程序员需要自己组织环境变量
int execv(const char* path, char* const argv[ ]);
argv[ ]:指针数组,保存的是给可执行程序传递的参数
数组的第一个元素应为可执行程序本身
数组的最后一个元素应为NULL
int execvp(const char* file, char* const argv[ ]);
int execve(const char* file, char* const argv[], char* const envp[]);
file:带有路径的可执行程序(待要替换的可执行程序)
argv[]:指针数组,保存的是给可执行程序传递的参数
数组的第一个元素应为可执行程序本身
数组的最后一个元素应为NULL
envp[]:程序员自己组织环境变量,最后一个参数一定为NULL
(1) 函数名称当中带有l和不带l的区别:
如果说函数名称当中带有l,则表示函数的参数为可变参数列表的形式
如果说函数名称当中不带有l,则表示函数没有可变参数列表
(2) 函数名称当中带p和不带p的区别:
如果函数名称当中有p,则表示会自动搜索环境变量
如果函数名称当中没有p,则表示不会自动搜索环境变量。这也是为什么execl函数在替换的时候,一定要给带替换的可执行程序带上路径的原因。
(3) 函数名称当中带有e和不带有e的区别:
如果说函数当中带有e,则表示程序员自己组织环境变量
如果说函数当中不带有e,则表示程序员不需要自己组织环境变量
7×24小时服务
作用:保护业务进程
(1) 启动业务进程的时候,不是直接启动业务进程,而是启动守护进程
(2) 让守护进程,创建一个子进程,让子进程进程程序替换业务程序进程
(3) 守护进程和业务进程进行进程通信,让守护进程得知业务进程的情况