📂知识点:深度理解“内核也是程序”
1. 从“出生”看:它也是代码编译的结果
普通程序(如hello.c)需要经过编译器(gcc)变成二进制文件才能运行。内核也不例外:
·来源代码: Linux 内核是由成千上万个.c和.h文件组成的,任何人都可以去下载它的源码。
·编译产物:当你编译内核后,会得到一个叫做vmlinuz的二进制文件。
·结论:从生命周期来看,它和普通程序一样,都是由人类编写、机器编译的指令集合。
2.从“存在”看:它也引发内存和CPU
程序不运行的时候是硬盘里的文件,运行的时候是内存里的数据。
·占用空间:内核启动后,会常驻在内存的一个特定区域。你可以通过命令free查看被系统占用的那部分内存,其中很大一部分就是内核程序在“住着”。
·消耗算力:当内核在处理网络数据包或读写硬盘时,它会占用CPU的运行时间。
·结论:从物理属性来看,它也需要消耗计算机的“生存资源”(内存和CPU)。
3.从“执行”看:它只是拥有“超级权限”的特殊程序
这是最关键的区别。如果把计算机比作一间办公室:
·普通程序(用户程序):像员工一样。只能在自己的工位上(内存空间)活动,想要使用打印机(硬件)必须填写申请表。
·内核程序:像办公室主管。主管也是“员工”(也是程序),但他手中有整栋大楼所有房间的房间,并负责员工处理的申请。
特性 | 普通程序(用户应用程序) | 内核程序(Kernel) |
文件格式 | ELF (执行文件) | ELF(通常为 vmlinuz) |
仓储位置 | /bin,/usr/bin | /boot |
运行级别 | 用户态(设定模式) | 内核态(特权模式) |
崩溃后果 | 程序闪退,不影响系统 | 系统崩溃(内核恐慌) |
执行目标 | 完成特定任务(如上网、办公) | 其他管理程序(调度资源) |

💡核心结论
内核不是硬件的一部分,它是启动后第一个运行起来、并且拥有最高管家权限的“超级程序”。
📖 核心概念:程序(Program)vs进程(Process)
1.静态与动态的转换
·程序(Program)是“静”的: 它是保存在硬盘里的二进制文件。就像一本菜谱,静静地躺在书架上,没有任何动作,也不消耗鸡蛋和面粉(内存和CPU)。
·进程(Process)是“动”的: 它是运行中的程序。就像厨师有菜谱开始炒菜。这个时候,它不仅有菜谱的内容,还要占用厨房空间(内存)、消耗煤气(CPU资源)并产生中间产物。
2.为什么说“程序”的意义更广泛?
在日常交流或书籍中,我们习惯用“程序”来指代一切,原因有二:
·包含关系:进程的“灵魂”是程序。没有程序代码,进程就失去了执行的指令。
·习惯语境:比如你会说“我写了一个程序”,这既指你写的那几行代码(静态),也指它运行起来后的功能(动态)。
3.技术层面的区别核心
维度 | 程序(程序) | 进程(进程) |
存在形态 | 文件(存在硬盘里) | 实体(存在内存里) |
生命周期 | 永久(除非你删除它) | 暂时(运行完就消失) |
资源占用 | 占硬盘空间 | 占用CPU、内存、网络等 |
关系 | 1 个程序 | 对应可以N个进程(比如你同时打开两个微信) |

💡 深度笔记:武内觉老师想表达的重点
在《Linux是工作的》第一章里,提到了这个概念是为了引出进程管理:
笔记要点: 内核(作为大管家)的任务,就是把硬盘里的“程序”搬进内存变成“进程”,并给这个进程分配一个ID号(称为PID,进程ID)。
📂知识点:CPU的特权模式(CPU Privilege Modes)
1. 核心定义
为了防止应用程序不小心(或无意)搞乱系统,硬件设计者在CPU内部设置了“特权”,这就是特权等级。
·内核模式(Kernel Mode):相当于“上帝模式”。CPU可以执行任何指令,访问任何内存地址,直接指挥硬件。
·用户模式(User Mode):相当于“访客模式”。CPU只能执行设定的指令,只能访问分配给该进程的内存空间。
2. x86架构的4个等级(Rings)
虽然主要书中讨论了“内核”和“用户”两种,但武内觉老师提到的“3个以上特权等级”在Intel/AMD的CPU(x86架构)中执行Ring 0到Ring 3:
·Ring 0 (最高特权):**操作系统**只有内核在这里运行。
·Ring 1 & Ring 2:不知设计给驱动程序使用,但在主流网络(如Linux和Windows)中基本被跳过不用。
·Ring 3(最低特权):所有的普通应用程序(浏览器、Python脚本等)都在这里运行。
为什么通常只说“两种”? > 因为现代Linux简化了硬件的设计,主要通过切换Ring 0(内核态)和Ring 3(用户态)来实现管理。这种“两极交换”的设计效率最高。
3.为什么需要这些等级?
·安全隔离:如果你的Python程序写错了(比如死循环),它只会拖慢自己的速度,而不会直接把硬盘里的数据删掉,因为它没有直接操作硬盘的特权。
·系统稳定性:所有的敏感操作(读写文件、网络通信)必须通过系统调用向Ring 0的内核“申请”。内核检查没问题后,才代为执行。
特性 | 用户模式(User Mode) | 内核模式(Kernel Mode) |
回复系统 | 环3 | 环 0 |
执行者 | 你的程序、各种App | Linux内核 |
硬件访问 | 禁止(必须打报告) | 允许(直接掌控) |
指令范围 | 制定指令集 | 全部指令集(包括敏感指令) |
比喻 | 只能在宴会厅活动的客人 | 拥有大门和金库大门的管家 |

