它从已存在的进程中创建一个新进程,新进程为子进程,而原进程为父进程
fork创建子进程,OS做了什么?
通常父子代码共享,父子在不写入时,数据也是共享的,当任意一方试图写入时,便以写时拷贝的方式产生各自的一份副本。实现了父子进程的彻底分离,完成了进程独立性,可以提高整机内存的使用效率。
可以使用echo $? 查看进程退出码
exit是库函数,会刷新缓冲区,在最后会调用_exit(系统接口)。而_exit 不会清理缓冲区
成功返回被等待进程的pid,失败返回-1。参数是输出型参数,获取子进程退出状态,不关心可以设置为NULL。wait是阻塞式等待。
阻塞等待(Blocking Wait)和非阻塞等待(Non-blocking Wait)是两种不同的等待机制,它们在进程或线程等待条件满足时的行为和效果上存在区别。
阻塞等待:
非阻塞等待:
总结:
阻塞等待会暂停执行并释放CPU资源,直到条件满足,而非阻塞等待会持续执行并轮询检查条件。阻塞等待确保资源的正确使用和分配,但可能导致较长的执行时间,而非阻塞等待可以减少执行时间,但可能导致CPU占用较高。选择使用阻塞等待还是非阻塞等待取决于具体的应用场景和需求。
status的构成:status并不是按照整数来整体使用的,而是按照比特位的方式,将32位比特位划分,我们只需要学习低16位,次低8位表示进程的退出码,最低的7位表示收到的信号。还有一位是core dump标志,表示是否发生了核心转储
(status>>8)&0xff // 得到的是退出码
status&0x7f // 得到的信号码,信号码非0,退出码就无意义,有可能是外部杀死
示例:
#include
#include
#include
#include
#include
#include
int main()
{
pid_t id = fork();
if(id < 0)
{
perror("fork");
exit(1); //标识进程运行完毕,结果不正确
}
else if(id == 0)
{
//子进程
int cnt = 5;
while(cnt)
{
printf("我是子进程:%d\n",cnt--);
sleep(1);
}
exit(11);
}
else
{
int status = 0;
pid_t ret = waitpid(id, &status, 0);
//阻塞式的等待!只有子进程退出了,父进程才会waitpid函数进行返回,,父进程依旧还活着。wait/waitpid可以让进程退出具有一定的顺序性
if(WIFEXITED(status))
{
printf("子进程正常退出:%d\n",WEXITSTATUS(status));
}
else
{
printf("子进程退出异常\n");
}
}
}
使用wait/waitpid函数等待子进程退出并获取其退出状态,相较于使用全局变量的方式,有以下几个优势:
阻塞等待:wait/waitpid函数可以实现阻塞等待子进程退出。这意味着父进程会被暂停,直到子进程退出为止。这样可以避免父进程在子进程还没有完成时继续执行,从而确保父进程能够获取到子进程的退出状态。
释放资源:通过使用wait/waitpid,操作系统可以及时回收子进程的资源,防止产生孤儿进程。子进程退出后,操作系统会清理子进程的资源,包括退出状态、内存、文件描述符等。这样可以有效避免资源泄漏和浪费。
处理多个子进程:如果父进程创建了多个子进程,使用wait/waitpid可以分开处理每个子进程退出的情况。wait/waitpid函数提供了灵活的参数设置,例如可以使用waitpid指定要等待的具体子进程PID,也可以使用WNOHANG选项进行非阻塞轮询等待。这样可以更好地组织和管理多个子进程的退出状态。
错误处理:wait/waitpid函数可以处理多种错误和退出状态。通过检查wait/waitpid返回值,可以得知子进程是正常退出还是异常终止,以及导致异常终止的原因。这对于父进程来说是很有价值的,可以根据返回值采取相应的后续操作,如重启子进程或记录异常日志等。
既然进程具有独立性,进程退出码也是子进程的数据,父进程怎么拿到的?
至少要保留该进程的PCB信息,里面保留了任何进程退出时的退出结果信息。wait的本质就是读取了子进程的PCB
wait和waitpid就是系统调用
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全新程序替换,从新程序的启动例程开始执行。调用exec并不是创建新进行,所以调用前后该进程的pid不变。
是通过特定的接口,加载磁盘上的一个全新的程序,加载到调用进程的地址空间中,让子进程执行其他的程序,并和当前进程的页表重新建立映射。
如果新程序替换成功,则原先的进程已经不存在,变成了全新的程序进程。如果原进程没有退出,那么新进程也不会执行。如果需要在运行新程序时保留原进程,可以使用fork创建子进程,然后在子进程中调用替换函数新程序,而父进程继续执行原程序。
详细说明:
execlp():
execle():
execvp():
execve():
#include
int main()
{
char *const argv[] = {"ps", "-ef", NULL};
char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
execl("/bin/ps", "ps", "-ef", NULL);
// 带p的,可以使用环境变量PATH,无需写全路径
execlp("ps", "ps", "-ef", NULL);
// 带e的,需要自己组装环境变量
execle("ps", "ps", "-ef", NULL, envp);
execv("/bin/ps", argv);
// 带p的,可以使用环境变量PATH,无需写全路径
execvp("ps", argv);
// 带e的,需要自己组装环境变量
execve("/bin/ps", argv, envp);
exit(0);
}