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 秒实验
type cd与type ls——一个说 builtin,一个给路径。这就是内建与外部的分界。cat /proc/meminfo | head -3与free -h对照,看 free 的数字是不是就来自那几个文件。ls /proc | head再cat /proc/self/status | head——self是个软链接,指向【正在读它的那个进程】。
常见误区
- 【ls、ps 是操作系统的一部分】。它们是普通用户程序,删掉系统照样跑,只是没有这个工具。
- 【内建命令是内核提供的】。内建是 shell 自己实现的,跟内核没关系。这才是它为什么能改到 shell 自己的状态(比如 cd)。
- 【/proc 是磁盘上的目录】。它不占磁盘,是内核现场生成的;所以你没法往 /proc/meminfo 里写东西(个别文件可写,但那是接口,不是存储)。
自测三问(点开对答案)
1. 为什么 `cd` 必须是内建命令? —— 工作目录是**进程自己的属性**。开子进程去 cd 只改子进程,父进程(shell)没变。 2. 内建命令与外部命令的实质差别是什么? —— **要不要再开一个进程**:内建在 shell 里直接执行,外部要 `fork` + `execve`。 3. `ps`、`free` 凭什么能看到内核数据? —— 内核把状态做成了文件:`/proc` 与 `/sys`。**读文件就是读内核**,这两个是伪文件系统,内容现场生成、不占磁盘。- ☐ 3.1 创建进程
相关笔记
- C标准库 — 命令下面那两层
- 创建进程 — 下一章就讲 fork 与 exec
- 现实中的进程 — 后面看 /proc 的地方还很多
- 🐧 Linux是怎样工作的 · 索引