简 述: 此处指 Linux 中系统内核发出的信号;而不是之前 Qt 学习的信号。
[TOC]
本文初发于 “偕臧的小站“,同步转载于此。
编程环境:
💻: uos20
📎 gcc/g++ 8.3
📎 gdb8.0
💻: MacOS 10.14
📎 gcc/g++ 9.2
📎 gdb8.3
信号初识:
特点:
- 简单
- 携带信息量很少
- 用在某个特定的场景中
型号的状态:
- 产生原因:
- 键盘 Ctrl + C
- 命令:kill
- 系统函数:kill()
- 软条件:定时器
- 硬件:段错误,除 0 错误
- 未决状态 – 没有被处理
- 递达状态 – 信号被处理了
- 产生原因:
处理方式:
- 忽略
- 捕捉,然后自定义动作
- 执行了默认动作
信号的四要素:
信号名、信号编号、信号默认动作、事件描述
“动作(Action)”栏 的 字母 有 下列 含义:
A 缺省动作是结束进程. B 缺省动作是忽略这个信号. C 缺省动作是结束进程, 并且核心转储. D 缺省动作是停止进程. E 信号不能被捕获. F 信号不能被忽略. 译注: 这里 “结束” 指 进程 终止 并 释放资源, “停止” 指 进程 停止 运行, 但是 资源 没有 释放,有可能 继续 运行。
通过 man 文档查看信号
- 执行
man 7 signal
- 注意(man 手册中有写):
SIGKILL
、SIGSTOP
这两个信号不能够被捕捉,阻塞,忽略的
The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.
- 执行
概念:阻塞信号集,未决信号集
- 是在 PCB 中
- 阻塞信号集:让信号处于一个未决的状态
- 未决信号集:如果信号被阻塞了,该信号集会对阻塞的信号做记录
图解进程产生和处理:
kill() 函数:
作用: 发射信号给指定进程、或者同组的信号。
int kill(pid_t pid, int sig);
参数:
- pid:
- pid > 0; 发送信号给指定的进程
- pid = 0; 发送信号给 调用 kill 函数进程属于同一个组的所有进程
- pid = -1;如果用户拥有超级用户权限,则信号将被发送到所有进程
- pid < -1;取 |pid| 发给对应进程组
- sig:
- 推荐使用完整的宏名称而非数字,在少数发行版下,可能指定宏对应的数值有变化
- pid:
返回值:
- 成功: 0
- 失败:-1(ID 非法,信号非法,普通信号杀 init进程等权限级别问题,设置 errno)
写一个小的例子验证 kill 信号的使用,子进程在 5S 后,通知系统内核发送 SIGKILL 给它的父进程,然后系统将父进程杀死。
int main(int argc, char *argv[])
{
pid_t pid = fork();
if (pid > 0) {
while (true) {
printf("this is a parent process = %d\n", getpid());
sleep(1);
}
} else if (pid == 0) {
sleep(5);
kill(getppid(), SIGKILL);
}
return 0;
}
运行效果:
raise() 函数:
作用: 自己给自己发射信号。在单线程程序中,它相当于 kill(getpid(), sig);
在一个多线程程序中,它等同于 pthread_kill(pthread_self(), sig)。
int raise(int sig);
比较简单,但依旧写一个例子使用一下:子进程给机子发射终止进程的信号。然后父进程在回收子进程的时候,答应其死亡的信号值
int main(int argc, char *argv[])
{
pid_t pid = fork();
if (pid > 0) {
int s;
pid_t wpid = wait(&s);
printf("this is a child process dide pid = %d\n", wpid);
if (WIFSIGNALED(s))
printf("dide by signal: %d\n", WTERMSIG(s));
} else if (pid == 0) {
raise(SIGINT); //给自己发射信号
}
return 0;
}
代码中 19 行,选中的 SIGINT
信号,默认动作是终止进程。其对应的事件为 当用户按下<ctrl + c>组合键时候,用户终端向正在运行中的 (由该终端启动的)程序 发出此信号。
运行截图:
abort() 函数:
作用: 给自己发送异常终止的信号。该函数没有参数和返回值,也永远不会调用失败。
void abort(void);
定时器:
alarm() 函数:
unsigned int alarm(unsigned int seconds);
设置定时器(每一个进程只有一个定时器)
使用的是自然定时法则;不受进程状态的影响
当时间到达之后,函数发出一个信号
SIGALRM
- SIGALRM – 调用 abort() 函数时候产生该信号 – 终止进程并产生 core 文件
返回值:始终是返回上一次调用此函数,还剩些的时间。
写一个例子:
int main(int argc, char *argv[]) { int ret = alarm(5); printf("ret = %d\n", ret); sleep(2); ret = alarm(10); //重新设定定时器,返回值是返回之前闹钟的剩余的时间 printf("ret = %d\n", ret); while (true) { printf("-------test-----\n"); sleep(1); } return 0; }
分析:
这里 ret = 0 ,是因为第一次调用,它的上一次的剩余时间为 0;然后 ret = 3 是因为 5 - 2 s,然后定时器被充重置了新的 10 s。故后面会打印 10 次输出语句,然后收到系统的终止信号,杀死本进程。因为是执行的
alarm()
的默认动作,为终止进程,且没有捕捉该函数发射的对应的信号,也没有定义自定义动作,所以本进程会被终止。运行结果:
分析程序运行的损耗:
实际耗时 == 用户 + 系统 + 损耗
写一个例子,检测一下计算机在 1s 内可以计算多少个数字?
代码如下:
int main(int argc, char *argv[]) { alarm(1); int n = 0; while (true) { printf("%d\n", n++); } return 0; }
运行演示:
分析:其中损耗文件来自文件 IO 操作
输出到终端: 1.012 = 0.31 + 0.34 + 损耗 (约 70w)
输出到文件: 1.012 = 0.93 + 0.07 + 损耗 (约 800w)
//执行 time ./myAlarmCount(输出到终端) 0.31s user 0.34s system 64% cpu 1.012 total (输出到 692977) //time ./myAlarmCount > file(输出到文本) 0.93s user 0.07s system 98% cpu 1.012 total (输出到 8686215)
setitimer() 函数:
- 作用: 定时器,并且实现周期性 定时。
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
struct itimerval {
struct timeval it_interval; /* 定时器的循环周期 */
struct timeval it_value; /* 第一次触发定时器的时间 */
};
struct timeval {
time_t tv_sec; /* 秒 */
suseconds_t tv_usec; /* 微秒 */
};
参数:
- which:
- ITIMER_REAL: 自然定时法,会发出信号
SIGALRM
- ITIMER_VIRTUAL:虚拟定时法,只会计算 用户时间 ,对应信号
SIGVTALRM
- ITIMER_PROF: 只计算 用户时间 + 系统时间 ,对应发射
ITIMER_VIRTUAL
信号
- ITIMER_REAL: 自然定时法,会发出信号
- new_value:我们需要设置的参数,总的定时时间是
tv_sec + tv_usec
之和 - old_value:传出参数,传出上一次定时器的设置,一般用不到,用 NULL
- which:
写一个例子:
int main(int argc, char *argv[]) { itimerval time; // time.it_interval = 3; //每隔 3s 一次循环定时 //第一次触发定时器的时间为 5s + 3ms time.it_value.tv_sec = 5; //5s time.it_value.tv_usec = 3; //3 ms setitimer(ITIMER_REAL, &time, NULL); while (true) { printf("-----printf()-----\n"); sleep(1); } return 0; }
运行效果:
下载地址:
欢迎 star 和 fork 这个系列的 linux 学习,附学习由浅入深的目录。