✨个人主页: Yohifo
🎉所属专栏: Linux学习之旅
🎊每篇一句: 图片来源
🎃操作环境: CentOS 7.6 阿里云远程服务器
- Good judgment comes from experience, and a lot of that comes from bad judgment.
- 好的判断力来自经验,其中很多来自糟糕的判断力。
文章目录
进程 创建后,需要对其进行合理管理,光靠 OS 是无法满足我们的需求的,此时可以运用 进程 控制相关知识,对 进程 进行手动管理,如创建 进程、终止 进制、等待 进程 等,其中等待 进程 可以有效解决僵尸 进程 问题

汽车的中控台,可以对汽车进行各种操作
本文涉及的代码都是以 C语言 实现的
在学习 进程控制 相关知识前,先要对回顾如何创建 进程,涉及一个重要的函数 fork
#include <unistd.h> //所需头文件
pid_t fork(void); //fork 函数
fork 函数的作用是在当前 进程 下,创建一个 子进程,子进程 创建后,会为其分配新的内存块和内核数据结构(PCB),将 父进程 中的数据结构内容拷贝给 子进程,同时还会继承 父进程 中的环境变量表
PCB子进程 发生改写行为,会触发写时拷贝机制fork 函数返回类型为 pid_t,相当于 typedef int,不过是专门用于进程的,同时它拥有两个返回值:
-10PID 值
通过代码理解 进程 创建
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h> //进程等待相关函数头文件
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 可能创建进程失败
在【进程地址空间】一文中,谈到了写时拷贝机制,实现原理就是通过 页表+MMU 机制,对不同的进程进行空间寻址,达到出现改写行为时,父子进程使用不同真实空间的效果
验证写时拷贝现象很简单,创建子进程后,使其对生命周期长的变量作出修改,再观察父子进程的结果即可
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h> //进程等待相关函数头文件
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 机制寻址不同的空间写时拷贝机制本质上是一种按需申请资源的策略

注意:
假设某个进程陷入了死循环状态,可以通过特定方法终止此程序,如在命令行中莫名其妙输入了一个指令,导致出现非正常情况,可以通过 ctrl + c 终止当前进程;对于自己写的程序,有多种终止方法,程序退出时,还会有一个退出码,供 父进程 接收

echo $?
main 函数中的最后一条语句 return 0 表示当前程序的退出码,0 表示程序正常退出,可以通过指令 echo $? 查看最近一次子进程运行的 退出码
退出码是给父进程看的,可以判断子进程是否成功运行
子进程运行情况:

进程退出后,OS 会释放对应的 内核数据结构+代码和数据
main 函数退出,表示整个程序退出,而程序中的函数退出,仅表示该函数运行结束
对一个正在运行中的进程,存在两种终止方式:外部终止和内部终止,外部终止时,通过 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()
僵尸进程 是一个比较麻烦的问题,如果不对其做出处理,僵尸进程 就会越来越多,导致 内存泄漏 和 标识符 占用问题

子进程运行结束后,父进程没有等待并接收其退出码和退出状态,OS 无法释放对应的 内核数据结构+代码和数据,出现 僵尸进程
为了避免这种情况的出现,父进程可以通过函数等待子进程运行结束,此时父进程属于阻塞状态
注意:
也就是说,父进程必须对子进程负责,确保子进程不会连累 OS,而子进程执行的结果是否正确,需要我们自行判断
系统提供的父进程等待函数有两个 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() 函数前面已经演示过了,这里着重介绍 waitpid() 返回值及其参数
wait() 中的返回值和参数,包含在 waitpid() 中
返回值:
>0 的值-10参数列表:
pid 表示所等子进程的 PIDstatus 表示状态,为整型,其中高 16 位不管,低 16 位中,次低 8 位表示退出码,第 7 位表示 code 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("子进程异常退出,code 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 的位操作需要多画图理解code dump 现阶段用不到,但它是伴随着终止信号出现的如果觉得 (status >> 8) & 0xFF 和 (status & 0x7F) 这两个位运算难记,系统还提供了两个宏来简化代码
WIFEXITED(status) 判断进程退出情况,当宏为真时,表示进程正常退出WEXITSTATUS(status) 相当于 (status >> 8) & 0xFF,直接获取退出码//options 参数
WNOHANG
//比如
waitpid(id, &status, WNOHANG);
父进程并非需要一直等待子进程运行结束(阻塞等待),可以通过设置 options 参数,进程解除 夯 状态,父进程变成 等待轮询 状态,不断获取子进程状态(是否退出),如果没退出,就可以干点其他事
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h> //进程等待相关函数头文件
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】
出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta
我对最新版本的Rails有疑问。我创建了一个新应用程序(railsnewMyProject),但我没有脚本/生成,只有脚本/rails,当我输入ruby./script/railsgeneratepluginmy_plugin"Couldnotfindgeneratorplugin.".你知道如何生成插件模板吗?没有这个命令可以创建插件吗?PS:我正在使用Rails3.2.1和ruby1.8.7[universal-darwin11.0] 最佳答案 随着Rails3.2.0的发布,插件生成器已经被移除。查看变更日志here.现在
在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',
如何使用RSpec::Core::RakeTask初始化RSpecRake任务?require'rspec/core/rake_task'RSpec::Core::RakeTask.newdo|t|#whatdoIputinhere?endInitialize函数记录在http://rubydoc.info/github/rspec/rspec-core/RSpec/Core/RakeTask#initialize-instance_method没有很好的记录;它只是说:-(RakeTask)initialize(*args,&task_block)AnewinstanceofRake
我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是
当我在Rails控制台中按向上或向左箭头时,出现此错误:irb(main):001:0>/Users/me/.rvm/gems/ruby-2.0.0-p247/gems/rb-readline-0.4.2/lib/rbreadline.rb:4269:in`blockin_rl_dispatch_subseq':invalidbytesequenceinUTF-8(ArgumentError)我使用rvm来管理我的ruby安装。我正在使用=>ruby-2.0.0-p247[x86_64]我使用bundle来管理我的gem,并且我有rb-readline(0.4.2)(人们推荐的最少
关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?
我正在使用Ruby2.1.1和Rails4.1.0.rc1。当执行railsc时,它被锁定了。使用Ctrl-C停止,我得到以下错误日志:~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`gets':Interruptfrom~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`verify_server_version'from~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.