【视频笔记】408新增知识点信号——里昂视频

文章目录

原视频来自于B站里昂,总长22min
视频链接

进程通信方法:管道,共享内存,消息队列,信号和信号量


2.信号

一个信号就是一条小消息,它通知进程系统中发生了一个某种类型的事件 。比如,如果当进程在前台运行时,你键入Ctrl+C(也就是同时按下Ctrl键和 C键),那么内核就会发送一个SIGINT信号给这个前台进程

在linux系统中使用 kill -l 命令查看系统上支持的不同类型的信号。

每种信号类型都对应于某种系统事件,用不同的整数表示,例如SIGINT信号用号码2表示

1~31号为非实时信号(不可靠信号)处于就绪队列多个相同的非实时信号只会被响应一次。其余的被丢掉。

34~64号信号为实时信号(可靠信号),处于就绪队列多个相同的实时信号全部会被响应。

比如,如果一个进程试图除以0 ,那么内核就发送给它一个SIGFPE信号(号码8 Floating Point Exception浮点异常)。
如果一个进程执行一条非法指令 ,那么内核 就发送给它一个SIGILL信号(号码4)。

sigill(ill生病,有病的,不健康的;不良的;不良的;〈美俚〉(因嫌疑而)被捕的,被拘留的;坏的,邪恶的,有害的;不舒服;)

shell 复制代码
kill -l
#查看系统上支持的不同类型的信号
3.信号的实现

在操作系统中,每个进程都有一个进程控制块(Process Control Block,PCB),它包含了进程的管理和控制信息.。

【例】Linux下,用一个名为task_struct的结构体类型来描述PCB,包括很多字段,如进程的状态进程的标识、进程的优先级等。每一种信息都用一个字段来实现。

cpp 复制代码
struct task struct
{
...
volatile long state; /进程状态
pid_t pid; //进程ID,每个进程都有一个唯一的PID,用于区分不同进程,相当于身份证号
unsigned long rt_priority;//进程优先级
stuct mm_struct*mm,*active_mm;//与内存管理有关的数据结构,包含进程内存使用的信息
...
}

因此,我们可以将信号记录在进程的task_struct(PCB)结构体 中。

例如,我们可以用位图来表示某个信号是否产生,每个比特位代表了某个特定的信号,比特位为0代表没有收到了信号,为1则代表收到了信号。进程收到信号,本质是位图被修改

4.信号的处理

通常有以下三种操作处理信号:

①忽略信号

不采取任何操作。但是有两种信号不能被忽略:SIGKILL(9)和SIGSTOP(19)

这样做的原因是系统管理员需要能够杀死或停止进程,如果进程能够选择忽略SIGKILL(使进程不能被杀死)或SIGSTOP(使进程不能被停止)将破坏这一权力。

②执行信号的默认操作

例如,进程收到SIGINT信号后会终止,本质上是向进程发送了一个编号为2的SIGINT信号,只不过这个信号是通过键盘输入的,然后经过操作系统处理后再发送给进程。

③捕获井处理信号

内核会暂停该进程正在执行的代码,并跳转到用户注册的函数。

SIGINT原本是用来结束进程的,但用signal自定义它的功能后就可以使它对进程的操作改变,

cpp 复制代码
#include<singal.h>
#include<stdio.h>
#include<unistd.h>
//handle的函数实现的功能是打印can't stop!"
void handle(int sigNum){
printf("can't stop! sigNum:%d\n",sigNum);
}
int main(){
	signal(SIGINT,handle);
//将SIGINT号信号的功能改成handle
	while(1){
		printf("a\n");
		sleep(1);
	}
return 0;

}

可以看到,每次按下Ctrl+C,都会打印对应内容(实现自定义的功能而不是像原来一样终止进程),而sigNum也证明Ctrl+C对应的信号值确实是2号(SIGINT)。

经常捕获的两种信号是 SIGINT 和 SIGTERM。

SIGKILL和SIGSTOP 不能被捕获,即无法通过自定义handle函数来修改其信号操作。

