信号
信号概念
信号机制
A给B发送信号, B收到信号之前执行自己的代码, 收到信号后, 不管执行到程序的什么位置, 都要暂停运行, 去处理信号, 处理完毕再继续执行, 与硬件中断类似——异步模式. 但信号是软件层面上实现的中断, 早期常被称为“软中断”
信号的特质: 由于信号是通过软件方法实现, 其实现手段导致信号有很强的延时性. 但对于用户来说, 这个延迟时间非常短, 不易察觉
每个进程收到的所有信号,都是由内核负责发送的,内核处理。
与信号相关的事件和状态
信号的状态: (1) 产生; (2) 未决状态, 产生和递达之间的状态。主要由于阻塞(屏蔽)导致该状态; (3) 递达, 递送并且到达进程
信号处理方式: (1) 执行默认动作; (2) 忽略(丢弃); (3) 捕捉(调用户处理函数)
Linux内核的进程控制块PCB是一个结构体, task_struct除了包含进程id, 状态, 工作目录, 用户id, 组id, 文件描述符表, 还包含了信号相关的信息, 主要指阻塞信号集和未决信号集
阻塞信号集, 未决信号集
阻塞信号集(信号屏蔽字): 将某些信号加入集合, 对他们设置屏蔽, 当屏蔽x信号后, 再收到该信号, 该信号的处理将推后(解除屏蔽后)
未决信号集:
- 信号产生, 未决信号集中描述该信号的位立刻翻转为1, 表信号处于未决状态. 当信号被处理对应位翻转回为0. 这一时刻往往非常短暂
- 信号产生后由于某些原因(主要是阻塞)不能抵达. 这类信号的集合称之为未决信号集. 在屏蔽解除前, 信号一直处于未决状态
信号4要素
每个信号也有其必备4要素, 分别是: (1)编号 (2)名称 (3)事件 (4)默认处理动作
默认动作:
Term:终止进程
Ign: 忽略信号(默认即时对该种信号忽略操作)
Core: 终止进程, 生成Core文件(查验进程死亡原因, 用于gdb调试)
Stop: 停止(暂停)进程
Cont: 继续运行进程
SIGKILL和19SIGSTOP信号, 不允许忽略和捕捉, 只能执行默认动作, 甚至不能将其设置为阻塞
另外需清楚, 只有每个信号所对应的事件发生了, 该信号才会被递送(但不一定递达), 不应乱发信号
基础API
kill
int kill(pid_t pid, int sig);
发送信号给指定进程,
参数: sig建议写宏的名字
raise
int raise(int sig);
给自己发送异常终止信号
没有参数返回值, 永远不会调用失败
abort
void abort(void);
该函数无返回
alarm
unsigned int alarm(unsigned int seconds);
设定定时器(每个进程只有一个定时器), 使用的是自然定时法, 时间运行不受当前进程的影响
参数: 秒, 当时间到达之后, 函数发出一个信号SIGALRM, alarm(0)
0表示取消闹钟设置, 返回0或剩余的秒数, 无失败
返回值: 上一个定时器还有多长时间发送信号
getitimer与setitimer
int getitimer(int which, struct itimerval *curr_value);
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
定时器, 实现周期性定时
参数:
which: 使用哪种定时器, 有三种定时器, 不同定时器的信号不同
自然定时: ITIMER_REAL: 发送4号SIGLARM; 计算自然时间
虚拟空间计时(用户空间): ITIMER_VIRTUAL 发送26号SIGVTALRM; 只计算进程占用cpu的时间
运行时计时(用户+内核): ITIMER_PROF, 发送27号SIGPROF; 计算占用cpu及执行系统调用的时间
old_value: 传出参数, 上一次设置定时器信息, 一般设置为NULL
struct itimerval {
struct timeval it_interval; // 定时周期
struct timeval it_value; // 第一次触发定时器的时间
};
struct timeval { // 两个值是相加的关系, 两个变量都需赋值, 否则是垃圾值
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
示例程序
kill用法
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <signal.h>
int main(int argc, const char *argv[]) {
pid_t pid = fork();
if (pid > 0) {
while (1) {
printf("parent process, %d\n", getpid());
sleep(1);
}
}
// child process
// 杀死父进程
else if (pid == 0) {
sleep(2);
kill(getppid(), SIGKILL);
}
return 0;
}
abort用法
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
int main(int argc, const char *argv[]) {
pid_t pid = fork();
if (pid > 0) {
// 父进程, 回收子进程资源
int s;
pid_t wpid = wait(&s);
printf("child died pid = %d\n", wpid);
if (WIFSIGNALED(s)) {
printf("die by signal: %d \n", WTERMSIG(s));
}
}
else if (pid == 0) {
// 自己给自己发送信号
//raise(SIGINT);
while (1) {
abort();
}
}
return 0;
}
/*
child died pid = 12779
die by signal: 6
*/
alarm用法
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
int main(int argc, const char *argv[]) {
int ret = alarm(5);
printf("ret = %d\n", ret);
sleep(2);
// 重新设置定时器
ret = alarm(2);
printf("ret = %d\n", ret);
while (1) {
printf("Hello World\n");
sleep(1);
}
return 0;
}
测试一秒钟程序能数多少数字
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
int main(int argc, const char *argv[]) {
// 设置定时器
alarm(1);
int i = 0;
while (1) {
printf("%d\n", i++);
}
return 0;
}
/*
timer ./a.out 查看程序执行时间
real = 用户 + 内核 + 损耗
*/
setitimer使用
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/time.h>
#include <signal.h>
int main(int argc, const char *argv[]) {
// 设置定时器
struct itimerval new_val;
// 第一次触发的时间
new_val.it_value.tv_sec = 2;
new_val.it_value.tv_usec = 0;
// 周期性定时
new_val.it_interval.tv_sec = 1;
new_val.it_interval.tv_usec = 0;
// 倒计时2s
setitimer(ITIMER_REAL, &new_val, NULL);
while (1) {
printf("haha\n");
}
return 0;
}