Linux进程信号(二)

本篇重点:

  1. 掌握信号产⽣的⼀般⽅式。
  2. 掌握信号捕捉的⼀般⽅式。
  3. 了解中断过程,理解中断的意义
  4. 掌握操作系统运⾏,系统调⽤原理,理解缺⻚异常或其他软件异常的基本原理
  5. 重新了解可重⼊函数的概念。
  6. 了解竞态条件的情景和处理⽅式。
  7. 了解SIGCHLD信号, 重新编写信号处理函数的⼀般处理机制

信号的产生

接着上篇我们学到了信号的产生通过终端按键产生信号,调用系统命令向进程发送信号,使用函数产生信号,软件条件产生信号,硬件异常产生信号。今天我们说一下后三个,前几个可查上一篇博文。

函数产生信号

kill

kill 命令是调⽤ kill 函数实现的。 kill 函数可以给⼀个指定的进程发送指定的信号。

NAME
     kill - send signal to a process
     
SYNOPSIS
     #include <sys/types.h>
     #include <signal.h>
     
     int kill(pid_t pid, int sig);

RETURN VALUE

     On success (at least one signal was sent), zero is returned. On error,
     -1 is returned, and errno is set appropriately.

我们可以尝试来实现自己的kill函数。

mykill代码样例:

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
// mykill -signumber pid
int main(int argc, char *argv[])
{
if(argc != 3)
{
std::cerr << "Usage: " << argv[0] << " -signumber pid" << std::endl;
return 1;
}
int number = std::stoi(argv[1]+1); // 去掉-
pid_t pid = std::stoi(argv[2]);
int n = kill(pid, number);
return n;
}

raise

raise 函数可以给当前进程发送指定的信号(⾃⼰给⾃⼰发信号)。

NAME
     raise - send a signal to the caller
SYNOPSIS
     #include <signal.h>
     
     int raise(int sig);  
RETURN VALUE

     raise() returns 0 on success, and nonzero for failure.

代码测试样例:

#include <iostream>
#include <unistd.h>
#include <signal.h>
void handler(int signumber)
{
// 整个代码就只有这⼀处打印
std::cout << "获取了⼀个信号: " << signumber << std::endl;
}
// mykill -signumber pid
int main()
{
signal(2, handler); // 先对2号信号进⾏捕捉
// 每隔1S,⾃⼰给⾃⼰发送2号信号
while(true)
{
sleep(1);
raise(2);
}
}

在这里插入图片描述
abort

abort 函数使当前进程接收到信号⽽异常终⽌。

NAME
     abort - cause abnormal process termination
     
SYNOPSIS
     #include <stdlib.h>
     void abort(void);
     
RETURN VALUE

     The abort() function never returns.
// 就像exit函数⼀样,abort函数总是会成功的,所以没有返回值。

代码示例

#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
void handler(int signumber)
{
// 整个代码就只有这⼀处打印
std::cout << "获取了⼀个信号: " << signumber << std::endl;
}
// mykill -signumber pid
int main()
{
signal(SIGABRT, handler);
while(true)
{
sleep(1);
abort();
}
}

在这里插入图片描述

软件中断产生信号

SIGPIPE 就是⼀种由软件条件产⽣的信号,主要来介绍 alarm 函数和 SIGALRM 信号。

NAME
    alarm - set an alarm clock for delivery of a signal
    
SYNOPSIS
    #include <unistd.h>
    unsigned int alarm(unsigned int seconds);
    
RETURN VALUE
    alarm() returns the number of seconds remaining until any previously
scheduled alarm was due to be delivered, or zero if there was no previ‐
ously scheduled alarm.

• 调⽤ alarm 函数可以设定⼀个闹钟,也就是告诉内核在 seconds 秒之后给当前进程发 SIGALRM 信号,该信号的默认处理动作是终⽌当前进程。
• 这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。打个⽐⽅,某⼈要⼩睡⼀觉,设定闹 钟为30分钟之后响,20分钟后被⼈吵醒了,还想多睡⼀会⼉,于是重新设定闹钟为15分钟之后响,“以 前设定的闹钟时间还余下的时间”就是10分钟。如果seconds值为0,表⽰取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数。

基本alarm验证-体会IO效率问题

程序的作⽤是1秒钟之内不停地数数,1秒钟到了就被SIGALRM信号终⽌。必要的时候,对SIGALRM信号进⾏捕捉

// IO 多
#include <iostream>
#include <unistd.h>
#include <signal.h>
int main()
{
int count = 0;
alarm(1);
while(true)
{
std::cout << "count : "
<< count << std::endl;
count++;
}
return 0;
}


count : 107148
count : 107149
Alarm clock

//IO  少
#include <iostream>
#include <unistd.h>
#include <signal.h>
int count = 0;
void handler(int signumber)
{
std::cout << "count : " <<
count << std::endl;
exit(0);
}
int main()
{
signal(SIGALRM, handler);
alarm(1);
while (true)
{
count++;
}
return 0;
}

count : 492333713

📌 结论:
• 闹钟会响⼀次,默认终⽌进程
• 有IO效率低

设置重复的闹钟

