开篇第一课,我们不写代码,先搞清楚一件最基础、也最容易被忽略的事:你每天开机就看到的那个东西,到底是什么。

从一个最日常的动作说起

你现在要做一件事:在电脑上新建一个文件夹,把一个文件拖进去。

你觉得这太简单了,鼠标点两下就完事。但请你停下来想一想——就在你拖动鼠标的那半秒钟里,电脑内部发生了多少事情:

  • 你的鼠标移动了,操作系统要时刻跟踪它的坐标,把它画成屏幕上的光标;
  • 你按下左键,操作系统要判断"你点中了哪个图标";
  • 你拖动文件,操作系统要判断"你要把它放到哪";
  • 你松开鼠标,操作系统要把这个文件从磁盘的一个位置"搬"到另一个位置。

这些事,没有一件是你的"应用程序"(比如文件管理器)自己干的。文件管理器只是画了个界面、告诉你"看起来文件被搬过去了"。真正弯腰搬砖的,是躲在所有程序底下的那个家伙——操作系统(Operating System,简称 OS)

所以第一个结论其实很简单:

操作系统,是所有程序脚下看不见的地基。

你写的每一个程序,微信、浏览器、游戏、你自己的 Python 脚本,都不是"直接"跑在电脑上的。它们是跑在操作系统之上的。操作系统替你扛下了所有又脏又累、又危险的活。


1.1 为什么需要操作系统?

要理解操作系统,先理解一个尴尬的局面:硬件和应用程序,天生合不来。

硬件是什么脾气?硬件很"野蛮"。它认的是寄存器、内存地址、端口号、中断向量。你想让网卡发一个数据包,你不能说"把这段话发出去",你得对着它的寄存器写一堆二进制,精确到哪个比特位是"开始发送"。

应用程序是什么脾气?应用程序很"娇贵"。它只想说:"帮我把这个数字算出来""帮我把这行字存起来"。它不想知道你的网卡是哪个牌子、你的硬盘是机械盘还是固态盘。

这就好比一家餐厅:

  • 硬件是后厨——厨师、冰柜、灶台、切菜板。你让它干活,得懂火候、懂刀工、懂每台设备怎么开。
  • 应用程序是顾客——只想坐下来吃口热乎的。
  • 中间谁协调?服务员

顾客(程序)不会自己冲进后厨开火炒菜。他只会对服务员(操作系统)说一句:"来份炒饭。"服务员替他协调厨师(CPU)、冰柜(内存)、灶台(设备),最后把饭端上来。

操作系统,就是这家餐厅里那个把所有"野蛮硬件"包装成"友好服务"的大管家。

用更技术一点的话说:操作系统向上给应用程序提供一套统一的、好用的接口(interface),向下管理五花八门的硬件。程序只跟操作系统打交道,一辈子不用直接碰硬件。


1.2 操作系统的四大职责

这个"大管家"每天到底在管什么?可以归纳成四块,而且这四块正好对应我们后面要学的几乎所有内容:

职责管什么打个比方你后面会学到
进程管理谁在跑、谁等一等、谁先谁后大管家安排每个员工干活第 4~5 课
内存管理程序住哪、内存不够了咋办大管家给每个员工分办公桌第四阶段
文件管理数据怎么存、放哪、怎么找大管家管理档案柜第五阶段
设备管理键盘、磁盘、网卡怎么用大管家维护所有机器第五、六阶段

请你记住这四个词:进程、内存、文件、设备。它们就是贯穿整个操作系统学习的主线。后面三十课,本质上就是把这四块一个一个拆开讲透。

这里有个很重要的心智转变要提前说:操作系统里的"进程",不是"程序"。

  • 程序是死的一堆代码,安静地躺在磁盘上,你双击之前它什么都不干。
  • 进程是活的那个东西,是程序"跑起来"之后的样子。

这个区别,我们第 4 课会专门展开,你现在只需要有个印象:程序是菜谱,进程是厨师照着菜谱正在炒的那道菜。


1.3 内核态 vs 用户态——为什么 App 不能碰硬件

现在问一个关键问题:凭什么不能让你写的程序直接操作硬件?

答案很现实:因为程序不可信。更准确地说,操作系统默认所有普通程序都不可信

想象一下,如果你写的 App 可以随便读写任意内存地址,会发生什么?

  • 它可以偷看浏览器里保存的密码;
  • 它可以改掉另一个程序正在计算的数据;
  • 它可以往网卡里塞垃圾数据;
  • 它甚至可以把整个系统搞崩溃。

所以操作系统必须画一条红线,把世界分成两个等级:

  • 内核态(Kernel Mode):特权等级,能执行 CPU 的一切指令、访问一切内存。操作系统自己的核心代码就运行在这里。
  • 用户态(User Mode):普通等级,只能执行"安全"的指令,只能碰分配给自己的内存。你写的所有程序、微信、浏览器,统统运行在这里。

这条红线是谁划的?是 CPU 硬件本身。注意,这不是操作系统"道德自律"决定的,而是 CPU 提供了一套特权级(privilege level)机制,强制执行。

举个具体的例子:在 x86 架构的 CPU 上,有一条指令叫 in / out,用来直接读写硬件端口。如果你的用户态程序胆敢执行这条指令,CPU 会当场拒绝——它检测到"你权限不够",直接抛出一个异常,把你的程序干掉。

这就是保护(protection)。分层不是为了装样子,而是安全的地基。

一句话记住:内核态是"上帝视角",用户态是"公民视角"。 上帝能改一切,公民只能在自己的小圈子里活动。想越界?CPU 会立刻把你拦下来。

