linux之进程控制

进程创建

fork函数初识

在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

我们发现打印了俩个,原因就是因为fork()函数之后创建了一个子进程,子进程执行了一次,父进程执行了一次。

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

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

pid_t fork(void):返回值:自进程中返回0,父进程返回子进程id,出错返回-1.

fork()函数为啥要有俩个返回值呢?

首先我们创建子进程是为了父和子执行不同的事情,因此为了执行不同的代码块,让fork()有了不同的返回值。
fork函数为什么要给子进程返回0,给父进程返回子进程的PID?

一个父进程可以创建多个子进程,而一个子进程只能有一个父进程。因此,对于子进程来说,父进程是不需要被标识的;而对于父进程来说,子进程是需要被标识的,因为父进程创建子进程的目的是让其执行任务的,父进程只有知道了子进程的PID才能很好的对该子进程指派任务。
fork()函数是怎么做到俩个不同的返回值的呢?

在这里我们可以先了解一下写实拷贝的概念。

写时拷贝

通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副

本。具体见下图

当我们的发生写实拷贝的时候,我们的虚拟内存是不变的,操作系统会对要进行数据修改的一方在操作系统重新开辟一个空间,通过页表的映射,找到该物理地址,因此发生写实拷贝的时候虚拟内存不变,而发生变化的是物理内存。

我们可以通过下面的代码来看看:

在上面的代码中,我们可以发现代码是具有独立性的,因此数据必须独立,不能因为子进程的改变而影响父进程。

fork常规用法

1一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。

2一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数

fork调用失败的原因

fork函数创建子进程也可能会失败,有以下两种情况:

系统中有太多的进程,内存空间不足,子进程创建失败。

实际用户的进程数超过了限制,子进程创建失败。

进程的终止

进程退出场景

进程退出只有三种情况:

代码运行完毕,结果正确。

代码运行完毕,结果不正确。

代码异常终止(进程崩溃)。

进程退出码

我们都知道main函数是代码的入口,但实际上main函数只是用户级别代码的入口,main函数也是被其他函数调用的,例如在VS2013当中main函数就是被一个名为__tmainCRTStartup的函数所调用,而__tmainCRTStartup函数又是通过加载器被操作系统所调用的,也就是说main函数是间接性被操作系统所调用的。

既然main函数是间接性被操作系统所调用的,那么当main函数调用结束后就应该给操作系统返回相应的退出信息,而这个所谓的退出信息就是以退出码的形式作为main函数的返回值返回,我们一般以0表示代码成功执行完毕,以非0表示代码执行过程中出现错误,这就是为什么我们都在main函数的最后返回0的原因。

当我们的代码运行起来就变成了进程,当进程结束后main函数的返回值实际上就是该进程的进程退出码,我们可以使用echo $?命令查看最近一次进程退出的退出码信息。


为什么以0表示代码执行成功,以非0表示代码执行错误?

因为代码执行成功只有一种情况,成功了就是成功了,而代码执行错误却有多种原因,例如内存空间不足、非法访问以及栈溢出等等,我们就可以用这些非0的数字分别表示代码执行错误的原因。

C语言当中的strerror函数可以通过错误码,获取该错误码在C语言当中对应的错误信息:


我们知道我们的命令行也是进程,当他运行不成功的时候也会有退出码。

进程常见退出方法

return退出

在main函数中使用return退出进程是我们常用的方法。


退出码为0,表示该进程成功退出。

exit()函数

使用exit函数退出进程也是我们常用的方法,exit函数可以在代码中的任何地方退出进程,并且exit函数在退出进程前会做一系列工作:执行用户通过atexit或on_exit定义的清理函数。

关闭所有打开的流,所有的缓存数据均被写入。

调用_exit函数终止进程。

下面的代码我没有用换行的符号,但是exit会将缓冲区的数据刷新。


_exit函数

例如,以下代码中使用_exit终止进程,则缓冲区当中的数据将不会被输出。

eturn、exit和_exit之间的区别与联系

只有在main函数当中的return才能起到退出进程的作用,子函数当中return不能退出进程,而exit函数和_exit函数在代码中的任何地方使用都可以起到退出进程的作用。

