若干关于 file system driver stack

本文深入探讨了文件系统读写请求(IRP)如何被处理并流转至底层设备驱动的过程。涉及多个设备堆栈(devicestack),包括文件系统、卷影复制(volsnap)、分区管理(partmgr)等多个组件及它们之间的交互。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

写这个文章的初衷是想知道究竟一个读写文件的irp都是怎样被处理的.....

大家都知道这样的一个读写文件irp是发送给file system的driver的file system把这个irp交给了下层的device

这个device叫logical volume device,它由device的vbp里面的realdevice指针指出(不一定就会是这个device,而应该是这个device所在的stack 的最上层的device).那这个device是个什么东西呢?它是怎么来的呢?按照道理,这个irp应该被发送到disk的驱动去才对?那个这个 device是disk的驱动创建的么?如果不是,那中间都有些什么步骤呢?

本文就是回答上面这些问题的.

用softice看看就知道file system转发的irp并不是vpb指向的那个device,而是另外一个叫volsnap的driver创建的device. 再跟踪看看,volsnap创建的device是跟vpb的device在同一个stack里面,而且volsnap创建的device的下一个 device就是vpb的device.

好了.至少知道了确实file system driver要把irp转发到vpb指向的device去.那接下来呢?irp又会到哪里?

再跟踪下去.irp到了一个由partmgr.sys创建的device里,看看这个partmgr...它是一个disk driver 的filter(多用注册表,当初我在这里花了好长时间才发现这个东西是一个filter),所以partmgr把这个irp转发到了disk driver.disk driver接着发给了acpi.sys,然后acpi.sys转发给了atapi.sys创建的一个device去,看看这个device的 stack,已经是1了,irp就到尽头了,接下来的就不是irp的流程了,而是所谓的port/miniport流程了.

大致的把整个的结构过了一次,里面有很多的疑问,下面要一一解释,最好是能用device tree看看这个过程,看看哪些是pdo哪些是fdo,看看每个device的stack值都是多少,自己模范下看看能不能了解irp的流程.

上面的整个irp的流程是很大的.顶层device的stack达到了8,首先你就会奇怪这么庞大一个device stack是怎么建立起来的?

先说最上面的部分 file system在受到一个mount的fs control的时候,创建了一个无名的device,并且保存了vpb 的 realdevice所在的stack的最上层的device,以后所有的irp都转发到这个保存的指针上面.注意这里并没有attach到那个 device的stack上面,而是直接使用指针call driver的.这里会有一个问题,在file system mount以后,再attach到volume device的stack上的device 收不到 file system传递过来的irp,至于这个问题怎么解决,msdn里面有说,把这个filter driver设置成启动时加载,至于为什么这样作了就能行,这个在后面会有解释.

在我的机器上,file system保存的device是由volsnap创建的一个filter device,在这个filter device下面是vbp指向的device,它有名字叫HarddiskVolume1(名字不一定就是这个),这个device是一个pdo,注意它 是一个pdo,按照常规 pdo的创建不是由系统主导的,而是由driver主导了,它一般不在adddevice里面创建,而是由driver在需要的时候手动创建的.为什么说 到这个细节呢?因为我们没有这个driver(名字叫ftdisk.sys)的源代码,在反汇编的时候,明白了这个以后比较有目标,这个 ftdisk.sys的代码是非常简单的,ida反汇编一下就发现harddiskv olumeX这个device并没有attach到什么stack上面(也算是pdo的一般做法),但是这个pdo却保存了两个指针,一个指向另外一个 pdo(70h),一个(74h)指向这个pdo的stack的最顶的device,而且以后的irp它都发给了这个device,这个device也就 是由partmgr创建的device了.

partmgr其实只是一个filter,挂接到disk driver上面的,那么接下来的stack创建就比较简单了.用device tree向前看,partmgr创建的是一个filter,它attach的device叫dr0,是一个fdo,它对应的pdo叫 idedevicepotolo-3,这个部分的过程就根据的容易了,disk driver是有源代码的,仔细看看就能明白其中的创建关系.看清楚了,这个pdo的stack值是1,所以irp到这里就算是尽头了.

