阿里云国际站官网,wordpress 按钮连接在哪里,互联网家装,免费看今天开始做女神的网站目录 进程的创建
fork函数初步认识
fork函数的返回值
写时拷贝
操作系统怎么知道什么时候要写时拷贝的呢#xff1f;
fork的常规用法
fork调用失败的原因
进程终止
进程的退出场景
进程常见退出方法
正常终止#xff08;可以通过 echo $? 查看进程退出码#xff…目录 进程的创建
fork函数初步认识
fork函数的返回值
写时拷贝
操作系统怎么知道什么时候要写时拷贝的呢
fork的常规用法
fork调用失败的原因
进程终止
进程的退出场景
进程常见退出方法
正常终止可以通过 echo $? 查看进程退出码
main中使用return
_exit函数
exit函数
异常退出
退出码vs错误码
退出码Exit Code
错误码Error Code
进程等待
什么是进程等待
进程等待的必要性
wait和waitpid
wait函数
waitpid函数
获取子进程的status
代码实例
除了手动得到退出码和状态码之外的方式
子进程退出会做什么
为什么不用全局变量获取Status
进程等待的方式
进程等待的代码实现
进程程序替换
替换原理
替换函数
函数解释
函数名的解释
exec_调用实例
程序替换的相关知识
预告下一篇博客将会模拟实现一个 进程的创建 之前我写的初识进程中提到过使用fork函数来创建进程这次我们来详细的展示fork的使用
fork函数初步认识 在linux中fork函数时非常重要的函数它从已存在进程中创建一个新进程。新进程为子进程而原进程为父进程。
#include unistd.h
pid_t fork(void);
//返回值自进程中返回0父进程返回子进程id出错返回-1进程调用fork当控制转移到内核中的fork代码后内核做下面的工作 分配新的内存块和内核数据结构给子进程 将父进程部分数据结构内容拷贝至子进程 添加子进程到系统进程列表当中 fork返回开始调度器调度 当一个进程调用fork之后就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以开始它们自己的旅程看如下程序
int main( void )
{pid_t pid;printf(Before: pid is %d\n, getpid());if ( (pidfork()) -1 )perror(fork()),exit(1);printf(After:pid is %d, fork return %d\n, getpid(), pid);sleep(1);return 0;
}运行结果看到了三行输出一行before两行after其中10517既有before又有after但10518却没有为什么呢因为fork之前父进程独立执行fork之后父子进程两个执行流分别执行但执行的先后顺序取决于调度器 fork函数的返回值 子进程的返回值为0 父进程的返回值为子进程的pid
写时拷贝 通常父子代码共享父子再不写入时数据也是共享的当任意一方试图写入便以写时拷贝的方式各自一份副本。具体见下图: 操作系统怎么知道什么时候要写时拷贝的呢 首先将自己的读写权限改成只读然后再创建子进程但用户不知道这个过程用户可能对某一批数据进行写入这时页表便会因为权限问题出错这时操作系统便可以介入了 出错的情况可能有两种一种是真错了第二种是误判这种错误会触发重新申请内存
fork的常规用法 一个父进程希望复制自己使父子进程同时执行不同的代码段。例如父进程等待客户端请求生成子进程来处理请求。 一个进程要执行一个不同的程序。例如子进程从fork返回后调用exec函数
fork调用失败的原因 系统中有太多的进程 实际用户的进程数超过了限制
进程终止
进程的退出场景 代码运行完毕结果正确 代码运行完毕结果不正确 代码异常终止
进程常见退出方法
正常终止可以通过 echo $? 查看进程退出码 从main返回main函数的返回值一般有三种第一是代码运行完毕结果正确第二是代码运行完毕结果错误三是代码异常终止 调用exit _exit
main中使用return return退出return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数
_exit函数 参数status 定义了进程的终止状态父进程通过wait来获取该值。ps虽然status是int但仅有低8位可以被父进程所用。所以_exit(-1)时在终端执行$?发现返回值是255。
exit函数 exit最后也会调用exit, 但在调用exit之前还做了其他工作 执行用户通过 atexit或on_exit定义的清理函数。 关闭所有打开的流所有的缓存数据均被写入 调用_exit
异常退出 ctrl c信号终止
退出码vs错误码 总的来说退出码主要用于表示程序或命令的执行结果状态而错误码则用于标识程序执行中的具体错误类型。退出码通常是整数值用于表示成功或失败而错误码则是用来描述错误的具体性质和原因。在实际编程中这两个概念经常同时使用以便更好地管理和处理程序执行过程中可能出现的各种情况。
退出码Exit Code 退出码是一个整数值用来表示一个程序或命令在执行完毕后的状态。 在 Unix/Linux 系统中程序执行完毕后会返回一个退出码。通常情况下0 表示成功完成而其他非零值则表示出现了某种错误或异常情况。 退出码可以用来告诉调用者程序执行的结果以便后续的处理或判断。比如在 Shell 脚本中可以根据不同的退出码进行条件判断和处理。
错误码Error Code 错误码是用来表示程序执行中发生错误或异常情况时的特定代码或值通常用于标识和区分不同类型的错误。 错误码可以是预定义的常量、枚举值也可以是系统提供的标准错误码。通过错误码程序可以更精确地了解发生的错误类型从而进行相应的处理。 错误码通常用来指示具体的错误原因帮助开发人员诊断和调试程序中的问题。
进程等待
什么是进程等待 通过wait/waitpid函数让父进程一般对子进程进行资源回收的等待
进程等待的必要性 之前讲过子进程退出父进程如果不管不顾就可能造成‘僵尸进程’的问题进而造成内存泄漏。 另外进程一旦变成僵尸状态那就刀枪不入“杀人不眨眼”的kill -9 也无能为力因为谁也没有办法杀死一个已经死去的进程。 最后父进程派给子进程的任务完成的如何我们需要知道。如子进程运行完成结果对还是不对或者是否正常退出。 父进程通过进程等待的方式回收子进程资源获取子进程退出信息。父进程创建子进程是为了让子进程来完成任务而子进程将任务的完成的情况要想让父进程知道要通过进程等待的方式获取子进程的退出信息信号编号、退出码
wait和waitpid 如果子进程已经退出调用wait/waitpid时wait/waitpid会立即返回并且释放资源获得子进程退出信息。 如果在任意时刻调用wait/waitpid子进程存在且正常运行则进程可能阻塞。 如果不存在该子进程则立即出错返回。
wait函数
#includesys/types.h
#includesys/wait.h
pid_t wait(int*status);
返回值成功返回被等待进程pid失败返回-1。
参数输出型参数获取子进程退出状态,不关心则可以设置成为NULLwaitpid函数
pid_ t waitpid(pid_t pid, int *status, int options);
返回值当正常返回的时候waitpid返回收集到的子进程的进程ID如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在
参数pidPid-1,等待任一个子进程。与wait等效。Pid0.等待其进程ID与pid相等的子进程。status:WIFEXITED(status): 若为正常终止子进程返回的状态则为真。查看进程是否是正常退出WEXITSTATUS(status): 若WIFEXITED非零提取子进程退出码。查看进程的退出码options:WNOHANG: 若pid指定的子进程没有结束则waitpid()函数返回0不予以等待。若正常结束则返回该子进程的ID。获取子进程的status wait和waitpid都有一个status参数该参数是一个输出型参数由操作系统填充。 如果传递NULL表示不关心子进程的退出状态信息。 否则操作系统会根据该参数将子进程的退出信息反馈给父进程。 status不能简单的当作整形来看待可以当作位图来看待具体细节如下图只研究status低16比特位 当一个进程因为收到信号退出了看退出码便没有意义了 怎么判断是否因为信号退出看退出码是否为零 如果手动杀掉子进程也会反馈退出码
代码实例
#include sys/wait.h
#include stdio.h
#include stdlib.h
#include string.h
#include errno.h
int main( void )
{pid_t pid;if ( (pidfork()) -1 )perror(fork),exit(1);if ( pid 0 ){sleep(20);exit(10);} else {int st;int ret wait(st);if ( ret 0 ( st 0X7F ) 0 ){ // 正常退出printf(child exit code:%d\n, (st8)0XFF);} else if( ret 0 ) { // 异常退出printf(sig code : %d\n, st0X7F );}}
}正常退出 手动杀掉进程 除了手动得到退出码和状态码之外的方式 子进程退出会做什么 子进程退出的后要修改进程状态为Z并将子进程退出信号和退出码写入PCB中
为什么不用全局变量获取Status 因为进程之间具有独立性不要觉得写在一份代码里就会混在一起父进程无法直接获取子进程的退出信息
进程等待的方式 阻塞式子进程不退出wait/waitpid不返回 非阻塞式如果等待的条件不满足wait/waitpid不阻塞而是立即返回 常常使用轮询非阻塞方案进行进程等待。好处是在等待的过程中可以顺便做一下自己手上占据时间并不多的事情。
进程等待的代码实现 wait/waitpid等待方式的选择原谅机翻 进程的阻塞等待方式
#include sys/wait.h
#include stdio.h
#include stdlib.h
#include string.h
#include errno.h
int main()
{pid_t pid;pid fork();if(pid 0){printf(%s fork error\n,__FUNCTION__);return 1;} else if( pid 0 ){ //childprintf(child is run, pid is : %d\n,getpid());sleep(5);exit(257);} else{int status 0;pid_t ret waitpid(-1, status, 0);//阻塞式等待等待5Sprintf(this is test for wait\n);if( WIFEXITED(status) ret pid ){printf(wait child 5s success, child return code is :%d.\n,WEXITSTATUS(status));}else{printf(wait child failed, return.\n);return 1;}}return 0;
}进程的非阻塞式等待
#include stdio.h
#include unistd.h
#include stdlib.h
#include sys/wait.h
int main()
{pid_t pid;pid fork();if(pid 0){printf(%s fork error\n,__FUNCTION__);return 1;}else if( pid 0 ){ //childprintf(child is run, pid is : %d\n,getpid());sleep(5);exit(1);} else{int status 0;pid_t ret 0;do{ret waitpid(-1, status, WNOHANG);//非阻塞式等待if( ret 0 ){printf(child is running\n);}sleep(1);}while(ret 0);if( WIFEXITED(status) ret pid ){printf(wait child 5s success, child return code is :%d.\n,WEXITSTATUS(status));}else{printf(wait child failed, return.\n);return 1;}}return 0;
} 三种返回值大于零代表成功等于零代表等待成功但对方还没退出小于零代表等待失败
进程程序替换 我们所创建的子进程所执行的代码都是父进程的一部分如果我们想让子进程执行新的代码和访问新的数据呢这就需要进程程序替换
替换原理 用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。 替换函数 其实有六种以exec开头的函数,统称exec函数
#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[]);
!--StartFragment--int execve\(const char \*path, char \*const argv\[\], char \*const envp\[\]\);!--EndFragment--函数解释 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。 如果调用出错则返回-1 所以exec函数只有出错的返回值而没有成功的返回值。
函数名的解释 l(list) : 表示参数采用列表 v(vector) : 参数用数组 p(path) : 有p自动搜索环境变量PATH e(env) : 表示自己维护环境变量
exec_调用实例
#include unistd.h
int main()
{char *const argv[] {ps, -ef, NULL};char *const envp[] {PATH/bin:/usr/bin, TERMconsole, 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);
}事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在man手册第3节。这些函数之间的关系如下图所示 程序替换的相关知识 当我们进行程序替换的时候子进程对应的环境变量是可以直接从父进程中来的 父进程的环境变脸来自于bash 环境变量被子进程继承下去是一种默认的行为不受程序替换的影响。并且是通过地址空间来让子进程继承父进程的环境变量
预告下一篇博客将会模拟实现一个