吉安网站建设jxthw,学会python做网站,做排版的网站,网站安全风险评估报告文章目录 一、信号入门1.1 生活中的信号1.2 进程角度的信号1.3 信号的概念1.4 信号的三种常见处理方式 二、信号的产生2.1 通过终端按键产生信号问题1#xff1a;OS怎么知道键盘输入了ControlC #xff1f;问题2#xff1a;按CtrlC终止进程和按Ctrl\终止进程#xff0c;有什… 文章目录 一、信号入门1.1 生活中的信号1.2 进程角度的信号1.3 信号的概念1.4 信号的三种常见处理方式 二、信号的产生2.1 通过终端按键产生信号问题1OS怎么知道键盘输入了ControlC 问题2按CtrlC终止进程和按Ctrl\终止进程有什么区别问题3核心转储 (core dump)1. 为什么核心转储在云服务器上是默认关闭的2. ulimit -c size 命令修改core文件的大小3. 核心转储有什么用4. 使用core文件进行gdb调试5. 退出状态的core dump比特位 2.2 命令级kill2.2 用系统调用向进程发信号️kill()函数️raise()函数️abort()函数 2.3 由软件产生信号2.4 由硬件产生信号 进程信号重点 1.掌握Linux信号的基本概念 2.掌握信号产生的一般方式 3.理解信号递达和阻塞的概念原理。 4.掌握信号捕捉的一般方式。 5.重新了解可重入函数的概念。 6.了解竞态条件的情景和处理方式 7.了解SIGCHLD信号 重新编写信号处理函数的一般处理机制 学习的顺序产生 - 保存 - 处理 一、信号入门
1.1 生活中的信号
你在网上买了很多件商品再等待不同商品快递的到来。但即便快递没有到来你也知道快递来临时你该怎么处理快递。也就是你能“识别快递”当快递员到了你楼下你也收到快递到来的通知但是你正在打游戏需5min之后才能去取快递。那么在在这5min之内你并没有下去去取快递但是你是知道有快递到来了。也就是取快递的行为并不是一定要立即执行可以理解成“在合适的时候去取”。在收到通知再到你拿到快递期间是有一个时间窗口的在这段时间你并没有拿到快递但是你知道有一个快递已经来了。本质上是你“记住了有一个快递要去取”当你时间合适顺利拿到快递之后就要开始处理快递了。而处理快递一般方式有三种 执行默认动作幸福的打开快递使用商品执行自定义动作快递是零食你要送给你你的女朋友忽略快递快递拿上来之后扔掉床头继续开一把游戏 快递到来的整个过程对你来讲是异步的你不能准确断定快递员什么时候给你打电话 1.2 进程角度的信号
进程就是你操作系统就是快递员信号就是快递。通过快递的例子可以给出对进程信号的特点
信号没有产生的时候其实进程已经能够知道怎么处理这个信号了信号的到来进程并不清楚具体什么时候信号到来相对于进程正在做的工作是异步产生的信号产生了进程不一定要立即处理它而是进程在合适的时候处理进程要有一种能力将已经到来的信号进行暂时保存 1.3 信号的概念 信号的概念信号是进程之间事件异步通知的一种方式属于软中断 信号来源 信号可以由操作系统、其他进程或者进程自身生成。例如操作系统可以通过内核向进程发送信号来通知某个事件的发生如进程终止请求。 信号处理 进程可以通过注册信号处理函数来定义在接收到特定信号时应该执行的操作。这样当进程收到信号时操作系统会调用相应的信号处理函数来处理该信号。 信号传递 信号可以通过操作系统向目标进程发送。例如可以使用kill命令向指定的进程发送信号。信号传递涉及到进程间通信因此通常需要经过操作系统来完成。
即使一个进程向另一个进程发送信号也需要经过操作系统。这是因为操作系统负责维护进程之间的隔离和通信机制确保正确且安全地进行信号传递。在Linux中可以使用系统调用如kill或库函数如kill()函数由一个进程来向其他进程发送信号但实际的信号传递过程会由操作系统处理。
异常的原因是因为进程收到了操作系统发的信号但是进程收到信号并不意味着法生了异常。
总的来说无论信号是由操作系统、其他进程还是进程自身生成都需要经过操作系统来传递。 1.4 信号的三种常见处理方式
sigaction函数稍后详细介绍可选的处理动作有以下三种
忽略此信号执行该信号的默认处理动作提供一个信号处理函数要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉(Catch)一个信号 二、信号的产生
产生信号的方式可以有很多譬如由操作系统、其他进程或者进程自身产生但是向目标进程发送信号只能由操作系统发送因为操作系统是进程的管理者发送信号的本质是修改PCB中的信号位图。
2.1 通过终端按键产生信号
面对下面的死循环程序
#include stdio.h
#include unistd.hint main()
{while (1){printf(hello signal!\n);sleep(1);}return 0;
}我们可以
按 CtrlC 终止该进程止有前台进程才能拿到这个信号bash支持多个后台进程和一个前台进程按 Ctrl \ 也可以终止该进程按 CtrlZ 可以暂停该进程前台进程不能被暂停如果此进程是前台进程按下该按键后该前台进程立即变成后台进程。
问题1OS怎么知道键盘输入了ControlC 以下是大致的处理流程 CtrlC键盘组合被按下 当用户在终端中按下CtrlC时终端设备会生成一个中断信号。 中断信号处理 中断信号通常由操作系统的中断处理机制处理。在这个特定的情况下CtrlC产生的中断信号是SIGINT中断信号。 终端驱动程序 终端驱动程序是一个运行在内核中的软件组件负责管理终端设备。当中断信号发生时终端驱动程序负责通知前台进程与该终端相关联的当前运行的进程。 前台进程的中断处理 操作系统会检查当前运行在终端的前台进程是否注册了对SIGINT信号的处理函数。如果注册了操作系统会执行相应的信号处理函数。 默认中断处理 如果前台进程没有注册对SIGINT的处理函数操作系统将采用默认的中断处理方式即终止该进程。 中断向量表 中断向量表是一个由操作系统维护的数据结构其中包含中断号和相应中断服务程序的映射关系。当中断发生时操作系统会根据中断号查找中断向量表以确定应该执行哪个中断服务程序。 总体而言CtrlC的作用是生成一个中断信号然后由终端驱动程序通知前台进程。前台进程对中断信号的处理方式可以是自定义的如果没有自定义系统将采用默认的中断处理方式即终止进程。中断向量表在这个过程中用于确定中断号对应的中断服务程序。 问题2按CtrlC终止进程和按Ctrl\终止进程有什么区别
按 CtrlC 实际上是向进程发送2号信号 SIGINT按 Ctrl/button 实际上是向进程发送3号信号 SIGQUIT
通过
man 7 signal查看这两个信号的默认处理动作可以看到这两个信号的 Action 是不一样的2号信号是 Term 而3号信号是 Core
Term和Core都代表着终止进程但是Core在终止进程的时候会进行一个动作那就是核心转储
问题3核心转储 (core dump)
1. 为什么核心转储在云服务器上是默认关闭的
用ulimit -a 查询当前资源限制的设定
在云服务器上默认情况下禁用核心转储主要是为了节省存储空间和保护用户隐私。生成核心转储文件可能会占用大量磁盘空间而且这些文件可能包含敏感信息。因此为了避免不必要的存储占用和信息泄露云服务器通常默认关闭核心转储功能。在一般的Linux虚拟机中核心转储默认打开。 2. ulimit -c size 命令修改core文件的大小
ulimit 命令用于设置或显示用户级资源限制。通过 ulimit -c 命令可以设置或查看核心转储文件的最大大小
ulimit -c unlimited 可以设置为不限制核心转储文件的大小ulimit -c 0 则表示禁用核心转储。 3. 核心转储有什么用
核心转储文件包含了程序崩溃时的 内存快照进程地址空间 和 寄存器状态 通过分析核心转储文件开发人员可以了解程序崩溃时的内部状态帮助定位和解决软件缺陷。核心转储文件还可以用于回溯调试还原崩溃时的上下文。这个磁盘文件也叫做核心转储文件一般命名为core.pid。
core文件的大小设置完毕后就相当于将核心转储功能打开了。此时如果我们再使用Ctrl\对进程进行终止就会发现终止进程后会显示core dumped 4. 使用core文件进行gdb调试
使用 gdbGNU Debugger来分析核心转储文件是常见的基本步骤 确保程序编译时开启了调试信息即 -g 选项 proc:process.ccg -o $ $^ -stdc11 -g
.PHONY:clean
clean:rm -f procrm -f core.*启动 gdb 并指定程序可执行文件和核心转储文件core-file core.pid 在 gdb 中可以使用 btbacktrace命令来查看函数调用栈info registers 命令来查看CPU寄存器状态
通过结合核心转储文件和 gdb 的功能开发人员可以更容易地理解程序崩溃的原因修复缺陷提高软件的稳定性。 5. 退出状态的core dump比特位
在 Linux 进程控制 中我们学习了使用wait和waitpid来等待子进程 #include sys/types.h#include sys/wait.hpid_t wait(int *status);pid_t waitpid(pid_t pid, int *status, int options);wait和waitpid都有一个status参数该参数是一个输出型参数由操作系统填充。如果传递NULL表示不关心子进程的退出状态信息。否则操作系统会根据该参数将子进程的退出信息反馈给父进程。status不能简单的当作整形来看待可以当作位图来看待具体细节如下图只研究status低16比特
如果子进程被信号所杀它传给父进程的status的第七个比特位会包含core dump信息第7个比特位为1即可说明子进程在被终止时进行了核心转储。
用下面的代码测试
#include stdio.h
#include stdlib.h
#include unistd.h
#include sys/wait.h
#include sys/types.hint main()
{if (fork() 0){//childprintf(I am running...\n);int *p NULL;*p 100;exit(0);}//fatherint status 0;waitpid(-1, status, 0);printf(exitCode:%d, coreDump:%d, signal:%d\n,(status 8) 0xff, (status 7) 1, status 0x7f);return 0;
}2.2 命令级kill
我们使用kill -l命令可以查看Linux当中的信号列表
其中1~31号信号是普通信号34~64号信号是实时信号普通信号和实时信号各自都有31个每个信号都有一个编号和一个宏定义名称可以查看/usr/include/linux/signal.h文件以获取系统支持的所有普通信号
实时信号的相关定义通常在signal.h头文件中。
当我们要使用kill命令向一个进程发送信号时我们可以以kill -信号名 进程ID的形式进行发送
注意如果开启了core dump想要kill掉后台的一个死循环进程右边的bash在kill之后要按两下回车才能看到Segmentation fault 信息。 因为在死循环进程终止掉之前已经回到了Shell提示符等待用户输入下一条命令,Shell不希望Segmentation fault信息和用 户的输入交错在一起,所以等用户输入命令之后才显示
指定发送某种信号的kill命令可以有多种写法,上面的命令还可以写成kill -SIGSEGV 4568 或kill -11 456811是信号SIGSEGV的编号。以往遇 到的段错误都是由非法内存访问产生的,而这个程序本身没错给它发SIGSEGV也能产生段错误。
2.2 用系统调用向进程发信号
️kill()函数
kill命令是通过调用kill系统调用实现的kill函数可以给指定pid的进程发送指定的信号函数原型如下
NAMEkill - send signal to a processSYNOPSIS#include sys/types.h#include signal.hint kill(pid_t pid, int sig);返回值成功返回0错误返回-1。
️raise()函数
kill命令是调用kill函数实现的kill函数可以给一个指定的进程发送指定的信号。raise函数可以给当前进程发送指定的信号自己给自己发信号类似执行kill(getpid(), sig)。
NAMEraise - send a signal to the callerSYNOPSIS#include signal.hint raise(int sig);DESCRIPTIONThe raise() function sends a signal to the calling process or thread.In a single-threaded program it is equivalent to:kill(getpid(), sig);返回值成功返回0错误返回-1。
️abort()函数
abort函数的作用是异常终止进程exit函数的作用是正常终止进程而abort本质是通过向当前进程发送SIGABRT信号而终止进程的。 [!tip] 使用exit函数终止进程可能会失败但使用abort函数终止进程总是成功的 exit 函数 exit 函数是正常终止进程的方式它允许进程执行一些清理工作例如关闭文件、刷新缓冲区、释放堆栈资源等然后返回到操作系统。如果 exit 函数失败通常是因为在清理过程中发生了错误可能导致进程无法正常终止。这可能包括未处理的异常、内存损坏或其他问题。 abort 函数 abort 函数用于立即终止进程不会执行任何清理工作。它是一种非正常的进程终止方式类似于向进程发送一个中断信号。由于 abort 不执行清理工作它通常能够成功地终止进程即使进程处于异常状态。 因此如果在使用 exit 函数时出现失败可能是因为清理过程中发生了问题而 abort 函数则更加直接和强制因此通常能够成功终止进程。然而abort 的使用应慎重因为它不允许进程进行任何善后工作。最好的做法是尽可能使用 exit 函数。 2.3 由软件产生信号
SIGPIPE是一种由软件条件产生的信号在 进程间通信 #匿名管道的读写规则 中已经介绍过了如果所有管道读端对应的文件描述符被关闭则write操作会产生信号SIGPIPE进而可能导致正在write的进程退出。 本节主要介绍 alarm函数 和 SIGALRM 信号。 调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。函数原型如下
#include unistd.h
unsigned int alarm(unsigned int seconds);alarm函数的返回值
若调用alarm函数前进程已经设置了闹钟则返回上一个闹钟时间的剩余时间并且本次闹钟的设置会覆盖上一次闹钟的设置。如果调用alarm函数前进程没有设置闹钟则返回值为0。
下面这个程序的作用是1秒钟之内不停地数数1秒钟到了就被SIGALRM信号终止我们用handler函数自定义捕捉了 SIGALRM 信号
#include stdio.h
#include stdlib.h
#include signal.h
#include unistd.hint count 0;
void handler(int signo)
{printf(get a signal: %d\n, signo);printf(count: %d\n, count);exit(1);
}
int main()
{signal(SIGALRM, handler);alarm(1);while (1){count;}return 0;
}2.4 由硬件产生信号
当我们程序当中出现类似于除0、野指针、越界之类的错误时进程会崩溃本质上是因为进程在运行过程中收到了操作系统发来的信号进而被终止。
硬件异常被硬件以某种方式被硬件检测到并通知内核然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令CPU的运算单元会产生异常操作系统发现CPU内的某个状态标志位被置位而这次置位就是因为出现了某种除0错误而导致的内核将这个异常解释为SIGFPE信号浮点数异常发送给进程。再比如当前进程访问了非法内存地址MMU内存管理单元会产生异常内核将这个异常解释为SIGSEGV信号发送给进程。 下面的流程图解释了操作系统如何在程序运行过程中处理硬件异常并向进程发送信号 #mermaid-svg-5AVSEYtCPmXE0CoH {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-5AVSEYtCPmXE0CoH .error-icon{fill:#552222;}#mermaid-svg-5AVSEYtCPmXE0CoH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5AVSEYtCPmXE0CoH .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-5AVSEYtCPmXE0CoH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5AVSEYtCPmXE0CoH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5AVSEYtCPmXE0CoH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5AVSEYtCPmXE0CoH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5AVSEYtCPmXE0CoH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5AVSEYtCPmXE0CoH .marker.cross{stroke:#333333;}#mermaid-svg-5AVSEYtCPmXE0CoH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5AVSEYtCPmXE0CoH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-5AVSEYtCPmXE0CoH .cluster-label text{fill:#333;}#mermaid-svg-5AVSEYtCPmXE0CoH .cluster-label span{color:#333;}#mermaid-svg-5AVSEYtCPmXE0CoH .label text,#mermaid-svg-5AVSEYtCPmXE0CoH span{fill:#333;color:#333;}#mermaid-svg-5AVSEYtCPmXE0CoH .node rect,#mermaid-svg-5AVSEYtCPmXE0CoH .node circle,#mermaid-svg-5AVSEYtCPmXE0CoH .node ellipse,#mermaid-svg-5AVSEYtCPmXE0CoH .node polygon,#mermaid-svg-5AVSEYtCPmXE0CoH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5AVSEYtCPmXE0CoH .node .label{text-align:center;}#mermaid-svg-5AVSEYtCPmXE0CoH .node.clickable{cursor:pointer;}#mermaid-svg-5AVSEYtCPmXE0CoH .arrowheadPath{fill:#333333;}#mermaid-svg-5AVSEYtCPmXE0CoH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-5AVSEYtCPmXE0CoH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-5AVSEYtCPmXE0CoH .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-5AVSEYtCPmXE0CoH .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-5AVSEYtCPmXE0CoH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-5AVSEYtCPmXE0CoH .cluster text{fill:#333;}#mermaid-svg-5AVSEYtCPmXE0CoH .cluster span{color:#333;}#mermaid-svg-5AVSEYtCPmXE0CoH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-5AVSEYtCPmXE0CoH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 通知内核 判断异常类型 是 包装成信号 执行信号处理函数 终止进程或其他操作 否 OS检测到CPU内的某个状态标志位被置位 内核异常处理 异常是否与进程相关? 识别异常的进程 发送信号给进程 进程处理信号 处理完成 其他处理方式