使用exit函数退出进程前,exit函数会执行用户定义的清理函数、冲刷缓冲,关闭流等操作,然后再终止进程,而_exit函数会直接终止进程,不会做任何收尾工作。

return、exit和_exit之间的联系

return num等于exit(num),,因为调用main函数运行结束后,会将main函数的返回值当做exit的参数来调用exit函数。

使用exit函数退出进程前,exit函数会先执行用户定义的清理函数、冲刷缓冲,关闭流等操作,然后再调用_exit函数终止进程。.

进程异常退出

情况一 向进程发生信号导致进程异常退出。

这个死循环程序,我们可以模拟实现一下进程这个信号异常退出的情况。

我使用了kill -19的信号暂停了该进程,

我使用了kill -9的信号杀死了该进程

情况二:代码错误导致进程运行时异常退出。

例如,代码当中存在野指针问题使得进程运行时异常退出,或是出现除0的情况使得进程运行时异常退出等。

进程等待

什么是进程等待呢?

通过系统调用wait/waitpid来进行对子进程进行状态检测与回收的功能。

为什么要进程等待呢?

我们在之前的博客写道,子进程退出的时候,需要等待父进程来回收他,不然就会变成僵尸进程,一直存在,存在内存泄露的风险。

我们应该怎么办呢?

通过系统调用接口wait/waitpid来回收僵尸进程

wait/waitpid函数的介绍

通过等待来改变进程的状态。

完整的进程创建完之后应该等待然后销毁掉

等待成功的时候就会将该进程僵尸状态给回收掉,因此进程等待是必须的,wait是等待任意一个进程结束的。

多进程的创建与销毁

wait当任意一个进程退出的时候,wait回收子进程,如果任意一个子进程不退出呢?

如果子进程一直不退出,那么父进程就会一直在等待,父进程一直在wait的时候,调用这个系统调用,也就不返回,默认叫做阻塞状态。

因此我们可以查看一下子进程的退出结果,而退出结果一般就是顺利运行,要么就是运行失败,要么就是收到了信号影响。

我们可以使用waitpid()第二个参数来接收子进程的退出码以及他是是否是正常退出的。

可是我们接收到的子进程的退出码是2861是啥原因呢?

因为在32位的数字中,我们只考虑低16位的数字,其中次低7位的表示终止信号,次低八位表示退出码。2816是有11的二进制序列转化而来,我们在代码中改成这个样子就可以转化过来了。

实际上我们可以让父进程不要一直等待子进程退出,而是当子进程未退出时父进程可以做一些自己的事情,当子进程退出时再读取子进程的退出信息,即非阻塞等待。
方法很简单,向waitpid函数的第三个参数potions传入WNOHANG,这样一来,等待的子进程若是没有结束,那么waitpid函数将直接返回0,不予以等待。而等待的子进程若是正常结束,则返回该子进程的pid。

例如,父进程可以隔一段时间调用一次waitpid函数,若是等待的子进程尚未退出,则父进程可以先去做一些其他事,过一段时间再调用waitpid函数读取子进程的退出信息。

代码示例:

进程替换

相关推荐
NE_STOP6 分钟前
SpringBoot--简单入门
java·spring
hqxstudying33 分钟前
Java创建型模式---原型模式
java·开发语言·设计模式·代码规范
Dcs1 小时前
VSCode等多款主流 IDE 爆出安全漏洞!插件“伪装认证”可执行恶意命令!
java
保持学习ing1 小时前
day1--项目搭建and内容管理模块
java·数据库·后端·docker·虚拟机
c30%001 小时前
内网渗透——红日靶场五
运维·服务器
京东云开发者1 小时前
Java的SPI机制详解
java
超级小忍2 小时前
服务端向客户端主动推送数据的几种方法(Spring Boot 环境)
java·spring boot·后端
程序无bug2 小时前
Spring IoC注解式开发无敌详细(细节丰富)
java·后端
小莫分享2 小时前
Java Lombok 入门
java
程序无bug2 小时前
Spring 对于事务上的应用的详细说明
java·后端