代码示例:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <vector>
#include <functional>
using func_t = std::function<void()>;
int gcount = 0;
std::vector<func_t> gfuncs;
// 把信号 更换 成为 硬件中断
void hanlder(int signo)
{
for(auto &f : gfuncs)
{
f();
}
std::cout << "gcount : " << gcount << std::endl;
int n = alarm(1); // 重设闹钟,会返回上⼀次闹钟的剩余时间
std::cout << "剩余时间 : " << n << std::endl;
}
int main()
{
gfuncs.push_back([](){ std::cout << "我是⼀个内核刷新操作" << std::endl; });
gfuncs.push_back([](){ std::cout << "我是⼀个检测进程时间⽚的操作,如果时间⽚到了,我会切换进程" << std::endl; });
gfuncs.push_back([](){ std::cout << "我是⼀个内存管理操作,定期清理操作系统内部的内存碎⽚" << std::endl; });
alarm(1); // ⼀次性的闹钟,超时alarm会⾃动被取消
signal(SIGALRM, hanlder);
while (true)
{
pause();
std::cout << "我醒来了..." << std::endl;
gcount++;
}
}

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

• 闹钟设置⼀次,起效⼀次
• 重复设置的⽅法
• 如果时间允许,可以测试⼀下 alarm(0)

如何理解软件条件

在操作系统中,信号的软件条件指的是由软件内部状态或特定软件操作触发的信号产⽣机制。这些条 件包括但不限于定时器超时(如alarm函数设定的时间到达)、软件异常(如向已关闭的管道写数据 产⽣的SIGPIPE信号)等。当这些软件条件满⾜时,操作系统会向相关进程发送相应的信号,以通知 进程进⾏相应的处理。简⽽⾔之,软件条件是因操作系统内部或外部软件操作⽽触发的信号产⽣。

如何简单快速理解系统闹钟

系统闹钟,其实本质是OS必须⾃⾝具有定时功能,并能让⽤⼾设置这种定时功能,才可能实现闹钟这样的技术。现代Linux是提供了定时功能的,定时器也要被管理:先描述,在组织。
内核中的定时器数据结构是:

struct timer_list {
	struct list_head entry;
	unsigned long expires;
	void (*function)(unsigned long);
	unsigned long data;
	struct tvec_t_base_s *base;
};

我们不在这部分进⾏深究,为了理解它,我们可以看到:定时器超时时间expires和处理⽅法function。操作系统管理定时器,采⽤的是时间轮的做法,但是我们为了简单理解,可以把它在组织成为"堆结构"。

硬件异常产⽣信号

硬件异常被硬件以某种⽅式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执⾏了除以0的指令, CPU的运算单元会产⽣异常, 内核将这个异常解释为SIGFPE信号发送给进程。再⽐如当前进程访问了⾮法内存地址, MMU会产⽣异常,内核将这个异常解SIGSEGV信号发送给进程。

模拟除0

#include <stdio.h>
#include <signal.h>
void handler(int sig)
{
printf("catch a sig : %d\n", sig);
}
// v1
int main()
{
//signal(SIGFPE, handler); // 8) SIGFPE
sleep(1);
int a = 10;
a/=0;
while(1);
return 0;
}

在这里插入图片描述

模拟野指针

#include <stdio.h>
#include <signal.h>
void handler(int sig)
{
printf("catch a sig : %d\n", sig);
}
int main()
{
//signal(SIGSEGV, handler);
sleep(1);
int *p = NULL;
*p = 100;
while(1);
return 0;
}

在这里插入图片描述

由此可以确认,我们在C/C++当中除零,内存越界等异常,在系统层⾯上,是被当成信号处理的。

📌 注意:

通过上⾯的实验,我们可能发现:发现⼀直有8号信号产⽣被我们捕获,这是为什么呢?上⾯我们只提到CPU运算异常后,如何处理后续的流程,实际上OS会检查应⽤程序的异常情况,其实在CPU中有⼀些控制和状态寄存器,主要⽤于控制处理器的操作,通常由操作系统代码使⽤。状态寄存器可以简单理解为⼀个位图,对应着⼀些状态标记位、溢出标记位。OS 会检测是否存在异常状态,有异常存
在就会调⽤对应的异常处理⽅法。除零异常后,我们并没有清理内存,关闭进程打开的⽂件,切换进程等操作,所以CPU中还保留上下⽂数据以及寄存器内容,除零异常会⼀直存在,就有了我们看到的⼀直发出异常信号的现象。访问⾮法内存其实也是如此,⼤家可以⾃⾏实验。

⼦进程退出core dump

在这里插入图片描述

#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
int main()
{
if (fork() == 0)
{
sleep(1);
int a = 10;
a /= 0;
exit(0);
}
int status = 0;
waitpid(-1, &status, 0);
printf("exit signal: %d, core dump: %d\n", status&0x7F, (status>>7)&1);
return 0;
}

在这里插入图片描述

在这里插入图片描述

思考总结

• 上⾯所说的所有信号产⽣,最终都要有OS来进⾏执⾏,为什么?OS是进程的管理者
• 信号的处理是否是⽴即处理的?在合适的时候
•信号如果不是被⽴即处理,那么信号是否需要暂时被进程记录下来?记录在哪⾥最合适呢
• ⼀个进程在没有收到信号的时候,能否能知道,⾃⼰应该对合法信号作何处理呢?
• 如何理解OS向进程发送信号?能否描述⼀下完整的发送处理过程?

今天的内容分享就到此结束,后续将持续更新,欢迎各位大佬在评论区讨论!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值