第687章 看不见的沙漏!

作品:《四合院开局四八,八岁带妹逃荒

    `KERNEL PANIC - OUT OF MEMORY`


    血红色的屏幕,如同地狱的判词,瞬间将整个实验室,从完美的乌托邦,拉回了残酷的现实。


    “内存耗尽?”


    “怎么可能!怎么会内存耗尽!”


    黄建功猛地从椅子上弹了起来,不敢相信自己的眼睛。


    内核恐慌!


    这个他们以为,在解决了“堆栈隔离”问题后,就再也不会见到的噩梦,竟然以一种全新的,更加诡异的方式,再次降临!


    而且,错误的原因,是“内存耗尽”!


    这怎么可能?


    他们的系统,现在总共就运行了五个极其简单的任务。


    内核本身,加上五个任务的堆栈,所占用的内存,加起来也不到100KB。


    而“盘古之心”拥有整整4MB的物理内存!


    这就好像,一个拥有着巨大湖泊的人,却被告知,他因为缺水而渴死了!


    这完全不符合逻辑!


    “快!查!”黄建功对着整个实验室,发出了嘶吼,“查明原因!是不是硬件出了问题?内存条坏了吗?”


    周老立刻带着人,冲向了硬件检测台,对“盘古之心”的内存模块,进行紧急检测。


    而钱学敏和孙立国,则第一时间,冲到了主控台前,开始疯狂地,翻阅系统崩溃前,留下的最后几页日志。


    试图从那片数据的汪洋中,找到一丝线索。


    几分钟后。


    周老脸色凝重地走了回来。


    “内存,没有问题。”


    “我们做了最高强度的读写测试,4MB的每一个字节,都完好无损。”


    这个消息,让所有人的心,沉得更深了。


    硬件没问题。


    那就意味着,问题,百分之百,又出在了他们引以为傲的,“天枢”内核上!


    “建功,你看这里!”


    钱学敏突然指着屏幕上的一片数据,她的声音,带着一丝不易察觉的,颤抖。


    黄建功立刻凑了过去。


    那是一段由内核内存管理器,在每次分配内存时,打印出的调试日志。


    `[ 1859.235] kmalloc: allocated 64 bytes at 0x0012c800. free_mem: 3.82 MB`


    `[ 1859.578] kmalloc: allocated 64 bytes at 0x0012c840. free_mem: 3.82 MB`


    `...`


    这些日志,记录了每一次内核为内部数据结构(比如任务控制块TCB)分配内存的动作,以及分配后,系统剩余的空闲内存大小。


    刚开始,一切正常。


    系统剩余内存,一直稳定在3.8MB左右。


    但当黄建功将日志,快速地向后翻动,翻到接近系统崩溃的那个时间点时。


    他的瞳孔,猛地一缩。


    `[ 1860.112] kmalloc: allocated 64 bytes at 0x003f8000. free_mem: 0.12 MB`


    `[ 1860.455] kmalloc: allocated 64 bytes at 0x003f8040. free_mem: 0.12 MB`


    `...`


    `[ 1861.988] kmalloc: allocated 64 bytes at 0x003fffc0. free_mem: 64 bytes`


    `[ 1862.331] kmalloc: FAILED! Cannot allocate 64 bytes!`


    日志,清晰地,记录下了整个“死亡”的过程!


    系统的空闲内存,在以一种肉眼可见的速度,被不断地蚕食!


    从3.8MB,到1MB,到100KB,再到最后的几十个字节……


    直到最后,当内核再次试图申请64个字节的内存时,它绝望地发现,整个内存池,已经空了。


    于是,它触发了最严重的“内核恐慌”,整个系统,轰然倒塌。


    “这……这到底是为什么?”一个年轻的研究员,声音发颤地问道,“我们的系统,从启动之后,就再也没有创建过新的任务。为什么内核会一直,不停地,在申请新的内存?”


    是啊。


    为什么?


    这就像一个看不见的沙漏。


    在所有人都没有察觉到的情况下,一点一点地,漏光了整个系统的生命之源。


    黄建功死死地盯着那几行日志,大脑在疯狂地运转。


    `kmalloc`... `kmalloc`... `kmalloc`...


    内核在不停地分配内存。


    但是,它分配了,却没有“释放”!


    一个可怕的词汇,如同惊雷般,在他的脑海中炸响。


    “内存泄漏(Memory Leak)!”


    当他将这个词说出口时,钱学敏的脸色,瞬间变得惨白。


    她也想到了。


    在计算机科学中,这是一种最常见,也最阴险的错误。


    程序在运行过程中,不断地向系统申请内存,但当这些内存不再被使用时,却没有及时地,将它们归还给系统。


    日积月累。


    这些被“遗忘”的,无法被再次使用的内存,就像血管里的血栓,会一点一点地,堵死整个系统。


    “我们在哪里泄漏了内存?”黄建功喃喃自语。


    他开始疯狂地,在脑中,回顾“天枢”V0.3的每一行业务逻辑。


    创建任务时,分配TCB,分配堆栈。


    这都是一次性的。


    之后,系统就在五个任务之间,不断地切换,调度。


    切换……调度……


    每一次切换,内核都需要保存当前任务的上下文……


    每一次调度,内核都需要测量切换的成本,计算新的时间片……


    这个过程,会产生新的内存分配吗?


    黄建功的目光,猛地落在了那段关于“上下文切换成本”的日志上。


    `Switch cost: 12 us.`


    `Switch cost: 15 us.`


    `Switch cost: 73 us.`


    为了计算这个成本,内核需要在切换前后,两次读取HPET的计数值。


    然后,进行一次减法运算。


    为了存储这两个计数值,和那个最终的差值,内核需要……临时的变量!


    而这些临时变量,是存放在……


    “堆栈里!”钱学敏和黄建功,几乎在同一时刻,失声喊了出来!


    他们瞬间,就定位到了那个“看不见的沙漏”,到底藏在哪里!


    每一次!


    每一次时间中断发生时,CPU都会将当前任务的执行现场,压入内核的堆栈。


    然后,跳转到中断服务程序。


    中断服务程序,也就是他们的调度器,为了计算切换成本,又在内核堆栈上,定义了几个临时的变量。


    当调度完成,切换到下一个用户任务时。


    那些被中断压入的用户任务现场,会被正确地弹出。


    但是!


    他们忘了!


    他们忘了去“清理”,那些在中断服务程序中,使用过的,临时的,属于内核自己的,堆栈空间!


    每一次中断,内核的堆栈指针,都会因为这些被遗忘的临时变量,而向下,移动那么微不足道的,几个字节。


    一次中断,几个字节。


    一秒钟,一百次中断,就是几百个字节。


    三十分钟……


    黄建功的心,凉了半截。


    他终于明白,那4MB的内存,是怎么被一点一点,耗光的了。


    不是用户任务的错。


    也不是内存管理器的错。


    是他们自己!


    是他们亲手编写的,那个被他们视为“绝对公平”的调度器,在每一次“心跳”的时候,都在为自己,挖深一点点的,坟墓!