[Linux]-----进程信号
创始人
2024-02-21 09:03:29
0

文章目录

  • 前言
  • 一、什么是信号
    • 我们是如何得知这些信号呢?
    • 我们知道对应的信号产生时,要做什么呢?
  • 二、进程信号
    • 前台进程和后台进程
    • 注意
  • 三、信号列表
    • 信号的捕捉
  • 四、信号产生前
    • 用户层产生信号的方式
    • signal函数
    • kill
    • raise
    • abort
    • 由软件条件产生信号
    • 硬件异常产生信号
    • 总结
    • core dump字段
  • 五、信号产生中
    • 阻塞信号
      • 1.信号其他相关常见概念
      • 2. 在内核中的表示
      • 3.sigset_t
      • 4.信号集操作函数
    • 信号的捕捉
  • 六、信号处理后
    • signaction
  • 总结


前言


正文开始!

一、什么是信号

生活中的信号有哪些呢?

红绿灯,下课铃声,信号枪,烽火台,旗语…

我们是如何得知这些信号呢?

别人教(能够认识这些场景下的信号以及所表示的含义)

我们知道对应的信号产生时,要做什么呢?

我早就知道了信号产生之后我们要做什么!即便当前信号还没有产生!—>我们提前已经知道了这个信号的处理方法!

即便这个信号还没产生,我们已经拥有处理信号的能力!

二、进程信号

信号是给进程发送的,进程要具备处理信号的能力!

  1. 该能力一定是预先已经拥有的。
  2. 进程能够识别对应的信号。
  3. 进程能够处理对应的信号。

进程处理信号的能力是程序员给予的!OS帮我们提供!
对于进程来讲,即便这个信号还没产生,我们进程已经具有识别和处理这个信号的能力!

前台进程和后台进程

在这里插入图片描述
前台进程运行的时候你输入其他的命令Bash是无法给你做出响应的!

./程序名 &

以后台进程去运行,你输入其他的命令是可以继续执行的!
但是打出来的东西很乱!

在这里插入图片描述
那么如何终止这个进程呢?

jobs

查看后台进程任务编号就是1
在这里插入图片描述
将该进程提至前台运行

fg(front ground) 编号

在这里插入图片描述
然后Ctrl+c终止进程即可!

让进城在放置后台运行

bg 编号

注意

  1. Ctrl+C 产生的信号只能发给前台进程。一个命令后面加个&可以放到后台运行,这样Shell不必等待进程结束就可以接受新的命令,启动新的进程。
  2. Shell可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到像 Ctrl-C 这种控制键产生的信号。
  3. 前台进程在运行过程中用户随时可能按下 Ctrl-C 而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到 SIGINT 信号而终止,所以信号相对于进程的控制流程来说是异步(Asynchronous)的。

三、信号列表

用kill -l命令可以察看系统定义的信号列表
在这里插入图片描述

每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到,例如其中有定 义 #define SIGINT 2

Linux下一共有62个信号!
Linux下的信号被分为了两批

  1. 1-31(普通信号)—>常用的信号
  2. 34-64(实时信号)—>常用于实时操作系统!

我们常用的计算机都是分时操作系统,所以就着重带大家了解前31个信号即可!

因为信号产生时是异步的,当信号产生的时候,对应的进程可能正在做更重要的事情,我们进程可以暂时不处理这个信号!

所以进程可能不需要立即处理这个信号!—>不代表这个信号不会处理!

但是必须记住这个信号已经来了(有信号来了吗?什么信号?)

信号的捕捉

  1. 默认动作
  2. 忽略信号
  3. 自定义动作

进程是如何记住这个信号的,在哪呢?

保存在进程的PCB中的!!

tash_struct{uint_32 sig;//位图结构,每一位代表一个信号,1表示发生
};
task_strcut是内核的数据结构!--->所以只有操作系统有权利修改task_struct内的数据位图!!
OS是进程的管理者,进程的所有属性的获取和设置,只能由OS来!-->无论信号怎么产生,最终一定是OS帮我们进行信号的设置的!!!

有没有产生?[bit位的内容]
什么信号产生?[bit位的位置]

举个栗子:0000 0010 就代表有信号产生,是二号信号!(默认从右往左数)

四、信号产生前

用户层产生信号的方式

  1. 键盘产生
    谁给进程发送的信号呢?—> 只能是操作系统—>更改PCB中的位图结构,该信号比特位由0置1

  2. 通过系统接口完成对进城发送信号的过程

  3. 由软件条件产生信号

  4. 硬件异常产生信号

signal函数

