一. 信号
1.什么是信号?
信号就是个 消息 , 可以被发送到 一个进程 或 多个进程(10086给广大用户发短信,好消息好消息,特大好消息,100G流量1块钱!)。
每个信号本质就是个 ”数字“ 而已。
pid ——> process id (进程ID)
tgid ——> thread group id (线程组ID)
Linux内核里面如何根据pid快速找到对应的 task_struct 呢?
答:内核使用散列表来维护pid 和 task_struct的关系
kill -9 2334 //你一定执行过这条命令 kill -9 进程号
linux内核先找到 pid=2334的进程,并杀掉这个进程以及tgid=2334的线程
给进程pid=2334发送 “9号” 信号:SIGKILL
2.信号的作用 & 什么时候发信号
3.信号处理大体流程
(1)信号发送
发送方式:
- kill(pid, sig) : 向pid所在的线程组,发送一个 “sig号” 信号
- tkill(pid, sig): 向指定的pid进程(或线程),发送一个 "sig号" 信号
- tgkill(pid, sig, tgid): 向指定的pid进程(或线程),发送一个 "sig号" 信号(检查下这个进程的tgid是否等于参数中的tgid)
| Linux中发送信号的API | 类型 | 方法说明 |
| kill(pid_t pid, int sig) | 系统调用/C库 | 向进程/进程组发送信号:pid>0时发送给指定进程;pid=0时发送给当前进程组;pid=-1时发送给所有有权限的进程;pid<-1时发送给进程组ID为-pid的进程组 |
| tkill(int tid, int sig) | 系统调用 | 已废弃,向指定线程ID发送信号,不检查线程组ID。推荐使用tgkill |
| tgkill(int tgid, int tid, int sig) | 系统调用 | 向指定线程ID发送信号,并验证其所属线程组ID(tgid)是否匹配,防止线程终止后ID重用导致误发 |
| *rt_sigqueueinfo(pid_t pid, int sig, siginfo_t uinfo) | 系统调用 | 发送信号并携带额外数据(siginfo_t结构),可用于传递自定义信息或文件描述符 |
| *rt_tgsigqueueinfo(int tgid, int tid, int sig, siginfo_t uinfo) | 系统调用 | tgkill与rt_sigqueueinfo功能的组合,向指定线程发送带数据的信号 |
| killpg(int pgrp, int sig) | C库函数 | 向指定进程组发送信号,等价于kill(-pgrp, sig) |
| raise(int sig) | C库函数 | 向当前进程自身发送信号,等价于kill(getpid(), sig) |
| sigqueue(pid_t pid, int sig, const union sigval value) | C库函数 | kill()的扩展版本,可附带一个整型或指针数据,需链接-rt库 |
| pthread_kill(pthread_t thread, int sig) | C库函数 | 向同一进程内的指定pthread线程发送信号,信号0用于检测线程是否存在 |
| abort(void) | C库函数 | 向当前进程发送SIGABRT信号并终止进程,即使捕获该信号也会终止 |
信号已经发送给A进程,但是A进程未处理的此信号,称为“挂起信号” (也叫待处理信号,会存储在 task_struck中)
task_struct 中的信号数据结构:
struct task_struct{
......
//信号处理 相关信息
struct signal_struct *signal; // 进程共享信号处理结构
struct sighand_struct *sighand; // 信号处理函数表
sigset_t blocked; // 被阻塞的信号集
sigset_t real_blocked; // 临时阻塞信号集(用于 rt_sigtimedwait)
sigset_t saved_sigmask; // 保存的信号掩码(用于TASK_RUNNING状态切换)
struct sigpending pending; // 待处理信号队列
unsigned long sas_ss_sp; // 信号处理备用栈指针
size_t sas_ss_size; // 信号处理备用栈大小
unsigned int sas_ss_flags; // 信号处理栈标志
......
}
(2)信号处理
每个信号都有默认的处理方式:
Terminate : 终止(杀死)进程—>【Term:终止进程(进程退出)】
Dump : 终止(杀死)进程, 将进程运行的上下文信息保存到文件中,方便查询进程相关信息 —>【Core:终止进程并生成core dump文件(用于调试)】
Ignore :信号被忽略—>【Ign:忽略该信号(进程不采取任何动作)】
Stop :停止进程,将进程的状态设置为 TASK_STOPPED—>【Stop:暂停进程(进程进入停止状态)】
Contineue : 如果进程的状态是 TASK_STOPPED, 那么把它设置为 TASK_RUNNING—>【Cont:进程转为就绪态或获得CPU时间片执行代码指令】
| 信号 | 信号名 | 默认处理方式 | 用途说明 |
| 1 | SIGHUP | Term | 终端挂起或控制进程结束,通常用于重新加载配置(如:reload nginx) |
| 2 | SIGINT | Term | 中断进程(Ctrl+C),请求优雅终止 |
| 3 | SIGQUIT | Core | 退出进程并生成core dump文件(Ctrl+\),用于调试 |
| 4 | SIGILL | Core | 非法指令,通常因二进制文件损坏、不兼容或栈溢出导致 |
| 5 | SIGTRAP | Core | 跟踪/断点陷阱,调试器专用(如gdb) |
| 6 | SIGABRT | Core | 进程主动中止(调用abort()),通常因断言失败或调用assert() |
| 7 | SIGBUS | Core | 总线错误(内存对齐错误、访问不存在的物理地址) |
| 8 | SIGFPE | Core | 浮点运算异常(除零、溢出、非法操作等) |
| 9 | SIGKILL | Term | 强制杀死进程,无法捕获或忽略(kill -9) |
| 10 | SIGUSR1 | Term | 用户自定义信号1,应用程序自由定义用途 |
| 11 | SIGSEGV | Core | 段错误(非法内存访问、越界、空指针),最常见崩溃原因 |
| 12 | SIGUSR2 | Term | 用户自定义信号2,应用程序自由定义用途 |
| 13 | SIGPIPE | Term | 管道破裂(读端关闭,写端继续写入),网络编程中常见 |
| 14 | SIGALRM | Term | 定时器到期(alarm()函数触发) |
| 15 | SIGTERM | Term | 默认终止信号,请求优雅退出(kill默认发送) |
| 16 | SIGSTKFLT | Term | 协处理器栈错误(Linux中已废弃,几乎不使用) |
| 17 | SIGCHLD | Ign | 子进程状态改变(停止或退出),父进程用wait()回收 |
| 18 | SIGCONT | Cont | 继续执行已停止的进程(与STOP对应) |
| 19 | SIGSTOP | Stop | 强制停止进程,无法捕获或忽略(Ctrl+Z) |
| 20 | SIGTSTP | Stop | 终端停止信号(Ctrl+Z),可捕获处理 |
| 21 | SIGTTIN | Stop | 后台进程组尝试从终端读取 |
| 22 | SIGTTOU | Stop | 后台进程组尝试向终端写入 |
| 23 | SIGURG | Ign | 套接字上出现紧急数据(带外数据) |
| 24 | SIGXCPU | Core | CPU时间限制超出(ulimit -t设定) |
| 25 | SIGXFSZ | Core | 文件大小限制超出(ulimit -f设定) |
| 26 | SIGVTALRM | Term | 虚拟时钟信号(进程实际使用的CPU时间) |
| 27 | SIGPROF | Term | 性能分析定时器(进程+内核CPU时间) |
| 28 | SIGWINCH | Ign | 窗口大小改变(终端窗口resize) |
| 29 | SIGIO | Term | 异步I/O事件通知(fcntl()设置) |
| 30 | SIGPWR | Term | 电源失效(UPS低电量警告) |
| 31 | SIGSYS | Core | 无效系统调用(seccomp过滤触发) |
| 32 | SIGRTMIN | Term | 实时信号最小值,用于自定义,支持排队 |
| 33-63 | SIGRTMIN+1~31 | Term | 实时信号,共31个,可自定义用途 |
| 64 | SIGRTMAX | Term | 实时信号最大值 |
(2.1)自定义信号处理—signal :
例如: 编写程序,在自己的进程中自定义信号处理
signal_test.c
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
//自己写的程序——M进程 自定义信号处理逻辑
void func(intg sig){
printf("sig=%d comeing!\n", sig);
}
int main(){
//signal(SIGINI, SIG_DFL); //默认处理————按2号信号的默认处理就行
//signal(SIGINT, SIG_IGN); //忽略信号————忽略了2号信号
signal(SIGINI, func); //捕获信号,然后调用自定义处理逻辑————接收2号信号,按我的方式来
return 0;
}
(2.2)自定义信号处理—sigaction :
(2.3)task_struct信号处理之——保存信号处理函数
注册的信号处理程序保存在进程对应的 task_struck 中, 通过 signal、sigaction 自定义的信号处理,都保存在 *sighand 中
struct task_struct{
struct sighand_struct *sighand; // 信号处理函数表
}
二. 中断
操作系统中的异常分为:
- 故障(Fault):可恢复,返回时重新执行错误指令
- 陷阱(Trap【系统待用】):可恢复,返回时执行下一条指令
- 中断(Interrupt):异步事件,不影响指令流
- 终止(Abort):不可恢复的严重错误
常识:一个CPU同一时间只能执行一条代码指令,正常情况下,CPU一般是在执行 进程/线程
中断的分类
1.异步中断
(1)I/O设备中断 它是, 外设I/O__设备 与 CPU 交互的一种方式
| I/O设备 | 发送中断时间 | 发送中断信号 | CPU执行__相对应的__中断处理程序 |
| 键盘 | 键盘点击输入字符 | 给CPU发中断信号 | CPU执行_键盘中断处理程序_处理键盘输入的字符 |
| 鼠标 | 鼠标点击 | 给CPU发中断信号 | CPU执行_鼠标中断处理程序_处理鼠标的坐标和点击状态 |
| 网卡 | 接收到 “数据包” | 给CPU发中断信号 | CPU执行_网卡中断处理程序_将数据包拷贝到内存,并解析 |
| 磁盘 | 数据通过DMA拷贝到物理内存完毕后 | 给CPU发中断信号 | 唤醒正在阻塞的进程,告诉它,你要的数据来了,在内存中呢,快去用吧! |
(2)时钟中断 它是, 固定的时间片如:10秒,时钟芯片 给 CPU 发送“时钟中断信号”
总结:
上面两类中断,都是由外部设备产生,可能发生在任意时间,这类中断并不和特定的进程关联,也就是在任意进程执行时,都有可能产生这类中断———它们叫——》
异步中断
如果是 I/O设备产生的中断, CPU可以屏蔽或忽略它———也称———》可屏蔽中断
2.同步中断
这类中断由CPU自身产生,并且和当前执行的进程密切关联—它们叫—>同步中断(或异常)
(1)CPU发出的中断—— “divide error 中断”
(2)CPU发出的中断——“缺页中断 或 缺页异常”
缺页异常(中断),是可以恢复的异常,我们也称它为 “故障(fault)” {属于同步中断,属于异常}
(3)CPU发出的中断——“80中断” (32位操作系统触发系统调用的方式)
中断处理程序在哪里?
每一种中断都有相对应的中断处理程序,它们到底在哪里呢?如何根据中断信号找到对应的处理程序呢?
1.中断描述符表
中断描述符表(Interrupt Descriptor Table, 简称 IDT ). 在Linux内核中,每种中断都有自己唯一的中断号
IDT 维护着:中断号—对应—》中断处理程序的内存首地址
IDT存放在一个名为 idt_table 的表中,有256个表项
IDT 是操作系统启动的时候,事先生成好存放在内存中
CPU中有个名为 idtr 的寄存器存放着IDT内存地址
外部中断就是I/O设备发出的中断
CPU内部就是CPU发出的中断
处理器间中断(多核CPU之间,cpu与cpu之间的交互方式)
0x00-0x1F:Intel保留的异常(不可屏蔽)
| 中断号 | 名称 | 类型 | Linux处理函数 | 说明 |
|---|---|---|---|---|
| 0 (0x00) | Divide Error | 故障 | divide_error | 除零错误 |
| 1 (0x01) | Debug | 陷阱/故障 | debug | 调试异常 |
| 2 (0x02) | NMI | 中断 | nmi | 不可屏蔽中断 |
| 3 (0x03) | Breakpoint | 陷阱 | int3 | 断点指令 |
| 4 (0x04) | Overflow | 陷阱 | overflow | 溢出陷阱 |
| 5 (0x05) | Bound Range Exceeded | 故障 | bounds | BOUND指令越界 |
| 6 (0x06) | Invalid Opcode | 故障 | invalid_op | 无效操作码 |
| 7 (0x07) | Device Not Available | 故障 | device_not_available | 设备不可用(无FPU) |
| 8 (0x08) | Double Fault | 中止 | double_fault | 双重故障 |
| 9 (0x09) | Coprocessor Segment Overrun | 故障 | coprocessor_segment_overrun | 协处理器段溢出(已弃用) |
| 10 (0x0A) | Invalid TSS | 故障 | invalid_TSS | 无效任务状态段 |
| 11 (0x0B) | Segment Not Present | 故障 | segment_not_present | 段不存在 |
| 12 (0x0C) | Stack-Segment Fault | 故障 | stack_segment | 栈段故障 |
| 13 (0x0D) | General Protection Fault | 故障 | general_protection | 通用保护故障 |
| 14 (0x0E) | Page Fault | 故障 | page_fault | 页故障(缺页) |
| 15 (0x0F) | Reserved | - | - | Intel保留 |
| 16 (0x10) | x87 FPU Error | 故障 | coprocessor_error | 浮点错误 |
| 17 (0x11) | Alignment Check | 故障 | alignment_check | 对齐检查 |
| 18 (0x12) | Machine Check | 中止 | machine_check | 机器检查 |
| 19 (0x13) | SIMD Floating-Point Exception | 故障 | simd_coprocessor_error | SIMD浮点异常 |
| 20-31 (0x14-0x1F) | Reserved | - | - | Intel保留,未来扩展 |
0x20-0x2F:可编程中断控制器(PIC)
| 中断号 | 名称 | Linux处理函数 | 说明 |
|---|---|---|---|
| 32 (0x20) | IRQ0 | timer_interrupt | 可编程定时器(PIT) |
| 33 (0x21) | IRQ1 | keyboard_interrupt | 键盘控制器 |
| 34 (0x22) | IRQ2 | cascade | 级联信号 |
| 35 (0x23) | IRQ3 | serial_interrupt | COM2串口 |
| 36 (0x24) | IRQ4 | serial_interrupt | COM1串口 |
| 37 (0x25) | IRQ5 | - | LPT2并口/声卡 |
| 38 (0x26) | IRQ6 | floppy_interrupt | 软盘控制器 |
| 39 (0x27) | IRQ7 | parallel_interrupt | LPT1并口 |
| 40 (0x28) | IRQ8 | rtc_interrupt | 实时时钟(RTC) |
| 41 (0x29) | IRQ9 | - | 重定向IRQ2 |
| 42 (0x2A) | IRQ10 | - | 保留/SCSI/网卡 |
| 43 (0x2B) | IRQ11 | - | 保留/SCSI/网卡 |
| 44 (0x2C) | IRQ12 | mouse_interrupt | PS/2鼠标 |
| 45 (0x2D) | IRQ13 | fpu_interrupt | 协处理器/FPU |
| 46 (0x2E) | IRQ14 | ide_interrupt | 主IDE通道 |
| 47 (0x2F) | IRQ15 | ide_interrupt | 从IDE通道 |
0x30-0x7F:用户定义中断
| 中断号 | 用途 |
|---|---|
| 48-127 (0x30-0x7F) | 用户自定义中断,常用于设备驱动和系统调用 |
0x80-0xEF:系统调用与扩展
| 中断号 | 名称 | Linux处理函数 | 说明 |
|---|---|---|---|
| 128 (0x80) | System Call | system_call | Linux传统系统调用入口(int 0x80) |
| 129-239 (0x81-0xEF) | - | - | 保留/未使用 |
0xF0-0xFF:高级可编程中断控制器(APIC)
| 中断号 | 名称 | Linux处理函数 | 说明 |
|---|---|---|---|
| 240 (0xF0) | APIC Spurious | spurious_interrupt | APIC伪中断 |
| 241-250 (0xF1-0xFA) | - | - | APIC保留 |
| 251 (0xFB) | APIC Error | apic_error_interrupt | APIC错误中断 |
| 252 (0xFC) | APIC Timer | local_timer_interrupt | APIC定时器 |
| 253 (0xFD) | APIC Performance | performance_monitoring_interrupt | 性能监控 |
| 254 (0xFE) | APIC Thermal | thermal_interrupt | 热管理中断 |
| 255 (0xFF) | APIC IPI | smp_call_function_interrupt | 核间中断(IPI) |
a. Linux实现特点:
- 前32个异常由
trap_init()初始化- 32-47的IRQ由
init_IRQ()设置- 0x80是经典32位系统调用入口(现代x86_64使用
syscall指令)
b. 实际地址:完整的中断描述符表(IDT)包含256个条目,每个条目8字节,存储在CPU的IDTR寄存器指向的内存区域。
2.IDT表的初始化
中断处理流程举例
(1).异步中断处理大体粗略流程
硬中断&软中断
外部I/O设备,时钟芯片,CPU芯片 等都是硬件发出的中断(它们必须要快速处理)——硬中断
软件程序代码触发的中断(它们会处理耗时的业务)——软中断
1.中断处理程序的特点
CPU在执行中断处理程序的时候,不会再去执行其他程序
中断处理程序 并不是 进程(它没有对应的task_struct噢!), 而是一个比较特殊的内核代码执行路径而已,所以,中断处理程序不会参与进程调度
中断处理程序会打断 A, B, C等进程的执行,但是中断处理程序一般是不会被打断的
中断处理程序必须非常快,否则:
- 会影响其他进程的执行
- 无法响应其他的中断请求
所以,中断处理程序一般都是短小精悍的,执行非常的快,一般不会做读写磁盘I/O等耗时操作。
2. 如果中断处理程序需要执行很慢的操作(业务),怎么办?
将中断处理逻辑分为 【上部分】 和 【下部分】 两部分。
- 上部分用来快速处理中断,主要负责处理跟硬件紧密相关或者时间敏感的事情
- 下部分用来延迟处理上部分未完成的工作,一般以【内核线程】的方式运行
3.Linux系统内核中的软中断处理程序
软中断的处理由内核线程 ksoftirqd 来做
下图我们可以看到 1号软中断 TIMER_SOFTIRQ 粗略处理流程
| 序号 | 软中断名 | 内核里到底在干啥(一句话场景说明) | 内核中的软中断处理程序(函数名) |
|---|---|---|---|
| 0 | HI_SOFTIRQ | 优先级最高的 tasklet,驱动想“插个队”时用;任何普通 tasklet 之前执行。 | tasklet_hi_action |
| 1 | TIMER_SOFTIRQ | 处理“传统低精度定时器”超时,时钟中断底半部把到期的 timer 链表在这里统一跑一遍。 | run_timer_softirq |
| 2 | NET_TX_SOFTIRQ | 网络发包下半部分:驱动把包递交给内核后,由 NET_TX 完成队列调度、实际 DMA 启动等。 | net_tx_action |
| 3 | NET_RX_SOFTIRQ | 网络收包底半部:NIC 收到包触发硬中断后,把帧挂到 CPU 的 poll 队列,NET_RX 负责协议栈层层解析。 | net_rx_action |
| 4 | BLOCK_SOFTIRQ | 块设备层下半部分:完成 I/O request 的回调、bio 结束处理、触发下一个 request 等。 | blk_done_softirq |
| 5 | IRQ_POLL_SOFTIRQ | “中断轮询”(NAPI) 的通用底半部;网卡关闭中断后,用轮询方式批量收包,提高 10G+ 网卡吞吐。 | irq_poll_softirq |
| 6 | TASKLET_SOFTIRQ | 普通 tasklet 的统一入口;驱动调用 tasklet_schedule() 时最终在此被执行。 | tasklet_action |
| 7 | SCHED_SOFTIRQ | 调度器下半部分:负载均衡、唤醒抢占、CPU 之间拉任务等,把硬中断里来不及做的调度决策延后执行。 | sched_softirq |
| 8 | HRTIMER_SOFTIRQ | 高精度定时器下半部分;硬件时钟事件设备(HPET/APIC-timer)把到期的 hrtimer 在这里统一处理。 | hrtimer_run_softirq |
| 9 | RCU_SOFTIRQ | RCU 宽限期处理与回调;CPU 检测到 RCU grace-period 结束后,把注册的回调函数在这里批量跑完。 | rcu_process_callbacks |
下图我们可以看到 3号软中断 NET_RX_SOFTIRQ 网卡收包粗略处理流程