@bit::Shadow
✧(≖ ◡ ≖✿
目录
🔗博客typeid().name()的使用"C++进阶知识3.0"
信号引入
生活中的信号:红绿灯🚥、闹钟、敲门声等等。
信号的基本理解
中断正在做的事,信号是一种进程的异步通知机制。
信号的对象是"进程",进程处理信号有两种模式:阻塞等待模式(一般模式)和非阻塞模式。
信号的产生,相对于进程是异步的。
基本结论
1.信号处理前,进程在信号还没有产生时就已经准备好了信号的处理机制。信号注册表(kill -l)
2.信号处理不一定是产生就立即处理,可延迟处理 。block 1
3.信号有很多,信号源更多。
☆☆用户层、C库层、内核层
由浅到深:用户层---C库层---内核层
用户使用的系统调用实际上是C库封装的Linux内核代码,这也就解释了为什么有时候在man下的查询在内核中找不到。
| 层次 | 组件 | 角色 | 代码在哪里? |
|---|---|---|---|
| 用户应用层 | 你的C程序 (read(fd, buf, count)) |
调用者 | 你自己写的 .c 文件 |
| C库层 | glibc (GNU C Library) | 包装器 | glibc 源码库 (独立于内核) |
| 内核层 | Linux Kernel (sys_read()) |
真实干活的人 | Linux 内核源码 |
所以在man 2中查询的调用可能在Linux内核中找不到。像 handler_t 内核中根本没有,内核中是__handler_t 。
信号认识
信号可被键盘、程序产生
信号总类型和处理模式
kill -l #查询信号宏集

使用man 7 signal 查询 Core Term pause() SIGCHILD等信息。
分布准则
【1,31】:普通信号。
【32,33】:为线程库预留,不对程序开放。
【34,64】:实时信号。(对实时信号不作探讨)
信号处理动作
1.默认处理动作。像:
2.自定义处理动作。使用接口自定义的行为。
cpp
//函数指针的重定义,返回类型是void
typedef void (*handler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
//更合适的sigaction()
int sigaction(int signum, struct sigaction* act, struct sigaction* oact);
signal()
拦截Ctrl+c的进程终止信号
cpp
void handler_2(int) {
printf("Ctrl+C被执行了,已成功拦截\n");
}
void test2()
{
//signal重定义SIGINT2号信号
signal(SIGINT, handler_2);
}
sigaction()
cpp
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
忽略信号动作
指收到信号
例子
前台进程、后台进程
进程定义与特点
- 当运行可执行程序./pc后默认将运行为前台进程,其余进程均为"后台进程"。
- 只有前台进程方可从键盘读入数据,前台进程任何时候只能有一个。
- 后台进程可以有多个,前后台进程均可以向显示器上打印。
后台进程
运行及监视
bash
//运行
./a.out &
//监视
jobs -l #带pid '-'代表上一次被替换的进程 '+'代表替换方
jobs #不带pid
实例
后台运行并查看


*前后台进程切换
后台--->前台
fg 【任务号】:将后台进程提到前台运行
前台--->后台
1.Ctrl + z :暂停(由于前台任务无法接受输入,所以必须先暂时性的暂停处理)。
2.bg 【任务号】:对指定任务号的暂停前台处理,将前台任务正式提到后台。
部分信号接口
kill() raise()
cpp
//kill -[signum] [pid]的系统调用格式
int kill(pid_t pid, int signum);
//send sig to caller
int raise(int signum);
特殊的void abort(void)
cpp
void abort(void); //特殊的恢复执行 (6)SIGART 信号
功能:
1.没有进行6号信号的捕捉重定义,正常执行终止进程。
2.设置了自定义信号处理函数。在执行自定义处理函数返回后,系统会恢复默认的 SIGABRT 行为,再次发送信号,最终终止进程。
*程序异常的信号处理
此处程序异常指在执行流中遇到的异常,像:除0 空指针解引用。
在库的制作与2.0原理文章中,MMU将寄存器CR3(存储物理地址)的地址转换为物理地址。此处MMU的作用是接受程序异常信号并返回给CPU,执行接下来的进程调度或其他工作,MMU是中间硬件单元。
闹钟alarm
cpp
unsigned int alarm (unsigned int seconds);
为当前设置定一计时闹钟,在seconds秒后OS为当前进程发送 (14)SIGALRM 信号执行此信号的动作,默认情况下是终止当前进程。
返回值

循环闹钟
利用闹钟结束后会产生信号调用动作催生循环。
cpp
void handler_2(int)
{
printf("14:ALRM信号\n");
sleep(1);
// 恢复默认动作 SIG_DFL
//typedef void (*handler)(int)
signal(SIGALRM, SIG_DFL);
printf("默认动作\n");
}
int main()
{
alarm(1);
signal(SIGALRM, handler_2);
sleep(1);
return 0;
}
进程调度与时间片
这是Linux2.6.18版本的时间片控制
使用计时器决断进程调度工作
cpp
struct timer_list {
struct list_head entry;
unsigned long expires;
void (*function)(unsigned long);//超时后执行的回调函数
unsigned long data;//传递给回调函数的参数
//指向该定时器所属的时间轮基座,水桶由小变大由上到下接水
struct tvec_t_base_s *base;
};
其中base时间轮算法:
使用槽位的方法来计时
cpp
struct tvec_t_base_s {
spinlock_t lock; // 保护该时间轮的锁
unsigned long timer_jiffies; // 该CPU上次处理定时器的时间
struct timer_list *running_timer; /* 当前正在执行的定时器 */
tvec_root_t tv1; 秒针 /* 第一级时间轮:256个槽位 */
tvec_t tv2; 分针 /* 第二级:64个槽位 */
tvec_t tv3; 时针 /* 第三级:64个槽位 */
tvec_t tv4; /* 第四级:64个槽位 */
tvec_t tv5; /* 第五级:64个槽位 */
};
信号的保存
信号的执行流程图
即在信号递达之前存在一"未决"状态,表示信号未被递达。

管理模式
2张位图+1张函数指针表管理信号。 Linux内核有所不同此处仅做参考
usigned int位图表【1,31】第32位未使用
1.位图的下标位置 对应信号宏。
2.位内0/1代表是否作用。

block表:表示是否对该信号发送了阻塞。
pending表:表示信号是否产生。 "信号产生"后在进入动作函数前pending位已被置零。
位图表的基本单位sigset_t
🔗博客typeid().name()的使用"C++进阶知识3.0"
cpp
std::cout << typeid(sigset_t).name() << std::endl;
// 输出10__sigset_t
| 部分 | 含义 |
|---|---|
10 |
后面紧跟的标识符(__sigset_t)的长度是 10 个字符 |
__sigset_t |
实际的类型名称(系统层--linux内核层) |
内核:
|------------------------------------------|--------------------------|
| typedef unsigned long __kernel_sigset_t; | /* at least 32 bits */ |
感谢支持,持续更新
欢迎关注
