欢迎来到Cefler的博客😁
🕌博客主页:折纸花满衣
🏠个人专栏:题目解析
🌎推荐文章:【LeetCode】winter vacation training
目录
- 👉🏻信号的概念与产生
- jobs命令
- 普通信号和实时信号
- 如何理解OS向进程发送信号?
- 👉🏻中断号和中断向量表
- 👉🏻signal函数
- 👉🏻信号产生的方式
- 1.通过终端按键产生信号
- 2.调用系统函数向进程发信号
- 3. 由软件条件产生信号
- 4.硬件异常产生信号
👉🏻信号的概念与产生
信号是一种进程间通信机制,用于向进程发送异步通知
(信号什么时候来不确定)。当某个进程接收到一个信号时,它可以选择忽略该信号、继续执行当前操作或者执行与该信号相关的特定操作。
信号是一种向目标进程发送通知消息的一种机制
在Unix/Linux系统中,信号由内核或其他进程产生,并以异步的方式发送给目标进程。常见的信号包括SIGINT(中断信号)、SIGTERM(终止信号)、SIGKILL(强制终止信号)等,每个信号都有一个唯一的编号用于标识该信号。
🍫信号的产生可以分为两种情况:硬件产生的信号和软件产生的信号。
硬件产生的信号是由底层硬件设备产生的,如非法内存访问、除以零、总线错误等。当这些错误发生时,操作系统会向受影响的进程发送相应的信号,以通知其发生了错误并可能需要终止程序。
软件产生的信号是由其他进程或系统调用等产生的,如kill指令可以向指定进程发送信号。在使用kill指令时,可以指定不同的信号类型,以达到不同的目的。
总之,信号是一种重要的进程间通信机制,可以用于通知进程发生了特定的事件,需要进行相应的处理。在进程编程中,了解信号的概念和产生方式是非常重要的,可以帮助开发者更好地理解和处理各种异常情况。
jobs命令
在Unix和类Unix系统中,jobs命令用于列出当前终端或shell中正在运行或挂起的作业(job)信息。作业是指由shell启动的一个或多个进程组成的任务,它可以是前台作业(foreground job)或后台作业(background job),也可以是正在运行或者已挂起的作业。
使用jobs命令可以查看当前终端或shell中所有的作业信息,包括作业编号、状态、进程ID等。常见的用法有:
- jobs:列出当前终端或shell中所有的作业信息。
- jobs -l:在列出作业信息的基础上,还会显示每个作业的进程ID和进程状态等详细信息。
- jobs -p:只列出每个作业的进程ID,不显示其他信息。
在jobs命令输出的作业信息中,每个作业都有一个唯一的编号,该编号可以用于对作业进行操作。常用的操作命令有:
- fg %n:将编号为n的作业切换到前台运行。
- bg %n:将编号为n的作业切换到后台运行。
- kill %n:杀死编号为n的作业。
例如,要将编号为3的后台作业切换到前台运行,可以使用fg %3命令;要杀死编号为2的作业,可以使用kill %2命令。
普通信号和实时信号
在 Linux 中,kill -l 命令可以列出所有可用的信号。这些信号可以被用于进程之间的通讯和控制。
信号可以被分为两类:普通信号和实时信号。
普通信号是异步信号,也就是说,信号发送者和接收者不需要同时执行。当一个进程收到一个信号时,它会停止当前操作并处理该信号。常见的普通信号包括:
- SIGHUP (1):终端挂起或控制进程终止
- SIGINT (2):来自键盘的中断信号
- SIGQUIT (3):来自键盘的退出信号
- SIGILL (4):非法指令
- SIGABRT (6):异常终止
- SIGFPE (8):浮点错误
- SIGKILL (9):强制终止(不能被自定义捕捉)
- SIGSEGV (11):无效内存引用
- SIGPIPE (13):管道破裂
- SIGALRM (14):定时器信号
- SIGTERM (15):终止信号
实时信号是同步信号,发送和接收都需要同时执行。实时信号可以被用于高精度定时器和进程间同步等场景。常见的实时信号包括:
- SIGRTMIN (34):实时信号最小值
- SIGRTMAX (64):实时信号最大值
实时信号的使用相对较少,仅在特定场景下使用。
如何理解OS向进程发送信号?
当操作系统向目标进程发送信号时,它会找到目标进程的进程控制块(Process Control Block,PCB),并更新其中的信号位图。信号位图是一个由比特位组成的数据结构,用于表示进程所接收到的各个信号的状态。
当将信号位图中对应信号的比特位由0置1后,操作系统会等待一个合适的处理时机。一般情况下,这个处理时机是在目标进程执行指令的过程中,也就是在进程的上下文切换点或系统调用返回时。
在进程的函数指针数组中,也称为信号处理表
(Signal Handler Table),根据信号编号对应的数组下标,可以找到与该信号相关联的信号处理函数。当操作系统检测到进程接收到一个信号时,它会查找信号处理表,找到对应的处理函数,并执行相关的处理操作。
👉🏻中断号和中断向量表
在计算机系统中,中断是指由硬件或软件发出的一种信号,用于暂停正在执行的程序并转而处理某个特定事件或请求。当发生中断时,处理器会停止当前的任务,保存当前的上下文,并跳转到一个称为中断服务例程(ISR,Interrupt Service Routine)或中断处理程序的代码段中去执行相应的操作。
为了管理和区分不同的中断类型,每个中断都会被分配一个唯一的中断号(Interrupt Number)。中断号通常是一个非负整数,用于标识特定的中断类型。
中断向量表(Interrupt Vector Table)是一个存储中断处理程序入口地址的数据结构。它是一个数组或表格,每个表项对应一个中断号,并包含该中断的处理程序的入口地址。当发生中断时,处理器会使用中断号作为索引,从中断向量表中获取相应中断的处理程序入口地址,并跳转到该地址开始执行中断服务例程。
中断向量表通常在系统启动时被初始化,并由操作系统或固件维护。每个操作系统或硬件架构可能有不同的中断向量表实现方式和存储位置。
通过中断向量表,系统可以快速地将中断事件分发给相应的中断处理程序,实现对不同中断类型的灵活处理和响应。同时,中断向量表也为开发者提供了一种机制来扩展和自定义中断处理程序,以满足特定的需求。
总之,中断号用于标识不同类型的中断,而中断向量表则用于存储中断处理程序的入口地址。它们是操作系统和硬件实现中重要的概念,用于实现中断机制并提高系统的可靠性和可扩展性。
👉🏻signal函数
signal函数是一个用于处理信号(Signal)的函数,位于C语言的信号处理库signal.h中。它允许程序注册对各种不同信号的处理函数,并在接收到相应信号时执行相应的操作。
signal函数的原型如下:
void (*signal(int signum, void (*handler)(int)))(int);
其中,signum表示要处理的信号的编号,而handler是一个指向函数的指针,用于指定信号处理函数。
signal函数的使用方式有三种常见的形式:
-
默认处理方式:如果handler参数为
SIG_DFL
(默认值),则恢复信号的默认处理方式。例如,当进程收到SIGINT信号(通常由Ctrl+C
产生)时,会中断当前的操作并退出程序。signal(SIGINT, SIG_DFL);
-
忽略信号:如果handler参数为
SIG_IGN
,表示忽略该信号。例如,当进程收到SIGTSTP信号(通常由Ctrl+Z
产生)时,可以选择忽略该信号而不暂停进程。signal(SIGTSTP, SIG_IGN);
-
自定义信号处理函数:将handler参数设置为一个自定义的信号处理函数,以便在接收到相应信号时执行特定的操作。自定义的信号处理函数应接受一个整数信号编号作为参数,通常使用siginfo_t结构体来获取更多关于信号的信息。
void handle_signal(int signum) { // 自定义信号处理逻辑 } signal(SIGUSR1, handle_signal);
在 Linux 系统中,Ctrl+Z 和 Ctrl+\ 是两个与终端交互相关的键盘快捷键,而 Ctrl+C 则是与进程控制相关的键盘快捷键。
具体来说:
- Ctrl+Z:默认情况下,这个快捷键可以将当前正在运行的进程暂停,并将其放入后台。当你输入该快捷键时,系统会向当前正在运行的前台进程发送一个 SIGTSTP 信号,表示要求暂停当前进程。可以使用 fg 命令将该进程恢复到前台继续运行,也可以使用 bg 命令将其转换为后台进程运行。
- Ctrl+\:这个快捷键可以用于强制终止当前正在运行的进程。当你输入该快捷键时,系统会向当前正在运行的进程发送一个 SIGQUIT 信号,表示要求进程结束运行。如果进程没有处理该信号,它就会被终止并退出。
- Ctrl+C:这个快捷键可以用于向当前正在运行的进程发送一个中断信号。当你输入该快捷键时,系统会向当前正在运行的前台进程发送一个 SIGINT 信号,表示要求进程终止运行。如果进程没有处理该信号,它就会被终止并退出。
需要注意的是,不同的进程对这些信号的处理方式可能会有所不同。一些进程可能会忽略这些信号,而另一些进程则会捕获并处理它们,以便采取适当的行动或执行清理操作。
当接收到SIGUSR1信号时,程序将执行自定义的信号处理函数来进行一些操作。
#include <stdio.h>
#include <signal.h>
// 自定义信号处理函数
void handle_signal(int signum) {
printf("Received signal %d\n", signum);
// 可以在这里执行其他操作
}
int main() {
// 注册自定义信号处理函数
signal(SIGUSR1, handle_signal);
// 模拟一个长时间运行的程序
while (1) {
// 做一些有意义的工作
}
return 0;
}
在上面的示例中,我们使用signal函数将SIGUSR1信号与自定义的handle_signal函数关联起来。当程序接收到SIGUSR1信号时,handle_signal函数将被调用,并打印接收到的信号编号。
为了让程序能够响应信号并执行相应的处理函数,通常需要保持程序处于运行状态。在示例中,我们使用一个无限循环来模拟一个长时间运行的程序。实际应用中,可能需要根据具体的需求设计程序的逻辑和退出机制。
👉🏻信号产生的方式
1.通过终端按键产生信号
信号是一种用于进程间通信和进程控制的机制,可以通过多种方式产生。其中一种常见的方式是通过终端按键产生信号。
当你在终端中按下某个特定的组合键时,终端会将这个键盘事件转换为一个信号,并将其发送给前台运行的进程。以下是几个常见的与终端按键相关的信号:
- SIGINT(中断信号):由 Ctrl+C 产生。当你在终端中按下 Ctrl+C 组合键时,终端会向前台进程发送 SIGINT 信号,通常用于请求进程终止运行。
- SIGQUIT(退出信号):由 Ctrl+\ 产生。当你在终端中按下 Ctrl+\ 组合键时,终端会向前台进程发送 SIGQUIT 信号,通常用于请求进程终止运行,并且如果进程没有处理该信号,还会生成一个 core dump 文件以供调试使用。
- SIGTSTP(停止信号):由 Ctrl+Z 产生。当你在终端中按下 Ctrl+Z 组合键时,终端会向前台进程发送 SIGTSTP 信号,通常用于请求暂停当前进程,并将其放入后台运行。
这些信号会被操作系统捕获,并发送给相应的进程。进程可以选择忽略信号、采取默认操作,或者自定义信号处理函数来响应这些信号。通过信号,终端与正在运行的进程之间可以进行交互和控制。
🌎 core dump文件
core dump 文件是一种在程序崩溃或异常终止时生成的二进制文件,用于保存程序在崩溃瞬间的内存映像和其他相关调试信息。它可以为开发人员提供有关程序崩溃原因的重要线索和调试信息。
当一个程序在运行过程中遇到严重错误或异常情况时,操作系统会生成一个 core dump 文件。该文件记录了程序在崩溃瞬间的内存状态
、寄存器值
、堆栈信息
等数据。这个快照可以帮助开发人员分析问题,定位错误,并进行调试。
core dump 文件通常包含以下信息:
-
内存映像:core dump 文件会将程序在崩溃时的内存内容保存下来,包括变量、数据结构、堆和栈的状态等。这些信息可以用于分析程序崩溃时的内存状态。
-
寄存器值:core dump 文件还会保存程序崩溃瞬间 CPU 的寄存器值,包括通用寄存器、指令指针和堆栈指针等。这些寄存器值对于确定程序崩溃位置和状态非常重要。
-
堆栈跟踪:core dump 文件中包含了程序崩溃时的堆栈跟踪信息,显示了函数调用链和代码路径。这对于定位错误的源头非常有帮助。
使用 core dump 文件,开发人员可以将其加载到调试器中进行分析和调试。调试器可以读取 core dump 文件,并提供各种调试功能,例如查看内存状态、变量值,跟踪函数调用链等。这样开发人员可以更好地理解程序崩溃的原因,并修复问题。
需要注意的是,core dump 文件可能会包含敏感信息,例如密码、私钥等。因此,在共享或发布 core dump 文件之前,应该谨慎处理,确保其中的敏感信息已经被删除或加密。
🌎ulimit命令
ulimit命令是一个用于控制用户进程资源限制的Linux/Unix命令。它可以用来设置和显示不同类型的资源限制,例如打开文件数、核心文件大小、CPU时间等。
ulimit命令的一般语法如下:
ulimit [选项] [参数]
常用的选项包括:
-a
:显示当前所有资源限制的值。-n
:设置或显示最大打开文件数。-c
:设置或显示核心文件大小限制。-t
:设置或显示CPU时间限制。-f
:设置或显示文件大小限制。-u
:设置或显示用户进程数限制。
示例:
- 显示当前所有资源限制的值:
ulimit -a
- 设置最大打开文件数为1024:
ulimit -n 1024
- 显示当前核心文件大小限制:
ulimit -c
通过ulimit命令,可以根据需要对不同类型的资源进行限制,以确保系统资源的合理分配和保护。请注意,ulimit命令设置的资源限制只对当前会话有效,并且可能受到系统管理员或其他限制策略的影响。
2.调用系统函数向进程发信号
在Linux/Unix操作系统中,信号是一种进程间通信机制,它可以被用来通知进程发生的事件或异常情况。信号可以由多种方式产生,其中两种常见方式是通过调用系统函数向进程发信号,如kill函数
和abort函数
。
- kill函数产生信号
kill函数可以向指定进程或进程组发送信号,其原型如下:
int kill(pid_t pid, int sig);
其中,pid参数为要发送信号的进程ID,sig参数为要发送的信号编号。当kill函数成功返回0时,表示信号已经成功发送;当返回-1时,表示发送信号出现了错误。
例如,可以使用以下代码向进程ID为1234的进程发送SIGTERM信号:
kill(1234, SIGTERM);
- abort函数产生信号
abort函数会使当前进程产生一个SIGABRT信号,导致进程异常终止。其原型如下:
void abort(void);
当调用abort函数时,会向当前进程发送一个SIGABRT信号,并且该信号的默认处理程序将会执行。在默认处理程序执行之前,如果进程注册了对SIGABRT信号的处理程序,则该处理程序将会被执行。
例如,以下代码将会导致进程异常终止,并向标准错误输出打印一条错误信息:
#include <stdlib.h>
#include <stdio.h>
int main() {
printf("Before abort()\n");
abort();
printf("After abort()\n");
return 0;
}
除了使用kill函数和abort函数之外,信号也可以由其他方式产生,例如硬件异常、操作系统事件等。对于每种信号,Linux/Unix操作系统都有一些默认的处理程序或行为,可以通过信号处理函数进行修改。
3. 由软件条件产生信号
信号指的是操作系统向进程发送的一种异步消息,通常用于通知进程某种事件的发生,如定时器到期、用户键入中断信号等。在Linux系统中,信号的产生方式有两种:软件条件产生信号和硬件异常产生信号。
软件条件产生信号,通常是基于进程执行过程中的某种条件触发,由进程自身发出。例如在C语言中,可以使用signal函数为进程注册一个信号处理函数,当满足指定的条件时,操作系统就会向进程发送相应的信号。
其中,alarm函数就是一种常见的软件条件产生信号的方法。该函数用于设置定时器,当定时器到期时,操作系统会向进程发送SIGALRM
信号。下面是一个示例代码:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void handler(int sig)
{
printf("Received SIGALRM signal.\n");
}
int main()
{
signal(SIGALRM, handler);
alarm(5); // 设置定时器,5秒后触发SIGALRM信号
sleep(10); // 进程睡眠10秒
return 0;
}
在上述代码中,我们首先通过signal函数为进程注册一个信号处理函数handler,并将SIGALRM信号与该处理函数关联起来。接着调用alarm函数设置定时器,让操作系统在5秒后向进程发送SIGALRM信号。最后,我们让进程睡眠10秒,以便观察信号的处理情况。
当程序运行时,首先会等待5秒钟,然后收到SIGALRM信号,调用关联的处理函数handler,输出"Received SIGALRM signal."的信息。
4.硬件异常产生信号
硬件异常产生信号是指由硬件设备或系统出现异常情况时,操作系统向进程发送的信号。这类信号通常与硬件故障、系统错误或异常事件相关。
在计算机系统中,硬件异常可以包括以下几种情况:
-
中断:当硬件设备需要处理或请求操作系统的服务时,会触发中断信号。例如,键盘输入、鼠标点击等。
-
故障:硬件设备出现故障时,会产生相应的故障信号。例如,内存错误、硬盘故障等。
-
异常事件:某些特殊的硬件或系统事件,如除零错误、内存保护错误等,也会触发异常信号。
操作系统会根据硬件异常的类型和严重程度,将相应的信号发送给受影响的进程。进程可以通过注册信号处理函数来捕获并处理这些信号。
在Linux系统中,常见的硬件异常产生的信号有:
-
SIGSEGV:当进程访问非法内存地址(如空指针)或执行无效的内存操作时,操作系统向进程发送SIGSEGV信号,表示段错误。
-
SIGFPE:当发生浮点运算异常(如除零错误、溢出等)时,操作系统向进程发送SIGFPE信号。
-
SIGBUS:当进程访问非法地址或进行无效的总线操作时,操作系统向进程发送SIGBUS信号。
这些信号可以通过signal函数或者更为灵活的sigaction函数来注册处理函数,进程在接收到相应的信号后,可以执行特定的操作,如记录日志、处理异常情况或终止程序的执行。
需要注意的是,硬件异常产生的信号是由操作系统自动发出的,而不是进程主动发起的。进程可以通过注册信号处理函数来响应和处理这些信号,以保证程序的稳定性和可靠性。
🫐🫐🫐
无论信号有多少种产生方式,永远只能让OS向目标进程发送,因为OS是进程的管理者
如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长