中断
信号是进程间通信,中断是cpu操作的,CPU对紧急事件的处理和响应, 以及进程的调度和上下文切换
信号概念
- 信号整体递送流程:
- 信号生成:外部中断(如按下CTRL+C);系统调用(如
kill
进程函数);或者软件条件(如除零操作)。当这些事件发生时,内核生成一个相应的信号- 信号的传递:内核分辨和处理信号发送到目标进程的task struct,修改对应的信号记录字段。目标进程在运行的时候,检测自己的task_struct,检测是否有信号
信号的排队:内核为信号创建一个信号队列项,并将其加入到目标进程的位图OR信号队列中。(信号也可能会覆盖旧的信号或者被丢弃)
- 信号靠位图实现
- 信号有掩码(位图):一个进程收到信号未必会执行,取决于对应掩码的bit位,若对应bit位是1,则信号被阻塞
- 已经递送但是还没有执行的信号被称为挂起信号(pending signal,未决信号) ,通常是由于信号被阻塞或进程暂时不能处理信号
- 信号未决 != 信号阻塞, 信号未决的意思是信号暂时未被执行, 而信号阻塞需要对应解除阻塞的操作。
- 信号阻塞是由进程的信号掩码控制的。
- 由进程的某个操作产生的信号称为同步信号(synchronous signals)(例如在代码中除0)。
- 像用户击键这样的进程外部事件产生的信号叫做异步信号(asynchronous signals)。
signal
signal(SIGINT, SIG_IGN); // 给2号信号设置忽略 signal(2, SIG_DFL);// 给2号信号恢复默认行为 signal(2, func);//调用func函数
- 通过signal注册一个信号处理函数,并且处理完毕一个信号之后, 还需要重新注册吗?
不需要重新注册,就能够捕捉下一个信号。
多个信号触发
信号处理的过程中:
- 接受到了另一个不同类型信号,当前的信号处理流程被中断,CPU先处理新的,再处理原来的
- 接收到相同类型信号,当前处理完再处理新的
- 连续接收到相同的信号,只能多执行一次,后面重复的会被忽略
sigaction
int sigaction(
int signum, // 要操作的信号编号(除了SIGKILL和SIGSTOP)
const struct sigaction *act,// 指定信号的新处理动作(如果非空)
struct sigaction *oldact// 获取信号的上一个处理动作(如果非空)
);
// 返回值: 成功时返回0,错误时返回-1
struct sigaction {
void (*sa_handler)(int);// 函数指针:指向一个信号处理函数 (和sa_sigaction选一个即可)
void (*sa_sigaction)(int, siginfo_t *, void *);// 函数指针:指向一个接受三个参数的信号处理函数
sigset_t sa_mask;// 信号集: 指定当前信号处理函数执行时需要阻塞的额外信号
int sa_flags;// 指定信号处理的选项和标志:
void (*sa_restorer)(void);// 过时,暂无用
};
struct siginfo_t {
pid_t si_pid; /* Sending process ID */
sigval_t si_value; /* Signal value */ // ......
};
sa_flags:信号处理方式掩码, 可以用来设置信号的处理模式。
SA_SIGINFO
:使用sa_sigaction
成员而不是sa_handler
作为信号处理函数。SA_RESETHAND
: 处理完捕获的信号以后,信号处理回归到默认( 一次注册只生效一次)SA_NODEFER
: 在信号处理函数执行期间,同一个信号设置可以再次被触发SA_RESTART
:使被信号打断的系统调用自动重新调用。用法
sa_handler
struct sigaction act, old; memset(&act, 0, sizeof(act)); act.sa_handler = func;//直接用sa_handler sigaction(SIGINT,&act,&old);
sa_sigaction(此用法跟上面一样)
struct sigaction act, old; memset(&act, 0, sizeof(act)); act.sa_sigaction = func;//用sa_sigaction act.sa_flags = SA_SIGINFO;//表示使用sa_sigaction sigaction(SIGINT,&act,&old);
sa_mask
```c sigemptyset(sigset_t *set);// 初始化信号集,清除所有信号。 sigfillset(sigset_t *set);// 添加所有信号到信号集中。 sigaddset(sigset_t *set, int signo);// 向信号集添加一个信号。 sigdelset(sigset_t *set, int signo);// 从信号集中删除一个信号。 sigismember(const sigset_t *set, int signo);// 检查一个特定信号是否在信号集中。
//example sigset_t sa_mask; sigemptyset(&sa_mask); sigaddset(&sa_mask,SIGINT); act.sa_mask = sa_mask;
### sigpending
> 用于检查当前进程的未决信号集,确定哪些信号已经被产生并等待处理,但尚未被当前进程捕获或忽略。
```c
int sigpending( sigset_t *set // 接收当前进程的未决信号集 );
//example
sigset_t set;
sigemptyset(&set);
sigpending(&set);//pending的信号被加入到set中
if(sigismember(&set,1)){
...
}
sigprocmask
用于在系统中,检查和更改进程的信号掩码mask,且是全程的,sigaction设置的sa_mask是临时的 ```c int sigprocmask( int how, // 可选: //SIG_BLOCK:把set内信号添加阻塞; //SIG_UNBLOCK:解除set内信号阻塞; //SIG_SETMASK:将信号掩码替换为set指定信号; const sigset_t *set, // 信号集合 sigset_t *oldset // 当前信号掩码 );
//example sigset_t set; sigempty(&set); sigfillset(&set);
sigprocmask(SIG_BLOCK, &set, NULL); sleep(5); siprocmask(SIG_UNBLOCK,&set, NULL);
### alarm
> 用于设置一个计时器,到期时内核会向该进程发送 `SIGALRM` 信号。如果程序没有捕获或忽略该信号,则其默认行为是终止进程。
```c
unsigned int alarm(unsigned int seconds);
//example
alarm(4);//一个进程只有一个计时器,重复使用alarm会更新为新的超时时间,返回值为原来计时器的剩余秒数
setitimer
高级版alarm
//getitimer,得到当前定时器的情况
int getitimer(
int which, // 定时器的类型
struct itimerval *curr_value //
);
struct itimerval {
struct timeval it_interval;// 间隔时间: 若被设置为非零值,定时器将变为周期性的
struct timeval it_value; // 定时器的剩余时间
// 当定时器的it_value达到0并触发信号后,it_value会被重新设置为 it_interval 的值,然后定时器再次开始计时
};
struct timeval {
long tv_sec; //秒
long tv_usec; //微秒
};
//settimer
int setitimer(
int which, // 定时器的类型
const struct itimerval *new_value, // 指定的新的定时器值
struct itimerval *old_value // 存储定时器的前一个值
)
which参数:指定定时器的类型。常用的类型包括:
ITIMER_REAL
:按照真实时间, 当时间到达, 发出一个SIGALRM
信号。ITIMER_VIRTUAL
:按照用户态代码执行时间计算, 当时间到达, 发出一个SIGVTALRM
信号。ITIMER_PROF
:按照用户态用户态和内核态代码执行时间计算, 当时间到达, 发出一个SIGPROF
信号。