🐧 第二章 用户模式实现的功能
📄 一句话概括

ls、ps、shell 都不是内核的一部分,它们是普通用户程序。内建命令与外部命令的差别只在要不要再开一个进程;它们能【看见】内核,靠的是 /proc 与 /sys 这两个伪文件系统。

📝 本节和前面几节的关系

前三节都在讲层:系统调用、包装函数、标准库。这一节跳到最外面——你每天真正打交道的那些命令,它们站在哪一层,凭什么能看到内核里的数字。

一、shell 也是程序

很多人以为【操作系统】就是那个黑窗口。不是。 shell 是一个普通用户程序:它读你敲的一行字,解析成命令名加参数,然后决定这件事怎么做。它自己没有特权,一样要过门。 bash、zsh、fish 都是这一类,彼此的差别在语法与交互,不在权限。 真正有特权的是内核,它在你和硬件之间,不进不出,也不理你。

二、内建与外部:差别只在要不要再开一个进程

内建命令(cd、pwd、echo、export):shell 自己就办了。 为什么 cd 必须是内建的?因为进程的工作目录是进程自己的属性——如果开一个子进程去 cd,改的是子进程的目录,父进程(你的 shell)一步没动。 外部命令(ls、cat、grep):shell 先 fork 一个自己,再 exec 成那个程序,等它结束再回来。 所以 ls 不是操作系统的一部分:它只是 /bin 里一个可执行文件,你把它删了,系统照样跑,只是没有 ls 用了。

这条区分非常有用:遇到【命令找不到】与【命令报错】是两件事——前者是 PATH 里没有,后者是找到了但跑失败了。

三、它们凭什么能看到内核

ps 能列出所有进程,free 能报内存,lsblk 能列磁盘——这些信息都在内核里,用户程序怎么拿到的? 答案很朴素:内核把它们做成了文件。/proc 下面每个数字都是一个文件的正文;/sys 下面每个设备都是目录。读文件 = 读内核状态。 这两个目录不占磁盘:你 cat 一次,内核现场生成一次内容。所以它们叫伪文件系统。 这条设计的好处:不用为每个信息点发明一个新系统调用,一切【查看类】需求都能用 cat 加管道解决——ps、free、top 本质上都是【读几个文件、排一下版】。

四、这一层为什么值得单独讲

因为它是你唯一能亲手摸到内核的地方。 写不了内核模块、也不想装调试器的时候,/proc 与 /sys 就是你的窗口:想知道调度器给你分了多少时间,看 /proc/<pid>/stat;想知道内存怎么分的,看 /proc/meminfo;想知道 IO 排队多长,看 /proc/diskstats。 后面每一章(进程、调度、内存、存储、文件系统、块设备),都会回到这两个目录来取证。

五、动手 Demo:操作系统自带的程序——shell 也不过是个普通程序

下面用一个极简分派表模拟 shell 的动作:命令名 → 函数。 看清一件事:所谓【命令】不过是表里的一行;表里没有的,才需要去 PATH 里找文件。

def pwd():
    return os.getcwd()

def ls(path="."):
    return "  ".join(sorted(os.listdir(path))[:6])

def echo(*a):
    return " ".join(a)

builtin = {"pwd": pwd, "ls": ls, "echo": echo}

# 表里有的:直接调;表里没有的:去 PATH 里找可执行文件
if name in builtin:
    builtin[name]()
else:
    fork(); execve(找到的路径)     # 开一个新进程换掉自己

实跑输出:

--------------------------------------------------------------------
操作系统自带的程序——shell 也不过是个普通程序
--------------------------------------------------------------------
① 三行【命令】走的是同一张表

  命令        落在哪个函数     结果
  --------------------------------------------------------------
  pwd         pwd()            I:\Desktop\自我教学\.workbuddy\s
  ls          ls()             _chk29.py  _d29a.py  _demos_
  echo hi     echo()           hi

② 表里没有的词,shell 的反应

      输入                        vim
      在表里吗                    False
      真实 shell 接下来做的事     去 PATH 里逐个目录找同名文件
      找到之后                    复制一个自己(fork)、换成那个程序(exec)

  **内建命令和外部命令,差别只在【要不要再开一个进程】。**
  **ls 不是操作系统的一部分,它只是 /bin 里一个文件——内核根本不认识它。**

  📋 在真 Linux 上看这一层:`type ls` 会告诉你它是内建、别名还是文件;
  `which ls` 给出路径,`file /bin/ls` 会写【ELF 64-bit executable】。

三行命令走的是同一张表——这就是 shell 最里面的样子。真 shell 比这复杂得多(管道、重定向、通配符、变量展开),但【查表、找不到就找文件、找到就开进程】这一层骨架不变。 注意 ls 在这里只是个函数,在真系统里是个可执行文件——两者地位一样:都不是内核。 内建与外部唯一的实质差别就是那句 fork(); execve():要不要再开一个进程。

📋 在真 Linux 上验证这三件事:type ls 告诉你它是文件还是内建;which ls 给出路径;file /bin/ls 会说它是一个 ELF 可执行文件。三条命令合起来,就把【命令】这个字拆干净了。

30 秒实验

  1. type cd 与 type ls——一个说 builtin,一个给路径。这就是内建与外部的分界。
  2. cat /proc/meminfo | head -3 与 free -h 对照,看 free 的数字是不是就来自那几个文件。
  3. ls /proc | head 再 cat /proc/self/status | head——self 是个软链接,指向【正在读它的那个进程】。

常见误区

  1. 【ls、ps 是操作系统的一部分】。它们是普通用户程序,删掉系统照样跑,只是没有这个工具。
  2. 【内建命令是内核提供的】。内建是 shell 自己实现的,跟内核没关系。这才是它为什么能改到 shell 自己的状态(比如 cd)。
  3. 【/proc 是磁盘上的目录】。它不占磁盘,是内核现场生成的;所以你没法往 /proc/meminfo 里写东西(个别文件可写,但那是接口,不是存储)。
自测三问(点开对答案) 1. 为什么 `cd` 必须是内建命令? —— 工作目录是**进程自己的属性**。开子进程去 cd 只改子进程,父进程(shell)没变。 2. 内建命令与外部命令的实质差别是什么? —— **要不要再开一个进程**:内建在 shell 里直接执行,外部要 `fork` + `execve`。 3. `ps`、`free` 凭什么能看到内核数据? —— 内核把状态做成了文件:`/proc` 与 `/sys`。**读文件就是读内核**,这两个是伪文件系统,内容现场生成、不占磁盘。

相关笔记