💡 深度思考
思考题:当一个程序试图在“用户模式”下执行只有“内核模式”才能执行的指令时,会发生什么? 答案: CPU会触发一个“异常”(异常),然后由内核介入,通常结果就是该程序被强行终止(报错:Segmentation Failure)。
📂知识点:系统调用与“陷入”(Trap)
1. 核心定义:系统调用(System Call)
·地位:它是用户空间进程请求内核服务的唯一合法入口。
·目的:当进程进行读写文件、创建新进程需要分配内存或网络通信等操作时,必须请求内核代劳。
2. 关键动作:“陷阱”
当进程发起系统调用时,CPU会发生一个蜡烛事件:
·模式转换:CPU从用户模式(User Mode )瞬间切换到内核模式( Kernel Mode)。
·权限交接:进程暂停执行,内核占用CPU,根据系统调用号执行对应的内核代码。
·返回:内核处理完成请求后,将结果返回给进程,投入CPU切回用户模式。
3.为什么不能“绕过”系统调用?
书里提到的“内核意义”就在此处。如果程序可以直接改变CPU特权模式:
·防御崩塌:任何恶意程序都可以把自己变成“内核模式”,从而方便读取你的账号密码、删除系统文件。
·秩序丧失:多个程序会为了抢同一个硬件(如打印机)而打架。
·结论:硬件(CPU)保证了只有内核才有权改变特权模式。普通程序没有权限执行“切换模式”的指令,除非它通过系统调用这个“安检口”。
🛡️系统安全保障机制
·入口唯一性:系统调用是进程与内核的唯一沟通渠道。
·硬件限制锁定: CPU特权模式的切换受指令集。
o正常路径:进程 -> 系统调用 ->线程-> 内核执行。
o路径:进程 -> 尝试修改 CPU 状态 ->触发 CPU 异常-> 进程被内核杀死(如 Segmentation Failure)。
金句摘录: “不存在绕过系统调用而从进程中直接改变CPU权限模式的方法,否则内核就失去了意义。”
💡实验小贴士
武内觉老师在书里提到的,系统调用是有开销的。
·为什么慢?就是因为每次系统调用都要发生“陷入”,CPU要保存现在的现场、切换特权级、执行内核代码、再切换回来。
·你可以尝试:用strace -c运行一个命令,它会统计这个命令期间总共发生了多少次系统调用,以及内核处理这些请求花了多少时间。
到这里,你已经了解了Linux的运行架构:进程在室外跑,内核在里面管,系统调用是唯一的门。
📂知识点:库封装(Library Wrapper)
1.为什么需要库?
如果每个程序员写程序都直接调用“系统调用”,会面临两个大坑:
·太复杂:系统调用非常简单,参数极其复杂(比如读个文件要处理像素、字节对齐等)。
·不通用:不同操作系统的系统调用接口不一样。直接写系统调用,你的代码在Linux能跑,到了Windows就废了。
库的作用:就像一个“多功能转换插头”。它把底层的系统调用封装成简单、通用的函数(如printf,scanf)。
2. 三层结构:透视视角 vs 运行视角
在 Typora 中建议记录这个关系:
1.应用程序层:程序员调用简单的库函数(如C语言的fopen)。
2.库函数层 (封装层):库内部将请求转换为一个或多个具体的系统调用(如 Linux 里的open)。
3.内核层:内核接收系统调用,操作硬件并返回结果。
3. 常见的库示例
·libc (glibc): Linux 中最核心的 C 标准库。几乎所有的 Linux 程序都会用到它。
·高阶语言库: Python、Java 的标准库。它们其实也是在底层调用了 C 库或直接进行系统调用。
角色 | 相当于... | 任务 |
应用程序 | 顾客 | 负责点菜(如:我要吃宫保鸡丁) |
库(图书馆) | 菜单/服务员 | 包装细节。把顾客的事情转达给后厨,顾客不需要知道怎么杀鸡、怎么切丁。 |
内核(内核) | 厨师 | 真正的责任(操作硬件,把菜做出来)。 |