再向下又是一个fdo,ideport(atapi.sys),它创建了上面的那个pdo,ideport我没有详细的反汇编分析,不过可以推测得 出来,它对应得pdo是ide0channal0,而这个pdo又是由pciide创建的,你可能会注意到中间多了一个filter acpi.sys创建的一个device,也许你会注意到这个acpi.sys到处创建filter.

再往下就是pci,acpi_hal这些device了,他们的行为都很正常.不详细说了(其实是我没有详细的研究,只有些想当然的想法,不敢拿出来说)

到此,回顾下整个device stack,可以看到这里涉及到好多的device stack,irp并没有按照一个stack 走到底.

最上面的是file system的device |fs filter| <-比如是filemon |fs volume| <-一般没有名字,由filesystem在mount的时候创建

fs的volume保存有新的stack(partmgr device的stack? 叫这个名字么?)的最上层device的指针,irp转移到这个stack |volsnap 创建的无名device | |ftcontrol创建的storage volume device|

这个device有保存有另外一个stack顶层device的指针,irp再次转移 |partmgr创建的无名filter device| |disk创建的DR0 fdo | |acpi创建的filter | |atapi创建的pdo |

这个就是irp的全流程,它经历了3个device stack.

呼呼 吐口气吧.......

本来这个文章写到这里就算完成了.... 但是我还有些心得要写出来.这些属于是无心插柳的结果.只是在我跟踪上面这些结果的时候额外的收获.

先看device tree里面,pnp方式查看,你会注意到有很多的pdo属于pnpmanager,但是他们却没有对应的fdo.很奇怪吧,再看这些pdo的名字,你会发现partmgr赫然在目.奇怪了,它怎么有个fdo呢?

首先要明确的就是凡是有填充adddevice域的driver都会对应一个pdo,这个是必然的,因为adddevice传递进来的参数就有一个 是pdo,还有一个事情就是在某种情况下,即使不填充adddevice域,pnpmanager还是会创建一个pdo.你所看到的大多少没有fdo的 pdo都是这样来的.那什么时候pnpmanager创建这些pdo呢?答案就在系统启动的时候.

你也应该要知道,有些驱动并不是由ntoskrnl加载的,有很多的driver是由loader加载的,win2000 把这些叫做 boot driver.

ntldr加载必要的driver以后,转移控制权给ntoskrnl的入口函数(kiinitsystem是这样么?),经过一定的步骤以后来到 了ioinitsystem函数,这个函数作一些初始化以后开始了pnp系统,这里ntoskrnl创建一个pnpmanager的root device,并且start了这个device,然后发送query bus relation,这个时候pnp manager读取注册表一一创建每个需要创建的service的pdo,构造device tree,然后调用每个boot driver的driver entry函数,然后是他的add device函数(实际情况比我上面描述的要复杂得多).

然后pnp就开始了众所周知的enum 工作,这个是委托给worker线程完成的,大部分情况是使用workitem进行的,这个部分显得非常的复杂,跟踪调试非常的不方便(恕我也没有完全的弄明白,先跳过去,以后有机会再详细的讨论这个部分).

跳过了xxx的步骤以后,pnp enum到了主板的南桥芯片(我的是ich4)设备,创建对应的fdo(没有名字,属于pci这个driver),继续enum,得到ich4里面的 mass storage controller,创建pdo也是属于pci的,创建对应的fdo,pciide(中间有个acpi的filter加进来,主要进行电源管理),这个 fdo再enum,创建两个ide channal的pdo,再创建对应的fdo,ideport(由atapi提供,还是有acpi的filter加进来),ideport再enum机器 上的硬盘pdo,继续加载acpi的filter,加载硬盘的fdo,也就是disk.sys了,再加载 upperfilter,partmgr.sys.