·

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include using namespace std;
void handler(int signo)
{cout << "我是一个进程,刚刚获取了一个信号: " << signo << endl;cout << cnt << endl;
}
int main()
{//这里不是调用hander的方法,这里只是设置了一个回调,让SIGINT(2)产生的时候,刚方法才会被调用//如果不产生SIGINT,该方法不会被调用!// ctrl + c :本质就是给前台进程产生了2号信号,发送给目标进程,目标进程默认对2号信号的处理是终止自己//我们更改了对二号信号的处理,设置了用户自定义处理方法signal(SIGINT, handler);sleep(3);cout << "进程已经设置完了!" << endl;while (true){cout << "我是一个正在运行中的进程: " << getpid() << endl;sleep(1);}return 0;
}

在这里插入图片描述

给上面代码设置3号信号的捕捉动作

signal(3, handler);

在这里插入图片描述

Ctrl+\是发送三号信号

注意:九号信号就算设置了默认处理函数,也照样可以杀掉该进程!

kill

在这里插入图片描述

自己实现kill命令

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include using namespace std;static void Usage(const std::string &proc)
{cerr << "Usage:\n\t" << proc << " signo pid" << endl;
}
//我想写一个kill命令
// mykill 9 1234
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(1);}if (kill(static_cast(atoi(argv[2])), atoi(argv[1])) == -1){cerr << "kill" << strerror(errno) << endl;}return 0;
}

在这里插入图片描述
在这里插入图片描述

raise

在这里插入图片描述

int main(int argc, char *argv[])
{signal(2, handler); //没有调用对应的handler方法,仅仅是注册while (1){sleep(1);raise(2);}return 0;
}

在这里插入图片描述

abort

在这里插入图片描述
发送的信号是SIGABRT信号

在这里插入图片描述
我们把SIGABRT设为默认处理会怎么样呢?

int main(int argc, char *argv[])
{signal(SIGABRT, handler); //没有调用对应的handler方法,仅仅是注册while (1){sleep(1);abort();}return 0;
}

在这里插入图片描述
所以六号信号是可以被捕捉的,但是还是要终止掉进程的!!!

由软件条件产生信号

在这里插入图片描述

调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。

