中断和信号详解

发布于:2025-05-26 ⋅ 阅读:(189) ⋅ 点赞:(0)

三种中断

中断分为三种:硬件中断、异常中断、软中断

硬件中断

设备向中断控制器发送中断请求,中断控制器生成对应中断号,然后通过中断引脚向cpu发送高电平,cpu收到请求后不会立即处理,cpu会处理完当前指令,然后检查IF标志(状态寄存器保存)和中断引脚,然后从中断控制器获取中断号,陷入内核,去中断向量表中执行中断函数

IF标志不是表示有没有中断,而是开不开中断,IF如果是0,那就是关中断,表示中断不进行处理。当cpu处理一个中断时硬件会自动关中断,防止中断处理过程被新中断打断,毕竟中断处理也是在执行指令

异常中断

cpu在执行指令的时候,比如运算器中有除零  或者  mmu查页表时发现数据还没加载  或者  mmu查地址空间发现越界,那么cpu就会自动触发中断,然后陷入内核,执行对应中断函数

软中断

当使用系统调用的时候,系统调用会有int 0x80 或 syscall 指令来触发中断,因为中断是指令触发的,因此系统调用触发的中断又称为软中断。将系统调用号保存在寄存器里,然后由int 0x80触发中断,陷入内核,查找中断向量表80号中断函数,然后在中断函数里面会根据寄存器里系统调用号去系统调用表里执行对应系统调用

现代操作系统将系统调用表地址直接保存到寄存器,使用syscall直接去系统调用表里面调用系统调用,直接省去中断向量表查中断函数的时间

中断函数处理前上下文保存

每个进程都拥有自己的内核栈,cpu触发中断陷入内核,会将用户态的寄存器状态保存在先前运行进程的内核栈里,然后在此内核栈里执行中断函数,执行完后从内核栈里恢复寄存器,回到用户态

信号的执行

当cpu执行完中断函数,会检查pcb里thread_info里的标志位,此时cpu还没有从内核栈恢复触发中断时的用户态寄存器,如果TIF_SIGPENDING被设置,那就去从头遍历pending表,找到第一个被设置pending并且block没有被设置的信号,清除其pending位,然后切换到用户态,执行对应信号处理函数,执行完后回到内核态重新从头遍历pending表,重复以上操作,直到没有需要处理的信号后,从内核栈中恢复触发中断时的用户态寄存器,然后切换回用户态

struct task_struct {
    struct thread_info *thread_info; 
    // ...
};

struct thread_info {
    struct task_struct *task;  // 指向对应的PCB(task_struct)
    unsigned long flags;       // 包含TIF_SIGPENDING等标志位
    // ... 架构相关字段(如寄存器状态)
};

信号产生

1.硬件中断设置信号

ctrl + \ :

键盘给中断控制器发送中断请求,中断控制器生成中断号,用中断引脚通知cpu,cpu执行完指令后判断IF标志和中断引脚,获取中断号,陷入内核,执行中断函数。中断函数会给前台进程设置信号pending,然后信号就设置好了

至于SIGQUIT信号的处理,在即将从内核态回到用户态时,处理这个信号,在信号处理函数中,会将进程状态改为Z,并将pcb里信号码改成SIGQUIT,core dump也会被设置,然后释放pcb所管理的资源如mm_struct、页表、物理内存块

2.使用kill命令

kill -11 pid

给pid进程设置11号信号

3.使用函数设置信号

int kill ( pid_t pid, int sig);
给进程设信号
int raise ( int sig);
给当前进程设置信号
void abort ( void );
给当前进程设置SIGABRT
SIGABRT信号的默认行为是退出进程加核心转储。假如将SIGABRT信号自定义函数处理,如果里面有exit那就做正常退出,如果没有exit那在信号处理结束后会执行默认行为,不只是这个信号,很多信号的自定义处理都是这样

4.核心转储

进程异常退出可以设置核心转储,核心转储文件会保存进程用户空间里的内存数据,用于后期调试,文件名通常是core或core.pid。 默认情况下,系统通过 ulimit -c 限制core文件大小为0(即不生成)。可以用ulimit命令来改变这个限制, 在Shell中修改 ulimit -c 后,该Shell启动的所有子进程 均会继承此设置。
​临时生效​​(当前Shell会话)
ulimit -c unlimited  # 不限制core文件大小
ulimit -c 1024       # 限制core文件为1024KB

信号保存

struct task_struct {
        struct sighand_struct * sighand ;
        sigset_t blocked
        struct sigpending pending ;
//...
};
struct sigpending {
        struct list_head list ;
        sigset_t signal;
};
1. sigset_t 类型​
  • ​底层实现​​:sigset_t 是一个​​位图(bitmask)​​,用于表示信号集(每个信号对应一个 bit)。
  • ​用途​​:
    • 用于 sigprocmask()(设置阻塞信号)
    • 用于 sigpending()(获取待处理信号)
    • 用于 sigaction()(设置信号处理方式)
2. pending表里是待处理的信号,block表里是阻塞的信号。
3. ​ SIG_IGN​:信号直接被丢弃,不会进入  pending 表
     block ​:信号被暂时阻塞,会进入  pending  表,解除阻塞后被处理。

信号集操作函数

# include <signal.h>
int sigemptyset ( sigset_t * set );
int sigfillset ( sigset_t * set );
int sigaddset ( sigset_t * set , int signo);
int sigdelset ( sigset_t * set , int signo);
int sigismember ( const sigset_t * set , int signo);

​信号处理相关系统调用详解:sigprocmasksigpendingsigaction

1. sigprocmask

​功能​

  • 用于​​阻塞或解除阻塞​​某些信号。
  • 被阻塞的信号不会进行处理,而是进入 pending 表,等待解除阻塞后再处理。

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

参数 说明
how 指定如何修改信号屏蔽字:
• SIG_BLOCK:将 set 中的信号加入阻塞列表
• SIG_UNBLOCK:从阻塞列表中移除 set 中的信号
• SIG_SETMASK:直接设置阻塞列表为 set
set 要修改的信号集(sigset_t 类型)
oldset 返回旧的信号屏蔽字(可传 NULL

2. sigpending 

​功能​

  • 获取当前​​待处理​​的信号(即 pending 表中的信号)。

int sigpending(sigset_t *set);

3. sigaction — 设置信号处理方式​

​功能​

  • 比 signal() 更强大的信号处理函数,可以:
    • 指定信号处理函数(handler
    • 设置信号处理的行为(如屏蔽其他信号)
    • 获取旧的信号处理方式

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

参数 说明
signum 要设置的信号(如 SIGINTSIGTERM
act 新的信号处理方式(struct sigaction
oldact 返回旧的信号处理方式(可传 NULL

struct sigaction结构

struct sigaction {
    void (*sa_handler)(int);          // 信号处理函数(类似 signal())
    void (*sa_sigaction)(int, siginfo_t *, void *);  // 更高级的信号处理
    sigset_t sa_mask;                 // 执行信号处理时额外阻塞的信号
    int sa_flags;                     // 控制信号行为(如 SA_RESTART)
};

sa_mask用来设置阻塞信号,一般都会阻塞本身,这个阻塞的持续时间是本次信号处理的整个过程,直到从内核栈恢复寄存器准备切换用户态才会解除阻塞,这个阻塞保证了每个信号在整个过程只会被处理一次,防止了死循环


网站公告

今日签到

点亮在社区的每一天
去签到