网站建设原创软文,91人才网赣州招聘网,水利建设专项收入在什么网站上申报,站长工具无吗经典文章目录 前言#xff1a;进程创建#xff1a;进程终止#xff1a;如何终止进程#xff1f;进程等待非阻塞等待#xff1a; 总结#xff1a; 前言#xff1a;
对于前面的地址空间的学习#xff0c;我们现在了解到原来所谓变量的地址其实是虚拟地址#xff0c;该虚… 文章目录 前言进程创建进程终止如何终止进程进程等待非阻塞等待 总结 前言
对于前面的地址空间的学习我们现在了解到原来所谓变量的地址其实是虚拟地址该虚拟地址是通过页表的映射关系从而找到内存中真实的物理地址下面我们进入到关于进程的控制
进程创建
现在我们对进程就有了新的定义进程 内核的相关管理数据数据结构(task_struct mm_struct 页表) 代码和数据。 其中代码是父子进程共享的数据是判断是否发生写实拷贝的。 fork函数的返回值 我们之前也有过介绍fork函数是用来创建子进程的创建子进程成功则返回0对于父进程的返回值则是子进程的pid这一点虽然我上次没有细讲但是在截图时就会发现。可以看看我之前博客——进程理解 子进程返回0父进程返回子进程的pid 为什么父进程返回的是子进程的pid给子进程返回的却是0呢 要记住我们在讲解进程状态的时候对于僵尸进程我们有过介绍父进程是会管理子进程的谈到管理永远是6个字——”先描述再组织“。 所以当然是为了让父进程方便对子进程进行标识进而进行管理 fork函数的常规用法 一个父进程希望复制自己使得父子进程同时执行不同的代码段。例如父进程等待客户端请求生成子进程来处理请求。一个进程要执行一个不同的程序。例如子进程从fork返回后调用exec函数。 fork调用失败的原因 系统中有太多的进程。实际用户创建数超过限制。
进程终止 进程终止是在做什么呢 首先我们应该清楚一件事情——当加载进程是应当是先创建PCB、页表和地址空间等再是加载代码和数据。 所以进程终止是在 释放曾经的代码和数据所占据的空间。释放内核数据结构若task_struct受僵尸状态则演示释放 进程终止的3种情况 提出问题为什么main函数最后要返回0呢为什么不是1或者100呢 #include stdio.h
#include unistd.h
#include sys/types.hint main()
{printf(I am a process! pid: %d, ppid: %d\n, getpid(), getppid());sleep(1);return 100;
}[!TIP] 我们可以使用指令echo $? 该指令代表父进程bash获取到子进程的退出码0表示成功非0表示失败。退出码的意义就是告诉关心方父进程我把任务处理的怎么了。 但我要是再次执行echo $?指令表示的就是刚刚子进程的退出码毕竟该指令是获取最近进程的退出码。 那这个退出码究竟有什么用你想返回100或者0不是都可以吗。诶这个时候操作系统会给我们提供个新的系统调用函数strerror函数用来获取系统错误信息或打印用户程序错误信息下面我们来用用这个系统调用 [!TIP] #include stdio.h
#include unistd.h
#include sys/types.h
#include string.hint main()
{int errorcode 0;for(errorcode 0; errorcode 255; errorcode){printf(%d - %s\n, errorcode, strerror(errorcode));}return 100;
} 我们会打印特别多的错误码每个错误码都对应了一个描述。所以为什么我们最后都会返回0就是因为如果全部代码都执行完毕后那肯定是不会存在问题的那么就返回0。如果其中有一处有错误系统就会直接进行返回相应的错误码。 这里是我随便ls一个文件夹因为在当前目录未找到该文件夹所以执行该指令的进程就会向bash返回相对应的错误码2。 所以我们在输入指令的时候本质也是OS创建子进程然后子进程在执行。这点我们通过指令echo $?可以很好的证明。 这也能很好地说明bash为什么要得到子进程的退出码为了知道子进程退出的情况是否成功失败又是什么原因当然bash只是提供信息不会自动解决这只是一种为用户负责的体现 我们也可以实现自定义退出码 #include stdio.h
#include unistd.h
#include sys/types.h
#include string.henum
{ERROR_DIV -1,NOMARL_DIV
};int exit_code NOMARL_DIV;int Div(int a, int b)
{if(b 0){exit_code ERROR_DIV;//printf(This is error!\n);return exit_code;}exit_code NOMARL_DIV;return a/b;
}const char* ErrorMode(int mode)
{switch(mode){case ERROR_DIV:return Div zero;case NOMARL_DIV:return Div nomarl;default:return Unknow error!;}
}int main()
{int ans Div(4, 0);printf(ans is- %d[%s]\n, ans, ErrorMode(exit_code));ans Div(4, 2);printf(ans is- %d[%s]\n, ans, ErrorMode(exit_code));return 0;
}通过判断分母是否为0从而实现判断结果是否是正确的 退出信号 int main()
{int* p NULL;*p 3;printf(%d\n, *p);return 0;
}对于上述代码我们执行起来百分之百会出现报错如果我们在VS上也写过这样的bug那么该错误就是一个很典型的——段错误 这个时候代码不会跑完因为在执行的过程中出现了异常就提前退出了。就像在VS写代码时代码崩溃了此时OS发现你的进程做了不该做的事情OS就会杀掉进程一旦出现了异常退出码就没意义了。 比如此时我使用指令echo $? 打印退出码是会发现退出码为139但是退出码在133后就是未知了 为什么会出现异常呢 本质上是因为进程收到了OS发给进程的信号。 我们可以使用指令kill -l 段错误的出现就是对应的11号信号 所以衡量一个进程的退出我们父进程bash只需要两个数字退出码 退出信号 第一步是先确认是否异常 若不是异常就一定是代码跑完了看退出码就好了。 [!IMPORTANT] 所以进程终止的3种情况 代码跑完结果正确代码跑完结果不正确代码执行时出现了异常提前退出了 如何终止进程
main函数的return表示进程终止非main函数的return代表函数结束代码调用exit函数在代码任意位置调用exit函数都表示进程退出通过系统调用函数_exit( ) exit( ) VS _exit( ) exit( )是库函数_exit( )是系统调用 exit函数会在进程退出的时候重置缓冲区而 _exit( )不会因此我们可以猜测缓冲区是在exit那一层也可以说明使用exit( )库函数的本质是在调用系统调用函数 _exit( ). #include stdio.h
#include stdlib.hint main()
{ printf(Hello, this is a test code!);exit(0);
} #include stdio.h
#include stdlib.hint main()
{ printf(Hello, this is a test code!);_exit(0);
}
进程等待
任何进程在退出的情况系一般必须要被父进程进行等待 为什么父进程要等待 进程在退出的时候如果父进程不管不顾退出进程会出现僵尸状态从而到时内存泄漏。所以父进程通过等待解决子进程出现的僵尸问题为了回收系统资源。这是一定要考虑的获取子进程的退出信息知道子进程是因为什么退出的。可选的功能 该怎么进行等待 我们可以使用系统调用函数wait( ) 和 waitpid( ) 函数 #include sys/types.h #include sys/wait.h pid_t wait(int* status); pid_t waitpid(pid_t pid, int* status, int options); wait( ) 父进程阻塞等待任意一个子进程子进程不退则父进程不退。该函数能够回收子进程资源以及获取子进程的 pid。 返回值成功返回被等待进程的pid失败返回-1. 参数输出型参数获取子进程退出状态不关心则可以设置成NULL #include stdio.h
#include unistd.h
#include sys/types.h
#include stdlib.h
#include sys/wait.hvoid ChildRun()
{int cnt 1;while(cnt 5){printf(%d- I am child process, pid: %d, ppid: %d\n, cnt, getpid(), getppid());sleep(1);cnt;}
}int main()
{printf(I am process!\n);pid_t id fork();if(id 0){// childChildRun();printf(Child quit...\n);exit(123);}// fatherpid_t rid wait(NULL);if(rid 0){printf(wait success, rid: %d\n, rid);}else{printf(wait failed!\n);}sleep(2);return 0;
} 最后是等待成功并且返回了子进程的pid waitpid( ) 回收子进程资源解决僵尸问题的同时还能够获取子进程退出信息。 返回值 pid_t 0等待成功子进程退出并且父进程回收成功pid_t 0等待失败pid_t 0检测是成功的只不过子进程还没退出需要你下一次重复等待。 参数 pid_t pid pid -1等待任意一个子进程与wait一样。pid 0等待其进程的ID与pid相等的子进程。 int* status 输出型参数用来保存退出信息保存退出码 退出信号让我们知道“退出”的情况如何 int options 指定父进程的等待方式为 0 则让父进程进行 阻塞 等待非 0 则进行 非阻塞 等待。 #include stdio.h
#include unistd.h
#include sys/types.h
#include stdlib.h
#include sys/wait.h
void ChildRun()
{int cnt 1;while(cnt 5){printf(%d- I am child process, pid: %d, ppid: %d\n, cnt, getpid(), getppid());sleep(1);cnt;}
}int main()
{printf(I am process!\n);pid_t id fork();if(id 0){// childChildRun();printf(Child quit...\n);exit(123);}// fatherint status 0;pid_t rid waitpid(-1, status, 0);if(rid 0){printf(wait success, rid: %d\n, rid);}else{printf(wait failed!\n);}sleep(2);printf(father process quit, status: %d\n, status);return 0;
} 最后的运行结果status是31488这么奇怪的数字。 分析status 我们介绍过status是输出型参数记录了该进程的退出码 退出信号但是我们应该如何利用一个值记录退出码和退出信号呢 [!NOTE] 我们都知道一个int* 变量的大小是4字节那么也就是32个比特位但status不能简单的当做int 来看待可以当做位图来看我们只研究低16比特位* 分为两种情况 正常终止时高8位为退出码信息低8位默认全部为0被信号杀掉时出现异常高8位不再使用低7位存储终止信号中间还有一位存core dump标志位 红色各段就代表着 退出码 退出信号退出码拥有8个比特位而终止信号拥有7个比特位 所以我们上述的31488其实是两个数据的整合但是我们也可以打印出来看看。 我们需要将退出码先挪动到低8位再转换为10进制所以我们可以用位运算操作符 打印退出码(status 8) 0xFF 打印退出信号status 0x7F printf(exit_code: %d, exit_signal: %d\n, (status 8)0xFF, (status 0x7F));最后的输出结果 因为我的子进程最后exit了123又因为这是正常终止所以返回退出码接收到了123. 但如果我加一个段错误再运行就会如下图所示 此时是异常退出status就会收到退出信号而非退出码。 我们也可以利用两个宏来查出退出码 WIFEXITED(status)若位正常终止子进程返回的状态则为真。主要是查看子进程是否正常退出本质上是查看signal位是否满足。WEXITSTATUS(status)若WIFEXITED非空提取子进程的退出码。 if(WIFEXITED(status))
{printf(child process quit success, chid exit code: %d\n, WEXITSTATUS(status));
}
else
{printf(child process quit unnormal!\n);
}非阻塞等待
我们上面讲的等待过程是属于阻塞等待还记得我们在进程状态部分讲解过阻塞态吗还记得阻塞态是会进入等待队列的吗如果你有疑问的话可以去看看我之前写的博客进程的祖册、挂起和运行状态。 那我们刚刚介绍waitpid( )系统调用时也介绍了参数option是控制关于非阻塞等待的如果参数option为0那就是阻塞等待父进程会等待子进程结束再进行父进程的操作这是阻塞等待。 非阻塞等待是父进程在等待子进程的过程中父进程同时也在做某些操作这就是非阻塞等待。 [!NOTE] 我们可以使用宏WNOHANG 来表示父进程进入非阻塞等待。 #include stdio.h
#include unistd.h
#include sys/types.h
#include stdlib.h
#include sys/wait.hvoid ChildRun()
{int cnt 1;while(cnt 5){printf(%d- I am child process, pid: %d, ppid: %d\n, cnt, getpid(), getppid());sleep(1);cnt;}
}void DoOtherThing()
{printf( I am doing my father things while child process is running!\n);
}int main()
{printf(I am a process, pid: %d, ppid: %d\n, getpid(), getppid());pid_t id fork();if(id 0){//childChildRun();printf(child process quit...\n);sleep(1);exit(0);}//fatherint status 0;while(1){pid_t rid waitpid(0, status, WNOHANG);if(rid 0){printf( Just checking child process...\n);DoOtherThing();sleep(1);}else if(rid 0){printf(wait suceess!\n);break;}else{printf(wait failed!\n);break;}}if(WIFEXITED(status)){sleep(1);printf(father process quit success!\n);printf(exit_code: %d, exit_signal: %d\n, WEXITSTATUS(status), status0x7F);}else{printf(quit unnormal!\n);}return 0;
}通过以上代码我们就能实现子进程运行的同时父进程也在运行自己的任务 总结
现在我们已经学会了有关进程创建的话题接下来我们将要讨论进程替换的话题。