尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Linux内核入门:五大子系统全景图与实战排查指南

Linux内核入门:五大子系统全景图与实战排查指南 从不少朋友私信问“内核怎么看才算入门”这个话题说起。很多人一上来就啃《深入理解Linux内核》啃到一半就卡在进程调度和内存管理的细节里出不来。实际上对于刚接触openEuler、或者想系统了解Linux内核的同学我更推荐先用“俯视”的方式把内核的骨架立起来——也就是本篇要讲的五大子系统。把这五个地图板块认清楚再去翻阅源码或追踪调用链你会发现方向感完全不一样。我会尽量用从业者的口吻把进程调度、内存管理、文件系统、网络协议栈、进程间通信这五块的核心逻辑讲透并穿插一些在openEuler环境里可以直接落地的观察命令和调试思路。无论你是在做运维、搞嵌入式、还是准备内核面试这篇都能帮你少走不少弯路。1. 为什么说Linux内核是“五个子系统在协作”1.1 内核态与用户态的边界是一切讨论的前提聊Linux内核之前得先明确一个基本前提系统资源被严格分成内核态和用户态两个世界。应用程序跑在用户态不能直接操作硬件、不能访问任意物理内存必须通过系统调用陷入内核态由内核代为完成敏感操作。这个设计不是因为内核想多管闲事而是因为如果每个进程都能随便改内存、随便写硬盘系统早就崩溃了。因此Linux把资源和权限牢牢攥在内核手里然后通过一层接口向用户态开放能力。这层接口包括fork()、execve()、open()、read()、write()、socket()这些系统调用。而系统调用的后端实现正是由五大子系统协同完成的。比如你执行一次cat /etc/os-release表面上只是读一个文件实际背后至少牵动三条主线文件系统子系统负责把文件名解析成实体位置内存管理子系统负责把读到的数据页映射到进程地址空间进程调度子系统负责让cat这个进程在CPU上排上队。有一个很常见的误区认为每个子系统都是孤立的一坨代码。实际上五大子系统更像是一张互相咬合的齿轮网进程调度器需要知道每个进程的内存映射情况才能做上下文切换内存管理器又依赖文件系统把匿名页换到swap分区文件系统写数据时又要借助网络协议栈把数据同步到远端存储。可以说“分层清晰跨界协同”才是Linux内核最底层的架构逻辑。1.2 五大子系统的常见划分口径在学术和教材里Linux内核常被拆成“五大子系统”进程调度子系统、内存管理子系统、文件系统子系统、网络协议栈子系统、进程间通信子系统。严格来说Linux内核源码目录还有驱动、设备模型、安全框架、时钟管理、内核同步机制等模块但我们在通识层面先把这五个核心板块理清楚就足以应对绝大多数阅读源码、分析性能、定位故障的场景了。需要强调的是这五种划分是一种教育的视角而不是内核源码的严格目录划分。比如kernel/目录下既有sched相关代码也有fork、signal相关代码mm/目录只管内存fs/目录管文件系统net/目录管网络协议栈ipc/目录管进程间通信。理解这个划分之后你再去翻代码就会心里有数遇到调度相关问题去kernel/sched/遇到内存不足去mm/遇到文件系统异常去fs/遇到网络性能问题去net/。这就是本篇作为“内核通识”最大的价值所在——给你一张检索地图而不是告诉你每一行代码的含义。2. 进程调度子系统让CPU时间变成一种公平分配的资源2.1 task_struct进程在内核中的“户口本”进程调度子系统的核心是task_struct也就是Linux的进程描述符。你可以把它理解成每个进程在内核里的“户口本”里面记录了PID、进程状态、打开的文件、内存描述符、信号处理函数、调度优先级、时间片信息等一大堆字段。创建一个新进程时内核会分配一个task_struct并用双向链表把所有进程串起来进程退出时这个结构体并不会马上销毁而是会变成僵尸进程等待父进程来回收子进程的退出码。最早版本的Linux调度器非常“裸”就是一个全局循环队列每次从头到尾扫描一遍谁的时间片到了就换人。从2.6版本开始调度器经历了O(1)调度器、CFS完全公平调度器再到后来引入调度类scheduling class的机制。调度类是一种分层次的设计实时进程优先用rt_sched_class普通进程用fair_sched_class此外还有idle调度类和deadline调度类。这种分层的好处是实时任务可以抢占普通任务但不会被普通任务反超从而保证关键业务的时延可控。在openEuler环境下查看进程调度信息最直接的方式是读取/proc/ /sched和/proc/ /status。比如运行一个高负载应用之后用chrt -p 能查看它的调度策略和优先级用cat /proc/ /sched可以看到这个进程得到的运行时间、平均运行时间、切换次数等统计。这些数据对排查“为什么CPU占用很高但业务还是很慢”这类问题非常有帮助。2.2 CFS调度器的核心虚拟运行时间CFSCompletely Fair Scheduler可以说是进程调度子系统中最值得一提的设计。它的核心思路并不复杂每个普通进程都有自己的“虚拟运行时间”vruntime进程在CPU上运行得越久它的vruntime就增长得越快而进程的优先级越高vruntime增长得就越慢。调度器每次都选择vruntime最小的那个进程去运行这样就实现了“谁用得少就让谁先跑”的公平原则。有人可能会问这和“时间片轮转”有什么区别区别在于CFS不是简单地为每个进程分配固定时间片而是把整个CPU的算力当作一个持续供给的资源池通过vruntime的相对比较来动态调整谁下一个运行。这样不光公平还能在交互式场景下获得很好的响应速度因为刚醒来的进程vruntime通常比较小会被优先调度。此外CFS还有组调度功能可以用cgroup把一批进程放进同一个调度组组内再按vruntime做公平排队这在容器云场景中特别重要。openEuler默认开启了很多CFS优化特性比如可选的调度域、NUMA感知的负载均衡等这些对跑分布式集群、大数据任务都有实际帮助。实际操作中我建议刚开始学内核的人不要急着改调度参数先学会用三个命令taskset指定CPU亲和性、chrt设置实时优先级、perf sched来记录调度事件。比如你可以用perf sched record -a sleep 1抓取一秒内所有CPU的调度行为再用perf sched latency查看每个进程的调度延迟分布。这个工具组合用来验证“为什么某进程响应慢”非常直观。2.3 调度子系统常遇到的问题与调参方向调度层面最常见的故障现象就是“系统空闲但某个应用卡顿”或者“多核服务器负载不均衡”。前者通常要考虑是不是某个CPU被硬中断软中断打满了后者可能和CPUs亲和性、NUMA拓扑有关。在openEuler上可以通过几个内核参数来改善调度体验。比如kernel.sched_autogroup_enabled决定是否启用自动任务分组默认开启把同一终端下的进程自动归组kernel.sched_migration_cost_ns控制调度器判断任务迁移的成本阈值调大了可以减少线程在不同CPU之间的频繁跳动但可能导负载不均衡。还有一个常用参数是sysctl kernel.sched_cfs_bandwidth_slice_us它控制CFS带宽分配的粒度在限制CPU配额时会有影响。需要提醒的是调度参数不是越大越好也不是越小越好。我此前在压测环境中把kernel.sched_migration_cost_ns从默认值调大结果单核负载失衡严重部分CPU打满而其他CPU闲置性能反而下降了。所以任何调优都要以监控数据为基准不要凭感觉乱改这是所有做内核性能优化的人最容易踩的坑。3. 内存管理子系统让每个进程都以为“整台机器都是我一个人的内存”3.1 虚拟内存与分页机制内存管理的基石你写一个C程序malloc一块1GB的内存程序立刻返回成功了但物理内存可能根本没有那么多空闲空间。这背后的魔法就是虚拟内存Virtual Memory。现代CPU的内存管理单元MMU负责把进程看到的虚拟地址翻译成物理地址翻译的粒度是4KB的页也可以是用2MB或1GB的大页。每个进程都有自己的页表记录虚拟页到物理页的映射关系。这样一来每个进程都拥有一个地址空间通常是0x0000000000000000到0x00007fffffffffff这样巨大的虚拟范围而物理内存是多个进程共享的由内核统一分配。进程申请内存时内核往往只是“记账”只在真正访问到那一页时才通过缺页异常page fault分配物理页。这就是为什么malloc一大块内存后你用top看RSS常驻内存并不高但VSS虚拟内存很高很正常。在页表之外还有一个非常重要的设计是TLBTranslation Lookaside Buffer它是CPU内部用于缓存虚拟地址到物理地址映射的高速缓存。如果TLB命中率高内存访问就快如果频繁触发缺页性能就会严重恶化。这也是为什么大页内存HugePages有价值的原因——用2MB页替代4KB页可以让TLB覆盖更大的内存范围减少TLB miss。我在openEuler上查看内存信息时最常用的文件是/proc/meminfo、/proc/buddyinfo和/proc/pagetypeinfo。/proc/buddyinfo会按物理页块大小输出剩余页数能看出内存碎片化的程度/proc/pagetypeinfo则按内存类型比如可移动、不可移动统计页块清楚展示哪些内存可以回收。对于定位“明明内存剩很多但分配大块内存失败”这类问题这两个文件几乎是必查的。3.2 Buddy分配器与slab分配器两种不同规模的“内存仓库”内核物理内存分配分为两大套机制buddy system伙伴系统负责分配物理页帧粒度比较大最小一页4KB支持2的幂次方块slab分配器则负责分配小的内核对象比如task_struct、socket描述符、dentry对象等这些对象经常被创建和销毁如果每次都从伙伴系统分配一整页再释放开销太大。slab机制的基本思想是预先向伙伴系统申请一批内存按固定大小切成多个对象然后缓存起来等待内核其他模块按需取用和归还。在openEuler中查看slab使用情况也有专门的proc文件一个常见命令是cat /proc/slabinfo会列出所有slab缓存类型、当前活跃对象数、对象总数、每个对象大小等信息。如果要看每一类内存实际消耗多少可以配合slabtop工具它会动态刷新并按内存占用排序非常直观。我见过不少“内存泄漏”排查场景追到最后不是用户态应用泄漏而是某个内核子系统不断创建slab对象不释放通过slabtop一眼就能扫出异常缓存。再扩展一点内核提供的内存管理还有vmalloc与kmalloc的区别。kmalloc分配的是物理连续的内存适合DMA设备访问vmalloc分配的是虚拟地址连续、物理地址可能不连续的内存适合大块但不需要物理连续的分配。普通驱动开发时小对象用kmalloc大块比如分配几MB的缓冲区则可以考虑vmalloc。但vmalloc的代价是需要修改页表性能略低所以不是越大越好。3.3 内存回收、OOM与openEuler的实际调优经验当系统物理内存紧张时内核会启动内存回收机制。回收途径主要有三一是回收干净页clean page直接丢弃即可二是回收脏页dirty page需要写回磁盘后再回收三是回收匿名页通过swap换出到swap分区。如果回收速度赶不上分配速度内核就会触发OOM Killer选择“最该杀”的进程强行终止。OOM Killer的选择逻辑并不是谁内存大杀谁而是基于oom_score的综合评分。每个进程都会有一个oom_score数值越高越可能被杀分数主要由内存占用大小、运行时长、进程优先级等因素决定。你可以在/proc/ /oom_score中查看某个进程的分数也可以调整/proc/ /oom_score_adj来手动干预。“重要的进程不想被误杀”这个需求在生产环境太常见了。openEuler在内存管理上也有自己的优化点比如默认支持透明大页Transparent HugePagesTHP。不过在数据库等大内存延迟敏感场景我建议把THP关掉或者改成madvise模式。原因很简单THP在运行时会持续做后台内存规整compaction把分散的4KB页合并成2MB大页而合并过程会引入不稳定的CPU开销和内存延迟抖动。实际案例里某些数据库在启用THP后性能波动明显关闭或改为按需启用反而更稳定。4. 文件系统子系统一切皆文件的抽象层4.1 VFS让“open、read、write”通吃一切Linux哲学中最迷人的一点就是“一切皆文件”。普通文件是文件设备是文件socket是文件管道是文件甚至进程信息也是文件/proc。这种统一性的基础是虚拟文件系统层VFSVirtual File System。VFS在用户态的系统调用与具体的文件系统实现之间架了一层抽象界面程序员只需要调用open()、read()、write()等接口根本不用关心底层到底是ext4、XFS、Btrfs还是网络文件系统。VFS的核心对象有四个超级块super_block描述整个文件系统的元信息索引节点inode描述单个文件或目录的元数据目录项dentry负责把文件名和inode关联起来文件对象file描述进程打开的实例。需要特别注意的是dentry并不一定对应磁盘上的真实目录项它可以是纯内存缓存目的是快速完成路径解析。比如你反复open同一个路径内核会在dentry cache中直接命中省掉很多磁盘I/O。在openEuler上查看VFS层缓存最常用的是free命令里的buff/cache部分以及cat /proc/meminfo里的Dentry、Inode等缓存占用。当发现系统明明内存充足但文件操作依然慢时合理增大dentry/inode缓存上限会有帮助相关参数在sysctl内核参数里也有对应项。4.2 页缓存、预读与脏页回写文件系统子系统的性能关键之一是页缓存page cache和预读机制。当进程read一个文件时内核并不是直接从磁盘拿一个字节给应用而是按页读取并缓存到page cache中这样下次再读同样的区域时就直接命中内存。同时内核还会猜测你大概率会顺序读后面的内容于是异步预读更多页进内存。这就是为什么第一次读大文件很慢而第二次读几乎瞬间完成的原因。写操作也很有讲究。应用write()只是把数据拷贝到了页缓存真正落到磁盘是在之后的某个时间点由pdflush相关线程按策略刷盘。这种延迟写方式极大提升了写性能但也增加了数据丢失的风险。因此在数据库这类要求强一致性的场景通常会使用O_DIRECT或fsync/fdatasync来绕过或强制刷盘。你如果发现自己程序的write系统调用性能很差不一定说明SSD不行也可能是页缓存写回方式设置不合理。openEuler中对这类行为有很大一部分可以通过sysctl调整比如vm.dirty_background_ratio后台开始刷盘脏页比例、vm.dirty_ratio强制同步刷盘脏页比例、vm.vfs_cache_pressure控制回收dentry/inode缓存的倾向程度。在生产环境中我一般把dirty_ratio设置在20左右、dirty_background_ratio设置在10左右既能发挥延迟写优势又不至于让脏页堆积到触发强制刷盘导致瞬间卡顿。4.3 主流文件系统对比与openEuler默认选型目前Linux主流文件系统里XFS在超大文件和大量并发读写场景下表现稳定是不少企业级Linux发行版默认选择ext4是经典选择兼容性和工具链最成熟中小规模业务用起来非常省心Btrfs支持快照、压缩、校验和但对性能要求极高的场景要谨慎使用此外还有专门给闪存设备设计的F2FS以及用于不可变存储的EROFS等。openEuler默认文件系统是ext4部分版本支持XFS作为备选安装选项对绝大多数服务器场景来说够用且可靠。如果你需要快照能力可以在安装阶段选择安装更多文件系统工具事后用Btrfs创建子卷和快照。从我实测的经验看在openEuler上做“系统盘用ext4、数据盘用XFS”的混合方案是一个稳妥且兼顾性能的搭配。数据盘使用XFS时建议创建文件系统时加上nobarrier选项吗不这个选项在某些场景反而降低安全性建议谨慎使用尤其是有掉电风险的情况下。我还会在这里分享一个小技巧如果某个目录下大量文件需要同时删除而文件数量达到百万量级直接rm删除可能会让系统进入长时间I/O等待。更好的做法是把目录重命名到另一位置然后用后台任务慢慢删或者直接对挂载点做卸载并配合后台fsck清理。这种经验来自实际生产维护在openEuler上同样适用。5. 网络协议栈子系统数据跨过千山万水的旅程5.1 一个数据包的收发全流程Linux网络协议栈是层次感最分明的一个子系统。以一个简单的HTTP请求为例数据从应用层经过socket接口进入内核TCP层处理流控和可靠传输IP层负责路由和分片最终由网卡驱动把帧发送到物理线路上。收包的方向则正好相反网卡收到数据帧后通过硬中断通知内核数据帧被封装成sk_buff结构体依次经过链路层、IP层、TCP层最终唤醒等待socket的进程。sk_buff是整个网络协议栈最核心的数据结构可以理解成“数据包的收纳箱”它既保存了实际数据又保存了各个协议层的头部信息指针。不同协议层通过对sk_buff做头部操作比如push、pull、trim来添加或剥离协议头而不需要反复拷贝数据。这种设计大大提高了数据包处理效率。不过如果驱动或协议模块错误操作sk_buff指针很容易出现内存越界或内核崩溃这也是网络驱动开发中非常容易踩坑的地方。用openEuler观察网络数据路径很多调整过的参数会出现在/proc/sys/net/目录下。比如TCP接收缓冲区大小可以看net.ipv4.tcp_rmem发送缓冲区看net.ipv4.tcp_wmem网卡队列中断合并可以看ethtool -c eth0输出的coalesce参数看多队列网卡的队列分布则用ethtool -l eth0。排查网络延迟时我会先用perf top或者cat /proc/interrupts确认是不是某个CPU的软中断处理线程ksoftirqd成为瓶颈。5.2 Netfilter包过滤、NAT与连接跟踪的家Linux网络协议栈里有一个几乎绕不开的框架叫Netfilter。它允许内核模块在数据包经过协议栈的特定钩子点如PREROUTING、LOCAL_IN、FORWARD、LOCAL_OUT、POSTROUTING注册回调函数从而实现对数据包的过滤、修改、转发决策和网络地址转换NAT。iptables、nftables、conntrack这些都是基于Netfilter框架工作的。举一个常见例子你在openEuler上部署Docker容器容器要访问外网就需要在宿主机开启IP转发并配置NAT规则让容器发出的请求源地址被改写为宿主机的公网地址。这背后的实现就是Netfilter在POSTROUTING链上执行SNAT规则同时通过conntrack记录连接状态以便回复包能够正确回溯到原始容器地址。nftables是iptables的现代替代品openEuler默认也已经支持。很多人从iptables迁移到nftables时觉得语法不习惯但nftables的规则集其实是更好理解的它将多个链和规则组织成一张表还能用脚本来定义和更新规则。如果在生产环境使用nftables我建议把规则集写成文件放在/etc/nftables.conf用systemctl管理避免命令行临时加规则久了自己都忘了加过什么。5.3 网络性能优化软中断、RPS与RFS网络性能优化的核心瓶颈往往不在CPU主频而在于中断处理与数据包分发。网卡收到数据包后产生硬中断硬中断处理大致只做最小工作然后把重活交给软中断softirq/ksoftirqd。如果所有包的软中断都堆在同一颗CPU上这颗CPU会忙得不可开交其他CPU空闲整体吞吐就上不来。解决办法之一是RPSReceive Packet Steering它可以把收到的数据包负载均衡到多个CPU上处理RFSReceive Flow Steering则进一步让同一个流的包尽量固定在同一核上以保持缓存热度。在openEuler上开启RPS的方法很简单就是给/sys/class/net/ /queues/rx- /rps_cpus写入一个CPU掩码比如写入f表示放到前四个CPU。不过要注意RPS本身会带来一些额外的调度开销不是无脑开启就好通常用在多核但网卡不支持多队列的场景。网卡多队列比如Intel的RSS或VLAN加速才是更本质的方案。通过ethtool -L eth0 combined 4把网卡队列数设置成和CPU核心数一致再配合中断亲和性把每个队列的中断绑定到不同CPU这样每个CPU都只处理自己的队列带宽和延迟都会有明显改善。我曾在openEuler 22.03 LTS上对一台16核机器做过对比开启RSS并绑核后四层业务的吞吐提升了将近25%而CPU整体利用率反而下降。6. 进程间通信子系统多个进程如何“搭上话”6.1 五类IPC方式盘点管道、消息队列、共享内存、信号量与信号进程间通信IPC在Linux里有非常多的手段常被归纳为几大类管道pipe、消息队列message queue、共享内存shared memory、信号量semaphore、信号signal。其中FIFO命名管道适合无亲缘关系的进程间做流式通信消息队列适合传递结构化小消息共享内存是速度最快的通信方式但必须配合信号量做同步信号则主要用于异步通知。管道可能是最早接触、也是理解成本最低的IPC方式。你执行command1 | command2就是shell为我们创建了一个管道command1的stdout接到管道写端command2的stdin从管道读端读取。管道在内核里实际是一个环形缓冲区数据在内存中流转不需要落到磁盘。管道非常适合“生产者-消费者”模型但缺点是半双工数据只能单向流动要实现双向通信就得建两个管道。共享内存则是当之无愧的性能之王。sysv共享内存shmget/shmat和POSIX共享内存shm_open/mmap都提供了让多个进程映射同一块物理内存的能力进程直接读写这块内存不需要系统调用延迟极低。但共享内存的痛点在于竞争同步两个进程同时写同一块数据怎么办通常需要配合信号量或者互斥锁。openEuler上查看共享内存的使用情况可以用ipcs -m如果发现System V共享内存泄漏一般就是进程崩溃前没有调用shmctl IPC_RMID或者使用POSIX共享内存忘了shm_unlink这种问题排查起来挺费劲的。6.2 信号内核向进程“喊话”的机制信号是一种特殊的进程间通信方式内核可以通过信号通知进程发生了特定事件。比如你按CtrlC终端驱动会向前台进程组发送SIGINT信号进程非法访问内存时内核发送SIGSEGV信号可以用kill命令手动发信号。信号处理分两种默认处理通常是终止进程或忽略和自定义处理进程可以通过signal()或sigaction()系统调用注册自己的处理函数。信号机制有一个比较隐蔽的坑信号处理函数中能安全调用的函数非常有限。因为进程可能在任意位置被信号打断如果一个信号处理函数调用了printf或者malloc这种非异步信号安全的函数可能导致死锁、内存损坏等未定义行为。这也是面试中经常被问到的“信号处理函数应该怎么做”。标准答案是直接设置一个volatile sig_atomic_t标志位主循环中检查标志然后执行真正的逻辑。在openEuler上进程收到的每个信号并不会留下审计日志除非你配置了audit规则或者用strace跟踪。我建议用strace -f -e signalall -p 来观察一个进程收到的信号这在定位“进程为什么莫名退出”的时候特别有效。有时候不是代码逻辑错了而是某个外部脚本定时向进程发送了SIGTERM。6.3 eventfd、epoll与现代高性能服务器的IPC实践现代高性能服务器很少直接用System V消息队列做核心通信因为阻塞、拷贝和锁竞争让它在高并发下表现不佳。当前更主流的做法是组合使用eventfd、signalfd、timerfd和epoll机制。eventfd尤其值得一讲它本质上是一个“计数器”文件描述符一个进程write一个整数进去另一个进程read就能读出来并且可以直接放进epoll监听。在网络服务中事件驱动引擎通常用eventfd来唤醒主线程比如由I/O线程完成数据接收后往eventfd里写一个字节主线程在epoll上被唤醒再去处理业务逻辑。这类模式的精髓不在于通信数据量大而在于用极低的开销传递“有事件发生”这个信号。和共享内存比eventfd拷贝4字节或8字节数据开销微不足道和信号比它不会打断进程任意执行点避免了信号处理函数的种种限制。因此从openEuler的内核源码中你会发现eventfd/io_uring、epoll这些机制越来越成为高性能网络的基石。给初学者的建议是先用管道和共享内存把原理吃透再去深入eventfd和epoll。以我的实际经验能讲清楚“epoll为什么比select/poll快”的人往往对等待队列wait queue和回调机制理解得比较深。而等待队列又是进程调度子系统与IPC子系统最明显的交汇点——进程在epoll上睡眠时实际上是被调度器从运行队列移到了等待队列事件到来时再由唤醒函数把进程重新放回运行队列。7. 通用排查手册用命令把五大子系统串起来7.1 一个故障排查实例为什么服务变慢了这里我分享一个真实排查场景帮助大家建立“五大子系统联动”的排查思维。假设有一台openEuler服务器上的Java服务最近延迟变高我们按图索骥先用top看CPU发现mysqld占CPU高而Java进程在D状态不可中断睡眠很多再用ps -eo pid,stat,wchan:30查看D状态进程阻塞在哪个内核函数上结果发现大量阻塞在wait_on_page_bit说明内存页回收或文件I/O出问题接着用iostat -x 1看磁盘util高达95%再用free -h看内存余量不多swap占用持续上涨。这一轮排查下来结论基本清晰内存不足导致系统频繁换页和脏页回写磁盘I/O被打爆Java线程等不到内存页于是进入D状态。解决方案不外乎加内存、调优JVM堆大小、或优化SQL减少磁盘访问。整个过程看起来是数据库问题实际根因在内存子系统和文件系统子系统的联合压力。掌握这个排查链比单独会看某个指标重要得多。7.2 五大子系统的观察命令速查子系统关键文件/接口常用命令进程调度/proc/ /sched、/proc/ /stattop、ps、chrt、perf sched、taskset内存管理/proc/meminfo、/proc/slabinfo、/proc/buddyinfofree、vmstat、slabtop、sar -r文件系统/proc/mounts、/proc/self/mountstatsdf、iostat、strace、lsof网络协议栈/proc/net/dev、/proc/sys/net/ethtool、ip、ss、netstat、tcpdump进程间通信/proc/sysvipc/ipcs、strace、lsof -i这张表只是起点最好能针对每个子系统各找一个深度场景比如进程调度用“为什么CPU占用越低反而越卡”来训练内存管理用“如何定位内存泄漏”来训练文件系统用“为什么rm大文件后磁盘空间不释放”来训练网络用“TCP重传率高怎么查”来训练IPC用“多个进程抢共享内存如何加锁”来训练。每解决一个这类问题你对五大子系统的理解就会深一层。7.3 学习内核源码的入口建议当你打算走进源码一探究竟时建议不要从main.c开始逐行读而是带着问题找代码。看进程调度先找kernel/sched/fair.c里的pick_next_task_fair()看内存管理先找mm/page_alloc.c里的__alloc_pages_nodemask()看文件系统先找fs/open.c里的do_sys_open()看网络协议栈先找net/ipv4/tcp.c里的tcp_v4_rcv()看IPC先找ipc/shm.c里的do_shmat()。这些都是非常经典的入口函数顺着它们往下追你能看到系统调用如何层层展开也更容易理解五大子系统之间的调用关系。用openEuler源码查看这些函数时建议本地准备好内核源码树并利用cscope或tags建立索引。我习惯先从一个系统调用如open()开始用strace -e traceopenat跟踪一个实际程序再回到源码里看do_sys_open到vfs_open的完整链路最后落到ext4或xfs的inode操作函数上。这样一条线走下来你对文件系统子系统的认知就能从“概念”变成“画面”之后再看别的子系统方法论完全可复用。8. 写到最后的一点个人体会从最早玩嵌入式Linux时只能对着printk输出猜问题到现在能在openEuler上比较从容地追踪调度、内存和网络问题最深的体会是内核学习不需要一开始就追求把所有细节都记住而是先建立“五大子系统”的全景图再带着实际问题去源码里找答案。每一个子系统都有自己的一套核心数据结构和关键函数只要把主线弄清楚了剩下的细节其实都是“沿着主线展开的枝叶”。而“打开源码就头大”这件事归根结底是缺少日常练习。我建议你每周挑一个系统调用比如read、write、mmap或sendmsg用上面说的方法从应用层追踪到内核实现把过程中碰到的所有未知名词都记下来并查一遍。坚持两三个月以后再回头看这篇通识你会发现自己已经能大致说出每个子系统的工作流程也知道该去哪里看调试信息和调优参数了。如果再给一点方向性建议我会优先推荐深入内存管理和文件系统子系统因为这两个部分既有大量的数据结构设计又频繁出现在生产故障的根因分析中。等这两个板块成熟起来回到进程调度和网络你会发现很多事情都能触类旁通。各大发行版的内核版本会不断更新openEuler也好、上游Linux也好五大子系统的框架在可预见的未来都不会有颠覆性变化这份知识值得你认真花时间投资。
返回列表