int main(int argc, char *argv[])
{alarm(1);for(;;){//是谁在推动操作系统做一系列的动作呢?//硬件---时钟硬件--给OS发送时钟中断printf("hello : %d\n",cnt++);//统计一个我们的进程1S cnt++多少次}return 0;
}

在这里插入图片描述

统计一个我们的进程1s会把cnt++多少次!

硬件异常产生信号

int main(int argc, char *argv[])
{int a=1;a/=0;return 0;
}

在这里插入图片描述
除零:CPU内部有一个状态寄存器,
当我们除零的时候,CPU内的状态寄存器会被设置成为,有报错:浮点数越界
CPU的内部寄存器(硬件),OS就会识别到CPU内有报错啦–>1.谁干的?
2.是什么报错?
(OS–>构建信号)—>目标进程发送信号–>目标进程在合适的时候–>终止进程

int main(int argc, char *argv[])
{int* p=nullptr;*p=1;return 0;
}

在这里插入图片描述
越界&&野指针:我们在语言层面上使用的地址(指针),
其实都是虚拟地址—>物理地址—>物理内存–>读取对应的数据和代码的
如果虚拟地址有问题,地址转化的工作是由(MMU(硬件)+页表(软件)),转化过程就会引起问题,表现在硬件MMU上—>OS发现硬件出现了问题

进程崩溃的本质是该进程收到了异常的信号!

为什么呢?

因为硬件异常,而导致OS向目标进程发送信号,进而导致进程终止的现象!

崩溃了,一定会导致进程终止吗??—>不一定!!

int main(int argc, char *argv[])
{   signal(3,handler);for(int i=1;i<32;i++){signal(i,handler);}sleep(3);cout<<"进程已经设置完了!"<

在这里插入图片描述

总结

  • 上面所说的所有信号的产生,最终都要有OS来进行执行,为什么?—>OS是进程的管理者!
  • 信号的处理是否是被立即处理的?–>在合适的时候
  • 信号如果不是被立即处理,那么信号是否需要暂时被进程记录下来?记录在哪里最合适呢?
  • 一个进程在没有收到信号的时候,能否知道,自己应该对合法信号作何处理呢?
  • 如何理解OS向进程发送信号?能否描述一下完整的发送处理过程?

core dump字段

这是在我带大家了解进程控制时留的小尾巴,因为当时没有办法去讲!
[ Linux ] 进程控制(下)----进程等待与进程程序替换

在这里插入图片描述

int main(int argc, char *argv[])
{   pid_t id=fork();if(id==0){//子进程int* p=nullptr;*p=10;//野指针问题exit(1);}//父进程int status=0;waitpid(id,&status,0);printf("exit code: %d,signo: %d,core dump flag: %d\n",(status>>8)&0xff,status&0x7f,(status>>7)&0x1);return 0;
}

在这里插入图片描述

man 7 signal

在这里插入图片描述我们可以看到不同的信号有不同的Action,有的是Term直接终止了。。。。
那么Core是什么呢?

ulimit -a

在这里插入图片描述
线上生产中,core字段默认是0,也就是关闭的!
打开

ulimit -c 100000

在这里插入图片描述
打开core字段后,我们发现core dump标记位直接被设置为1了

并且还多了core.28389这个大文件

28389就是我们引起进程发生错误的id号!

在这里插入图片描述
core dump会把进程在运行中,对应得异常上下文数据,core dump到磁盘上,方便调试的!—>并且将status中的core dump标记位置为1!
在这里插入图片描述
然后我们进行gdb调试,运行该文件,我们就直接定位到出现异常的那一行了!!!

但是,core dump位一般是关闭的,因为他所占内存大小太大了!

五、信号产生中

阻塞信号

1.信号其他相关常见概念

  • 实际执行信号的处理动作称为信号递达(Delivery)。
  • 信号从产生到递达之间的状态,称为信号未决(Pending)。
  • 进程可以选择阻塞(block)某个信号。
  • 被阻塞的信号产生时将保持在未决状态,知道进程解除对此信号的阻塞,才执行递达的操作。
  • 注意,阻塞和忽略是不同的,只要信号被阻塞就不会被递达,而忽略实在递达之后可选的一种处理动作!

2. 在内核中的表示

信号在内核中的表示示意图

在这里插入图片描述

  • 每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到该信号递达才清除该标志。在上图的例子中,SIGHUP信号为阻塞也未产生过,当他递达时执行默认处理动作。
  • SIGINT信号产生过,但是正在被阻塞,所以暂时不能抵达。虽然他的处理动作是忽略,但在没有接触阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后在解除阻塞。
  • SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函sighandler。如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次。

3.sigset_t

从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞可以使用想的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的"有效"和"无效"的含义是该信号是否被阻塞,而在未决信号集中"有效"和"无效"
的含义是该信号是否处于未决状态。稍后我来带大家了解信号集的各种操作。阻塞信号集也叫作当前进程的信号屏蔽字(Signal Mask),这里的"屏蔽"应该理解为阻塞而不是忽略。

4.信号集操作函数

sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做任何解释,比如用printf直接打印sigset_t变量是没有意义的.

#include 
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
  • 函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有效信号。
  • 函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系统支持的所有信号。
  • 注意,在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信。

函数的使用

static void showPending(sigset_t *pendings)
{for (int sig = 1; sig < 32; sig++){if (sigismember(pendings, sig)){cout << '1';}elsecout << '0';}cout << endl;
}
int main(int argc, char *argv[])
{   sigset_t bsig,obsig;sigemptyset(&bsig);sigemptyset(&obsig);for(int sig=1;sig<=31;sig++){//2.给2号信号设置自定义选项signal(sig,handler);//3.屏蔽二号信号//3.1添加二号信号到信号屏蔽字中sigaddset(&bsig,sig);}//3.2设置用户级的信号屏蔽字到内核中,让当前进程屏蔽到二号信号sigprocmask(SIG_SETMASK,&bsig,&obsig);//1.不断的获取当前进程的pending信号集sigset_t pendings;int cnt=0;while(true){//1.1清空信号集sigemptyset(&pendings);//1.2获取当前进程(谁调用,获取谁)的pendings信号集if(sigpending(&pendings)==0){showPending(&pendings);}sleep(1);}return 0;
}

在这里插入图片描述
在这里插入图片描述
while(true)中加入部分代码

while(true){//1.1清空信号集sigemptyset(&pendings);//1.2获取当前进程(谁调用,获取谁)的pendings信号集if(sigpending(&pendings)==0){showPending(&pendings);}sleep(1);cnt++;if(cnt==10){cout<<"解除对所有信号的block "<

在这里插入图片描述

信号的捕捉

进程处理信号,不是理解处理的。—>在合适的时候!---->是什么时候呢?

当当前进程从内核态,切换回用户态的时候,进行信号的检测与处理!
在这里插入图片描述

六、信号处理后

在这里插入图片描述
在这里插入图片描述

进程的信号在被合适的时候处理----从内核态返回到用户态的时候—>检测----处理

  1. 如何理解内核态和用户态

  2. 进程的生命周期中,会有很多次机会去陷入内核(中断,陷阱,系统调用,异常…),一定会存在很多次的机会进行内核态返回用户态!

对信号处理使用自定义方法,我们执行的是用户代码!!!只能用用户态的身份去执行的!—>因为这部分代码是用户写的!!—>如果写的是一段恶意的代码呢!!!

signaction

在这里插入图片描述


#include 
#include 
#include 
using namespace std;void handler(int signo)
{cout << "获取到一个信号,信号的编号是: " << signo << endl;
}
int main()
{struct sigaction act, oact;act.sa_handler = handler;//自定义方法// act.sa_handler=SIG_IGN;//忽略方法// act.sa_handler=SIG_DFL;//默认处理方法act.sa_flags = 0;sigemptyset(&act.sa_mask);sigaction(2, &act, &oact);while (true){cout << "main running" << endl;sleep(1);}return 0;
}

当某个信号的处理函数被调用的时候,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么他会被阻塞到当前处理结束为止。如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。sa_flags字段包含一些选项,本章的代码都把sa_flags设为0,sa_sigaction是实时信号的处理函数,此处我们不做研究!

void handler(int signo)
{cout << "获取到一个信号,信号的编号是: " << signo << endl;sigset_t pending;//增加handler信号的处理时间,永远都会在处理二号信号while (true){sigpending(&pending);for (int i = 1; i <= 31; i++){if (sigismember(&pending, i))cout << "1";elsecout << "0";}cout << endl;sleep(1);}
}
int main()
{struct sigaction act, oact;act.sa_handler = handler;//自定义方法// act.sa_handler=SIG_IGN;//忽略方法// act.sa_handler=SIG_DFL;//默认处理方法act.sa_flags = 0;sigemptyset(&act.sa_mask);sigaction(2, &act, &oact);while (true){cout << "main running" << endl;sleep(1);}return 0;
}

在这里插入图片描述


总结

(本章完!)

相关内容

热门资讯

志愿者活动心得体会 志愿者活动心得体会500字(通用6篇)  当我们经过反思,对生活有了新的看法时,马上将其记录下来,这...
教育名著读书心得 教育名著读书心得(通用29篇)  当我们积累了新的体会时,就十分有必须要写一篇心得体会,这样能够给人...
洛阳实习报告 洛阳实习报告  一段充实而忙碌的实习生活结束了,相信你积累了不少实习心得,让我们一起来学习写实习报告...
《小英雄雨来》读书笔记 《小英雄雨来》读书笔记45篇  读完一本书以后,大家心中一定是萌生了不少心得,何不写一篇读书笔记记录...
团支部个人心得体会 团支部个人心得体会范文(精选5篇)  当我们积累了新的体会时,不如来好好地做个总结,写一篇心得体会,...
劳动节活动心得体会 劳动节活动心得体会(通用5篇)  当在某些事情上我们有很深的体会时,可用写心得体会的方式将其记录下来...
护理学专业的心得体会 护理学专业的心得体会(通用13篇)  在平日里,心中难免会有一些新的想法,马上将其记录下来,这样有利...
工作心得体会感悟 工作心得体会感悟(通用18篇)  从某件事情上得到收获以后,往往会写一篇心得体会,如此就可以提升我们...
采购课程培训心得体会 采购课程培训心得体会范文(通用13篇)  我们心里有一些收获后,可以通过写心得体会的方式将其记录下来...
疫情期间做社区志愿服务心得 疫情期间做社区志愿服务心得  有了一些收获以后,写一篇心得体会,记录下来,这样可以帮助我们分析出现问...
被隔离人员心得体会 被隔离人员心得体会  我们有一些启发后,将其记录在心得体会里,让自己铭记于心,这么做可以让我们不断思...
从优秀到卓越读书心得 从优秀到卓越读书心得(通用18篇)  当阅读了一本名著后,大家心中一定有不少感悟,是时候静下心来好好...
小学生读书心得体会 小学生读书心得体会范文(精选10篇)  当我们受到启发,对生活有了新的感悟时,不妨将其写成一篇心得体...
教师学习心得体会 实用的教师学习心得体会(通用15篇)  我们得到了一些心得体会以后,往往会写一篇心得体会,它可以帮助...
档案管理的心得体会 档案管理的心得体会(精选11篇)  我们得到了一些心得体会以后,可以记录在心得体会中,这样我们就可以...
超市社会实践心得体会 超市社会实践心得体会范文(精选14篇)  我们在一些事情上受到启发后,可以寻思将其写进心得体会中,这...
保密工作心得 保密工作心得保密工作心得通过学习中央领导关于保密工作重要指示精神,了解到了保密工作在我们工作中的重要...
作风建设心得体会 作风建设心得体会(精选16篇)  当我们经过反思,有了新的启发时,可以记录在心得体会中,这样可以不断...
参加辅导员培训班心得 参加辅导员培训班心得范文  参加了咱们学校本学期举办的辅导员培训班后,我有所体会,获益匪浅。  很高...
解读《中学教师专业标准》心得 解读《中学教师专业标准》心得  《中学教师专业标准》的基本理念是:  (一)学生为本:尊重中学学生权...