性能文章>从猫蛇之战看内核戏CPU>

从猫蛇之战看内核戏CPU原创

2年前
708702

小时候曾经目睹过猫与蛇战斗,面对昂首发威的毒蛇,小猫不慌不忙,挥舞前爪,沉着冷静,看准时机进攻,胆大心细。

在网上搜一下,可以看到很多猫蛇战斗的照片,看来猫蛇之战是很多人都喜欢看的“精彩节目”。

image.png

再来一张更清晰一些的。

image.png

之所以想到猫蛇之战,是因为今天在“格友会讲”群里一位同行问了一个很有深度的问题。

(前方内容只适合技术控,其他读者止步)

简单说问题是,调试器是如何访问不能访问的内存的。

看了这个问题,我立刻觉得这位同行是有功力的。因为普通的程序员是问不出这样的问题的。

要理解这个问题,必须有些底层的基础。

第一个基础是要有保护模式的概念。很多同行都知道,今天的CPU是运行在所谓的保护模式中,软件访问的内存空间都是虚拟空间。而且这个虚拟空间中的内容是分三六九等的,是分平民区和富人区的,是分道路和深坑的。因为此,访问内存时是要小心的,有些地方可以访问,有些地方一访问就可能出大问题的,爆炸崩溃甚至“死亡”的。
大多数的应用程序崩溃和系统蓝屏都是因为访问了不该访问的地方。

第二个基础是对调试器有比较深的认识,知道在调试器里可以放心大胆地想访问哪里就访问哪里,不用那么小心。

举例来说,在普通程序里,如果访问空地址,那么不死也伤半条命(处理不好,就被系统杀了)。但是在调试器里,dd 0没有问题,调试器会给出一串串可爱的问号,代表不可访问,子虚乌有。

6: kd> dd 0
00000000`00000000  ???????? ???????? ???????? ????????
00000000`00000010  ???????? ???????? ???????? ????????
00000000`00000020  ???????? ???????? ???????? ????????
00000000`00000030  ???????? ???????? ???????? ????????
00000000`00000040  ???????? ???????? ???????? ????????
00000000`00000050  ???????? ???????? ???????? ????????
00000000`00000060  ???????? ???????? ???????? ????????
00000000`00000070  ???????? ???????? ???????? ????????

那么问题来了,为啥普通程序一碰就爆炸,而调试器访问却安然无恙呢?

坦率说,第一次在脑海中出现这个问题时,也令我困惑了一阵。直到后来发现了内核中的一个神秘机制。这个机制是跨操作系统的,Windows中有,Linux也有,而且都是相同的名字,叫Probe。

有点令人诧异的是,连函数名很类似,比如Windows(NT内核)中的两个函数为:

6: kd> x nt!probe*
fffff800`06581d70 nt!ProbeForWrite (void)
fffff800`06518ad0 nt!ProbeForRead (<no parameter info>)

而Linux内核中的两个函数为:

root@gedu-VirtualBox:/home/gedu/labs/linux-source-4.8.0# sudo cat /proc/kallsyms | grep "\bprobe_ke"
ffffffff811a5f00 W probe_kernel_read
ffffffff811a5fc0 W probe_kernel_write

搜一下KDB/KGDB的源代码,可以看到很多地方调用了上面两个函数:

image.png

简单来说,内核里封装了两个特殊的函数,提供给包括调试器在内的一些特殊客户使用。

接下来的问题是,probe函数内部是如何做的呢?有关的源代码如下。

image.png

更完整的请(https://elixir.bootlin.com/linux/v4.8/source/mm/maccess.c#L23
其中的关键是在__copy动作前后分别有:

    pagefault_disable();
    pagefault_enable();

也就是先禁止了pagefault,访问好之后再启用。这有点像是在耍蛇之前,先把它的毒牙包上。

继续深挖,在目前的Linux内核实现中,是维护一个计数器:pagefault_disabled。

image.png

在处理页错误的do_page_fault函数中,会判断这个标志,如果发现禁止条件,则忽略这次访问错误。

讲到这里,问题说清了一半,要继续深追的话,还有一些细节,今天有点晚了,改日再叙。

请先登录,感受更多精彩内容
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

在调试器里看LINUX内核态栈溢出
图灵最先发明了栈,但没有给它取名字。德国人鲍尔也“发明”了栈,取名叫酒窖。澳大利亚人汉布林也“发明”了栈,取名叫弹夹。1959年,戴克斯特拉在度假时想到了Stack这个名字,后来被广泛使用。
LONG究竟有多长,从皇帝的新衣到海康SDK
转眼之间初中毕业30年了,但我仍清楚的记得初中英语的一篇课文,题目叫《皇帝的新装》(“The king’s new clothes”)。这篇课文的前两句话是:”Long long ago, there
雕刻在LINUX内核中的LINUS故事
因为LINUX操作系统的流行,Linus 已经成为地球人都知道的名人。虽然大家可能都听过钱钟书先生的名言:“假如你吃个鸡蛋觉得味道不错,又何必认识那个下蛋的母鸡呢?” 但是如果真是遇到一个“特别显赫”
从猫蛇之战再看内核戏CPU
连续写了几天的代码,有些疲倦,吃过晚饭,换个工作方式,继续和大家聊猫蛇之战。蛇不仅丑陋,而且可能伤人害命,是邪恶的象征。猫与蛇战,代表着讨伐奸邪,是正义之战。猫与蛇战,技艺娴熟,举重若轻,叫人拍手叫绝
如何使用Linux内核中没有被导出的变量或函数?
本文详细介绍了使用EXPORT_SYMBOL宏导出函数或变量、使用kallsyms_lookup_name()查找函数或变量的虚拟地址以及内核模块中直接使用内核函数的虚拟地址等3种方案解决没有被EXPORT_SYMBOL 相关的宏导出的变量或函数不能直接使用的问题
LINUX网络子系统中DMA机制的实现
我们先从计算机组成原理的层面介绍DMA,再简单介绍Linux网络子系统的DMA机制是如何的实现的。 计算机组成原理中的DMA 以往的I/O设备和主存交换信息都要经过CPU的操作。不论是最早的轮询方式,
那些吃CPU的大户
最近沉浸在代码的世界里,处于两耳不闻窗外事的状态。今晚偶有闲暇,看了一眼任务管理器,IDLE进程的时间居然有800多小时,还有多个进程的CPU净时间达到小时级别。如下图所示,IDLE进程的CPU净时间
内存泄漏(增长)火焰图
本文总结了在分析内存增长和内存泄漏问题用到的4种追踪方法得到有关内存使用情况的代码路径,使用栈追踪技术对代码路径进行检查,并且会以火焰图的形式把它们可视化输出,在Linux上演示分析过程,随后概述其它系统的情况。