1.4 系统调用——请求 OS 帮忙的唯一通道

好,问题又来了:既然用户态程序不能碰硬件,那它想读个文件、开个进程、发个网络请求,怎么办?

答案是:敲门。

操作系统给用户态程序留了一扇"门",这扇门叫系统调用(System Call,简称 syscall)。用户态程序想要任何特权能力,唯一的办法就是发起一次系统调用:

  1. 程序发起系统调用;
  2. CPU 从用户态切换到内核态(这一步叫"陷入内核");
  3. 内核替它把活干完;
  4. 内核切回用户态,把结果交给程序。

整个流程可以用文字画出来:

用户态程序 ──发起系统调用(陷入内核)──▶ 内核态执行 ──返回结果──▶ 用户态程序

这里有个特别容易让人产生错觉的地方,我必须戳破:你平时写代码,几乎感觉不到系统调用的存在。

你以为你在"读文件",其实你调用的是 C 标准库的 fopen;你以为你在"打印",其实 printf 底层最终会调用一个叫 write 的系统调用;你写 Python 的 print("hello"),它一路往下掉,最后也落在 write 上。

所以一个反直觉但重要的结论是:

你的程序能做的所有"真正的动作",最终都是通过系统调用完成的。 平时你觉得"没调用",只是因为标准库和编程语言帮你把系统调用包了一层又一层,藏起来了。

动手实验:亲眼看见系统调用

光说不练假把式。我们来写一个最小的 C 程序,然后用一个神器 strace 把它背后调用的系统调用一个一个揪出来

先建一个文件 lesson01.c

// lesson01.c —— 一个只做一件事的程序:向屏幕写一句话
#include <stdio.h>
#include <unistd.h>

int main(void) {
    write(1, "hello os\n", 9);  // 1 = 标准输出,直接调 write 系统调用
    return 0;
}

这里我故意不用 printf,而是直接用 write,因为 write 本身就是系统调用,我们能看到最直接的那一层。

编译并运行,同时用 strace 观察它发了哪些系统调用:

gcc lesson01.c -o lesson01
strace -c ./lesson01     # -c 表示"统计",最后会给你一张调用次数汇总表

你会看到类似这样的输出(每个系统的路径可能略有不同):

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
  0.00    0.000009           9         1           write
  0.00    0.000012          12         1           exit_group
------ ----------- ----------- --------- --------- ----------------
100.00    0.000021                     2           total

看到了吗?你写的那个"只会打印一句话"的小程序,背后其实向操作系统发起了 write(写内容)和 exit_group(退出进程)两次系统调用。

strace 的价值就在这:它让你看见程序向 OS 发起的每一次请求。以后遇到"程序到底在干嘛""为什么卡住了"这类问题,strace 是你最锋利的侦察工具之一。


深入点:两个值得挖一挖的问题

如果你是初学者,上面这些已经足够你建立正确的心智模型了。但作为一门"有深度"的课,我再抛两个进阶问题,供你课后咀嚼。

① 特权级(CPU 保护环)为什么是"环"?

x86 的 CPU 把权限分成了 4 个环:Ring 0(最高权限,内核)到 Ring 3(最低权限,用户)。理论上,驱动可以跑在 Ring 1、Ring 2,形成更细的权限分层。

但现实里,主流操作系统(Linux、Windows)几乎只用两个环:Ring 0 和 Ring 3,中间的 Ring 1、Ring 2 长期闲置。为什么?因为多环带来的是性能开销和复杂度,而"两级权限"已经够解决绝大多数安全问题了——更细的隔离,现代系统更愿意用"虚拟机、容器"这类软件手段来做,而不是依赖 CPU 那几个几乎没人用的环。

② 一次系统调用到底有多贵?

很多人以为系统调用跟普通函数调用差不多,其实差远了。

一次系统调用,CPU 要走完整套流程:陷入内核(切换权限)→ 保存当前上下文 → 执行内核代码 → 恢复上下文 → 切回用户态 → 返回。这比普通函数调用(只是跳转一下、压个栈)慢得多。

这解释了一个你可能听说过的编程经验:高性能代码要尽量减少系统调用次数。 比如,与其一次读 1 个字节、读一万次(一万次 syscall),不如一次读一万个字节(一次 syscall)。这也是为什么"缓冲区(buffer)"这个概念那么重要——后面讲缓存与 I/O 时还会再碰到它。


小结与思考题

这一课,我们把最底层的世界观搭起来了:

  • 操作系统是所有程序脚下的地基,是把"野蛮硬件"包装成"友好服务"的大管家;
  • 它有四大职责:进程、内存、文件、设备
  • CPU 用内核态 / 用户态两层权限划出安全红线,普通程序只能待在用户态;
  • 用户态程序想用特权能力,唯一的门就是系统调用

留三个问题给你课后动手:

  1. 用一句话回答:为什么不能让 App 直接操作硬件?(想清楚"保护"两个字背后的逻辑)
  2. 自己跑一遍 strace ls,看看 ls 这个命令背后调了哪些系统调用,最频繁的是哪一个?你觉得为什么是它?
  3. printfwrite 是什么关系?(提示:想想"缓冲"——printf 并不是每次都立刻写出去)
下一课,我们要把镜头拉到最前面:你按下电源键的那一瞬间,电脑里到底发生了什么。 从一个"黑屏"到一个能打字的 shell,中间藏着一整条惊险的接力赛。

标签: 计算机基础, 操作系统, Linux

添加新评论