Linux中进程控制(上)

目录

进程创建

写时拷贝

fork常用场景

fork调用失败的原因

进程终止

进程退出场景

退出码

_exit函数

exit函数

进程等待

进程等待必要性

进程等待方法

wait方法

​编辑

waitpid方法

获取⼦进程status

阻塞和非阻塞等待


进程创建

在linux中fork函数是⾮常重要的函数,它从已存在进程中创建⼀个新进程。新进程为⼦进程,⽽原进程为⽗进程。

进程调⽤fork,创建子进程,当控制转移到内核中的fork代码后,内核做:

  • 分配新的内存块和内核数据结构给⼦进程。
  • 将⽗进程部分数据结构内容拷⻉⾄⼦进程。
  • 添加⼦进程到系统进程列表当中。
  • fork返回,开始调度器调度。

fork之前,父进程独立执行,fork之后,父子进程分别执行后续代码,也就是说代码是共享的。

而谁先执行完,由调度器决定。

fork的返回值:子进程返回0,父进程返回子进程的pid。

写时拷贝

通常,⽗⼦代码共享,⽗⼦再不写⼊时,数据也是共享的,当任意⼀⽅试图写⼊,便以写时拷⻉的⽅ 式各⾃⼀份副本。

因为有写时拷⻉技术的存在,所以⽗⼦进程得以彻底分离离!完成了进程独⽴性的技术保证! 写时拷⻉,是⼀种延时申请技术,可以提⾼整机内存的使⽤率。

fork常用场景

  • ⼀个⽗进程希望复制⾃⼰,使⽗⼦进程同时执⾏不同的代码段。例如,⽗进程等待客⼾端请求, ⽣成⼦进程来处理请求。
  • ⼀个进程要执⾏⼀个不同的程序。例如⼦进程从fork返回后,调⽤exec函数。

fork调用失败的原因

  • 系统中有太多的进程
  • 实际⽤⼾的进程数超过了限制

进程终止

进程终⽌的本质是释放系统资源,就是释放进程申请的相关内核数据结构和对应的数据和代码。

进程退出场景

  • 代码运⾏完毕,结果正确
  • 代码运⾏完毕,结果不正确
  • 代码异常终⽌

有这三种场景,我们在平常肯定需要去查看我们进程运行结束的结果。

所以有了进程退出码,我们可以查看某个进程结束之后的退出码,即可查看进程是否运行符合预期。但是一旦异常了,退出码就没有意义!

进程结束了,会将自己的退出码存在task_struct中,最终这个退出码会返回给父进程,我们之前说过,父进程要知道子进程的退出信息!而退出码实际上都代表着子进程的退出结束信息。

退出码

退出码(退出状态)可以告诉我们最后⼀次执⾏的命令的状态。在命令结束以后,我们可以知道命令 是成功完成的还是以错误结束的。其基本思想是,程序返回退出代码 0 时表⽰执⾏成功,没有问题。 代码 1 或 0 以外的任何代码都被视为不成功。

大小一个字节。

Linux Shell 中的主要退出码:

  • 退出码 0 表⽰命令执⾏⽆误,这是完成命令的理想状态。
  • 退出码 1 我们也可以将其解释为"不被允许的操作"。例如在没有sudo权限的情况下使⽤ yum;再例如除以 0 等操作也会返回错误码 1 ,对应的命令为 a=1/0
  • 130 ( SIGINT 或 ^C )和 143等终⽌信号是⾮常典型的,它们属于128+n 信号,其中 n 代表终⽌码。
  • 可以使⽤strerror函数来获取退出码对应的描述。

我们需要理解一些:

main函数结束,进程结束,而其他函数结束,只是代表函数调用结束,然后返回main函数。

而一旦代码异常(直接退出),退出码也就无意义了。

我们尝试打印一下退出码信息:

我们可以看到0代表成功,这也就是为什么我们每次main函数结尾都要写上return 0的原因!

当进程结束我们可以查看它的退出码:

echo $? 这个命令它会打印最进的那个进程的退出码。

_exit函数

  • 说明:虽然status是int,但是仅有低8位可以被⽗进程所⽤。所以_exit(-1)时,在终端执⾏$?发现 返回值是255。

注意:这是一个系统调用。

在这里的作用相当于return 0,但其实这两者的区别还是有的。

return是⼀种更常⻅的退出进程⽅法。执⾏returnn等同于执⾏exit(n),因为调⽤main的运⾏时函数会 将main的返回值当做exit的参数。

我们看到只打印了x字符串,却没打印y字符串,进程就直接退出了,这里也就显现了和return 的区别,不管任何地方使用了_exit函数,就代表进程结束,不会执行后续代码!

exit函数

图:

我们看到_exit和exit相同的结果,但其实还是有区别的:

我们之前也说过,当printf中没带\n时,缓冲区不会刷新,也就不会打印出来,只有进程结束才能看到打印情况,现在看这两种情况,我们可以得出:

区别:

按理来说只有OS能决定进程退出与否,而库函数却能退出函数为什么?

_exit是系统提供的,也就是说是系统调用,exit是C语言提供的,但它的底层调用了_exit函数,也就是说封装了系统调用。

所以我们可以得出我们谈论的缓冲区是在哪里呢?

它一定不在OS内部,而是库缓冲区,是C语言提供的缓冲区!

