c++高级篇(一) —— 初识Linux下的进程控制

linux的信号

信号的概念

在Linux中,信号是一种用于进程间通信和处理异步事件的机制,用于进程之间相互传递消息和通知进程发生了事件,但是,它不能给进程传递任何数据。

信号产生的原因有很多种,在shell中,我们可以使用killkillall来发送信号

kill -信号的类型  进程编号
killall -信号的类型 进程名

信号的类型

常见信号类型:

  • SIGINT:终止进程(键盘快捷键 ctrl+c
  • SIGKILL: 采用kill -9 进程编号,强制杀死程序

信号的处理

进程对信号的处理方法一般有三种:

  • 对该信号进行默认处理,一般是终止该进程
  • 设置中断的处理函数,受到该型号的函数进行处理
  • 忽略该信号,不做如何处理

signal()函数可以设置程序对信号的处理方式

函数的声明:

sighandler_t signal(int signum,sighandler_t handler)

注释

参数signum表示信号的编号

参数handler表示信号的处理方式,有三种情况:

  1. SIG_DFL:恢复参数signum所指信号的处理方法为默认值
  2. 一个自定义的处理信号的函数,信号的编号为这个自定义函数的参数
  3. SIG_IGN:忽略signum所指的信号

示例代码:

#include <iostream>
#include <unistd.h>
#include <signal.h>using namespace std;void func(int signum)
{cout<<"收到了信号"<<signum<<endl;signal(1,SIG_DFL);//将函数的处理方式由自定义函数改为了默认方式处理
}int main(int argc,char *argv[],char *envp[])
{signal(1,func);signal(15,func);signal(2,SIG_IGN);//忽略信号2while(1){cout<<argc<<endl;sleep(1);}return 0;
}

信号的作用

​ 服务程序运行在后台,如果想终止它,一般不会直接杀死它,以防止出现意外。

​ 我们·一般会选择向进程去发送一个信号,当程序收到这个信号的时候能够调用函数,并通过函数中英语善后的代码,原计划的退出。

​ 我们也可以向其发送0的信号来确保程序是否存活

示例代码:

#include <iostream>
#include <signal.h>using namespace std;void Exit(int signum)
{cout<<"收到了"<<signum<<"信号"<<endl;cout<<"开始释放资源并退出"<<endl;//释放资源的代码cout<<"退出程序"<<endl;exit(0);
}
int main()
{for(int i=1;i<=64;i++) {signal(i,SIG_IGN);}signal(2,Exit);signal(15,Exit);while(1){cout<<"fengxu\n";}
}

进程终止

进程的终止

main()函数中,return的返回值就是终止状态,如果没有return语句或者调用exit(),那么该进程终止状态为0

我们可以通过

echo $?

来查看线程终止的状态

正常终止进程函数有三个:

void exit(int status);
void _exit(int status);
void _Exit(int status);

status也是进程终止的状态

注意:进程如果被异常终止,终止状态也为非0

资源释放

return表示函数返回,会调用局部对象的析构函数,main()函数中的return还会调用全局对象的析构函数

exit()表示进程终止,它不会调用局部对象的析构函数,只会调用全局变量的析构函数

注意:exit()会执行清理工作再退出,但是_EXIT()——exit()不会执行清理工作

进程的终止函数

进程可以利用atexit函数来登记终止函数(最多32个),这些函数将由exit()自动调用。

**注意:**运行登记函数的顺序与登记函数顺序相反

示例代码:

#include <iostream>
#include <stdlib.h>using namespace std;void fuc1()
{cout<<"调用了fuc1()"<<endl;
}void fuc2()
{cout<<"调用了fuc2()"<<endl;
}int main()
{atexit(fuc1);atexit(fuc2);exit(0);
}

输出:

在这里插入图片描述

调用可执行程序

system函数

system()函数提供了一种简单的执行程序的方法,把需要执行的参数用一个字符串传给system()函数就行了

函数声明:

int system(const char *string);

返回值:0成功,非0失败

示例代码:

#include <iostream>
#include <stdlib.h>using namespace std;int main()
{int ret=system("ls -l");cout<<ret<<endl;perror("system");return 0;
}

输出

在这里插入图片描述

exec函数族

前言

我们在用fork()函数去创建一个进程的时候,当我们想继续使用这个进程去去执行其他函数的时候,我们可以去调用exec函数,这样该进程将被替换为全新的程序,而且调用exec函数,前后函数的进程不变

exec函数族函数的功能

它能够在调用进程内部去执行一个可执行文件,它既可以是二进制文件,也可以是任何Linux下的可执行脚本文件

exec函数的返回值

exec函数族的函数在执行成功后不会返回,调用失败则会返回-1并设置error值,并从原程序调用点继续往下执行

exec函数的种类

exec族函数一个有六种:

  1. execl(const char *path, const char *arg, ...):接受一个以NULL结尾的参数列表,第一个参数是要执行的可执行文件的路径,后面的参数是传递给可执行文件的命令行参数。
  2. execlp(const char *file, const char *arg, ...):与execl函数类似,但它在可执行文件的搜索路径中查找该文件,而不是仅使用给定的路径。
  3. execle(const char *path, const char *arg, ..., char *const envp[]):类似于execl函数,但允许指定新的环境变量参数(通过envp数组传递)。
  4. execv(const char *path, char *const argv[]):与execl函数类似,但接受一个以NULL结尾的参数数组来传递命令行参数。
  5. execvp(const char *file, char *const argv[]):与execv函数类似,但在可执行文件的搜索路径中查找文件,而不是仅使用给定的路径。
  6. execvpe(const char *file, char *const argv[], char *const envp[]):与execvp函数类似,但允许指定新的环境变量参数

对exec参数的说明

path:可执行文件的路径

arg:可执行文件所带的参数,第一个为文件的名字,不带路径且以NULL结尾

file:如果参数file中带有\,则将其视作路径处理,否则即在当前PATH环境变量按照其指定的各个目录去搜寻可执行文件

exec族函数的分类

exec族函数参数比较难记忆,但是当我们可以通过函数名中的字符来辅助我们记忆

  • l:使用参数列表
  • P:使用文件名,并从PATH环境中寻找可执行文件
  • V:构造一个指向各参数的指针数组,将数组的地址作为这些

函数的参数

  • e:多了envp数组,利用写的环境变量代替了进程的环境变量

接下来是对每种类型的具体描述:

l类函数

带l的一类exac函数(l表示list),包括execlexeclpexecle,要求将新程序的每个命令行参数都说明为 一个单独的参数。这种参数表以空指针结尾。

execl函数为例

//echoarg.cpp
#include <iostream>using namespace std;int main(int argc, char *argv[])
{for(int i=0;i<argc;i++){printf("argv[%d]=:%s\n",i,argv[i]);}return 0;
}
//execl.cpp
#include <iostream>
#include <unistd.h> using namespace std;int main()
{cout<<"before execl"<<endl;if(execl("/home/lib/项目课源码/app/进程与通信/exec族函数/out/echoarg","echorag","abc",NULL)==-1){cout<<"execl error"<<endl;}cout<<"after execl"<<endl;
}

输出结果:
在这里插入图片描述

说明

我们先用g++编译echoarg.cpp,生成可执行文件echoarg并放在目录下。文件echoarg的作用是打印命令行参数。然后再编译execl.c并执行execl可执行文件。用execl 找到并执行echoarg,将当前进程main替换掉,所以”after execl” 没有在终端被打印出来。

p类函数

带p的一类exac函数,包括execlpexecvpexecvpe,如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。举个例子:

#include <iostream>
#include <unistd.h>using namespace std;int main()
{cout<<"before execlp\n";if(execlp("ls","ls","-l",NULL)==-1){cout<<"execlp error\n";}cout<<"after execlp\n";return 0;
}

带v不带l的函数

带v不带l的一类exac函数,包括execvexecvpexecve,应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。

以下面代码作为例子

#include <iostream>
#include <unistd.h>using namespace std;int main()
{cout<<"before execvp";char *argv[]={"ls","-l",NULL};//最后一个元素必须为NULLif(execvp("ls",argv)==-1){cout<<"execvp error"<<endl;}cout<<"after execvp";return 0;
}

输出结果:

在这里插入图片描述

e类函数

带e的一类exac函数,包括execleexecvpe,可以传递一个指向环境字符串指针数组的指针。

进程

进程的创建

整个Linux系统全部的进程是一个树状结构

0号进程(系统进程):所有进程的祖先,创建了1号进程与2号进程

1号进程(systemd):负责执行内核的初始化工作与继续系统配置

2号进程(kthreadd):负责所有内核线程的调度与管理

我们可以使用pstree命令来查看进程树,命令为:

pstree -p 进程编号

示例:

在这里插入图片描述

进程标识

每一个进程的有一个非负整数标识的唯一的进程ID,虽然是唯一,但是我们可以复用进程ID,当一个进程终止以后,该进程ID自然就成为了复用的候选者,但Linux本身采用的是延迟服用算法,让新建进程的ID不同于最近计数进程的ID,防止被误以为进程尚未终止

pid_t getpid(void) //获取当前进程的ID
pid_t getppid(void) //获取父进程的ID

说明:

pid_t:非负整数

进程的创建

fork函数

一个现有的进程能够调用fork()函数去创建一个新的进程

pid_t fork(void);

fork()创建的进程叫做子进程,下面演示一个例子:

//demo1.cpp
#include <iostream>
#include<unistd.h>using namespace std;int main()
{fork();cout<<"hello world"<<endl;sleep(100);cout<<"over";return 0;
}

我们运行一下结果如下:

在这里插入图片描述

我们使用命令来查看一下它的进程

ps -ef |grep demo1

在这里插入图片描述

我们用上面的查看进程树命令来试一下:

pstree -p 214251

在这里插入图片描述

我们可以看到它创建了一个子进程

分割子进程与父进程

fork()会返回值,而子进程与父进程的返回值不同,示例代码如下:

#include <iostream>
#include <unistd.h>using namespace std;int main()
{fork();int pid=fork();cout<<pid<<endl;
}

输出结果:

在这里插入图片描述

我们可以发现:

子进程的返回值:0

父进程的返回值:父进程的进程ID

所以我们可以通过这个来选择父进程与子进程所执行的代码

示例代码:

#include <iostream>
#include <unistd.h>using namespace std;int main()
{int pid = fork();if(pid==0)  cout<<"现在执行的是子进程"<<endl;if(pid>0)  cout<<"现在执行的是父进程"<<endl;
}

输出结果:

在这里插入图片描述

子进程与父进程之间的关系

在子进程被创建之后,它与父进程之间并不是共享堆栈以及数据空间的而是子进程获得了父进程的数据空间以及堆栈的副本

fork()的两种用法

  1. 父进程复制自己,然后父进程与子进程分别执行不同的代码,多见于网络服务程序,父进程等待客户端的连接请求,当请求到达的时候,父进程调用fork(),让子进程处理请求,而父进程等待下一个连接请求
  2. 进程需要执行另一个程序,这种多见于shell中,让子进程去执行exec族函数

共享文件

fork()的一个特性是在父进程中打开的文件描述符都会被复制到子进程中,父进程与子进程共享一个文件偏移量(子进程所写的内容会在父进程所写内容的后面)

注意:如果父进程与子进程写同意描述符指向的文件,但是每一然后显示的同步,那么它们的输出可能相互混合

vfork()函数

vfork()函数的调用与fork函数相同,但两者的语义不同

vfork()函数用于创建一个新进程,而新进程的目的是exec一个新程序,由于我们要求子进程必须立即执行,所以它不复制父进程的地址空间

vfork()fork()的宁一个区别:vfork()保证子进程先执行,保证了子进程调用exec函数或exit()之后父进程才恢复执行

僵尸进程

前言

如果父进程比子进程先退出,子进程将被1号进程所托管(这是一种让进程在后台运行的方法),而如果子进程比父进程先退出,且父进程并没有处理子进程退出的信息的话,那么子进程将成为僵尸进程。

代码示例:

#include <iostream>
#include <unistd.h>using namespace std;int main()
{if(fork()==0) return 0;for(int i=0;i<1000;i++){cout<<"hello world"<<endl;sleep(100);}return 0;
}

在这里插入图片描述

在这里插入图片描述

我们可以看到哪怕子进程已经退出了,但是我们查找进程的时候,子进程依旧存在,这时候它就成为了一个僵尸进程。

僵尸进程的危害

Linux内核给每一个子进程都保留了一个数据结构,它包括了进程编号,终止状态,使用cpu时间等等。当父进程处理了子进程的退出之后内核会将这个数据结构释放掉,而父进程如果没有将子进程的退出处理掉,内核就不会释放这个数据结构,这样会导致子进程的基础编号一直被占用,而进程编号的数量是有限的,这样将影响系统去创建新的进程

如何避免僵尸进程

  • 子进程退出的时候,内核需要向父进程发出SIGCHLD信号,如果父进程用signal(SIGCHLD,SIG_INT)来表示对子进程的退出不做处理,内核将自动释放子进程的数据结构

  • 父进程通过wait/waitpid函数等待子进程结束,子进程退出前,父进程将被阻塞

     pid_t wait(int *stat_loc);pid_t waitpid(pid_t pid, int *stat_loc, int options);pid_t wait3(int *stat_loc, int options, struct rusage *rusage);pid_t wait4(pid_t pid, int *stat_loc, int options, struct rusage *rusage);
    

    返回值是子进程的编号

    变量的说明

    • pid_t pid:要等待的进程的进程ID。
    • int *stat_loc:用于保存进程退出状态的指针。如果不关心进程的退出状态,可以传递 NULL
    • int options:等待选项,可用于指定等待行为的一些附加选项。常见的选项包括 WNOHANG (非阻塞等待)和 WUNTRACED (等待暂停子进程状态)。
    • struct rusage *rusage:用于保存子进程资源使用情况的结构体指针。如果不关心子进程的资源使用情况,可以传递 NULL
    • stzt_loc是子进程终止的信息,如果是正常终止,宏WIFEEXITED(stat_loc)返回真,WEXITSTAUTS(stat_loc)可获取终止状态,如果是异常状态,宏WTERMSIG可获取终止进程的信号

    我们来用一段代码实验一下上述知识点:

    #include <iostream>
    #include<unistd.h>
    #include <sys/types.h>
    #include <sys/wait.h>using namespace std;int main()
    {//父进程if(fork()>0){int sts;pid_t pid=wait(&sts);cout<<"已经终止子进程的进程编号为:"<<pid<<endl;if(WIFEXITED(sts)){cout<<"子进程正常退出"<<"子进程的退出状态为"<<WEXITSTATUS(sts)<<endl;}else{cout<<"子进程异常退出"<<"子进程的退出状态为"<<WTERMSIG(sts)<<endl;}   }//子进程else{sleep(30);cout<<"byebye"<<endl;exit(1);}
    }
    

在这里插入图片描述

我们如果尝试使用kill指令去强行结束子进程:

在这里插入图片描述

在这里插入图片描述

  • 如果父进程很忙,我们可以考虑捕获SIGCHLD信号,在信号处理函数里面调用wait()/waitpid()

    代码示例:

    #include <iostream>
    #include <unistd.h>
    #include<sys/types.h>
    #include<sys/wait.h>using namespace std;void  func(int signal)
    {int sts;pid_t pid=wait(&sts);cout<<"子进程pid为"<<pid<<endl;if(WIFEXITED(sts)){cout<<"子进程正常退出\n"<<"子进程的退出状态为"<<WEXITSTATUS(sts)<<endl;}else{ cout<<"子进程异常退出\n"<<"子进程的退出状态为"<<WTERMSIG(sts)<<endl;}   
    }
    int main()
    {signal(SIGCHLD,func);if(fork()>0){while(true){sleep(1);cout<<"父进程忙碌中"<<endl;}}else{sleep(10);int *p=0;*p=10;exit(1);}
    }
    

    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/3030014.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

三、配置带HybridCLR的ARCore开发环境

预告 本专栏将介绍如何使用这个支持热更的AR开发插件&#xff0c;快速地开发AR应用。 专栏&#xff1a; Unity开发AR系列 插件简介 通过热更技术实现动态地加载AR场景&#xff0c;简化了AR开发流程&#xff0c;让用户可更多地关注Unity场景内容的制作。 “EnvInstaller…”支…

代码覆盖率:度量软件质量的关键指标

一、引言 代码覆盖率是衡量软件测试质量的一个重要指标&#xff0c;它描述了测试用例覆盖了多少代码。高的代码覆盖率可以帮助我们增加对软件质量的信心&#xff0c;并发现潜在的问题。在本文中&#xff0c;我们将深入探讨代码覆盖率&#xff0c;并通过实例来演示如何计算和提…

多目标跟踪入门介绍

多目标跟踪算法 我们也可以称之为 Multi-Target-Tracking &#xff08;MTT&#xff09;。 那么多目标跟踪是什么&#xff1f; 不难看出&#xff0c;跟踪算法同时会为每个目标分配一个特定的 id 。 由此得出了目标跟踪与目标检测的区别&#xff08;似乎都是用方框来框出目标捏…

番外篇 | 利用PyQt5+YOLOv5来搭建目标检测系统(附可视化界面+功能介绍+源代码)

前言:Hello大家好,我是小哥谈。PyQt5是一个Python绑定的Qt库,是用于创建图形用户界面(GUI)和其他应用程序组件的工具包。PyQt5提供了许多GUI元素,如按钮、文本框、标签等,也提供了许多Qt的功能,如网络、数据库、XML等。通过PyQt5可以在Python中使用Qt的丰富功能和强大的工…

jenkins使用gitLab(极狐)认证登陆

jenkins安装 GitLab Authentication插件 我因为java版本和最新GitLab Authentication 1.19版本不兼容&#xff0c;选择了本地安装 找个历史版本1.13版本&#xff0c;然后下载到电脑上 - 本地上传插件并安装 在极狐上创建一个应用 - 配置应用信息 应用名&#xff1a;jenkinsLo…

OC foudation框架(下)的学习

OCfoudation框架&#xff08;下&#xff09; 前面学习了有关OCfoudation框架的部分内容&#xff0c;我们现在对于后面的内容继续学习。 文章目录 OCfoudation框架&#xff08;下&#xff09;数组&#xff08;NSArray和NSMutableArray&#xff09;对集合元素整体调用方法排序使用…

弱监督语义分割-对CAM的生成过程进行改进1

一、仿射变换图像结合正则项优化CAM生成 论文&#xff1a;Self-supervised Equivariant Attention Mechanism for Weakly Supervised Semantic Segmentation &#xff08;CVPR,2020&#xff09; 1.SEAM方法 孪生网络架构&#xff08;Siamese Network Architecture&#xff09…

【网络编程】UDP协议和TCP协议1

UDP协议格式 UDP 报文分为 UDP 报头和 UDP 数据区两部分。报头由 4 个 16 位长&#xff08;2字节&#xff09;字段组成&#xff0c;分别说明该报文的源端口、目的端口、报文长度和校验值。 UDP协议如何将报头和有效载荷分离 UDP报头是一种定长报头&#xff0c;长度为8个字节。…

kaldi学习参考

HMM模型 https://www.cnblogs.com/baixf-xyz/p/16777438.htmlhttps://www.cnblogs.com/baixf-xyz/p/16777438.htmlGMM-HMM 基于GMM-HMM的语音识别系统https://www.cnblogs.com/baixf-xyz/p/16777439.html https://www.cnblogs.com/baixf-xyz/p/16777426.htmlhttps://www.cnbl…

全网最详细使用war包的方式结合Tomcat和向量数据库PostgreSQL本地部署Xwiki教学

部署 XWiki 在 CentOS 7 上的过程涉及多个步骤&#xff0c;主要包括安装环境依赖、配置数据库以及安装和配置XWiki。以下是一个详细的步骤说明&#xff1a; 1. 系统准备 首先&#xff0c;确保您的系统是最新的&#xff1a; sudo yum update2. 安装 Java 这里需要注意一下版本…

【静态分析】软件分析课程实验A4-类层次结构分析与过程间常量传播

官网&#xff1a;作业 4&#xff1a;类层次结构分析与过程间常量传播 | Tai-e 参考&#xff1a;https://www.cnblogs.com/gonghr/p/17984124 ----------------------------------------------------------------------- 1 作业导览 为 Java 实现一个类层次结构分析&#xf…

又被System.out.print给坑了一把

学过java的同学都应该知道&#xff0c;第一个程序很多人都是这样&#xff1a; public class Hello {public static void main(String[] args) { System.out.print("Hello,world&#xff01;");} } 打印结果是&#xff1a;Hello,world&#xff01; 接着可能会…

中国地面基本气象逐小时数据获取方式

环境气象数据服务平台提供了全国大约2100个点位&#xff0c;2023年1月1日至今的小时级数据。包括气温、气压、湿度、风、降水等要素。 数据基于ECMWF ERA5-Land Hourly陆面再分析资料和中国地面基本气象观测逐三小时数据&#xff0c;使用机器学习模型加工所得&#xff0c;对比…

记一次DNS故障导致用户无法充值的问题(下)

上一篇说到DNS故障导致无法充值&#xff0c;后来我们通过拨测发现业务域名的解析目标地址被解析到了【127.0.0.1】IP。 1、联系阿里云厂商&#xff0c;通过沟通&#xff0c;阿里云反馈我们的域名被XX省通管单位封禁了&#xff0c;导致解析到了不正确的地址。 2、为了解决用户问…

ADS基础教程11 - TouchStone文件的导出及导入

目录 一、 T o u c h S t o n e 介绍 \color{#4285f4}{ \mathbf{ 一、TouchStone介绍}} 一、TouchStone介绍 二、文件导出、导入方式 \color{#4285f4}{ \mathbf{ 二、文件导出、导入方式}} 二、文件导出、导入方式1.原理图操作1&#xff09;原理图中导出2.原理图中导入 3.DDW中…

云服务器性能大揭秘:4核与8核你知道多少?

​  云计算服务中&#xff0c;通常使用具有多个 CPU 的服务器集群&#xff0c;以便为企业和个人等提供可扩展的计算资源。这使他们能够处理大量数据处理和存储&#xff0c;而无需自己投资昂贵的硬件。像术语“4核”和“8核”&#xff0c;在云服务器领域中就比较常见&#xff…

中国地面气候资料日值数据获取方式

数据简介 环境气象数据服务平台提供了全国大约2100个点位&#xff0c;2000年至2023年的逐日数据。包括气温、气压、湿度、风、降水等要素。 数据基于ECMWF reanalysis-era5-land、reanalysis-era5-single-levels 以及中国2100站点地面气候资料日值观测数据&#xff0c;使用机器…

winform植物大战僵尸

winform植物大战僵尸 植物大战僵尸源码 半成品 需要的拿去学习 登陆注册选择关卡 向日葵 豌豆射手 双枪豌豆射手 项目获取&#xff1a; 项目获取&#xff1a;typora: typora/img (gitee.com) 备用项目获取链接1&#xff1a;yifeiyixiang/kamo: 源码下载 (github.com) 备用…

Capl复合数据类型:枚举

用于定义一组有穷的、命名的常量。常量有了命名更容易理解和记忆&#xff0c;枚举中常量的命名&#xff0c;必须在整个capl程序中唯一。 定义的枚举类型变量&#xff0c;里面的成员如果没有赋值&#xff0c;默认从左到右&#xff0c;依次为012345...。如果有赋值就该成员就代表…