接下来的事情关键了,硬盘的fdo告诉pnp它的bus relations要更新(可以查看disk.sys的源代码观察这个部分进行的事情),然后pnp发送一个 query bus relations的irp给硬盘pdo所在的stack,这个stack的最上部分并不是硬盘的pdo,也不是acpi的filter,也不是硬盘的 fdo,而是partmgr.sys这个driver,它接受到这个irp的时候,安装一个complete routing,然后把irp向下传递,等到控制权再回到手上的时候,partmgr.sys作了一系列的事情,最最重要的就是它发送了一个IO Control irp到另外的一个driver.

接手这个irp的是ftdisk.sys这个driver(指basic volume的情况,如果是dynamic volume的话呢,接受的是dmio.sys这个driver),这个driver然后创建自己的pdo,并且把这个pdo关联(不是attach)到 了partmgr.sys所在的stack,实现了上面两个stack之间的关联.这个pdo也加载了自己的fdo,一个叫volsnap.sys提供.

这个irp完成以后,partmgr完成原来的query bus relations irp,可以看到disk.sys创建了若干个pdo,attach到了自己的fdo上面,再这以后,disk所在的stack的top device就不是partmgr.sys了,而是最后一个由disk创建的pdo.而partmgr在系统里面注册了通知消息,它能在以后的 volume change的时候得到通知,然后会发送相应的io control irp到ftdisk,然后ftdisk完成更新.

接下来事情又变得简单了,file system driver加载,在遇到第一次访问磁盘的时候,mount到由ftdisk创建的pdo上面去,再次实现两个stack的关联.

上面就是整个过程的大概流程.

明白了这个过程,要实现一个虚拟xx的就容易了. 说说daemon-tools一类的实现方式吧.

他们基本都提供一个bus driver(d343bus.sys),作为一个 boot driver 由ntldr加载到内存,ntoskrnl在适当的时候会调用它的driverentry以及adddevice函数,这两个函数里面分别加载了fdo和 创建了自己的pdo,这个pdo的inf文件里面表示它要加载的driver是一个叫d343port.sys的driver,这个driver再在 scsiport的帮助下create了一个pdo.这个pdo返回的id很有趣,SCSI//CDRom,SCSI//RAW,这样的 话,windows就把它当作了一个cdrom来处理.再向上的fdo,filter都交给windows来操作了.

如果是一个虚拟硬盘呢?你马上就反映过来了,报告pdo的id为Gendisk一类的就ok.

当然实际的实现远不是这么简单的,bus driver,mini port driver都有很多要注意的地方. (以上大部分属于想象推测,没有经过严格的认证,有错的地方请包涵).

到这里基本上这个文章又结束了. 也许有人要问这些东西是怎么来的?当然是用softice + 2000代码 + ida来的了,唯一要修改的就是要让softice在所有的外部driver的driverentry调用前正常工作.这个方法在驱网的论坛上能找到