后续再仔细讲解缓冲区。

exit最后也会调⽤_exit,但在调⽤_exit之前,还做了其他⼯作:

  1. 执⾏⽤⼾通过atexit或on_exit定义的清理函数。
  2. 关闭所有打开的流,所有的缓存数据均被写⼊
  3. 调⽤_exit

进程等待

进程等待必要性

  • 之前讲过,⼦进程退出,⽗进程如果不管不顾,就可能造成'僵⼫进程'的问题,进⽽造成内存 泄漏。
  • 另外,进程⼀旦变成僵⼫状态,那就⼑枪不⼊,"杀⼈不眨眼"的kill-9也⽆能为⼒,因为谁也 没有办法杀死⼀个已经死去的进程。
  • 最后,⽗进程派给⼦进程的任务完成的如何,我们需要知道。如,⼦进程运⾏完成,结果对还是 不对,或者是否正常退出。
  • ⽗进程通过进程等待的⽅式,回收⼦进程资源,获取⼦进程退出信息

进程等待方法

wait方法

用法:

status是输出型参数,当我们需要得到子进程的退出码信息时,就可以传参进去,如果不关心子进程退出状态,可以设置成NULL.

waitpid方法

  • 如果⼦进程已经退出,调⽤wait/waitpid时,wait/waitpid会⽴即返回,并且释放资源,获得⼦ 进程退出信息。
  • 如果在任意时刻调⽤wait/waitpid,⼦进程存在且正常运⾏,则进程可能阻塞。
  • 如果不存在该⼦进程,则⽴即出错返回。

现在一步一步讲解这个函数。

参数pid ,我们可以将需要等待的某个子进程的pid传进去,表示等待这个子进程,传 -1 表示等待任意一个子进程,和wait等效。

参数status用法和wait函数一样。

参数 option 表示等待类型,阻塞等待和非阻塞等待的区别。

获取⼦进程status

  • wait和waitpid,都有⼀个status参数,该参数是⼀个输出型参数,由操作系统填充。
  • 如果传递NULL,表⽰不关⼼⼦进程的退出状态信息。
  • 否则,操作系统会根据该参数,将⼦进程的退出信息反馈给⽗进程。

status不能简单的当作整形来看待,可以当作位图来看待(32位比特位),这也就是为什么上面程序要调用WEXISTATUS函数才能获得真正的子进程的退出状态信息。

如果我们需要拿到status的退出码信息,我们需要进行位运算或者调用函数获得!

如:

需要得到子进程退出码,我们就要得到status那8个比特位表示的整数值,(按照上图)所以先右移动8位,再按位与上0xFF(8个比特位都是1,换算成16进制就是0xFF),这样就能得到子进程退出码!

当然也可以调用函数拿到子进程退出码。

异常例子:

注意:

如果子进程没有异常退出,低7个比特位,为0,一旦低7个比特位不为0,就是进程异常退出(代码没跑完,所以退出码无意义)。

阻塞和非阻塞等待

wait是默认阻塞等待,而waitpid可以利用传options参数来选择阻塞等待还是非阻塞等待。

区别:

阻塞等待就是父进程会阻塞在wait调用处,一直等待子进程退出,就像scanf一样,命令行一直阻塞在scanf这里。而非阻塞等待是父进程等待子进程的过程中,依然可以做自己的事情,也就是父子并发,非阻塞调用往往效率高一点!

例子:

1.调用waitpid时,非阻塞等待返回值和阻塞等待的返回值不同,非阻塞等待时,waitpid返回值大于0时,等待结束(返回值是子进程pid),返回值等于0,调用结束,但子进程没有退出,返回值小于0时,等待失败。

2.同时传options参数(WNOHANG),表示非阻塞等待,在等待期间,根据waitpid的返回值就可以获取每一时刻子进程的执行情况!所以注定父进程需要是个循环完成!

3.从返回值就能看出,阻塞等待是,waitpid返回值大于0(返回子进程pid),就意味着等待成功,子进程已经执行完毕(异常除外,异常也是返回子进程pid,当然也大于0,但是子进程不一定执行完毕),小于0,代表等待失败。

结果:

发现父子进程一起执行!

好了,我们下期见!

相关推荐
钟烁卓3 分钟前
C++日志
开发语言·c++
m0_751018666 分钟前
阿里云域名 绑定 华为云服务器ip
服务器·阿里云·华为云
Pafey13 分钟前
在 Qt 中实现动态切换主题(明亮和暗黑)
开发语言·qt
啊吧怪不啊吧15 分钟前
C++之初识模版
开发语言·数据结构·c++
noravinsc24 分钟前
windows powershell 判断 进程号是否存在
windows
19778354636 分钟前
Linux系统安全
linux
FBI HackerHarry浩43 分钟前
Linux云计算训练营笔记day13[CentOS 7 find、vim、vimdiff、ping、wget、curl、RPM、YUM]]
linux·运维·笔记·centos·云计算
*星星之火*44 分钟前
【GPT入门】第39课 OPENAI官方API调用方法
java·服务器·gpt
煤灰2421 小时前
简单的基于sqlite的服务器和客户端实现
服务器·sqlite
Thomas_YXQ1 小时前
Unity3D序列化机制详解
java·开发语言·游戏·unity·游戏引擎