💡 深度笔记:武内觉老师的提示
书中强调这一点是为了让你明白:虽然你在写代码的时候没有看到系统调用,但它一直在底层发生。
实验观察
当你写一个最简单的C程序时:
C
#includeint main() {printf("Hello World\n");return 0;}
·你调用的是库函数printf。
·但如果你用strace命令去观察它,你会发现底层其实执行了一个叫做write的系统调用。
·这就是“库封装”把基础复杂的操作变简单的过程。

📂 知识点:libc —— C 程序的根基
1.什么是libc?
·全称:标准C语言库(Standard C Library)。
·在Linux中的实现:最常用的是glibc (GNU C Library)。
·地位:它是Linux系统中最基础的API层。几乎所有在Linux上运行的程序(无论是用C、C++还是其他语言编写的,甚至包括Python解释器本身)最终都会依赖它。
2.为什么程序要依赖它?
就像武内老师在书里提到的“库封装”逻辑一样,libc 承担了两个极其重要的角色:
1.系统调用的封装器:
o内核只认“系统调用号”,而程序员喜欢调用“函数名”。
o比如说你想写文件,你会调用libc提供的fwrite(),然后libc会帮你把它转换成内核能听懂的write系统调用。
2.常用功能的集合:
o提供字符串处理(strlen)、数学计算、内存分配(malloc)等不直接涉及硬件、但每个程序都需要的功能。
3.软件层次中的libc
C 程序代码( printf)
↓
libc 库(将printf转换为write调用)
↓
系统调用接口(触发“陷入”进入内核)
↓
Linux 内核(驱动显卡输出文字)

💡深度总结(金句):
“如果把Linux内核比作大脑,那么libc就像是神经末梢的接口,它把大脑复杂的信号转换成了程序能直接使用的简单动作。”

📂知识点:为什么系统调用必须借助?
1. 语言权限的婚纱
C语言或Python等高级语言本质上只能执行算术破坏和逻辑控制(比如a + b或if...else)。
·核心矛盾:改变CPU特权模式(从用户态切换到内核态)需要执行特殊的CPU指令(例如x86架构下的syscall指令,或旧式的int 0x80中断指令)。
·结论:高级语言的代码库里没有这些特殊的“特权切换指令”,这些指令只存在于某些语言中。
2. “翻译官”的角色:libc
高级语言写不了,那为什么我们在C语言里能用open()呢?
·因为libc库内部已经用别的语言写好了这部分代码。
·当你调用C函数open()时,其实是进入了libc的代码段,里面有一段专业的构建代码负责:
1.把“系统调用号”放入指定的CPU注册(如rax)。
2.执行syscall设置指令,触发语法。
描述 C 程序如何最终触动硬件的:
1.高级语言层(C代码):调用read()函数。
2.库函数层 (libc):
o将函数参数(文件、iPad地址)存入注册。
o执行指令:执行mov $0, %rax(0 是 read 的系统调用号) 以及syscall。
3.模式切换: CPU捕获到syscall,硬件自动将模式切为内核态。
4.内核层(Kernel):根据寄存器里的0号,找到内核里的sys_read代码并执行。

总结金句(存入笔记):
“高级语言提供了逻辑,定制语言提供了权限。系统调用就是通过一两行关键的编译指令,推开通往内核模式的大门。”
📂 知识点:静态库 vs 共享库(静态库 vs 共享库)
1. 核心定义
虽然都提供通用的功能集合,但它们与你的程序“合体”的方式不同:
·静态库 (Static Library): *文件后缀: Linux 中通常是.a(Archive),Windows 中是.lib。
o动作:在编译阶段,库的代码会被完整复制到最终的执行文件中。
·共享库 (Shared Library): *文件后缀: Linux 中是.so(Shared Object),Windows 中是.dll(Dynamic Link Library)。
o动作:在编译阶段只做个“标记”,直到程序运行时,系统才会把库的代码加载到内存并与程序链接。
2.优缺点深度对比
特性 | 静态库(静态) | 共享库(共享) |
链接时间 | 编译时(构建时间) | 运行时(Run Time) |
执行文件大小 | 新增(包含了库的副本) | 较小(只包含引用说明) |
内存占用 | 第一个(每个程序都有自己的副本) | 极低(内存中只有一份,多进程共享) |
更新便捷性 | 差(库更新后,程序必须重新编译) | 优(只需更换.so文件,程序占用了) |
独立性 | 强 (不依赖外部库文件即可运行) | 弱 (运行时找不到库文件会报错) |

