Linux 是什么,以及你为什么老是装不上软件

难度 ★☆☆☆☆ · 前置:无 | 一句话结论:内核、发行版、shell 是三样不同的东西,只有内核叫 Linux。apt: command not found 这类事,全部来自把它们混成一个词。

入职前我在自己那台 CentOS 7 虚拟机上折腾了一下午 htop,网上搜到的教程清一色 sudo apt install htop,照敲,返回 apt: command not found,换一篇说该用 yum,敲完是另一个错,再换一篇说要从源码编译,得先 ./configure,跑到一半又报缺 gcc,我只好回头去装 gcc,而装 gcc 的教程第一步又是 apt install,绕回来了。那天下午我就卡在这个圈里,反复确认自己有没有拼错字,反复重启虚拟机,最后连镜像都重新下了一遍,因为网上有人说 CentOS 7 的源早就挂了,我居然信了。

那时我以为自己装了个假系统。

(这个问题我后来问过身边几个人,答案几乎一致,每个人都怀疑过一次,然后每个人都把系统重装了一遍,装完还是不行。)

后来才明白,那三篇教程各自假设了一种发行版,而我手上是第四种,apt 是 Debian 系的东西,CentOS 用 yum 或 dnf,这件事跟内核版本一点关系都没有,跟硬件、跟镜像、跟我有没有装错,也都没有关系。写教程的人默认了读者跟他用同一个系统,这个默认从来不写在标题里,也不写在第一步,它藏在那句复制粘贴就能用里,等你复制完了、敲出第一行报错,才发现整段路的起点就不一样,而起点不一样,后面每一步都不会一样。

这也是我写博客的原因之一,有些东西不写下来,过两个月自己都会重新踩一遍。

三个东西被叫成了同一个名字

Linux 这个词在不同场合指三个不同的层次,平时混着用也不出事,直到你照着别人的命令敲不下去。

内核跟硬件打交道,进程调度、内存分配、文件系统、网络协议栈、设备驱动都归它管,编译出来几 MB,你平时看不见它,但每一次让 CPU 干活都得经过它。发行版是内核加一堆用户态程序加包管理器加安装镜像,Ubuntu、Debian、CentOS、Fedora、Arch、Alpine 全是这个东西,它们的内核差别其实不大,都是同一份 Linux,真正不一样的是上面那层用户态,装软件用什么命令、配置文件放在哪、默认带了哪些工具、连 ls 输出的颜色和字段顺序都可能是各自改过的,所以同一份脚本在 Ubuntu 上跑得通、搬到 CentOS 上就报错,问题十有八九出在这一层,而不是你把系统搞坏了。

shell 是个普通程序。

你敲的字由它读,由它解析,由它去调别的程序,bash、zsh、fish 都算。

它甚至不属于 Linux。

macOS 自带 zsh,Windows 装了 WSL 一样能跑 bash,你换一个 shell,上面那些程序一个都不受影响。

图里那个系统调用我没解释过,它就是用户程序请内核替它办事的那个动作,你在程序里写的 open、read、fork 都是,自己一步也迈不进内核,只能递请求。至于 shell 和 ls 平级这件事,我第一次看的时候也愣了一下,都是用户态程序,shell 并没有什么特殊地位,它只是专门用来启动别的程序的那一个,除此之外它跟 ls 拿到的权限、能碰到的内存,没有任何区别。

一条命令的四次交接

你敲下 ls,中间经过四次转手。

终端程序先收到你的按键,把字符一个字节一个字节喂给 shell,shell 读到换行,判断这是一条命令,去 PATH 里挨个目录找 ls 在哪,找到 /usr/bin/ls 之后 fork 出一个子进程,子进程 execve 把自己换成 ls。最后 ls 调 open、getdents64、write,由内核替它读目录、往屏幕上写。为什么非要分成复制和替换两步,而不是直接拿另一个程序把自己盖掉,这个问题的答案藏在 fork 之后那段时间里,子进程要把文件描述符(就是它当前打开着的那些文件,内核给它编了号,顺着号就能找到文件)、信号处理、工作目录这些环境收拾好,再交给 execve,一步做完就没有这个缝了。

四次转手,只有最后一次跨过那道门。

这些 fork、execve、open、write 都是系统调用,名字先混个脸熟就行,不用记,它们是同一类东西,都是从用户态敲内核那扇门的动作。

(第一次看到 fork 是复制出一个一模一样的自己、再把那个自己换掉,我承认我有点不适应,感觉像自己把自己吃了。)

你到底站在哪一层