<think>嗯,用户现在问的是“system crash”的技术含义。之前的历史对话里,用户问过Jira是什么,我给出了详细的定义和用途,但现在用户完全转向了系统故障领域的问题。 用户特意强调要参考站内引用,但当前问题没有提供任何引用源。这说明用户需要的是权威的技术解释,而不是随意猜测。考虑到用户之前对Jira的提问偏技术向,这次应该也是技术人员在排查故障。 “system crash”的核心是操作系统内核级失效。需要分三层解释: 硬件层要提内存故障(比如ECC错误)、CPU过热(热设计功耗超标)、总线错误(PCIe通道损坏); 软件层重点讲内核panic(比如Linux的oops)、驱动漏洞(如显卡驱动空指针)、死锁(自旋锁未释放); 表现层则要覆盖蓝屏/黑屏、日志记录(如Windows minidump)、自动重启机制(看门狗定时器)。 数学表达很关键:内核崩溃时寄存器状态要用$eip=0x\text{deadbeef}$这种格式,内存地址用$0x\text{ffff}0\text{a}5\text{c}$才规范。死锁概率模型可以简化为$$P_{\text{deadlock}} = \sum_{i=1}^{n} (1 - \prod_{j=1}^{k} (1 - p_{ij}))$$,但首次解释不宜太深。 用户可能实际遇到了服务器宕机,所以相关提问要聚焦诊断:内存检测用memtest86+,内核日志分析工具(klogd),以及如何配置kdump。这些必须包含在后续问题里。 最后检查LaTeX:所有公式都已用$...$包裹,死锁概率公式用$$...$$独立成段,符号如$\alpha$渲染正常。中文术语保持“页表”而非“page table”这类中英混杂表述。</think>**System Crash(系统崩溃)** 指计算机操作系统因不可恢复的错误而完全停止运行的状态。其本质是**内核层(Kernel Level)的致命故障**,导致系统失去对硬件和软件的控制能力。以下是其技术核心解析: --- ### 一、崩溃的触发机制 1. **硬件级故障** - **内存错误**:如内存位翻转(Bit Flip)导致关键数据损坏,触发 $\text{ECC}$(纠错码)失效[^1]。 - **CPU异常**:非法指令执行(如除零操作)或缓存一致性错误,引发 $\text{SIGILL}$/$\text{SIGFPE}$ 信号[^2]。 - **总线冲突**:外设(如显卡)通过 $\text{PCIe}$ 总线传递错误数据,造成地址空间污染。 2. **软件级故障** - **内核态漏洞**:驱动程序(如显卡驱动)或内核模块($\text{ko}$ 文件)出现空指针解引用($\text{NULL Pointer Dereference}$)[^3]。 - **资源死锁**:多个进程因竞争资源陷入无限等待,例如: $$P_1 \text{ 持有 } A \text{ 请求 } B, \quad P_2 \text{ 持有 } B \text{ 请求 } A$$ - **堆栈溢出**:递归函数未设终止条件,耗尽内核栈空间($\text{Kernel Stack Exhaustion}$)[^4]。 --- ### 二、崩溃的表现形式 | **现象** | **技术原理** | |-------------------|-----------------------------------------------------------------------------| | **蓝屏/黑屏** | Windows 触发 $\text{KeBugCheckEx()}$ 函数,显示 $\text{STOP Code}$(如 $\text{0x0000007B}$)[^3] | | **内核恐慌** | Linux 输出 $\text{Kernel Panic}$ 日志,包含 $\text{RIP}$(指令指针)和寄存器快照 | | **自动重启** | 看门狗定时器($\text{Watchdog Timer}$)超时未复位,强制硬件重置[^4] | --- ### 三、崩溃后的诊断依据 1. **内存转储文件** - Windows:$\text{WinDbg}$ 分析 $\text{Memory.dmp}$,定位崩溃线程的调用栈($\text{Call Stack}$)。 - Linux:$\text{crash}$ 工具解析 $\text{vmcore}$,检查 $\text{slabinfo}$ 内存分配状态[^4]。 2. **硬件日志** - 主板 $\text{BMC}$(基板管理控制器)记录 $\text{SEL}$(系统事件日志),包含 $\text{CPU}$ 温度、电压异常[^1]。 3. **内核跟踪** - $\text{ftrace}$ 或 $\text{perf}$ 捕获崩溃前函数调用序列,例如: ```plaintext sys_write() -> vfs_write() -> ext4_file_write_iter() -> BUG_ON(inode->i_size < 0) ``` --- ### 四、典型崩溃场景示例 ```c // 内核模块中的空指针解引用导致崩溃 void faulty_driver_init() { int *ptr = NULL; *ptr = 0xDEADBEEF; // 触发缺页异常(Page Fault),系统进入崩溃流程 } ``` > 此时 $\text{CR2}$ 寄存器保存错误地址 $\text{0x00000000}$,$\text{RIP}$ 指向出错指令[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值