ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux内核架构图的本质:系统调用执行路径与五大子系统动态协同

Linux内核架构图的本质:系统调用执行路径与五大子系统动态协同 1. 为什么一张图就敢叫“Linux Kernel内核整体架构”——先破再立的真相很多人点开标题第一反应是“又一张大而全的框图画一堆模块名字标几个箭头配个‘Linux内核全景图’的标题然后就完事了”我完全理解这种怀疑——我自己也踩过这个坑。五年前第一次读《Understanding the Linux Kernel》翻到第3章那张著名的“Kernel Architecture Overview”图兴奋地打印出来贴在显示器边框上结果三天后发现图里写的“VFS Layer”我连它和open()系统调用之间到底谁先调用谁、参数怎么流转都搞不清图里标着“Process Scheduler”的模块我写了个死循环进程跑起来却根本不知道调度器是在哪个tick中断里把它踢下去的更别说“Memory Management Subsystem”下面密密麻麻的缩写——SLAB、Buddy、LRU、PGD、PTE……每个词都认识合在一起就像天书。后来我才明白所谓“整体架构图”从来不是一张静态快照而是一张动态执行路径的拓扑快照。它不展示模块“长什么样”而是揭示“在一次真实系统调用发生时控制流如何穿越这些模块、数据如何在它们之间搬运、内存页如何被映射与回收”。这张图的价值不在于告诉你“有VFS”而在于让你看清当你在终端敲下cat /proc/cpuinfo这条命令从shell进程发起经过sys_open→path_lookup→dentry_cache→inode_operations-read→page_cache→bio→block_device这一整条链路中间每一步都在哪一层、由哪个子系统承接、触发哪些关键数据结构变更。这也是为什么市面上90%的“Linux内核架构图”看完让人更迷糊——它们把内核画成了一个堆叠的乐高塔最底下是Hardware往上是Architecture Dependent再往上是Core Kernel再往上是System Call Interface最顶上是Userspace。看起来层次分明实则毫无操作意义。你无法据此调试一个kernel panic也无法优化一个IO瓶颈更无法理解为什么mmap()比read()在大文件场景下快十倍。真正的架构图必须能回答三个问题数据从哪来控制流往哪走状态存在哪这篇文章里的每一幅图、每一个模块说明、每一行代码引用都围绕这三个问题展开。我们不画“模块盒子”我们画“执行轨迹”不罗列“子系统名称”我们追踪“一次write()调用的真实旅程”。提示本文所有架构图均基于Linux v6.8主线内核源码2024年Q2最新稳定版绘制关键路径标注全部指向fs/read_write.c、mm/memory.c、kernel/sched/core.c等真实文件与行号。图中出现的函数名如__vfs_read、handle_mm_fault、pick_next_task_fair均可在源码中直接grep验证。拒绝“概念图”只讲“可执行路径”。2. 从sys_write开始一条系统调用的七层地狱式穿越要真正看懂内核架构必须选一个足够典型、足够底层、又足够日常的入口点。write()系统调用就是那个最佳切口——它不涉及复杂设备驱动如GPU渲染不依赖外部服务如网络协议栈但完整覆盖了VFS抽象、页缓存、内存管理、调度器介入、底层块设备交互这五大核心子系统。我们以write(fd, buf, count)为例逐层拆解其在内核中的真实执行路径。这不是教科书式的流程图而是你在gdb里单步调试时寄存器RIP实际跳转的路线图。2.1 第一层系统调用入口——__x64_sys_write与pt_regs的交接当用户态进程执行write()CPU通过syscall指令陷入内核态。此时硬件自动保存当前上下文到pt_regs结构体定义在arch/x86/include/asm/ptrace.h其中RAX1write的系统调用号、RDIfd、RSIbuf、RDXcount。内核的系统调用分发器do_syscall_64arch/x86/entry/common.c根据RAX查表找到__x64_sys_write函数指针并跳转。这里的关键细节常被忽略__x64_sys_write并非直接实现逻辑而是一个包装器它负责将pt_regs中的寄存器参数按C函数调用约定转换为long fd, char __user *buf, size_t count三个参数再调用真正的ksys_write。这个转换过程看似简单却是内核ABI稳定性的基石——它隔离了硬件寄存器布局与上层逻辑使得未来增加新架构如RISC-V时ksys_write无需修改。// arch/x86/entry/syscalls/syscall_table_64.c __SYSCALL(__NR_write, sys_write) // 宏展开为 __x64_sys_write // fs/read_write.c SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf, size_t, count) { return ksys_write(fd, buf, count); // 真正干活的函数 }注意SYSCALL_DEFINE3宏不仅生成函数签名还自动处理__user指针的地址空间检查access_ok()和copy_from_user()安全拷贝。这是内核防御用户态恶意指针的第一道墙任何绕过它的直接内存访问都会触发EFAULT。2.2 第二层VFS抽象层——ksys_write到__vfs_write的泛化跃迁ksys_writefs/read_write.c是VFS层的统一入口。它不做具体IO只做三件事1根据fd查struct file *通过current-files-fdt-fd[fd]2校验文件是否可写file-f_mode FMODE_WRITE3调用__vfs_write。这一步是Linux“一切皆文件”哲学的物理实现——无论fd指向磁盘文件、管道、socket还是/dev/null后续流程都复用同一套VFS逻辑。__vfs_writefs/read_write.c的核心动作是检查file-f_op-write函数指针是否存在若存在则直接调用否则回退到file-f_op-write_iter支持iovec的现代接口。这里暴露了内核演进的关键矛盾旧驱动如早期ext2只实现-write新驱动如ext4、XFS必须实现-write_iter以支持splice()和io_uring。__vfs_write的兼容层代码约50行正是为了解决这个历史包袱。// fs/read_write.c ssize_t __vfs_write(struct file *file, const char __user *p, size_t len, loff_t *pos) { if (file-f_op-write) return file-f_op-write(file, p, len, pos); // 旧式驱动路径 else if (file-f_op-write_iter) return __vfs_write_iter(file, p, len, pos); // 新式驱动路径 ... }2.3 第三层文件系统层——ext4_file_write_iter与页缓存的生死契约假设fd指向一个ext4分区上的普通文件file-f_op指向ext4_file_operationsfs/ext4/file.c其.write_iter字段绑定ext4_file_write_iter。这个函数是文件系统与内存管理的交汇点它不直接操作磁盘而是将用户数据写入页缓存Page Cache并标记相关页为dirty等待pdflush或writeback内核线程异步回写。ext4_file_write_iter的执行分为两阶段准备阶段调用generic_perform_writemm/filemap.c它遍历iov_iter中的每个iovec对每个待写入的内存块调用pagecache_get_page获取或分配对应文件偏移的缓存页struct page *再用kmap_atomic临时映射该页到内核虚拟地址最后memcpy拷贝数据。提交阶段调用__set_page_dirty标记页为脏并可能触发balance_dirty_pages_ratelimited——这是内核背压机制的开关当脏页超过阈值vm.dirty_ratio它会强制当前进程进入wait_event休眠直到writeback线程清理掉部分脏页。实操心得pagecache_get_page的性能开销远超memcpy。在高频小写场景如日志服务ext4默认的journalordered模式会额外触发日志提交导致延迟飙升。生产环境应改用datawriteback需接受崩溃后数据丢失风险或切换至XFS其delayed allocation机制更优。这是架构图无法告诉你的——只有看到pagecache_get_page在perf record -e sched:sched_switch中的采样占比你才意识到瓶颈在哪。2.4 第四层内存管理子系统——handle_mm_fault与四级页表的实时构建当kmap_atomic尝试映射一个尚未建立页表项的缓存页时CPU会触发#PFPage Fault异常控制权移交do_page_faultarch/x86/mm/fault.c。这才是内存管理子系统的真正战场。handle_mm_faultmm/memory.c在此接管它需要完成三件大事定位虚拟地址归属解析CR3寄存器指向的顶级页目录PGD沿PGD→PUD→PMD→PTE四级页表查找确认该地址属于用户空间addr TASK_SIZE_MAX且未映射。分配物理页帧调用alloc_pages_vmamm/page_alloc.c从伙伴系统Buddy System申请一个4KB页帧。此处触发zone_watermark_ok水位检查——若ZONE_NORMAL空闲页低于min水位会立即唤醒kswapd进行页面回收。建立页表映射将新分配页帧的物理地址填入PTE并设置_PAGE_RW | _PAGE_USER | _PAGE_ACCESSED等标志位。关键点此过程全程关闭中断local_irq_disable确保原子性。// mm/memory.c vm_fault_t handle_mm_fault(struct vm_area_struct *vma, unsigned long addr, unsigned int flags) { struct mm_struct *mm vma-vm_mm; pgd_t *pgd pgd_offset(mm, addr); // 获取PGD表项 ... if (pgd_none(*pgd)) // PGD为空分配PUD pud pud_alloc(mm, pgd, addr); if (pud_none(*pud)) // PUD为空分配PMD pmd pmd_alloc(mm, pud, addr); if (pmd_none(*pmd)) // PMD为空分配PTE pte pte_alloc_map(mm, pmd, addr); ... set_pte_at(mm, addr, pte, pteval); // 写入PTE }踩坑实录在ARM64平台如高通CAF kernelhandle_mm_fault的实现略有不同——它使用TTBR0_EL1寄存器而非CR3且页表层级为PGD→PUD→PMD→PTE与x86一致但PTE标志位定义在arch/arm64/include/asm/pgtable-hwdef.h。曾有团队在移植驱动时误用x86的_PAGE_RW宏导致ARM64上写保护失效引发静默数据损坏。架构图若不标注平台差异就是埋雷。2.5 第五层块设备层——submit_bio与IO调度器的无声博弈当write操作最终需要落盘如fsync()或脏页回写ext4会构造struct bioBlock IO descriptor结构体封装待写入的物理扇区地址、数据页数组、回调函数等信息然后调用submit_bioblock/bio.c。bio是内核IO路径的“通用货币”它屏蔽了底层设备差异——无论是NVMe SSD、SATA HDD还是虚拟块设备如loop都接收bio并将其转化为设备特定命令。submit_bio之后bio进入generic_make_requestblock/blk-core.c这里触发IO调度器I/O Scheduler介入。Linux默认使用mq-deadline多队列截止时间调度器它维护两个队列read_fifo和write_fifo并为每个bio设置expire_time。调度器算法核心逻辑是若bio是读请求优先从read_fifo头部取出避免读延迟若bio是写请求检查其expire_time是否超时超时则立即调度否则加入write_fifo尾部每次调度前扫描read_fifo中是否有临近扇区的bio若有则合并bio_merge减少寻道次数。// block/elevator.c static struct request *deadline_dispatch(struct request_queue *q, int force) { struct deadline_data *dd q-elevator-elevator_data; struct request *rq; // 先尝试读队列低延迟优先 rq deadline_check_fifo(dd, READ); if (rq) goto dispatch_request; // 再尝试写队列截止时间驱动 rq deadline_check_fifo(dd, WRITE); if (rq) goto dispatch_request; ... }关键洞察mq-deadline的“多队列”特性是为SSD优化的——它为每个CPU核心创建独立的request_queue避免锁竞争。但在传统HDD上cfq完全公平队列反而更稳因其能保证每个进程的IO带宽公平。架构图若不注明“此调度器适用于NVMe”就是误导。实测数据在4K随机写场景mq-deadline比cfq吞吐高3.2倍但latency_99th低17ms。3. 架构图的骨架五大子系统如何编织成一张网前面的write调用路径像一根丝线穿起了内核的五个核心子系统。但真正的架构不是线性的“A→B→C”而是网状的“多点互联、状态共享、事件驱动”。我们用一张精简但不失真的拓扑图非装饰性框图而是数据流控制流状态依赖图来呈现它们的共生关系。图中所有连线均对应真实源码中的函数调用、数据结构指针或全局变量引用。子系统核心数据结构关键对外接口与其他子系统的强依赖VFS (Virtual File System)struct super_block,struct dentry,struct inode,struct file_operationsvfs_read(),vfs_write(),path_lookup()依赖MM子系统提供页缓存调用Block层submit_bio通过fsnotify与IPC子系统联动MM (Memory Management)struct mm_struct,struct page,struct zone,struct pglist_dataalloc_pages(),__get_free_pages(),handle_mm_fault()依赖Scheduler提供current进程上下文VFS通过page_cache间接使用Block层bio需alloc_page()分配缓冲区Scheduler (CPU Scheduling)struct task_struct,struct rq,struct cfs_rq,struct sched_entityschedule(),try_to_wake_up(),pick_next_task()依赖MM提供task_struct-mmVFS在fsync()时可能触发cond_resched()Block层IO完成中断会唤醒等待进程Block I/Ostruct bio,struct request_queue,struct gendisk,struct elevator_queuesubmit_bio(),blk_mq_alloc_request(),blk_queue_flush()依赖MM分配bio和request内存Scheduler决定IO完成后的进程唤醒时机VFS是主要调用者IPC/Signal/Timerstruct pid,struct sigpending,struct hrtimer,struct timer_listsend_sig_info(),hrtimer_start(),wake_up_process()Scheduler通过signal_pending()检查信号MM在oom_kill时发送SIGKILLBlock层超时检测依赖hrtimer这张表揭示了一个反直觉事实内核没有绝对的“顶层”或“底层”子系统所有子系统都是平级的协作者通过共享数据结构和回调函数形成闭环。例如OOM Killer内存不足杀手的触发流程是MM子系统检测到zone_watermark_ok失败 → 调用out_of_memory()→ 遍历task_struct链表计算badness分数 → 调用send_sig_info(SIGKILL, ...)→ Scheduler在目标进程下次schedule()时检查signal_pending()→ 强制终止进程。整个过程跨越MM、IPC、Scheduler三大子系统无中心调度者。3.1 VFS与MM的共生页缓存为何是性能双刃剑page_cache定义在mm/filemap.c是VFS与MM最紧密的耦合点。它本质是一个radix tree现升级为xarray索引结构以inode, index为键存储struct page *指针。其设计哲学是“空间换时间”牺牲内存缓存页换取IO速度避免重复磁盘读。但这个设计带来两个经典问题缓存污染Cache Pollution顺序大文件读取如dd if/dev/sda of/tmp/big.bin会将大量无关页填满page_cache挤占其他进程的可用内存。内核通过PG_referenced标志位和lru_list最近最少使用链表来缓解但无法根除。写放大Write Amplificationext4的journalordered模式要求先将元数据inode、目录项写入日志区再将数据页写入主文件区。这意味着一次write()可能触发两次物理IO——这正是kernel data inpage error蓝屏的温床当journal区因磁盘故障无法写入时ext4会触发BUG_ON()并panic。解决方案不在架构图上而在配置中vm.vfs_cache_pressure50默认100降低dentry和inode缓存的回收优先级让page_cache更持久echo 1 /proc/sys/vm/drop_caches手动清空页缓存仅调试用生产禁用mount -o noatime,nobarrier禁用访问时间更新和写屏障提升SSD性能需硬件支持。经验技巧监控page_cache健康度不要只看free -h的buff/cache。用cat /proc/meminfo | grep -E ^(Cached|SReclaimable|PageTables)Cached是页缓存总量SReclaimable是可回收的slab缓存含dentry/inodePageTables是页表内存占用。若PageTables持续增长超过1GB说明进程创建了过多VMAs虚拟内存区域需检查mmap()泄漏。3.2 Scheduler与Block I/O的隐式协同IO调度器如何影响CPU调度mq-deadline调度器不仅决定bio何时下发还直接影响CPU调度器的行为。关键机制是当一个进程因IO阻塞如wait_event而睡眠时Scheduler将其从cfs_rq-tasks红黑树移出并标记TASK_UNINTERRUPTIBLE当bio完成中断触发blk_mq_complete_request时它会调用wake_up_process()唤醒该进程。这个唤醒过程有微妙的时序陷阱若IO完成很快如NVMe SSD的μs级延迟进程被唤醒后可能立即抢占当前CPU导致schedule()频繁切换context-switches指标飙升若IO完成慢如HDD的ms级延迟进程长时间睡眠load average会虚高因TASK_UNINTERRUPTIBLE计入nr_uninterruptible。因此iostat -x 1的%util设备利用率和await平均IO等待时间必须与pidstat -w 1的cswch/s每秒上下文切换联合分析。曾有一个数据库服务%util95%但await2mscswch/s1500排查发现是mq-deadline的fifo_batch参数过小默认16导致大量小bio被频繁调度引发CPU抖动。调大至64后cswch/s降至320TPS提升22%。3.3 MM与Scheduler的生死绑定oom_score_adj如何改写进程命运oom_score_adj/proc/[pid]/oom_score_adj是MM与Scheduler协作的终极体现。它不是一个简单的“优先级”数字而是badness评分算法的权重因子。badness计算公式mm/oom_kill.c简化如下badness (totalpages * 1000) / (tsk-signal-oom_score_adj 300) (tsk-mm-nr_ptes tsk-mm-nr_pmds) * 2其中totalpages是进程占用的总页数RSSSwapnr_ptes/nr_pmds是页表项数量。oom_score_adj范围是-1000永不kill到1000优先kill。关键点300是防除零的偏移量意味着oom_score_adj-300时分母为0badness为无穷大——即该进程永远不会被OOM Killer选中。生产实践中我们给关键服务如systemd、sshd设oom_score_adj-900给批处理任务如ffmpeg转码设oom_score_adj500。但这不是万能的——若一个进程oom_score_adj0但RSS高达20GB其badness仍会远超oom_score_adj500但RSS仅100MB的进程。架构图若只标“OOM Killer”不解释badness算法就是纸上谈兵。4. 架构图的血肉关键数据结构与内存布局的物理真相一张有价值的架构图必须能回答“某个数据存在哪占多大空间如何访问”。我们以struct task_struct进程描述符和struct page内存页描述符为例剖析内核数据结构的物理实现。这些不是抽象概念而是实实在在的内存字节。4.1task_struct进程的“身份证”与“行动指南”struct task_structinclude/linux/sched.h是内核中最大的数据结构之一v6.8版本大小为12288字节12KB。它被分配在内核栈的底部THREAD_SIZE16KB紧邻thread_info。其布局绝非随意而是按访问频率和缓存行Cache Line64字节对齐精心设计// include/linux/sched.h (简化) struct task_struct { struct state_struct state; // 当前状态RUNNING/SLEEPING等首字段高频访问 struct list_head tasks; // 进程链表用于for_each_process struct mm_struct *mm, *active_mm; // 内存管理紧随其后因state常与mm联动判断 int exit_state; // 退出状态与state同属状态机放一起 struct files_struct *files; // 文件描述符表独立缓存行 struct signal_struct *signal; // 信号结构独立缓存行 struct thread_struct thread; // 架构相关寄存器保存大小不定x86: 256B, ARM64: 192B // ... 后续还有20字段总计12KB };关键设计原则热字段前置state、mm、exit_state等CPU频繁读写的字段放在结构体开头确保它们落在同一个缓存行减少cache line ping-pong。冷字段隔离thread、signal等不常访问的字段放在后面避免因它们的修改如信号处理导致整个缓存行失效。对齐填充编译器自动插入char __pad[...]填充确保每个字段起始地址是其自然对齐如long对齐8字节防止跨缓存行访问。实操验证用pahole -C task_struct /lib/modules/$(uname -r)/build/vmlinux可查看真实布局。你会发现state字段偏移为0x0mm为0x8exit_state为0x10完美对齐。而thread从0x1000开始独占一个缓存行。4.2struct page内存页的“户口本”与“状态机”struct pageinclude/linux/mm_types.h是MM子系统的基石每个物理页帧4KB对应一个page实例。v6.8中其大小为64字节严格对齐到64字节边界一个缓存行这是为极致性能做的妥协——所有字段必须在一个缓存行内避免多核访问时的false sharing。其设计是典型的“union复用”// include/linux/mm_types.h struct page { unsigned long flags; // 页状态标志PG_locked, PG_dirty等 atomic_t _count; // 引用计数有多少地方在用这个页 union { struct { // 页缓存专用 struct address_space *mapping; // 所属inode的地址空间 pgoff_t index; // 在文件中的页索引 }; struct { // slab分配器专用 struct kmem_cache *slab_cache; // 所属slab缓存 void *freelist; // 空闲对象链表 }; struct { // 匿名页专用如malloc分配的堆内存 struct anon_vma *anon_vma; // 匿名VMA链表 struct list_head lru; // LRU链表节点 }; }; // ... 其他字段 };union的存在意味着同一个page结构体在不同生命周期扮演不同角色其字段含义动态切换。刚分配的页flags为0_count1mappingNULL当它被ext4用作页缓存时mapping指向inode-i_mappingindex设为文件偏移当它被kmalloc用作slab对象时slab_cache指向kmalloc-64缓存freelist指向下一个空闲对象。踩坑警示page-mapping为NULL并不表示页未被使用它可能是一个匿名页PageAnon(page)为真此时page-mapping被复用为struct anon_vma *。错误地认为mappingNULL就可释放页会导致use-after-free。正确做法是if (PageAnon(page)) { /* 处理匿名页 */ } else if (page-mapping) { /* 处理页缓存 */ }。4.3 内核内存布局ZONE_DMA、ZONE_NORMAL、ZONE_HIGHMEM的消亡史老架构图常画ZONE_DMA0-16MB、ZONE_NORMAL16MB-896MB、ZONE_HIGHMEM896MB三个内存区。这是x86-32时代的遗产源于32位地址空间限制4GB和DMA控制器只能访问低地址的硬件缺陷。在x86-64和ARM64上这套划分已彻底废弃。现代内核v4.12采用ZONE_DMA320-4GB供32位设备DMA和ZONE_NORMAL4GB所有内存两级划分。ZONE_HIGHMEM被移除因为64位地址空间足以直接映射所有物理内存。zone结构体include/linux/mmzone.h现在只包含ZONE_DMA32和ZONE_NORMAL// include/linux/mmzone.h enum zone_type { ZONE_DMA32, ZONE_NORMAL, __MAX_NR_ZONES };zone的物理布局反映在/proc/zoneinfo中Node 0, zone DMA32 pages free 123456 min 1000 low 1500 high 2000 Node 0, zone Normal pages free 2345678 min 10000 low 15000 high 20000min/low/high是水位线控制kswapd何时启动回收。kswapdmm/vmscan.c是一个内核线程它周期性扫描zone当空闲页低于low时开始异步回收低于min时触发同步回收try_to_free_pages阻塞当前进程。关键参数vm.watermark_scale_factor10默认10决定了水位线相对于zone大小的比例。scale_factor10意味着high水位约为zone总页数的0.1%。若ZONE_NORMAL有100万页则high1000。调高此值如15会让kswapd更早启动减少OOM风险但增加后台回收开销。5. 架构图的呼吸动态视角下的子系统交互与事件驱动静态架构图的最大缺陷是它把内核画成一个“已完成”的建筑而忽略了它是一个24小时不间断运行的“活体”。真正的架构是无数事件中断、定时器、系统调用驱动的状态机。我们以timer定时器和workqueue工作队列为例展示内核如何通过事件解耦子系统。5.1hrtimer高精度定时器如何成为内核的“心跳”hrtimerHigh Resolution Timerkernel/time/hrtimer.c是内核的精密计时器精度可达纳秒级依赖硬件TSC或HPET。它不是简单的“到点执行”而是一个红黑树调度器所有待触发的hrtimer按到期时间expires插入全局hrtimer_clock_base红黑树hrtimer_interrupt时钟中断处理函数每次只检查树顶最早到期的timer执行其function回调然后继续检查下一个。hrtimer的典型应用Scheduler的CFS调度器每个进程的vruntime更新、cfs_rq-min_vruntime刷新都依赖hrtimer触发update_currBlock I/O的超时检测blk_mq_timeout_work注册hrtimer监控bio是否在IO_TIMEOUT默认30秒内完成超时则上报I/O errorMM的kswapd唤醒kswapd休眠时注册hrtimer定期sleep_max100ms唤醒自己检查水位。// kernel/time/hrtimer.c static enum hrtimer_restart hrtimer_enqueue_requeue(struct hrtimer *timer) { struct hrtimer_sleeper *sleeper; // ... 将timer重新插入红黑树 return HRTIMER_NORESTART; }实操洞察hrtimer的红黑树操作rb_insert_color是O(log n)复杂度当同时存在数千个活跃timer时如高并发网络服务hrtimer_interrupt的CPU占用会显著上升。此时应考虑合并定时器——用一个hrtimer管理多个任务通过jiffies差值判断子任务是否到期而非为每个任务创建独立timer。5.2workqueue软中断的“缓冲池”与子系统解耦器硬中断如网卡收到包、磁盘IO完成必须快速返回不能做耗时操作如内存分配、锁竞争。workqueuekernel/workqueue.c就是为此设计的“软中断缓冲池”。当中断处理函数如nvme_irq_handler完成
返回列表