📝 形象比喻
·静态库=罐装泡面里的调料。每个桶泡面里都自带一包调料(库代码)。虽然方便,去哪各吃,但如果调料配方升级了,你得把还没卖出去的泡面全部拆开重装。
·共享库 = 食堂里的调料台。泡面里不带调料(缩小体积)。你想吃的时候,得去食堂里的调料台(内存)加。如果食堂升级了秘制辣椒酱,所有去吃饭的人都能重新享受到新的口味,也不用担心泡面本身。
📂知识点:为什么共享库(.so / .dll)是主流?
1.节省内存:多进程共享一份副本
这是“减少整体系统大小”的最直接体现。
·原理:假设有100个程序都依赖libc库。
o静态库:内存中会出现 100 个libc拷贝,白白浪费了几百 MB 内存。
o共享库:物理内存中只加载一个libc。这100个进程通过虚拟内存技术,都指向这同一个物理地址。
·结果:极大地节省了RAM(运行内存)和磁盘空间。
2.动态热更新:修复修复Bug
当书中提到“出现问题可以覆盖消失版本”时,指维持的是成本的巨大差异:
·场景:发现libc库存在一个严重的安全漏洞(如著名的 Heartbleed 漏洞)。
o如果是静态库:你必须重新编译全世界所有依赖它的程序,并让用户重新下载。这几乎是不可能完成的任务。
o如果是共享库:开发者只需发布一个新的.so文件,覆盖掉旧文件。下次程序启动时,会自动加载修复后的代码,重新编译程序。
🚀 效率提升:减少系统体量
·磁盘空间:多个文件占用一个库文件,减少了格式化。
·内存空间:现代操作系统的“页缓存”机制能够确保内存中只有一份相同的共享库。
🛠️ 维护优势:解耦(Decoupling)
·逻辑分离:程序的业务逻辑与基础底层库分离。
·平滑升级:只需接口(ABI)保持兼容,库的内部实现可以随意更换,而不会破坏上层应用。

⚠️ 潜在的小代价
虽然共享库应用广泛,但它也有一个著名的坑:“依赖地狱”(Dependency Hell)。
·如果你删除了一个共享库,或者版本不匹配,所有依赖它的程序都会报错无法启动(提示error while loading shared libraries:)。
·这就是为什么 Linux 的包管理器(如apt或yum)那么的原因——它们负责帮助处理好这些错综复杂的共享库关系。
📂知识点:Go语言与静态链接的恢复
虽然共享库(动态链接)在节约空间上有绝对优势,但 Go 语言(以及 Rust 的某些模式)选择默认静态链接,是为了解决现代开发中的最大痛点:部署效率与环境一致性。
1.核心原因:摆脱“依赖地狱”(Dependency Hell)
·动态链接的痛点:在服务器部署时,最怕看到error while loading shared libraries: libxxx.so.1: cannot open shared object file。这意味着你不仅要上传程序,还要确保服务器上的库版本与开发环境完全一致。
·Go的做法:编译时把所有需要的“调料”(库)全部打入一个可执行文件中。
·结果:程序变成了一个“单兵作战”的文件。你只要把这个文件丢到服务器上,它就能跑起来,不依赖服务器环境里的任何库。
2. 云吞吐量与容器化需求
在 Docker 和 K8s 盛行的今天:
·极限:容器镜像满足越小越好。如果程序是静态链接的,我们可以使用scratch(完全空白)的镜像作为基础,里面甚至不需要安装libc。
·快速分发:静态编译的Go程序通常只有几MB到几十MB,分发速度极快。

传统 vs 现代
维度 | 传统视角(支持共享库) | 现代视角(支持静态库/Go) |
最宝贵的资源 | 内存和磁盘(以前很贵) | 人的时间/运维成本(现在更贵) |
配置方式 | 操作系统预装好所有库 | 容器化、微服务、独立运行 |
核心挑战 | 减少发音 | 环境一致性(各地到处跑) |
典型代表 | C / C++ / 系统基础工具 | Go / Rust / 云原生应用 |

🧠总结思考
技术的演进往往是循环的。 > 以前内存贵,所以我们用共享库换空间;现在开发效率和部署稳定性更贵,所以我们用静态链接换安稳。
武内觉老师的观点依然正确:共享库减少了系统整体大小。 去的选择也正确:静态链接减少了占用资源的心理负担。

到这里,第一章关于“Linux 是如何构成的”地图就基本拼完整了:
1.内核(管家程序)
2.用户模式/内核模式(权力边界)
3.系统调用(请求通道)
4.库(方便工具箱)