当前位置:首页>Linux>第一章 linux概述

第一章 linux概述

  • 2026-10-09 12:58:48
第一章 linux概述

📂知识点:深度理解“内核也是程序”

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.库(方便工具箱)

最新文章

随机文章