一、进程创建
fork函数
一个父进程希望复制自己,使父子进程创建一个新进程,新进程为子进程,原进程为父进程
#include <unistd.h>
pid_t fork(void);
//子进程返回0,父进程返回子进程的id,出错返回-1
4、Fork返回,开始调度器调度
写时拷贝
父子代码共享,父子在不写入时数据也是共享的,当任意一方试图写入时,便以写时拷贝的方式生成一份副本,这样就保证了进程的独立性。
二、进程终止
一个父进程创建了一个子进程,在子进程代码执行结束后有三种结果:
1、代码运行完毕,结果正确
2、代码运行完毕,结果不正确
3、代码异常终止echo $?可以打印最近一个程序的退出码
系统定制的一些退出码代表的意义如下:
return erron 可以返回结果不正确时的系统定制的退出码,strerror(errno)可以显示具体含义;
进程运行完毕结果正不正确由进程的退出码决定,一旦代码异常终止,一般就是进程收到了信号退出码就无意义了。
exit()与_exit()的区别
任何地方调用exit()和_exit()表示进程结束,并返回给父进程子进程的退出码,但是:
进程调用的exit()退出会进行缓冲区的刷新,进程调用_exit()退出不会进行缓冲区的刷新;
exit()是库函数,_exit()是系统函数,库函数exit()里面封装了系统函数_exit();
缓冲区一定不是操作系统的缓冲区,而是在c语言提供的库缓冲区。
三、进程等待
进程为什么要等待?
1、子进程退出时父进程如果不管不顾的话,就可能造成僵尸进程的问题,僵尸进程就会造成内存泄漏。
2、进程一旦进入僵尸状态,就连kill -9也无能为力。
3、父进程要知道派给子进程的任务完成的如何。
4、父进程通过进程等待的方式,回收子进程资源,获取子进程的退出信息。
wait()和waitpid()
wait(int *status)
等待任意一个退出的子进程,它的返回值是目标子进程的pid,等待失败返回-1
如果在等待的子进程时,子进程没有退出,父进程会阻塞在wait调用处
waitpid(pid_t pid,int *status,int options)
返回值大于0,即返回pid:等待结束
返回值等于0:调用结束,但是子进程没有退出
返回值小于0,调用失败
参数pid规定的具体等哪个进程,pid=-1时表示等待任意一个进程等价与wait;
参数options表示阻塞等待(默认为0)和非阻塞等待(WNOHANG);
参数*status是一个输出型参数,
获取status的方法:
查看进程的退出码WEXITSTATUS(status): 若WIFEXITED⾮零,提取⼦进程退出码。
查看进程是否是正常退出WIFEXITED(status): 若为正常终⽌⼦进程返回的状态,则为真。
四、进程的程序替换
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
也就是说,在程序替换的过程中,并没有创建新的进程,只是把当前进程的代码和数据用新程序的代码和数据覆盖式的进行替换,一旦程序替换成功就去执行新代码了原始代码的后部分就已经不存在了,且在子进程里面使用程序替换并不会影响父进程,因为在替换的时候相当于把代码和数据都进行了写时拷贝
替换函数
共有6种替换函数
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回,如果调用出错则返回-1,所以exec函数只有出错的返回值而没有成功的返回值。只有execve是真正的系统调用,其它五个函数最终都调用execve,所以execve在man手册 第2节,其它函数在man手册第3节,其他5个函数都封装了execve函数。