exp1

cpp 复制代码
#include<singal.h>
#include<stdio.h>
#include<unistd.h>
//handle的函数实现的功能是打印can't stop!"
void handle(int sigNum){
printf("can't stop! sigNum:%d\n",sigNum);
}
int main(){
	signal(SIGKILL,handle);
//将SIGINT号信号的功能改成handle
	while(1){
		printf("still alive\n");
		sleep(1);
	}
return 0;

}

exp2

cpp 复制代码
#include<singal.h>
#include<stdio.h>
#include<unistd.h>
int count=0;
//handle的函数实现的功能是打印"count=%d, still alive"
void handle(int sigNum){
printf("count=%d, still alive sigNum:%d\n",sigNum);
}
int main(){
	signal(SIGKILL,handle);
	signal(SIGTERM,handle);
	signal(SIGINT,handle);
//将SIGINT号信号的功能改成handle
	while(1){
		count++;
		sleep(1);
	}
return 0;

}

2:sigint

15:sigterm

9:sigkill


几个Linux支持的典型信号:

SIGCHLD

当进程终止或停止时,内核会给进程的父进程发送此信号。在默认的情况下SIGCHLD是被忽略 的,如果进程对它们的子进程是否存在感兴趣,那么进程必须显式地捕获并处理该信号。
SIGFPE

不考虑它的名字,该信号代表所有的算术异常 ,而不仅仅指浮点数运算相关的异常,异常包括溢出、下溢和除以0.

默认的操作是终止进程并形成内存转储文件,但进程可以捕获并处理该信号。
SIGILL

当进程试图执行一条非法机器指令 时,内核会发送该信号。默认操作是终止进程并进行内存转储进程可以选择捕获并处理SIGILL。
SIGINT

当用户输入中断符(通常是Ctrl-C)时,该信号被发送给所有前台进程组中的进程默认的操作是终止进程。进程可以选择捕获并处理该信号,通常是为了在终止前进行清理工作

5.信号的产生

信号通常由以下方式产生:

通过终端按键(键盘)产生信号 例如,Ctrl+C发送2号信号SIGINT、Ctrl+\发送3号信号SIGQUIT
程序异常时操作系统会向程序发送信号来终止进程。
调用函数
- kill系统调用:

kil()调用会从一个进程向另一个进程发送信号:

int kill(pid t pid,int signo);//调用kill给pid代表的进程发送信号signo。

kill命令本质上是通过系统调用实现的。

- raise系统调用

【例子】创建一个raise程序,将其2号信号的处理函数改为自定义函数,该信号每隔一段时间便会给自己发送信号

cpp 复制代码
#include<singal.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>

void handle(int signo){
printf("get a signal : %d\n", signo);
}
int main(){
	signal(2,handle);
//将2号信号自定义。
	while(1){
		printf("I'm a process, pid = %d\n",getpid());
		sleep(1);
		raise(2);
		//进程自己给自己发送2号信号。
	}
return 0;

}
abort 系统调用

abort 使当前进程接受到信号而异常终止

cpp 复制代码
#include<singal.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>

void handle(int signo){
printf("get a signal : %d\n", signo);
}
int main(){
	signal(6,handle);
//将SIGINT号信号的功能改成handle
	while(1){
		printf("I am a process, pid : %d\n",getpid());
		sleep(1);
		abort();
	}
return 0;

}
④由于软件条件产生信号

例如alarm()函数可以设置定时器,当定时器倒计时结束,就会向进程发送一个SIGALARM信号。

exp1
cpp 复制代码
#include<singal.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>

void handle_alarm(int signo){
printf("Alarm clock!\n");
}
int main(){
//设置信号处理函数
	signal(SIGALRM,handle_alarm);
//做其他事情或者简单地等待
	while(1){
		printf("running\n");
		alarm(2);
		sleep(2);
	}
return 0;

}
exp2

pause()函数的作用是使当前进程进入睡眠状态,直到接收到一个信号为止。

