信号概念
信号是进程之间事件异步通知的一种方式,属于软中断。
异步:在异步操作中,任务可以独立执行。一个任务的开始或完成不依赖于其他任务的状态。
同步:在同步操作中,任务之间的执行是相互依赖的。一个任务必须等待另一个任务完成后才能继续执行。
同步:顺序执行、可能阻塞。异步:并行执行、非阻塞。
前台进程 后台进程
前台进程:在linux下我们直接运行的进程,此时我们输入指令并不会执行,Ctrl+c可终止。
前台进程是用户当前正在交互的进程,通常在用户的终端或窗口中运行。
后台进程:在命令后加上&将进程放入后台 eg.command &
此时我们输入指令并可以执行,Ctrl+c不可终止。
后台进程是在用户不直接交互的情况下运行的进程。
关闭后台进程方法:
1.向后台进程发送信号,eg.kill -9 后台进程idfg命令后台运行的作业调回前台
2.fg id将一个在后台运行的作业调回前台。再Ctrl+c:向前台进程发送2 SIGINT 信号
常见信号:
1~31信号编号
在 Unix 和 Linux 系统中,信号是一种用于通知进程某种事件发生的机制。每个信号都有一个唯一的编号,进程可以通过这些编号来处理特定的事件或执行特定的操作。
信号编号是操作系统用来标识不同信号的整数值。
例如,当你按下 Ctrl+C 时,系统会向当前运行的进程发送一个中断信号(SIGINT),其信号编号为 2。程序可以选择捕获这个信号并执行自定义的处理逻辑,或者默认终止进程。
我们发送信号,进程不一定会立即处理信号,而是在合适的时机处理信号。进程在哪里保存信号呢?
在进程对应PCB中的信号位图里,我们向进程发送信号,其实是修改对应进程PCB中的信号位图,修改bit位0->1
kill -l查看系统支持的信号列表
信号编号 信号名称 描述
1 SIGHUP 终端挂断信号
2 SIGINT 中断信号(通常由 Ctrl+C 产生)
3 SIGQUIT 退出信号(通常由 Ctrl+\ 产生)
4 SIGILL 非法指令信号
5 SIGTRAP 跟踪陷阱信号
6 SIGABRT 异常终止信号
7 SIGBUS 总线错误信号
8 SIGFPE 浮点异常信号
9 SIGKILL 强制终止信号(不可被捕获或忽略)
10 SIGUSR1 用户定义信号 1
11 SIGSEGV 段错误信号
12 SIGUSR2 用户定义信号 2
13 SIGPIPE 管道破裂信号
14 SIGALRM 定时器到期信号
15 SIGTERM 终止信号
16 SIGSTKFLT 堆栈故障信号
17 SIGCHLD 子进程状态改变信号
18 SIGCONT 继续执行信号
19 SIGSTOP 停止进程信号(不可被捕获或忽略)
20 SIGTSTP 停止信号(通常由 Ctrl+Z 产生)
21 SIGTTIN 后台进程试图读取终端信号
22 SIGTTOU 后台进程试图写入终端信号
23 SIGURG 紧急数据到达信号
24 SIGXCPU 超过 CPU 时间限制信号
25 SIGXFSZ 超过文件大小限制信号
26 SIGVTALRM 虚拟定时器到期信号
27 SIGPROF 统计定时器到期信号
28 SIGWINCH 窗口大小变化信号
29 SIGIO I/O 可用信号
30 SIGPWR 电源故障信号
31 SIGSYS Bad system call(错误的系统调用)
信号如 -9 SIGKILL 和 -19 SIGSTOP 是不可被捕获或忽略的。
signal() 重新定义在接收到特定信号时应采取的行为
void (*signal(int signum, void (*handler)(int)))(int);
参数
- signum:要捕获的信号编号,例如
SIGINT
、SIGTERM
等。- handler:指向信号处理函数的指针。(SIG_DFL (默认处理) SIG_IGN (忽略信号))
#include<signal.h> #define SIG_DFL ((void (*)(int)) 0) // 默认处理 #define SIG_IGN ((void (*)(int)) 1) // 忽略信号
void (*handler)(int) handler是一个函数指针,表示接收一个参数为一个int的函数,返回值类型是void。(int参数是接收到的信号编号。)
返回值
- 返回值是先前的处理函数的地址(如果有),或者返回
SIG_ERR
表示出错。
signal()用法
我们知道ctrl+c是向前台进程发送2 SIGINT 中断信号,进程获取到信号执行默认行为(系统定义的行为 终止)。
我们接收到2信号,但不想让它执行默认行为。我们可以直接实现一个void (int) 返回值无 参数一个int(用于接收信号编号)的函数,里面是我们想实现的行为。
把它当作signal()的第二个参数回调函数,第一个参数和回调函数的int参数一样,是要捕捉的信号编号。这样我们就可以把对应信号执行默认行为该为执行我们定义的行为。
信号如 -9 SIGKILL 和 -19 SIGSTOP 是不可被捕获或忽略的。所有-9 -19是不能被signal()重新定义的。
产生信号的5种方式
1.系统指令 kill
系统指令可以发送信号
eg.kill -9 id
2.系统函数 kill() raise() abort()
一个进程可以使用 kill() 函数向另一个进程发送信号。
pid_t pid目标进程pid int sig 要发送信号的编号
#include <signal.h> #include <unistd.h> int kill(pid_t pid, int sig);
成功时返回 0。
失败时返回 -1,并设置 errno。
raise() 函数用于向当前进程(自己)发送一个信号。
sig 要发送信号的编号
#include <signal.h> int raise(int sig);
成功时返回 0。
失败时返回 -1,并设置 errno。
abort() 函数给自己发送 SIGABRT 信号,用于立即终止当前进程,并生成一个核心转储(core dump),这有助于后续调试。
#include <stdlib.h> void abort(void);
3.键盘 ctrl+c/z
特定的用户操作也会产生信号。例如:
用户在终端按下 Ctrl+C 时,通常会向正在运行的进程发送 SIGINT 信号。
用户按 Ctrl+Z 时,进程会接收到 SIGTSTP 信号,使其暂停执行。
4.软件条件
定时器或闹钟 alarm()
进程可以使用定时器(如 alarm()、setitimer())来产生信号。
alarm ()使得在指定的秒数后发送一个 14 SIGALRM 信号到当前进程。
#include <unistd.h> unsigned int alarm(unsigned int seconds);
seconds 表示设置的秒数 返回值为剩余的秒数。
int n=alarm(0);
表示取消当前进程的定时器,n表示之前设置的剩余秒数。如果没有设置过定时器,则返回 0。
5.异常
在C++中,我们访问野指针,/0等操作会让进程崩溃。其实是系统给进程发送对应的信号,导致进程的退出。
系统内核会在特定条件下自动向进程发送信号。
例如:
1.当进程除以零时,会发送 SIGFPE 信号。
2.当进程尝试访问无效内存时,会发送 SIGSEGV 信号。
3.当进程超出资源限制时,会发送 SIGXCPU 或 SIGXFSZ 信号。
我们知道当进程除以零时,会发送 SIGFPE 信号,如果我们signal()重新定义SIGFPE信号对应的行为,不让它终止,每接收到一次SIGFPE 信号就打印,但不终止。
可以看到程序会一直打印,意味着系统会一直给进程发送SIGFPE 信号。为什么?
在执行除法操作之前,CPU 会检查除数寄存器的值。如果发现除数为零,CPU 不会执行实际的除法运算。CPU 会生成一个异常信号,并中断当前的指令执行。
但我们重新定义的该信号的行为,导致进程没有被杀。但cpu不会一直运行这个程序,会进行轮转,等到再次调度该进程,发现又是/0操作再成一个异常信号。这样就会表现出系统一直给该进程发送信号的景象。
Core和Term
Core和Term是进程退出行为,但它们有什么不同?
Term: 一般是指正常的进程终止(通常是通过接收到 SIGTERM 信号)。
Core:当进程由于接收到 SIGSEGV、SIGABRT、SIGQUIT 等信号而崩溃时,操作系统可以在当前目录下生成一个核心转储文件。这个文件包含了进程的内存映像和状态,可以用于调试崩溃原因。最后进程终止。
核心转储文件的文件名一般是core,有的版本下会在后面加该进程的id。坏处是如果不解决问题,每次运行该进程都会生成core.id文件,因为每次进程运行的id不同,core文件名不同就会生成内容重复的文件。导致磁盘空间的浪费
core文件作用
核心文件可以与调试器(如
gdb
)结合使用,以分析崩溃时程序的状态。
输出因为什么崩溃的,在哪一行。
core dump标志
还记得pid_t waitpid (pid_t pid, int *status, int options),中status是一个位图,带回子进程的退出信息,里面有一个字节是core dump标志吗?
如果接收到的是SIGSEGV、SIGABRT、SIGQUIT等信号core dump标志就会置为1,生成core文件