不用猜,三条命令问出来。

1
uname -a

我在一台 Ubuntu 22.04 的测试机上跑,输出是这样。

1
Linux testbox 5.15.0-75-generic #82-Ubuntu SMP Tue Jun 6 15:42:56 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

这一行八个字段,从左到右分别是内核名、主机名、内核版本、编译信息、硬件平台、处理器类型、平台细节、操作系统。我一开始以为第二个字段是版本号,还困惑过为什么每换一台机器它就变一次,后来才知道那是主机名,5.15.0-75-generic 在第三个字段上,而这个字段恰好是别人问起你内核版本时唯一要看的那个东西,其余七个基本不用管,我那次数了三遍才数对位置。

1
cat /etc/os-release
1
2
3
4
5
6
7
PRETTY_NAME="Ubuntu 22.04.3 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.3 LTS (Jammy Jellyfish)"
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"

这是发行版身份,看到 ID_LIKE=debian 就该知道它按 Debian 的规矩来,装软件用 apt。

1
echo $SHELL
1
/bin/bash

这是 shell 身份,这里有个坑,$SHELL 记的是你登录时用的那个 shell,不一定是此刻正在跑的那个,你在 bash 里再敲一次 zsh,这个变量不会跟着变,想知道当前到底是谁,用 echo $0 或者 ps -p $$。

分不清会踩到什么

症状 真实原因 属于哪层
apt: command not found 这是 CentOS / Fedora,包管理器是 dnf 或 yum 发行版
ls 输出颜色不一样 不同的 shell 别名或 LS_COLORS 配置 shell
某个新功能用不了 内核版本太低 内核
sudo 说没有权限 你不在 sudoers 里,跟内核无关 发行版配置

最后一条值得单独说一下,sudo 的权限判断完全是用户态程序自己干的,内核根本不认识 sudoer 这个概念,它只认 uid,sudo 靠那个 SUID 位以 root 的身份跑起来(SUID 是一个特殊权限位,作用是让普通用户执行某个程序时临时获得文件属主的权限),然后代替你去执行命令,所以它说没有权限的时候,问题既不在内核也不在你的密码,而在 /etc/sudoers 里有没有你这一行,这个文件最容易被改坏,所以 visudo 才会在做完语法检查之后才允许落盘,一套权限判断连谁有资格改它都替你考虑过了,这套机制在讲权限模型那篇里展开。

(这条我踩过,当时以为是内核不让我干,其实是 /etc/sudoers 里压根没加我。)

再补两个我早期确实搞混过的。

uname -a 第一个字段永远是 Linux,因为它问的是内核,不会告诉你发行版,想知道发行版,只能去看 /etc/os-release。

发行版之间内核不同这个说法半对,内核版本和配置确实有差异,Ubuntu 打了一堆补丁,Alpine 用 musl 并把镜像压得很小,但底下那个 Linux 是同一个,真正的差别还在用户态,至于内核版本越高越好这件事,得看你跑什么,新内核有新特性和新驱动,也有新 bug,生产环境一般跟着发行版的长期支持版本走,不追新,我第一次接手线上机器时做的第一件事就是去看它的内核,发现它跑着一个比我笔记本还老的版本,而它稳得很,那一刻我才明白,新跟稳在服务器上是两件事,很多时候还是相反的两件事。

(那个内核我到现在也没敢动,同事说等下次维护窗口,然后就没有下次。)

顺手能记住的

uname -r 问内核版本,这是日常最常用的一条,输出就一行 5.15.0-75-generic,uname -m 问架构,x86_64 还是 aarch64,cat /proc/version 更啰嗦(/proc 是个假目录,里面的文件都是内核现造的,不在磁盘上),连编译器版本和构建机都报出来,这条我一般只在自己编过内核之后才看,因为它跟 uname -a 的第三个字段是同源的,区别只是啰嗦程度。

1
Linux version 5.15.0-75-generic (buildd@lcy02-amd64-045) (gcc (Ubuntu 11.3.0-1ubuntu1~22.04.1) 11.3.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #82-Ubuntu SMP Tue Jun 6 15:42:56 UTC 2023

里面的 (buildd@lcy02-amd64-045) 是 Launchpad 的构建机,说明这内核是 Ubuntu 官方编的,不是你自己编的那个。

ps -p $$ -o comm= 里的 $$ 是 shell 的特殊变量,值是它自己的 PID,这条命令在问内核,PID 是多少多少的那个进程叫什么名字。

装软件报 command not found,八成是发行版不对,不是系统坏了。