cpp 复制代码
int pause(void);
cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
int main( ) {
	pid_t child_pid = fork();
	//fork一个子进程。 子进程的pid不一样。
	if(child_pid < 0) {//创建新进程失败。
		perror("Fork failed");
		exit(1);
	}
	if (child pid == 0){ // 子进程
		while (1){
			pause(); // 等待信号
	}else { // 父进程
		while (1) {
			sleep(2);//每2秒发送一次信号
			kill(child_pid, SIGUSR1);// 向子进程发送信号
			printf("Sent SlGUSR1 to child process (PID: %d)\n", child_pid);
			}
	return 0;
}
⑤ 硬件异常产生信号

发生硬件异常时,它被硬件以某种方式检测到并通知内核,然后内核向当前进程发送适当的信号。

例如当前进程执行了除零的指令CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号,并将该信号发送给进程。

总之,使用信号的两个主要目的:

① 让进程知道已经发生了一个特定的事件。

② 强迫进程执行它自己代码中的信号处理程序,
注意,并不是系统中所有进程都可以向其他进程发送信号,只有核心和超级用户可以。

普通进程只可以向拥有相同uid(用户标识号)和gid(组标识号)或者在相同进程组中的进程发送信号。

当信号产生时,内核将进程taskstruct中的信号相应标志位设置为1,表明产生了该信号。

系统对置位之前该标志位已经为1的情况不进行处理,这说明进程只处理最近接收的信号。
信号产生后并不马上送给进程,它必须等待直到进程再一次运行时才交给它。

每当进程从系统调用中退出时,内核会检查它的signal和blocked字段(位图),查看是否有需要发送的非屏蔽信号,若有则立即发送信号。

如果信号的处理被设置为缺省,则系统内核将会处理该信号,否则会执行用户提供的信号处理程序。

【练习1】信号是用户按下Ctrl+C时默认发送给前台进程组的信号?

A. 'SIGKILL'

B.'SIGSTOP'

C.'SIGINT'

D.'SIGTERM'

答案:C

【练习2】哪个信号用于终止进程,并且不能被进程捕获或忽路?

A. 'SIGKILL`

B."SIGBUS"

C. 'SIGINT

D.'SIGTERM

答案:A

【练习3】在信号的进程通信机制中,以下哪个说法是正确的?

A.对于任意的信号都可以忽略

B.对于任意的信号都可以捕获

C.系统中所有进程都可以向其他进程发送信号

D.系统对置位之前该标志位已经为1的情况不进行处理,这说明进程只处理最近接收的信号

答案:D

相关推荐
琼火hu1 小时前
R语言笔记(五):Apply函数
开发语言·笔记·r语言·apply
光明中黑暗2 小时前
Python 学习笔记
笔记·python·学习
山里灵活的狗_3 小时前
蓝桥杯练习笔记(十九-质数筛)
笔记·职场和发展·蓝桥杯
YuCaiH4 小时前
【STM32】LED闪烁 & LED流水灯 & 蜂鸣器
笔记·stm32·单片机·嵌入式硬件
codists4 小时前
《使用Gin框架构建分布式应用》阅读笔记:p212-p233
笔记·golang·gin·编程人·codists·gin框架
Nepethens4 小时前
【OccNeRF: Advancing 3D Occupancy Prediction in LiDAR-Free Environments】阅读笔记
笔记·3d
小c君tt5 小时前
MFC文件管理-学习笔记
笔记·学习·mfc
白狐欧莱雅5 小时前
使用Python中的DrissonPage库爬取小说网站并保存章节内容(bqg)
经验分享·笔记·python·自动化·drissonpage
呵呵哒( ̄▽ ̄)"6 小时前
尚硅谷-react教程-求和案例-@redux-devtools/extension 开发者工具使用-笔记
javascript·笔记·react.js
Stardep6 小时前
算法2—八大常用排序算法(下)
c语言·数据结构·笔记·算法·排序算法·1024程序员节