绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
为什么你学得比别人慢?
2022-08-29 15:46:53

前几天我写了讲解 Linux 0.11 信号原理的文章,第48回 | 信号,从不知道信号的实现原理,到理解了它的原理,再到终写成文章,我只用了一个多小时的时间就搞定了。


当然不是说很深入的那种,但我觉得从不了解到了解并写成文章理顺了这个过程所花的时间,应该算是很短的了。


后来我复盘了一下,为什么我能在很短的时间完成这些事呢?


------


我理解并讲解信号原理的切入点是,为什么按下 CTRL + C 后程序就退出了。


首先我脑子里一定是先有了一个大概的判断,就是按下 CTRL + C 后,一定触发了某段程序,这个程序又给进程"发送"了一个叫信号这个概念的"东西",然后又一定有另一段程序,对"信号"做出了反应,使得进程退出。


关于按下 CTRL + C 后怎么触发了某段程序这一点,我在写 第42回 | 用键盘输入一条命令 时就已经搞清楚了这一点。




所以这块对我来说是没有任何障碍的。


直接顺藤摸瓜找到了我想看到的代码,就是给进程"发送"了一个叫信号这个概念的"东西"这段代码。

// kernel/chr_drv/tty_io.c
void tty_intr (struct tty_struct *tty, int mask) {
    int i;
    ...
    for (i = ; i < NR_TASKS; i++) {
        if (task[i] && task[i]->pgrp == tty->pgrp) {
            task[i]->signal |= mask;
        }
    }
}

这段代码中把进程 task_struct 结构中的 signal 中的某一位进行了改变。


这同样也非常好理解,因为在 第44回 | 进程的阻塞与唤醒 中就是通过修改 task_struct 中的 state 字段来改变了进程的状态,然后由另外一段程序通过读取这个状态来产生不同的行为。


此外,在 认认真真的聊聊"硬"中断 与 认认真真的聊聊"软"中断 中,硬中断与软中断的触发,与信号的触发是更为类似的,都是通过修改某一位的值,来达到似乎实时触发的效果。


所以这块对我来说是依然是没有额外的理解成本的。


再往后,不同信号的处理方式是不同的,这也和不同中断的处理方式是不同的是一个道理。中断的处理是寻找中断处理函数,那么信号的处理也一定是寻找信号处理函数,是不是很容易想象?


比如软中断通过软中断标志位确定是哪个软中断,再通过软中断向量表确定执行哪个软中断处理程序。



所以信号也一定是一样的,tast_struct 中的 signal 就是信号的标志位,那么一定有另一个类似信号向量表的东西,存储着信号处理函数。


这块的逻辑对我来说也是合理的猜测。


顺藤摸瓜,这个东西也存储在 tast_struct 中,叫 sigaction 数组,同样,通过处理信号的代码也可以佐证这一点。

// kernel/signal.c
void do_signal (long signr ...) {
    ...
    struct sigaction *sa = current->sigaction + signr - 1;
    sa_handler = (unsigned long) sa->sa_handler;
    // 如果信号处理函数为空,则直接退出
    if (!sa_handler) {
        ...
        do_exit (1 << (signr - 1));
        ...
    }
    // 否则就跳转到信号处理函数的地方运行
    *(&eip) = sa_handler;
    ...
}

可以看到,如果信号处理函数为空,那么就 do_exit 导致进程退出,如果不为空,就执行相应的处理函数。


所以按下 CTRL + C 导致退出,一定是因为信号处理函数为空导致的,这就基本解惑了。


那么,按下 CTRL + C 后触发的信号是什么,以及信号都有哪些种类,在写 第42回 | 用键盘输入一条命令 时提到了 UNIX 的 termios 标准,通过 cc_t 字段的类型以及 Linux 0.11 源码可知。



CTRL + C 表示 INTR 字符,而这个字符会触发 SIGINT 信号。



再通过这两张表格,我们还可扩展得知其它字符模式以及信号种类,这一块知识体系就慢慢建立起来了。


还有很多其它的细节,比如执行信号处理函数的方式,是通过给 eip 寄存器赋值。

// kernel/signal.c
void do_signal (long signr ...) {
    ...   
    *(&eip) = sa_handler;
    ...
}

这和 execve 变换到一个新程序运行的方式是一样的,这个在 第35回 | execve 加载并执行 shell 程序 时解释 execve 原理的时候就讲过了。


再比如,信号和管道都属于进程间通信的一种方式,而管道的原理,我通过 第45回 | 解析并执行 shell 命令 时就讲到了,因此相当于对和信号这个概念平级的一个概念有了提前的了解。


等等等等,还有很多。


------


所以,我想说的是,为什么学习底层知识对上层知识有帮助,为什么基本功扎实了学习新东西的速度会变快。


很多人不相信这个道理,总觉得我知道操作系统原理了,对我理解 SpringBoot 能有啥帮助。


这个问题确实不好解释,也没必要解释。你看我之所以理解信号的原理这么快,正是因为我已经对键盘输入、中断、管道、进程调度、execve 等等和信号看似没有太大关系的知识有所了解。


这些周边知识,有的是直接帮助理解信号,比如键盘输入流程,我就不用因为要学信号而重新看一遍了。有的是间接帮助,比如软中断的流程和原理和信号有很大相似的部分,这就加速了我对信号的理解。


当然还有很重要的原因,是我通过不断翻看 Linux 0.11 源码,已经对其各个部分很熟悉了,所以能够快速定位到我想看的逻辑,并快速跳过我已经知道的代码,看未知的部分。


所以,为什么你学得比别人慢?我还是没有给出直接正面的回答,你心里有答案了么?


这个道理,也影响了我的生活,我们总认为这个没用,那个没用,其实我们看到的很多都是没有直接作用,但是,往往终起关键作用的,正是那些参与间接帮助的事情。


对于这点,我现在深信不疑。


分享好友

分享这个小栈给你的朋友们,一起进步吧。

唠唠linux常用知识点
创建时间:2020-06-16 09:26:38
就唠唠常用的一些服务 命令 架构体系
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

栈主、嘉宾

查看更多
  • 长得太帅没人爱
    栈主
戳我,来吐槽~