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://www.hqwc.cn/news/698426.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C/C++ VScode: launch: program ...... dose not exist

VScode: launch: program … dose not exist 介绍 参考VS Code 配置 C/C 编程运行环境&#xff08;保姆级教程&#xff09;教程配置了VSCode。在配置launch.json适用多个.c 文件编译时&#xff0c;弹出下面错误。 原因和解决方法 是task.json 默认配置的问题。 默认的 cwd参…

Java数据类型:基本类型

Java是一种强类型语言&#xff0c;定义变量时&#xff0c;必须指定数据类型。 // 变量必须指定数据类型 private String username;初学者不免有个疑问&#xff1a;在实际编写代码的过程中&#xff0c;该如何选择数据类型呢&#xff1f; 回答这个问题之前&#xff0c;先来解决…

黑马点评项目总结及个人优化

怎么根据前端代码实现自己的后端业务,实现不同接口 查阅文档:如果有完善的接口文档,可以直接查阅文档来了解后端所有接口的业务逻辑和功能。 阅读后端代码:通过阅读后端代码,特别是控制器(Controller)层和服务(Service)层的代码,可以了解后端所有接口的具体实现逻辑。…

vscode主侧栏源代码管理(Source Control)不见了!!!

今天上班突然发现vscode中主侧栏中源代码管理&#xff08;Source Control&#xff09;不见了&#xff0c;项目又着急赶工&#xff0c;没时间找它&#xff0c;可真愁死我了。 以为这样代码没办法提交了&#xff1f;&#xff1f;&#xff1f; 嘿嘿&#xff0c; 还好我用命令&a…

不限经验,专业,也能月薪2-3W,这个神仙副业今年太火了!

哈喽&#xff0c;大家好&#xff0c;我是醒醒团队电商花花。 一个月前&#xff0c;有朋友找我咨询视频号的问题&#xff0c;关于营业执照的问题&#xff0c;问我做视频号小店用什么营业执照&#xff0c;我就把视频号小店的营业执照问题给讲一下。 因为个体店执照在视频号上不…

PCB笔记(二十六):PCB检查

前言 首先检查元器件是否100&#xff05; 放置 文章目录 1、打开DRC2、database check3、检查DRC4、检查多余的线5、其他需要注意的点a.检查差分线、等长线是否已调好b.注意检查晶振、电感等元件上/下方是否其他线经过&#xff08;一般不允许线经过&#xff09;c.打开place_bo…

【启明智显技术分享】SSD201/SSD202D核心板UI界面开发全攻略:LVGL使用指南

提示&#xff1a;作为Espressif&#xff08;乐鑫科技&#xff09;大中华区合作伙伴及sigmastar&#xff08;厦门星宸&#xff09;VAD合作伙伴&#xff0c;我们不仅用心整理了你在开发过程中可能会遇到的问题以及快速上手的简明教程供开发小伙伴参考。同时也用心整理了乐鑫及星宸…

浅谈电动汽车充电站的电气安全

1 引言 1月14日日上午10点左右&#xff0c;青岛市市北区辽宁路63号公交停车场内&#xff0c;一辆报废公交车突然起火&#xff0c;由于大风天气&#xff0c;大火很快引燃了停在旁边的几辆报废车。消防人员快速赶到&#xff0c;迅速控制住火势。11时30分&#xff0c;停车场内的…

AR项目的技术难点

AR项目的技术难点主要体现在以下几个方面&#xff0c;AR项目的技术难点体现在多个方面&#xff0c;需要从多个角度进行综合考虑。随着技术的进步和标准的完善&#xff0c;AR项目开发将会变得更加容易&#xff0c;AR技术也将得到更加广泛的应用。北京木奇移动技术有限公司&#…

电脑桌面便签软件推荐,电脑桌面怎么设置便签

在日常工作中&#xff0c;电脑已成为我们不可或缺的办公工具。面对繁杂的工作任务和信息&#xff0c;如何在电脑桌面上高效管理待办事项&#xff0c;成为了提升工作效率的关键。为了更好的管理内容&#xff0c;很多人会选择一款优秀的电脑桌面便签软件&#xff0c;这类软件能帮…

华为设备display查看命令

display version //查看版本信息 display current-configuration //查看配置详情 display this //查看当前视图有效配置 display ip routing-table //查看路由表 display ip routing-table 192.168.3.1 //查看去往3.1的路由 display ip interface brief //查看接口下ip信息 dis…

jupyter notebook中调整图片大小

截屏 ctrl V 这个目前只能保证是截屏大小&#xff0c;改变不了&#xff0c;要么之久 把图形缩小后再截图 感觉很模糊 png文件导入 markdown 代码1 <img src"./1.png" width250 height200>markdown 代码2 <img src"./1.png" width938 height…