进程创建
fork函数
它从已存在的进程中创建一个新进程,新进程为子进程,而原进程为父进程
fork创建子进程,OS做了什么?
* 分配新的内存块和内核数据结构给子进程
* 将父进程部分数据结构内容拷贝至子进程
* 添加子进程到系统进程列表中
* fork返回,开始调度器调度
写时拷贝
通常父子代码共享,父子在不写入时,数据也是共享的,当任意一方试图写入时,便以写时拷贝的方式产生各自的一份副本。实现了父子进程的彻底分离,完成了进程独立性,可以提高整机内存的使用效率。
进程终止
* 进程终止时,操作系统做什么?
释放进程申请的相关内核数据结构和对应的数据和代码,本质就是释放系统资源
* 进程退出场景:代码运行完毕且结果正确;代码运行完毕,结果不正确;代码异常终止
* 退出方式:从main函数返回;调用exit;_exit
可以使用echo $? 查看进程退出码
exit是库函数,会刷新缓冲区,在最后会调用_exit(系统接口)。而_exit 不会清理缓冲区
进程等待
进程等待的必要性
* 子进程退出,父进程不管子进程,子进程就处于僵尸状态,一直维护就会导致内存泄漏
* 父进程通过进程等待的方式,回收子进程资源,获取子进程退出码
*
进程等待的方式
wait
成功返回被等待进程的pid,失败返回-1。参数是输出型参数,获取子进程退出状态,不关心可以设置为NULL。wait是阻塞式等待。
waitpid
* pid
* pid=-1,等待任意一个子进程,与wait等效
* pid>0,等待指定进程
* status
* WIFEXITED:查看进程是否是正常退出。正常退出则为真
* WEXITSTATUS:查看子进程退出码。若WIFEXITED非0,则提取退出码
* options
* WNOHANG:表示非阻塞等待。若pid指定的子进程没有结束,则函数返回0。正常结束就返回该子进程的PID。
* 默认是0,表示阻塞等待。
阻塞等待(Blocking Wait)和非阻塞等待(Non-blocking
Wait)是两种不同的等待机制,它们在进程或线程等待条件满足时的行为和效果上存在区别。
阻塞等待:
* 当进程或线程进行阻塞等待时,它会暂停执行,并释放CPU资源给其他可执行的进程或线程。
* 进程或线程在等待期间无法继续执行其他任务,直到等待的条件满足。
* 阻塞等待是一种同步操作,它会让进程或线程等待在等待队列中,直到某个事件发生或条件满足。
* 阻塞等待可以确保在等待期间资源得到正确使用和分配,但可能导致整体执行时间较长。
非阻塞等待:
* 当进程或线程进行非阻塞等待时,它会持续执行,并不会暂停或释放CPU资源。
* 进程或线程在等待期间可以继续执行其他任务,并轮询检查等待的条件是否满足。
* 非阻塞等待是一种轮询式的等待机制,它可以在等待的同时继续执行其他任务。
* 非阻塞等待可以减少整体执行时间,但可能会导致CPU占用较高,因为进程或线程需要主动检查等待的条件。
总结:
阻塞等待会暂停执行并释放CPU资源,直到条件满足,而非阻塞等待会持续执行并轮询检查条件。阻塞等待确保资源的正确使用和分配,但可能导致较长的执行时间,而非阻塞等待可以减少执行时间,但可能导致CPU占用较高。选择使用阻塞等待还是非阻塞等待取决于具体的应用场景和需求。
status的构成:status并不是按照整数来整体使用的,而是按照比特位的方式,将32位比特位划分,我们只需要学习低16位,次低8位表示进程的退出码,最低的7位表示收到的信号。还有一位是core
dump标志,表示是否发生了核心转储
(status>>8)&0xff // 得到的是退出码 status&0x7f // 得到的信号码,信号码非0,退出码就无意义,有可能是外部杀死
示例:
#include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h>
#include <sys/types.h> #include <sys/wait.h> 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创建子进程,然后在子进程中调用替换函数新程序,而父进程继续执行原程序。
详细说明:
* execl
* 函数原型:int execl(const char *path, const char *arg, …);
* 功能:根据指定的程序路径执行一个新程序,并传递命令行参数作为参数。
* 特点:需要给出新程序的完整路径,并以NULL作为参数列表的结束标志。
* 示例:execl(“/bin/ls”, “ls”, “-l”, NULL);
*
execlp():
* 函数原型:int execlp(const char *file, const char *arg, …);
* 功能:根据文件名称在路径中执行指定的可执行程序,并传递命令行参数作为参数。
* 特点:使用名称搜索可执行文件,搜索路径由系统环境变量PATH控制。
* 示例:execlp(“ls”, “ls”, “-l”, NULL);
*
execle():
* 函数原型:int execle(const char *path, const char *arg, …, char *const envp[]);
* 功能:根据文件路径执行指定的可执行程序,并传递命令行参数和环境变量作为参数。
* 特点:需要指定可执行程序的完整路径,并且需要传递环境变量参数。
* 示例:execle(“/bin/ls”, “ls”, “-l”, NULL, envp);
* execv
* 函数原型:int execv(const char *path, char *const argv[]);
* 特点:需要指定可执行程序的完整路径。
* 功能:根据文件路径执行指定的可执行程序,并传递命令行参数作为参数。
* 示例:execv(“/bin/ls”, argv);
*
execvp():
* 函数原型:int execvp(const char *file, char *const argv[]);
* 功能:根据文件名称在路径中执行指定的可执行程序,并传递命令行参数作为参数。
* 特点:使用路径搜索可执行文件,搜索路径由系统环境变量PATH控制。
* 示例:execvp(“ls”, argv);
*
execve():
* 函数原型:int execve(const char *filename, char *const argv[], char *const
envp[]);
* 功能:根据文件路径执行指定的可执行程序,并传递命令行参数和环境变量作为参数。
* 特点:需要指定可执行程序的完整路径,并且需要手动传递环境变量参数。
* 示例:execve(“/bin/ls”, argv, envp); #include <unistd.h> 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); }