帮助文档
专业提供香港服务器、香港云服务器、香港高防服务器租用、香港云主机、台湾服务器、美国服务器、美国云服务器vps租用、韩国高防服务器租用、新加坡服务器、日本服务器租用 一站式全球网络解决方案提供商!专业运营维护IDC数据中心,提供高质量的服务器托管,服务器机房租用,服务器机柜租用,IDC机房机柜租用等服务,稳定、安全、高性能的云端计算服务,实时满足您的多样性业务需求。 香港大带宽稳定可靠,高级工程师提供基于服务器硬件、操作系统、网络、应用环境、安全的免费技术支持。
服务器资讯 / 香港服务器租用 / 香港VPS租用 / 香港云服务器 / 美国服务器租用 / 台湾服务器租用 / 日本服务器租用 / 官方公告 / 帮助文档
Linux进程控制【创建、终止、等待】
发布时间:2024-02-27 19:43:51   分类:帮助文档
Linux进程控制【创建、终止、等待】 ✨个人主页: Yohifo 🎉所属专栏: Linux学习之旅 🎊每篇一句: 图片来源 🎃操作环境: CentOS 7.6 阿里云远程服务器 Good judgment comes from experience, and a lot of that comes from bad judgment. 好的判断力来自经验,其中很多来自糟糕的判断力。 文章目录 🌇前言🏙️正文1、进程创建1.1、fork函数1.2、写时拷贝 2、进程终止2.1、退出码2.2、退出方式 3、进程等待3.1、等待原因3.2、等待函数3.3、等待时执行 🌆总结 🌇前言 进程 创建后,需要对其进行合理管理,光靠 OS 是无法满足我们的需求的,此时可以运用 进程 控制相关知识,对 进程 进行手动管理,如创建 进程、终止 进制、等待 进程 等,其中等待 进程 可以有效解决僵尸 进程 问题 汽车的中控台,可以对汽车进行各种操作 🏙️正文 本文涉及的代码都是以 C语言 实现的 1、进程创建 在学习 进程控制 相关知识前,先要对回顾如何创建 进程,涉及一个重要的函数 fork 1.1、fork函数 #include //所需头文件 pid_t fork(void); //fork 函数 fork 函数的作用是在当前 进程 下,创建一个 子进程,子进程 创建后,会为其分配新的内存块和内核数据结构(PCB),将 父进程 中的数据结构内容拷贝给 子进程,同时还会继承 父进程 中的环境变量表 进程具有独立性,即使是父子进程,也是两个完全不同的进程,拥有各自的 PCB假设 子进程 发生改写行为,会触发写时拷贝机制 fork 函数返回类型为 pid_t,相当于 typedef int,不过是专门用于进程的,同时它拥有两个返回值: 如果进程创建失败,返回 -1进程创建成功后 给子进程返回 0给父进程返回子进程的 PID 值 通过代码理解 进程 创建 #include #include #include #include //进程等待相关函数头文件 int main() { //创建两个子进程 pid_t id1 = fork(); if(id1 == 0) { //子进程创建成功,创建孙子进程 pid_t id2 = fork(); if(id2 == 0) { printf("我是孙子进程,PID:%d PPID:%d\n", getpid(), getppid()); exit(1); //孙子进程运行结束后,退出 } wait(0); //等待孙子进程运行结束 printf("我是子进程,PID:%d PPID:%d\n", getpid(), getppid()); exit(1); //子进程运行结束后,退出 } wait(0); //等待子进程运行结束 printf("我是父进程,PID:%d PPID:%d\n", getpid(), getppid()); return 0; //父进程运行结束后,退出 } 观察结果不难发现,两个子进程已经成功创建,但最晚创建的进程,总是最先运行,这是因为 fork 创建进程后,先执行哪个进程取决于调度器 得到子进程后,此时可以在一个程序中同时执行两个进程!(父进程非阻塞的情况下) 注意:fork 可能创建进程失败 系统中的进程过多时实际用户的进程数超过了限制 1.2、写时拷贝 在【进程地址空间】一文中,谈到了写时拷贝机制,实现原理就是通过 页表+MMU 机制,对不同的进程进行空间寻址,达到出现改写行为时,父子进程使用不同真实空间的效果 验证写时拷贝现象很简单,创建子进程后,使其对生命周期长的变量作出修改,再观察父子进程的结果即可 #include #include #include #include //进程等待相关函数头文件 const char* ps = "This is an Apple"; //全局属性 int main() { pid_t id = fork(); if(id == 0) { ps = "This is a Banana"; //改写 printf("我是子进程,我认为:%s\n", ps); exit(0); //子进程退出 } wait(0); //等待子进程退出 printf("我是父进程,我认为:%s\n", ps); return 0; } 不难发现,子进程对指针 ps 指向内容做出改变时,父进程并不受影响,这就是写时拷贝机制 通过地址打印,发现父子进程中的 ps 地址一致,因为此时是虚拟地址在虚拟地址相同的情况下,真实地址是不同的,得益于 页表+MMU 机制寻址不同的空间 写时拷贝机制本质上是一种按需申请资源的策略 注意: 写时拷贝不止可以发生在常规栈区、堆区,还能发生在只读的数据段和数据段写时拷贝后,生成的是副本,不会对原数据造成影响 2、进程终止 假设某个进程陷入了死循环状态,可以通过特定方法终止此程序,如在命令行中莫名其妙输入了一个指令,导致出现非正常情况,可以通过 ctrl + c 终止当前进程;对于自己写的程序,有多种终止方法,程序退出时,还会有一个退出码,供 父进程 接收 2.1、退出码 echo $? main 函数中的最后一条语句 return 0 表示当前程序的退出码,0 表示程序正常退出,可以通过指令 echo $? 查看最近一次子进程运行的 退出码 退出码是给父进程看的,可以判断子进程是否成功运行 子进程运行情况: 运行失败或异常终止,此时出现终止信号,无退出码运行成功,返回退出码,可能出现结果错误的情况 进程退出后,OS 会释放对应的 内核数据结构+代码和数据 main 函数退出,表示整个程序退出,而程序中的函数退出,仅表示该函数运行结束 2.2、退出方式 对一个正在运行中的进程,存在两种终止方式:外部终止和内部终止,外部终止时,通过 kill -9 PID 指令,强行终止正在运行中的程序,或者通过 ctrl + c 终止前台运行中的程序 内部终止是通过函数 exit() 或 _exit() 实现的 之前在程序编写时,发生错误行为时,可以通过 exit(-1) 的方式结束程序运行,代码中任意地方调用此函数,都可以提前终止程序 void exit(int status); void _exit(int status); 这两个退出函数,从本质上来说,没有区别,都是退出进程,但在实际使用时,还是存在一些区别,推荐使用 exit() 比如在下面这段程序中,分别使用 exit() 和 _exit() 观察运行结果 int main() { printf("You can see me"); //exit(-1); //退出程序 //_exit(-1); //第二个函数 return 0; } 使用 exit() 时,输出语句 使用 _exit() 时,并没有任何语句输出 原因: exit() 是对 _exit() 做的封装实现_exit() 就只是单纯的退出程序而 exit() 在退出之前还会做一些事,比如冲刷缓冲区,再调用 _exit()程序中输出语句位于输出缓冲区,不冲刷的话,是不会输出内容的 3、进程等待 僵尸进程 是一个比较麻烦的问题,如果不对其做出处理,僵尸进程 就会越来越多,导致 内存泄漏 和 标识符 占用问题 3.1、等待原因 子进程运行结束后,父进程没有等待并接收其退出码和退出状态,OS 无法释放对应的 内核数据结构+代码和数据,出现 僵尸进程 为了避免这种情况的出现,父进程可以通过函数等待子进程运行结束,此时父进程属于阻塞状态 注意: 进程的退出状态是必要的进程的执行结果是非必要的 也就是说,父进程必须对子进程负责,确保子进程不会连累 OS,而子进程执行的结果是否正确,需要我们自行判断 3.2、等待函数 系统提供的父进程等待函数有两个 wait() 和 waitpid(),后者比较常用 #include #include pid_t wait(int* status); pid_t waitpid(pid_t pid, int* status, int options); wait() 函数前面已经演示过了,这里着重介绍 waitpid() 返回值及其参数 wait() 中的返回值和参数,包含在 waitpid() 中 返回值: 等待成功时,返回 >0 的值等待失败时,返回 -1等待中,返回 0 参数列表: pid 表示所等子进程的 PIDstatus 表示状态,为整型,其中高 16 位不管,低 16 位中,次低 8 位表示退出码,第 7 位表示 core dump,低 7 位表示终止信号options 为选项,比如可以选择父进程是否需要阻塞等待子进程退出 需要特别注意 status 通过代码演示 waitpid() 的使用 int main() { //演示 waitpid() pid_t id = fork(); //创建子进程 if(id == 0) { int time = 5; int n = 0; while(n < time) { printf("我是子进程,我已经运行了:%d秒 PID:%d PPID:%d\n", n + 1, getpid(), getppid()); sleep(1); n++; } exit(244); //子进程退出 } int status = 0; //状态 pid_t ret = waitpid(id, &status, 0); //参数3 为0,为默认选项 if(ret == -1) { printf("进程等待失败!进程不存在!\n"); } else if(ret == 0) { printf("子进程还在运行中!\n"); } else { printf("进程等待成功,子进程已被回收\n"); } printf("我是父进程, PID:%d PPID:%d\n", getpid(), getppid()); //通过 status 判断子进程运行情况 if((status & 0x7F)) { printf("子进程异常退出,core dump:%d 退出信号:%d\n", (status >> 7) & 1, (status & 0x7F)); } else { printf("子进程正常退出,退出码:%d\n", (status >> 8) & 0xFF); } return 0; } 不发出终止信号,让程序自然跑完 发出终止信号,强行终止进程 waitpid() 的返回值可以帮助我们判断此时进程属于什么状态(在下一份测试代码中表现更明显),而 status 的不同部分,可以帮助我们判断子进程因何而终止,并获取 退出码(终止信号) 在进程的 PCB 中,包含了 int _exit_code 和 int _exit_signal 这两个信息,可以通过对 status 的位操作间接获取其中的值 注意: status 的位操作需要多画图理解正常退出时,终止信号为0;异常终止时,退出码没有,两者是互斥的code dump 现阶段用不到,但它是伴随着终止信号出现的 如果觉得 (status >> 8) & 0xFF 和 (status & 0x7F) 这两个位运算难记,系统还提供了两个宏来简化代码 WIFEXITED(status) 判断进程退出情况,当宏为真时,表示进程正常退出WEXITSTATUS(status) 相当于 (status >> 8) & 0xFF,直接获取退出码 3.3、等待时执行 //options 参数 WNOHANG //比如 waitpid(id, &status, WNOHANG); 父进程并非需要一直等待子进程运行结束(阻塞等待),可以通过设置 options 参数,进程解除 夯 状态,父进程变成 等待轮询 状态,不断获取子进程状态(是否退出),如果没退出,就可以干点其他事 #include #include #include #include //进程等待相关函数头文件 int main() { //演示 waitpid() pid_t id = fork(); //创建子进程 if(id == 0) { int time = 9; int n = 0; while(n < time) { printf("我是子进程,我已经运行了:%d秒 PID:%d PPID:%d\n", n + 1, getpid(), getppid()); sleep(1); n++; } exit(244); //子进程退出 } int status = 0; //状态 pid_t ret = 0; while(1) { ret = waitpid(id, &status, WNOHANG); //参数3 设置为非阻塞状态 if(ret == -1) { printf("进程等待失败!进程不存在!\n"); break; } else if(ret == 0) { printf("子进程还在运行中!\n"); printf("我可以干一些其他任务\n"); sleep(3); } else { printf("进程等待成功,子进程已被回收\n"); //通过 status 判断子进程运行情况 if(WIFEXITED(status)) { printf("子进程正常退出,退出码:%d\n", WEXITSTATUS(status)); break; } else { printf("子进程异常退出,code dump:%d 退出信号:%d\n", (status >> 7) & 1, (status & 0x7F)); break; } } } return 0; } 程序正常运行,父进程通过 等待轮询 的方式,在子进程执行的同时,执行其他任务 当然也可以通过 kill -9 PID 命令使子进程异常终止 可以看到程序能分别捕捉到正常和异常的情况 注意: 如果不写进程等待函数,会引发僵尸进程问题 🌆总结 以上就是关于 Linux进程控制(创建、终止、等待) 的相关知识了,我们学习了 子进程 是如何被创建的,创建后又是如何终止的,以及 子进程 终止 父进程 需要做些什么,有了这些知识后,在对 进程 进行操作时能更加灵活和全面 如果你觉得本文写的还不错的话,期待留下一个小小的赞👍,你的支持是我分享的最大动力! 如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正 相关文章推荐 Linux进程学习【进程地址】 Linux进程学习【环境变量】 Linux进程学习【进程状态】 Linux进程学习【基本认知】 =============== Linux工具学习之【gdb】 Linux工具学习之【git】 Linux工具学习之【gcc/g++】 Linux工具学习之【vim】
香港云服务器租用推荐
服务器租用资讯
·广东云服务有限公司怎么样
·广东云服务器怎么样
·广东锐讯网络有限公司怎么样
·广东佛山的蜗牛怎么那么大
·广东单位电话主机号怎么填写
·管家婆 花生壳怎么用
·官网域名过期要怎么办
·官网邮箱一般怎么命名
·官网网站被篡改怎么办
服务器租用推荐
·美国服务器租用
·台湾服务器租用
·香港云服务器租用
·香港裸金属服务器
·香港高防服务器租用
·香港服务器租用特价