
温馨提示:信号和信号量 二者之间没有任何关系
🚀 信号是 Linux 系统提供的一种向指定进程发送特定事件的方式,进程会对信号进行识别和处理。
信号的产生是异步的
使用 kill -l 指令查看信号()

🍉 具体的信号采取的动作和详细信息可查看:man 7 signal

分析:
基本特点:
( sigaction 函数后面博客来详细介绍),现在先说可选的以下三种处理动作
在看相关内容之前,先插播一个小知识 signal
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);参数:
返回值:返回值为一个函数指针,指向之前的信号处理器;如果之前没有信号处理器,则返回 SIG_ERR
如果signal函数的 func 参数为 SIG_DFL,则系统将使用默认的信号处理动作。
#include<iostream>
#include<signal.h>
#include<unistd.h>
int main()
{
::signal(2, SIG_DFL); // defalut 默认
while(true)
{
std::cout << "I am a process, I am waiting signal!" << std::endl;
sleep(1);
}
}
如果signal函数的 func 参数为 SIG_IGN,则系统将忽略该信号

注意看源码:
#define SIG_DFL ((__sighandler_t) 0) /* Default action. */
#define SIG_IGN ((__sighandler_t) 1) /* Ignore signal. */
/* Type of a signal handler. */
typedef void (*__sighandler_t) (int);
// 其实SIG_DFL和SIG_IGN就是把0,1强转为函数指针类型信号自定义处理,其实是对信号进行捕捉,然后让信号执行自定义的方法
#include<iostream>
#include<signal.h>
#include<unistd.h>
void hander(int signo)
{
std::cout << "get a new signal: " << signo << std::endl;
}
int main()
{
::signal(2, hander);
while(true)
{
std::cout << "I am waiting signal!, pid: " << getpid() << std::endl;
sleep(1);
}
}运行如下:

这里 signal(2, handler)
注意: ^\Quit 表示 kill -3,相当于从键盘输入了 Ctrl + \
同时我们也可以对多个信号进行捕捉

信号的保存和发送理解:
补充个知识:(前后台进程)--> 理解信号异步
理解: OS如何得知键盘有数据?

初步理解 【信号起源】 • 信号其实是从纯软件⻆度,模拟硬件中断的行为 • 只不过硬件中断是发给CPU,而信号是发给进程? • 两者有相似性,但是层级不同,这点我们后⾯的感觉会更加明显
#include <iostream>
#include <unistd.h>
#include <signal.h>
int main()
{
while(true){
sleep(1);
}
}
$ g++ sig.cc -o sig // step 1
$ ./sig & // step 2
$ ps ajx |head -1 && ps ajx | grep sig // step 3
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
211805 213784 213784 211805 pts/0 213792 S 1002 0:00 ./sig$ kill -SIGSEGV 213784
$ // 多按⼀次回⻋
[1]+ Segmentation fault ./sigNAME
kill - send signal to a process
SYNOPSIS
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
参数分析:
pid:指定进程pid,如果 pid 是负数,信号将被发送到与 pid 的绝对值相同的进程组中的所有进程
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 命令
#include<iostream>
#include<signal.h>
#include<unistd.h>
// 形成 自己的 kill 命令
void Usage(std::string proc){
std::cout << "Usage: " << proc << " signumber processid " << std::endl;
}
int main(int argc, char *argv[]){
if(argc != 3){
Usage(argv[0]);
exit(1);
}
int signumber = std::stoi(argv[1]);
pid_t id = std::stoi(argv[2]);
int n = ::kill(id, signumber);
if(n < 0){
perror("kill");
exit(2);
}
exit(0);
}运行结果如下:

#include <signal.h>
int raise(int sig);样例:
#include<iostream>
#include<signal.h>
#include<unistd.h>
void hander(int sig){
std::cout << "get a sig: " << sig << std::endl;
}
int main() {
int cnt = 3;
::signal(2, hander);
while(true){
raise(2);
cnt--;
if(cnt<=0) raise(9);
sleep(1);
}
}过 3 s 后进程被杀死

#include <stdlib.h>
void abort(void);#include<iostream>
#include<signal.h>
#include<unistd.h>
int main(){
int cnt = 3;
while(true){
std::cout << "IsLand 1314" << std::endl;
cnt--;
if(cnt<=0) abort();
sleep(1);
}
}
注意事项:
🔥 使用管道通信时,当读端关闭,但是写端一直写,操作系统就会给写端进程发送13号信号SIGPIPE,终止进程。SIGPIPE 就是一种由软件条件产生的信号,SIGPIPE 是⼀种由软件条件产生的信号。
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
结论:
#include<iostream>
#include<signal.h>
#include<unistd.h>
#include <functional>
#include <vector>
#include <string>
#include <signal.h>
#include <sys/wait.h>
// 定时器功能
using func_t = std::function<void()>;
int gcount = 0;
std::vector<func_t> gfuncs;
// 1. handler 是一个信号处理函数,当接收到 SIGALRM 信号时被调用
// 2. 在处理函数内部,通过遍历 gfuncs 中的所有函数并调用它们,执行所有注册的任务
// 3. 然后输出 gcount 的值,并重新设置一个定时器(alarm(1)),使得下一个 SIGALRM 信号在 1 秒后再次发送
// 如果我们把下面操作,信号 更换成 硬件中断,那么就是 OS 的操作原理
void handler(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, handler);
while(true)
{
pause();
std::cout << "我醒来了..." <<std::endl;
gcount++;
}
}运行结果如下:

结论:

🔥 在操作系统中,信号的软件条件指的是由 软件内部状态 或 特定软件操作触发 的信号产生机制。
🐅 系统闹钟,其实本质是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
硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号
关于进程中的计算问题,一般都是交由 cpu 来完成的,在计算的过程中,难免会出现错误的计算,比如说除0,那么 cpu 又是如何知道的呢? 如下:

🐇 这就要提到 cpu 中的寄存器了,cpu 中是有很多的寄存器的,其中有一个寄存器:EFLAGS 寄存器(状态寄存器)
我们要知道 cup 内部是只有一套寄存器的,寄存器中的数据是属于每一个进程的,是需要对进程上下文进行保存和恢复的。
上下文切换是一个相对耗时的过程,包括保存和恢复寄存器、堆栈等信息
代码演示如下:

这个问题就与页表,MMU及CR2,CR3寄存器有关联了
🐍 MMU 和 页表 是操作系统实现虚拟内存管理和内存保护的关键机制,它们通过虚拟地址到物理地址的转换来确保程序的正确运行和内存安全。
🐍 CR2 寄存器用于存储引起页错误的线性地址(即虚拟地址)。
先来看看 Core 的意思

信号示例:

eg:关于core dump的演示


注意:如果 ulimit -c 10240 失败,如下:
已经ulimit打开了core,但是运行的时候没有生成core文件,图片是我的运行情况,然后右边是我把core关了,但是用代码去记录core的时候,打印的确还是1(正确来说,应该是0才对)

echo "./core.%e.%p" | sudo tee /proc/sys/kernel/core_pattern
./core.%e.%p 后面的 %e %p 代表对应后缀 执行这一句,普通用户的话,就在前面加sudo

然后这个我们就要切换成主用户 root 来【su -root】操作
那么关于进程信号的处理与产生我们就讲到这里啦,后面将会更新关于信号保存和处理的知识,敬请期待吧