一份完整的 Linux 学习笔记。学多少记多少,重点讲"为什么这么设计"(费曼式 📌 讲解),配 draw.io 图。
图表在
/Linux学习笔记/diagrams/目录。图注名即原图文件名(如图注"图 1 · Linux 架构分层"对应图1_Linux架构分层.d2/图1_Linux架构分层.d2.svg),改图请编辑同名.d2源文件后运行scripts/build-d2.ps1重新渲染。
| 流派 | 包管理器 | 代表发行版 |
|---|---|---|
| Debian 系 | apt |
Ubuntu · Kali · Linux Mint |
| Red Hat 系 | dnf / yum |
RHEL · Fedora · Rocky Linux |
| 层 | 作用 | 你能看到的例子 |
|---|---|---|
| 用户应用程序 | 最顶层,用户直接交互 | 浏览器、终端、自己写的脚本 |
| GNU 工具链/Shell | 封装 syscall 的命令行工具 | cat、ls、bash、gcc |
| 系统调用 (syscall) | 内核对外暴露的 API,是用户空间进入内核的唯一合法入口 | open()、read()、write()、fork() |
| Linux 内核 | 唯一能直接操控硬件的软件 | 进程调度、内存分配、文件系统、驱动 |
| 硬件 | 物理资源 | CPU、RAM、磁盘、网卡 |
用户程序永远不能直接操控硬件,必须通过 syscall 请求内核代劳。这是 Linux 安全隔离的基础。
📌 打个比方:内核像银行金库,硬件是金库里的钱。普通职员(你的程序)不能自己进金库拿钱,必须填一张单子(syscall)递给柜员(内核),柜员核验后帮你取。柜台窗口(syscall 接口)就是金库唯一的合法入口。
设计者面对的问题:如果让每个程序都能直接读写磁盘、内存、网卡,会怎样?
解法:CPU 硬件层面分两种模式
程序想读文件时,执行一条特殊 CPU 指令(如 syscall),CPU 从用户态切换到内核态,跳进内核代码执行;内核做完事,再切回用户态把结果交给程序。这一进一出叫"上下文切换"。
📌 所以 syscall 不是普通函数调用:普通函数调用是在你自己的代码里跳转;syscall 是"敲内核的门",伴随一次 CPU 模式切换,开销更大。这也是为什么频繁的小 I/O 慢——每次都在敲门。理解这点,后面学"缓冲区""批量读写"为什么能提速就顺理成章了。
以 cat /etc/os-release 为例,图中红色注解展示了从输入到输出穿越每一层的过程:
cat 是外部命令,去 $PATH 找到 /bin/catopen("/etc/os-release") 和 read() 进入内核write() 到终端Linux 只有一个根 /,所有文件都在这棵树下。这一点确实像 Windows 只有一个 C 盘——你在 C 盘里照样可以建无数子目录。
真正的区别在于额外的磁盘怎么接入:
/data)——目录树还是一棵,只是那个子目录的内容现在来自新磁盘注意:Linux 有分区,只是分区不以盘符暴露。你看到的
/、/home、/data背后可能各自是独立的分区(各有自己的文件系统和 inode 表),只是被挂载进同一棵目录树,对用户透明。df -h可以看到当前所有挂载的分区。
📌 为什么这么设计?Windows 盘符把"物理设备"这个细节暴露给了用户和程序——你得知道文件在哪块盘。Linux 把物理设备藏在统一目录树后面:程序只管访问 /data,至于背后是本地盘、U盘还是网络存储,全由挂载这个动作透明化了。
好处:路径稳定。把 /data 从一块盘迁移到更大的盘?重新挂载,路径不变,上层程序完全无感。Windows 里 D 盘改成 E 盘,所有硬编码路径的程序就可能找不到文件了。
| 目录 | 关键点 |
|---|---|
/etc |
纯文本配置,改配置就改这里的文件,如 /etc/passwd(用户列表)、/etc/ssh/sshd_config |
/proc |
不是真实文件,是内核暴露给用户空间的实时数据接口,读 /proc/cpuinfo 就是在问内核 |
/dev |
设备也是文件,/dev/sda 是第一块磁盘,/dev/null 是数据黑洞 |
/var/log |
所有日志在这里,出问题先来这里查 |
/tmp |
任何人可写,重启后清空,不要在这里放重要文件 |
📌 “一切皆文件”到底是什么意思?这是 Linux 最核心的设计思想。设计者让磁盘、键盘、内核状态……全都伪装成“文件”,这样你只需学会 open/read/write 几个操作,就能操作万物。
cat /proc/cpuinfo(读文件)> /dev/null(写文件)/dev/sda(还是文件)不用为每种设备学一套新 API——这就是为什么 cat、grep 这些“只会处理文件”的小工具能通吃整个系统。
📌 /proc 怎么理解?它里的文件不占磁盘,是内核实时生成的“假文件”。你 cat /proc/cpuinfo 的一瞬间,内核才临时把 CPU 信息组装成文本给你。所以 /proc 里看到的永远是“此刻”的状态,是你与内核对话的窗口。
要真正理解 Linux 文件,得先认识 inode。
📌 inode 是什么?磁盘上一个文件其实分两部分存:文件内容(真正的数据块)和 inode(记录文件的元信息:大小、权限、所有者、时间戳,以及数据块在磁盘哪个位置)。inode 里唯独没有文件名。
那文件名在哪?在目录里。目录本质是一张表,记录"文件名 → inode 号"的对应关系。所以"打开文件"的真实过程是:文件名 →(查目录)→ inode 号 →(查 inode)→ 数据块。
理解了 inode,两种链接就一目了然:
| 硬链接 | 软链接(符号链接) | |
|---|---|---|
| 本质 | 同一 inode 的另一个名字 | 存"目标路径"的独立文件 |
| 删原文件后 | 仍可用(inode 还在,引用计数减1) | 失效(指向的路径没了,成死链接) |
| 跨文件系统 | 不行(inode 号只在本分区唯一) | 可以 |
| 链接目录 | 不允许 | 可以 |
ls -l 显示 |
和普通文件一样 | 链接名 -> 目标 |
📌 打个比方:硬链接像"一个人有两个身份证号都指向同一个真人"——销毁一个号,人还在。软链接像"一张写着某人家地址的便利贴"——人搬走了(删原文件),便利贴就成了无效地址。
📌 ls -l 第2列那个数字(硬链接数)现在懂了吧?它就是"有多少个文件名指向这个 inode"。每 ln 一次加1,删一个名字减1,减到 0 内核才真正回收磁盘数据。这也是为什么删文件叫 unlink——删的是"名字到 inode 的链接",不是数据本身。
硬链接不是"设计出来的功能",而是 inode 机制的自然产物。目录本质是"文件名 → inode 号"的映射表,往表里多加一条记录就是硬链接——内核几乎不用额外代价。
但硬链接有两个根本限制:
find、du 这类递归工具会无限循环软链接是为了补这两个洞而专门设计的:它是一个独立的小文件,内容就是一串路径字符串,与 inode 无关,所以能跨分区、能链目录。代价是多一次路径解析,以及原文件删了就变死链接。
日常 90% 用软链接,但硬链接有一个重要的杀手级用途:节省空间的增量备份。
rsync --link-dest 备份原理:
结果是:/backup/2026-01-01/ 和 /backup/2026-01-02/ 看起来都是"完整备份",但没变的文件只占一份磁盘空间(两个目录名指向同一个 inode)。macOS 的 Time Machine 就是这个原理。
软链接不行,是因为删掉某天的备份目录后,其他天的软链接就全断了。硬链接则互相独立,删哪天都不影响其他天。
-x:单字母,可合并写 -alh--word:单词形式,不可合并,更易读,如 --all📌 打个比方:一条命令就是一句话。命令 是动词(做什么),选项 是副词(怎么做),参数 是宾语(对谁做)。
比如 ls -l /etc = "列出(动词)/etc(宾语),用详细方式(副词)"。理解这个结构,看任何陌生命令都不慌——先找动词,再看它对谁、怎么做。
ls 调用 getdents() 系统调用读取目录项,本质是读一个特殊文件(目录也是文件)。
第1列权限字符串 分4段(下节权限章节详讲):
第2列硬链接数:目录至少为2(本身 + .),普通文件通常为1。
📌 为什么 ls 默认不显示隐藏文件?因为以 . 开头的文件多是配置文件(如 .bashrc),平时不该被误改、误删,藏起来更清爽。这不是"加密",只是"眼不见为净"——加 -a 就全看见了。
📌 -h 为什么必须配合 -l?因为只有 -l 长格式才会显示文件大小这一列,-h 是把那一列的 4096 美化成 4.0K。没有 -l,根本没有大小列可美化,-h 就无处施展。
| 选项 | 含义 | 记忆规律 |
|---|---|---|
-a |
显示隐藏文件(以 . 开头的文件) |
all — 全部 |
-l |
长格式,显示权限、大小、时间等详细信息 | long — 长格式 |
-h |
文件大小用人类可读格式(K/M/G),必须配合 -l |
human-readable |
-S |
按文件大小降序排列 | Size — 大写,影响排序 |
-t |
按修改时间降序排列(最新的在前) | time |
-r |
逆序排列(常与 -t 或 -S 组合) |
reverse |
-R |
递归列出子目录 | Recursive — 大写 = 小写的升级版(影响范围更大) |
原理:读取进程的当前工作目录(内核为每个进程维护一个 cwd 属性)。
绝对路径 vs 相对路径:
📌 打个比方:绝对路径像写完整地址"中国北京市朝阳区XX路1号"——在任何地方寄信都能送到;相对路径像说"往前走两个路口右拐"——只有知道你现在站哪才有意义。
什么时候用哪个:脚本里永远用绝对路径(脚本可能在任何目录被执行,相对路径会算错);手动敲命令图省事用相对路径。这是新手脚本最常见的 bug 来源。
场景:mkdir -p 是脚本中最常用的,避免因父目录不存在而报错。
本质:调用 open() + utimens() 系统调用。常用于让 make 认为文件被修改过,触发重新编译。
-a vs -r 的区别:-r 只递归复制,-a 还保留所有元信息(权限、符号链接等),备份时用 -a。
原理:同一文件系统内 mv 只修改目录项(极快,不复制数据);跨文件系统则等于 cp + rm。
🚧 rm -rf / 会删光整个系统。现代系统对此有保护,但 rm -rf /important/ 一样没救。删前先 ls 确认。
场景:短文件用 cat,长文件用 less(cat 会一下刷屏)。
交互操作(less 进入后的按键):
| 按键 | 动作 |
|---|---|
空格 / f |
向下翻一页 |
b |
向上翻一页 |
g |
跳到文件开头 |
G |
跳到文件末尾 |
/pattern |
向下搜索(n 下一个,N 上一个) |
q |
退出 |
为什么叫 less:less 是对早期 more 命令的改进,more 只能向下翻,less 可以双向,所以"less is more"。
tail -f 的原理:内部用 inotify(Linux 内核文件监控接口)监听文件变化,有新数据写入就打印。
grep 逐行读取文件,用正则表达式匹配,打印匹配的行。名字来自 ed 编辑器命令 g/re/p(globally search regular expression and print)。
| 选项 | 含义 | 记忆规律 |
|---|---|---|
-n |
显示行号 | number |
-i |
忽略大小写 | case-insensitive |
-v |
反向匹配(输出不匹配的行) | invert |
-r |
递归搜索目录下所有文件 | recursive |
-l |
只输出文件名,不输出匹配行 | list(只列清单) |
-c |
只输出匹配行数 | count |
-E |
使用扩展正则(支持 + ? ` |
`) |
--color |
高亮匹配部分(终端默认已开启) | 直接看词义 |
find 从指定目录开始递归遍历整个目录树,对每个文件测试所有条件,通过则执行动作(默认打印路径)。
每个进程启动时,内核自动为它打开三个文件描述符:
📌 打个比方:想象每个程序身上插着三根管子:
为什么要把正常输出和错误输出分成两根管?这样你可以"把成品存起来、报错单独看"——比如 find / -name foo > 结果.txt 2>/dev/null,把找到的存文件,一堆"Permission denied"报错直接丢掉。
|原理:内核创建一个匿名管道(内存缓冲区),把 cmd1 的 stdout 接到 cmd2 的 stdin。两个进程并发运行,不是串行。
📌 打个比方:管道就是工厂流水线。每个命令是一个工位,只干一件事,做完把半成品传给下一个工位。cat 文件 | grep 过滤 | sort 排序 就像"原料 → 筛选 → 打包"。
这正是 Linux 的核心哲学:与其造一个万能的大工具,不如造很多只干一件事的小工具,用管道自由组合。所以 ls、grep、sort 都很简单,但拼起来威力无穷。
📌 打个比方:重定向就是"改水管接口"。程序的输出默认像水一样流到屏幕,> 就是把水管拔下来接到文件上。> 是接上新桶(覆盖),>> 是往老桶里继续注(追加)。
2>&1(从设计者视角)这个写法第一眼很懵:为什么是 &1 而不是 1?要看懂它,得先理解 shell 设计者面对的问题。
第一步:文件描述符就是几个整数
内核为每个进程维护一张"打开文件表",表的下标就是文件描述符(fd)。约定俗成:
这些数字不是魔法,就是这张表的下标编号。> 重定向的完整写法其实是带编号的:
所以 N> 文件 的通用格式是:把 fd N 接到某个文件。
第二步:设计者遇到的歧义难题
现在我想把 stderr 也指到 stdout 已经指的地方(比如都写进同一个文件)。最自然的写法似乎是:
但 2> 1 会被解析成"把 fd 2 重定向到一个名叫 1 的文件"!因为 > 右边按规矩就是文件名,1 在这里是个普通文件名,不是文件描述符。
这就是设计者必须解决的歧义:右边的 1,到底是"文件名 1"还是"文件描述符 1"?光看数字分不清。
第三步:用 & 消除歧义
设计者的解法:在数字前加 &,明确告诉 shell "这是个文件描述符,不是文件名"。
2> 文件 → fd 2 接到一个文件2>&1 → fd 2 接到 fd 1 现在指的地方这里的 & 可以理解为 C 语言里的"取地址 / 引用"——&1 不是数字 1,而是"1 号管子指向的目标的引用"。一加 &,歧义消失,shell 立刻知道你说的是描述符而非文件。
📌 一句话记牢:> 右边默认是文件名;想说"我指的是某个 fd",就给它戴上 & 帽子。所以 2>&1 = "让 2 号管,去 1 号管现在指的地方"。
第四步:为什么顺序不能反
2>&1 复制的是 fd 1 此刻的指向,是一次"快照",不是"永久绑定"。所以顺序至关重要:
第五步:bash 提供了简写
正因为 > out.txt 2>&1 又长又容易写错顺序,bash 后来加了个简写:
&> 直接表示"stdout 和 stderr 都重定向",省心。但 &> 是 bash 专有,写可移植脚本(如 /bin/sh)时还得用经典的 > file 2>&1。
手册分章:
| 章节 | 内容 |
|---|---|
| 1 | 用户命令(最常用) |
| 2 | 系统调用(syscall) |
| 3 | C 库函数 |
| 5 | 文件格式与配置 |
| 8 | 系统管理命令(root 用) |
man 2 open 可以查看 open() 系统调用的内核文档——这就是上面架构图里 syscall 层的直接体现。
Linux 是多用户系统,每个文件都有三个问题的答案:
内核在每次文件访问时检查:你是 owner?是 group 成员?还是 other?然后按对应的权限位决定放不放行。
rwx 的含义因文件类型而异:
| 权限位 | 对普通文件 | 对目录 |
|---|---|---|
r |
可以读取文件内容 | 可以 ls 列出目录内容 |
w |
可以修改文件内容 | 可以在目录中创建/删除文件 |
x |
可以执行该文件 | 可以 cd 进入目录 |
目录没有
x权限就进不去,即使有r也只能看到文件名,无法访问文件内容。
每组权限是三个二进制位,相加得到数字:
📌 为什么是 4/2/1 而不是 1/2/3?这是设计者的巧思:用二进制的位来表示权限。rwx 刚好是三个开关,对应二进制三位:
因为 4、2、1 是二进制的位权(2²、2¹、2⁰),任意组合相加的结果都不会冲突——4+1=5 只能是 r-x,不可能是别的组合。如果用 1/2/3,那 1+2=3 和单独的 3 就分不清了。这就是为什么用 2 的幂。
快速心算:7=rwx、6=rw-、5=r-x、4=r--。看到 755 就是 rwx/r-x/r-x,熟了一眼就认出来。
常见权限组合:
| 数字 | 符号 | 用途 |
|---|---|---|
755 |
rwxr-xr-x |
目录、可执行脚本(owner全权,其他只读执行) |
644 |
rw-r--r-- |
普通文本文件(owner可写,其他只读) |
600 |
rw------- |
私密文件,如 SSH 私钥(只有 owner 能读写) |
700 |
rwx------ |
私有脚本(只有 owner 能用) |
777 |
rwxrwxrwx |
所有人全权,生产环境严禁 |
符号模式语法:[ugoa][+-=][rwx]
| 符号 | 记忆规律 |
|---|---|
u |
user(owner) |
g |
group |
o |
other |
a |
all(= u+g+o) |
+ |
加权限 |
- |
去权限 |
= |
直接赋值(覆盖原有) |
只有 root 或文件的 owner 才能修改归属,普通用户不能把文件"送给"别人(防止绕过权限)。
Linux 的用户信息存在三个文件里:
| 文件 | 内容 |
|---|---|
/etc/passwd |
用户账户信息(用户名、UID、家目录、shell) |
/etc/shadow |
密码哈希(root 才能读) |
/etc/group |
用户组信息 |
UID 规律:
0 = root(超级用户)1~999 = 系统账户(服务用,无法登录)1000+ = 普通用户visudo 编辑 sudoers 文件(有语法检查,比直接 vi 安全)。
| 权限 | 数字 | 名称 | 作用 |
|---|---|---|---|
| SUID | 4xxx | Set UID | 执行时临时获得 owner 的权限(如 passwd 命令需要以 root 身份改 /etc/shadow) |
| SGID | 2xxx | Set GID | 目录下新建文件自动继承该目录的所属组 |
| Sticky | 1xxx | 粘滞位 | 目录中只有文件 owner 才能删自己的文件(如 /tmp) |
📌 SUID 要解决什么难题(设计者视角)?考虑一个矛盾:你要改自己的密码,密码存在 /etc/shadow,而这个文件是 600 root —— 只有 root 能写。那普通用户怎么改密码?
/etc/shadow 开放给所有人写?那你能改别人密码,灾难。passwd 这个程序运行时临时变身 root,只在这个受控程序里才有权限改 shadow。这就是 SUID。所以 ls -l /usr/bin/passwd 看到 -rwsr-xr-x 的 s:谁执行它,运行期间就临时拥有文件 owner(root)的身份。本质是“用受信任的程序做代理,而不是直接摆开权限”。也因此 SUID 程序是黑客重点目标——一旦 SUID root 程序有漏洞,就能被利用提权到 root。
第 7 节建过用户、加过组,但"主组""附加组"到底什么区别,很多人没搞清。
📌 一个用户可以属于多个组,分两类:
sudo 组就能用 sudo,加进 docker 组就能免 sudo 跑 docker。
::::::tip
📌 -aG 为什么必须带 a?这是经典翻车点:-G 是"设置附加组列表",不带 -a 会用新组覆盖用户原有的所有附加组——比如本来在 sudo 组,一条 usermod -G docker 就把 sudo 弄丢了,可能直接没了管理员权限。记牢:加组永远用 -aG。
📌 改组后为什么要重新登录才生效?因为组身份是在你登录、创建 shell 那一刻确定并写进进程的,呼应 06 进程章——已运行的 shell 不会自动更新。退出重登(或 newgrp)让新 shell 带上新组身份。
Shell 脚本就是把一系列命令写进文件,让 shell(通常是 bash)按顺序执行。它能加变量、判断、循环,把手动操作自动化。
Shell 是什么:架构图里 GNU 工具层的那个 bash,既是你敲命令的交互界面,也是脚本解释器。
逐行解释:
#!/bin/bash — Shebang,必须在第一行第一列。它告诉内核用哪个程序解释这个文件。#! 后跟解释器的绝对路径。# 这是注释 — 除 Shebang 外,# 开头都是注释。echo — 打印文本到标准输出。内核执行时,读取文件首行,看到 #!/bin/bash,于是实际执行的是:
📌 “Shebang”这个名字从哪来?首行开头是 #! 两个符号。# 在英语口语里叫 sharp 或 hash,! 叫 bang,连起来就是 “sharp-bang”→“shebang”。它不是给人看的,是给内核看的标记。
📌 内核怎么知道用哪个解释器?当你跑 ./script.sh,内核读文件头几个字节,看到 #! 这个“魔数”,就明白“这是脚本,不是二进制程序”,于是拿 #! 后面的路径当解释器,把脚本文件当参数传进去。这就是为什么 Python、Perl 脚本也用 Shebang(#!/usr/bin/python3)——机制是通用的。
为什么推荐 #!/usr/bin/env bash:直接写死 /bin/bash 在某些系统路径不同时会失效;env 会去 $PATH 里找 bash,更可移植。
| 方式 | 是否需要执行权限 | 是否开子进程 | 说明 |
|---|---|---|---|
bash script.sh |
不需要 | 是 | 显式指定解释器,最常用 |
./script.sh |
需要 chmod +x |
是 | 靠 Shebang 找解释器 |
source script.sh 或 . script.sh |
不需要 | 否 | 在当前 shell 执行 |
关键差异:./script.sh 在子进程运行,脚本里 cd 或改环境变量不影响当前终端;而 source 在当前 shell 运行,能改变当前环境(所以 source ~/.bashrc 能让配置立即生效)。
📌 为什么 ./script.sh 里的 cd 管不了外面?联系 06 进程章:./ 运行会 fork 出一个子 shell 去执行脚本。子进程改自己的当前目录、环境变量,都是改“复制品”,子进程一退出,这些改动随之消失,父 shell(你的终端)根本没受影响。
而 source 是“把脚本里的命令拿到当前 shell 里逐条执行”,不开子进程,所以改动直接作用于当前环境。记住:改了 .bashrc、.profile 这种环境配置,必须 source 才生效,用 ./ 执行是白忙。
🚧 赋值 name="Tom" 等号两边不能有空格,name = "Tom" 会被当成执行 name 命令。
运行 bash test.sh hello world:
$? 退出码:每个命令结束都返回一个数字,0 = 成功,非 0 = 失败。脚本里靠它判断上一步是否成功。
📌 为什么是 “0 成功、非 0 失败”,和日常直觉相反?这是设计者的巧思:成功只有一种,失败有千百种。用唯一的 0 表示成功,剩下的 1~255 可以用来区分不同的错误原因(如 1=一般错误、2=误用、3=文件不存在……)。
这让脚本能精确判断:if 命令; then 背后就是检查退出码是不是 0。也是为什么 cmd1 && cmd2(前者成功才跑后者)、cmd1 || cmd2(前者失败才跑后者)能工作——它们都在看 $?。
🚧 [ ] 是 test 命令的简写,方括号内侧必须有空格:[ -f file ] 对,[-f file] 错。
常用测试条件:
| 条件 | 含义 | 记忆规律 |
|---|---|---|
-f file |
文件存在且是普通文件 | file |
-d dir |
目录存在 | directory |
-e path |
路径存在(文件或目录) | exists |
-z str |
字符串长度为0(空) | zero |
-n str |
字符串非空 | not empty |
str1 = str2 |
字符串相等 | |
n1 -eq n2 |
数字相等 | equal |
n1 -ne n2 |
数字不等 | not equal |
n1 -lt n2 |
小于 | less than |
n1 -gt n2 |
大于 | greater than |
字符串比较用
=,数字比较用-eq,别混。
注意:bash 函数的 return 只能返回 0-255 的退出码,要"返回"数据用 echo 输出再用 $() 捕获。
逐段解释:
src="$1" — 把第1个参数存入变量[ -z "$src" ] — 检查是否没传参数exit 1 — 出错时以非0码退出,调用者可用 $? 检测[ ! -d "$src" ] — ! 取反,判断"不是目录"$(date +...) — 命令替换生成时间戳文件名进程 = 正在运行的程序实例。程序是磁盘上的静态文件,运行起来后内核给它分配内存、CPU 时间、文件描述符,就成了进程。
每个进程有:
Linux 创建新进程只有一种机制,分两步:
举例:你在 bash 里敲 ls:
fork() 生出一个子 bashexec("/bin/ls"),自己变成了 ls 进程wait() 回收它PID 1:系统启动的第一个进程(现代是 systemd),是所有进程的祖先。父进程死了的"孤儿进程"会被 PID 1 收养。
📌 为什么要 fork + exec 两步,而不是一步“创建并运行新程序”(设计者视角)?这是 Unix 最妙的设计之一。拆成两步是为了在“复制”和“运行”中间留出一个口子,让你能动手脚。
比如 shell 要实现 ls > out.txt:
fork() 复制出子进程(此时还是 bash)exec("/bin/ls"),ls 继承了调整好的 stdout,输出自然进了文件如果是一步完成,你根本没机会插手设置重定向、环境变量。所以重定向、管道这些机制能存在,根源就是 fork/exec 分家。
📌 什么是僵尸进程(Zombie)?子进程死了,但它的退出码还留在进程表里等父进程来“收尸”(wait())。这段期间它就是僵尸——人死了但档案还在。如果父进程写得不好、不收尸,僵尸就会堆积。为什么要保留退出码?因为父进程可能想知道子进程是成功还是失败(还是那个 $?)。
| 列 | 含义 |
|---|---|
USER |
进程所有者 |
PID |
进程号 |
%CPU / %MEM |
CPU 和内存占用百分比 |
VSZ |
虚拟内存大小(KB) |
RSS |
实际占用物理内存(KB) |
TTY |
关联的终端,? 表示无终端(守护进程) |
STAT |
进程状态(见图中②) |
COMMAND |
启动命令 |
aux 的含义:a=所有用户的进程,u=显示详细信息,x=包括无终端的进程。这是 BSD 风格选项,不加 -。
top 交互按键:
| 按键 | 动作 |
|---|---|
P |
按 CPU 排序 |
M |
按内存排序 |
k |
输入 PID 杀进程 |
q |
退出 |
top 头部信息:
负载值约等于"等待 CPU 的进程数",超过 CPU 核心数就说明繁忙。
进程间通过信号通信。kill 不只是"杀",是"发信号"。
常用信号:
| 信号 | 编号 | 作用 | 记忆 |
|---|---|---|---|
| SIGTERM | 15 | 礼貌终止(默认),进程可清理后退出 | TERMinate |
| SIGKILL | 9 | 强制杀死,进程无法拦截 | 数字9好记 |
| SIGINT | 2 | 中断 = Ctrl+C | INTerrupt |
| SIGSTOP | 19 | 暂停 = Ctrl+Z | STOP |
| SIGHUP | 1 | 挂起,常用于让服务重载配置 | HangUP |
顺序很重要:先用
kill 15(礼貌),让进程有机会保存数据;无效再kill -9(强杀,可能丢数据)。
📌 为什么 kill 叫“kill”却不一定是杀?这是个历史遗留的起名失误。kill 实际是“向进程发一个信号”,信号可以是“你该重载配置了”(SIGHUP)、“你暂停一下”(SIGSTOP),只不过默认发的是“请终止”(SIGTERM)而已。把它理解成 send-signal 就顺了。
📌 为什么 kill -9 万能但不该乱用?SIGTERM(15)是“敲门通知”,进程可以接住信号、先保存数据再优雅退出;SIGKILL(9)是内核直接拔插,进程压根没机会反应(这也是为什么 9 拦不住)。所以顺序是先 15 后 9:给个体面退场的机会,实在不行再强杀。直接 9 可能让进程来不及写盘、锁不释放。
作业 vs 进程:作业是 shell 层面的概念(用 %1 引用),进程是内核层面的(用 PID 引用)。
nice 值越高,进程越"谦让",CPU 紧张时优先让给别人。只有 root 能设负值(提高优先级)。
服务器的核心价值就是提供网络服务。理解 IP、端口、TCP,才能排查"连不上""端口被占""DNS 解析失败"这类日常问题。
数据从应用发出时逐层封装,接收时逐层解封:
| 层 | 作用 | 关键概念 |
|---|---|---|
| 应用层 | 具体应用协议 | HTTP、SSH、DNS |
| 传输层 | 端到端传输 | TCP/UDP、端口号 |
| 网络层 | 跨网络寻址路由 | IP 地址、路由 |
| 链路层 | 物理网络传输 | MAC 地址、网卡 |
TCP vs UDP:
| TCP | UDP | |
|---|---|---|
| 可靠性 | 可靠,有确认重传 | 不保证送达 |
| 速度 | 较慢(有握手开销) | 快 |
| 场景 | 网页、SSH、文件传输 | 视频、游戏、DNS |
📌 打个比方:IP 地址像一栋楼的地址(定位是哪栋楼),端口像楼里的门牌号(定位是哪个房间)。只有 IP 找到机器还不够——一台服务器上跑着网站、SSH、数据库几十个程序,端口才能告诉系统“数据该送给哪个程序”。所以 IP:端口 合起来才能唯一定位一个服务。
IP 分类:
10.0.0.0/8172.16.0.0/12192.168.0.0/16常见端口:
| 端口 | 服务 |
|---|---|
| 22 | SSH |
| 80 | HTTP |
| 443 | HTTPS |
| 53 | DNS |
| 3306 | MySQL |
| 6379 | Redis |
0-1023 是知名端口,需 root 权限才能监听。
📌 为什么低端口要 root 才能用?这是安全设计:80、443 这些是“大家约定俗成”的服务端口,浏览器默认去 80 找网站。如果任意普通用户都能占 80 端口,就能冒充官方网站骗人。要 root 才能监听,相当于“只有管理员能挂招牌”。
建立连接前,客户端和服务器要"对暗号"确认双方收发正常:
📌 为什么偏偏是三次,不是两次或四次(设计者视角)?核心是要让双方都确认“我能发、我能收,你也能发能收”。拿打电话打个比方:
这也是为什么断开连接要四次挥手:建连时服务器的“同意+请求”能合成一包,断开时可能一方还有数据没发完,得拆开成四步。
一句话总结:三次握手是为了双方都确认彼此的发送和接收能力都正常,少一次就有一方无法确认。
/24 表示前 24 位是网络号,即子网掩码 255.255.255.0。
ping 用的是网络层的 ICMP 协议,不涉及端口。ping 通说明网络层连通,但不代表对应的服务端口开着。
ss(socket statistics)取代了老的 netstat:
选项记忆:
| 选项 | 含义 | 记忆 |
|---|---|---|
-t |
TCP | tcp |
-u |
UDP | udp |
-l |
监听状态 | listen |
-n |
显示数字端口(不解析服务名) | numeric |
-p |
显示进程 | process |
0.0.0.0:22 表示监听所有网卡的 22 端口;127.0.0.1:3306 则只监听本机。
排查"端口被占用":
选项记忆:-I=headIng(头),-o=output,-L=Location(跟随跳转),-X=请求方法。
域名要先解析成 IP 才能连接:
/etc/hosts 的用途:手动指定域名对应 IP,常用于测试环境或屏蔽网站。优先级高于 DNS。
SSH 免密原理:公钥放服务器 ~/.ssh/authorized_keys,私钥留本地。登录时服务器用公钥加密挑战,只有持有私钥的你能解开,无需传密码。
07 章前面用 scp 传文件,但 rsync 才是运维备份/同步的主力,因为它只传有变化的部分(增量传输),还能断点续传。
📌 rsync 比 scp 强在哪?scp 是"全量复制"——每次都把所有文件重传一遍。rsync 用差量算法只传变化的块:第一次同步 10G,之后改了几个文件再同步,可能只传几 MB。所以定时备份、部署代码都用 rsync,配合 09 章 cron/timer 就是自动备份方案。
结尾 / 的坑:rsync 源/ 目标/(源带斜杠)= 把源目录"里面的内容"放进目标;rsync 源 目标/(源不带斜杠)= 把源目录"本身"放进目标。先用 -n 演练确认。
当"连不上、丢包、协议对不对"靠日志看不出来时,tcpdump 直接抓网卡上的数据包,是网络排查的终极手段。
📌 什么时候才用 tcpdump?排查顺序是先 ping(通不通)、ss(端口开没)、curl(应用层响应),这些都看不出问题时再 tcpdump 抓包看最底层到底收发了什么。比如"客户端说发了请求、服务端说没收到",抓包就能判断包到底有没有到网卡——呼应 07 章 TCP/IP 分层,tcpdump 看的是链路层实际流量。
抓 HTTPS 内容是加密的看不到明文,但能看到握手、连接是否建立、有没有重传,足够定位大多数网络问题。
一个软件常常依赖几十个库。手动安装要逐个找依赖、按顺序编译,极其痛苦。包管理器自动完成依赖解析、下载、安装、升级、卸载。
📌 什么是“依赖地狱”?想装 A,A 需要 B 和 C;B 又需要 D;C 需要 D 的另一个版本……手动装你会陷入“装一个发现还缺三个”的无底洞,这就是依赖地狱(dependency hell)。
📌 包管理器怎么解决?它维护一份“全量软件目录”,里面记着每个包依赖谁、版本要求是什么。你说装 A,它自动算出整棵依赖树,一次性把 B/C/D 按正确版本都装好。这就是为什么你只需 apt install nginx 一句话的原因。
| Debian 系(你的 Ubuntu) | Red Hat 系 | |
|---|---|---|
| 包格式 | .deb |
.rpm |
| 高层工具 | apt |
dnf / yum |
| 底层工具 | dpkg |
rpm |
| 源配置 | /etc/apt/sources.list |
/etc/yum.repos.d/ |
高层 vs 底层:apt 会自动处理依赖、从网络源下载;dpkg 只能安装本地单个 .deb 文件,不解决依赖。日常用 apt。
updatevsupgrade别混:update只刷新"有哪些新版本"的索引清单,不安装;upgrade才真正下载安装新版本。标准流程是apt update && apt upgrade。📌 用“网购”打个比方记住区别:
apt update= 刷新商品页面,看看现在都有什么新货、什么价(只更新信息,不买);apt upgrade= 真正下单买下来装上。为什么装软件前要先update?因为不刷新目录,你手里的是旧价目录,可能“照着旧地址去拿货”发现货架空了(报找不到包)。
update 不动你的软件,只更新这份"目录"。所以装新软件前先 update,否则可能装到旧版本或报"找不到包"。
场景:从官网下载的 .deb 用 dpkg -i 安装。如果报依赖错误,再用 sudo apt install -f 自动补依赖。
软件源就是存放 .deb 包的服务器地址。
换国内镜像源(加速下载):把 sources.list 里的官方地址替换成阿里云/清华镜像,再 apt update。这是新装系统后常做的优化。
现代 Linux 还有跨发行版的通用打包方式:
| 格式 | 特点 |
|---|---|
| Snap | Ubuntu 主推,自带依赖,沙箱隔离(snap install) |
| Flatpak | 跨发行版桌面应用 |
| AppImage | 单文件免安装,下载即运行 |
你系统里的 /snap 目录就是 Snap 包的挂载点。
systemd 是现代 Linux 的初始化系统和服务管理器,就是进程章节里的 PID 1。它负责:
取代了老的 SysVinit(/etc/init.d/ 脚本)。
📌 为什么需要一个 PID 1 总管家?回想 06 章:所有进程都是 fork 出来的,能追溯到一个最初的祖先——这个祖先就是 PID 1。开机时内核只负责启动 PID 1,剩下“启动网络、启动 SSH、挂载磁盘……”全交给它。你可以把 systemd 想象成公司的总经理:老板(内核)只雇了他一个人,其他员工都是他招的,出事也找他。
📌 systemd 为什么能加快开机?老的 SysVinit 是一个接一个串行启动服务(像排队);systemd 看懂服务间的依赖关系,把互不依赖的服务并行启动,就像多个收银台同时开工,自然快。
systemd 把所有管理对象抽象为 Unit(单元),按后缀分类:
| 类型 | 作用 |
|---|---|
.service |
后台服务(最常用,如 nginx.service) |
.socket |
套接字监听 |
.timer |
定时器(替代 cron) |
.target |
一组 unit 的集合(如开机目标) |
.mount |
挂载点 |
Unit 文件位置:
/lib/systemd/system/ — 软件包自带的(不要改)/etc/systemd/system/ — 管理员自定义的(改这里)关键区别:
enabled(开机是否启动)和active(现在是否运行)是两个独立概念。可以 enabled 但当前 stopped,也可以 active 但 disabled。
📌 用“闹钟”区分 enabled 和 active:
active(运行中)= 闹钟现在正响。start/stop 控制它。enabled(已启用)= 你设了“每天早上自动响”这个闹铃。enable/disable 控制它。两者独立:你可以手动按响闹钟(active)但没设闹铃(disabled)——重启后就不响了;也可以设了闹铃(enabled)但此刻它没响(stopped)。部署服务要两者都要:enable --now = 设好闹铃 + 现在立马响。新手常踩的坑:start 了但忘了 enable,服务器一重启服务就没了。
| 字段 | 含义 |
|---|---|
● 圆点颜色 |
绿=运行,红=失败,灰=停止 |
Loaded |
unit 文件路径 + 是否 enabled |
Active |
当前状态 + 运行时长 |
Main PID |
主进程的 PID |
CGroup |
该服务所有进程(systemd 用 cgroup 追踪) |
systemd 用 journal 统一收集所有服务日志:
排查服务起不来:systemctl status 看概况,journalctl -u 服务名 -n 50 看详细报错。
把你自己的脚本/程序变成 systemd 管理的服务:
逐段解释:
| 字段 | 含义 |
|---|---|
[Unit] Description |
服务描述 |
After=network.target |
在网络就绪后才启动 |
[Service] Type=simple |
前台运行的普通进程 |
User |
以哪个用户身份运行(安全:别用 root) |
ExecStart |
启动命令(必须绝对路径) |
Restart=on-failure |
崩溃时自动重启 |
RestartSec=5 |
重启前等 5 秒 |
[Install] WantedBy |
enable 时挂到哪个 target(multi-user 是常规多用户模式) |
应用流程:
🚧 每次修改 .service 文件后,必须 daemon-reload systemd 才知道变化。
一块裸磁盘要经过四步才能存文件,对应回 02 章"一切挂载到目录树":
📌 为什么要分这么多步,不能插上磁盘就用?用“盖楼”打个比方:
每一层职责不同,才能灵活组合:同一块盘可以分多个区、每个区用不同文件系统、挂到不同目录。这就是为什么不能“一步到位”。
字母 a/b/c 表示第几块盘,数字表示第几个分区。
| 命令 | 视角 | 用途 |
|---|---|---|
df |
文件系统级 | 看整个分区还剩多少空间 |
du |
目录/文件级 | 看具体哪个目录占用大 |
排查磁盘满:先 df -h 找出满的分区,再 du -sh /path/* 逐层定位大目录。
🚧 以下操作会清空数据,务必先 lsblk 确认设备名,别操作错盘。
临时挂载重启后失效,要永久挂载得写进 /etc/fstab。
| 字段 | 含义 |
|---|---|
| 设备 | 设备名或 UUID(推荐 UUID,设备名可能变) |
| 挂载点 | 挂到哪个目录 |
| 文件系统 | ext4 / xfs / swap |
| 选项 | defaults 是常规默认;noatime 可提升性能 |
| dump | 备份标记,通常 0 |
| fsck | 开机检查顺序,根分区 1,其他 2,不检查 0 |
🚧 fstab 写错会导致开机失败!改完一定用 mount -a 测试无报错再重启。
传统分区大小固定,扩容麻烦。LVM 在分区和文件系统之间加一层抽象,让存储空间可以动态调整。
📌 传统分区痛在哪?传统分区像把蛋糕切好分给几个人——切完就固定了。某个分区快满了,旁边分区还空着,也没法把空闲的匀过去,只能重新分区(风险极大)。
📌 LVM 的三层为什么这么设计?用“蓄水”打个比方:
加了“水库”这个中间层,带来灵活性:某根水管(LV)不够用了,从水库再分点量给它就行;水库也不够了,加块新磁盘当新水源注进去。全程不用捣上层的文件。这就是为什么生产服务器几乎都用 LVM。
把多块磁盘的分区(PV)汇成一个池(VG),再从池里切出逻辑卷(LV)。LV 用起来和普通分区一样,但能在线扩容。
LVM 最大价值:磁盘空间不够时,加块新硬盘 → 加进 VG → 扩 LV → resize 文件系统,全程不停机、不重装。
一个经典坑:df -h 显示分区还有大把空间,但创建文件却报 No space left on device。
📌 根因是 inode 耗尽(呼应 02 章)。文件系统在格式化时就固定了 inode 的总数。每个文件(哪怕 0 字节)都要占一个 inode。如果你有海量小文件(比如几百万个缓存碎片、session 文件),inode 先被用光了,而数据块还空着——于是有空间也建不了新文件。
定位哪个目录小文件最多:
📌 两种"满"要分清:df -h 满 = 数据太大(删大文件/扩容解决);df -i 满 = 文件太多(删碎文件解决)。报"空间不足"时两个都要查,否则对着 df -h 怎么也想不通。
上一节手动清日志只是救急。日志会一直增长,迟早撑爆磁盘。logrotate 是系统自带的方案,自动切割、压缩、删除旧日志。
📌 什么叫"轮转"?把当前日志 app.log 改名归档(app.log.1),新日志写进空的 app.log;下次再把 .1 变 .2……保留 N 份后最老的删掉。像超市只留最近几天的监控录像,旧的自动覆盖,既留了近期记录又不会无限占空间。
给自己的应用写一条规则 /etc/logrotate.d/myapp:
📌 copytruncate 解决什么问题?呼应 06 进程章:程序打开日志文件后一直握着那个文件句柄写。如果直接把日志改名,程序还在往"旧句柄"写,新文件一直是空的。copytruncate 先复制内容再把原文件清空(truncate),程序的句柄没变继续写,无需重启应用。另一种方案是轮转后给程序发 HUP 信号让它重开日志(呼应 06 章信号)。
📌 logrotate 自己怎么定时跑?它本身不是常驻服务,而是被 cron 或 systemd timer 每天调用一次(呼应 15 章 cron / 09 章 timer)。这就是为什么配置里写 daily 它就能每天执行——上层有定时器在驱动它。
Linux 服务器安全围绕三个"最小化":
📌 为什么是“最小化”而不是“防得越多越好”?安全里有个概念叫“攻击面”——你暴露给外界的每个端口、每个服务、每个高权限进程,都是一扇可能被撬开的门。门越多,黑客可试的入口越多。
所以安全的本质不是“加多少锁”,而是“少开几扇门”。用低权用户跑服务——即使被攻破,黑客也只拿到低权限;只开必需端口——没开的门根本被不了。这比事后装一堆防护软件有效得多。
防火墙按规则过滤进出的网络包。底层都是内核的 netfilter 框架,上层工具只是它的配置前端:
数据包到达时,防火墙检查:源 IP?目标端口?协议?匹配规则后决定放行还是丢弃。
🚧 启用防火墙前务必先 ufw allow 22!否则规则生效瞬间你的 SSH 会被切断,远程服务器再也连不上。
📌 为什么这个坑特别致命?因为 ufw default deny incoming 一旦生效,默认拒绝所有入站连接——包括你正在用的这条 SSH!你人在外地,机器在机房,连不上就只能去机房接显示器。这是运维新手最经典的“把自己锁门外”事故。顺序必须是:先 allow 22,再启用防火墙。
SSH 是最常被攻击的入口。编辑 /etc/ssh/sshd_config:
🚧 改 SSH 配置时保留当前连接别断开,另开一个新连接测试能登录后,再关旧连接。否则配置写错会把自己锁在外面。
密钥登录原理(比密码安全):
自动封禁多次登录失败的 IP:
原理:监控日志(如 /var/log/auth.log),某 IP 短时间内失败次数超阈值,就用防火墙临时封禁它。
漏洞修复主要靠及时更新,长期不更新的系统是最大的安全隐患。
系统性能瓶颈无非四类资源之一。监控就是找出哪类资源紧张,再定位到哪个进程:
负载 ≈ 等待 + 运行的进程数。判断标准:和 CPU 核心数比。
📌 用“收银台排队”理解 load average:把 CPU 核心想象成超市收银台,进程是顾客。
所以要和核心数比:4 核机器有 4 个收银台,load 4 才是满载。为什么给 1/5/15 分钟三个值?看趋势——如果 1 分钟高、15 分钟低,说明是刚起的瞬间峰;三个都高,说明持续繁忙该查原因了。
| 字段 | 含义 |
|---|---|
us |
用户态进程占用 |
sy |
内核态占用 |
id |
空闲 |
wa |
等待 I/O(高说明磁盘是瓶颈,不是CPU) |
📌 wa(iowait)是最容易误判的指标:很多人看到系统卡,第一反应是“CPU 不够”,加 CPU 却没用。其实要先看 wa:它高说明 CPU 在干等磁盘(比如查询在等慢硬盘返回数据),CPU 本身是空的(id 也可能不低)。这种情况加 CPU 毫无用处,该换快盘或优化 I/O。记住:id 高但系统卡 → 第一个怀疑 wa。
关键:别看 free 列(Linux 会拿空闲内存做缓存),看 available 才是真正可用的。buff/cache 是可回收的缓存,需要时会自动让出。
📌 为什么 free(空闲)列总是很小,是内存不够了吗?不是!这是 Linux 的聪明之处:空着的内存是浪费的,所以它把暂时不用的内存拿去做磁盘缓存(buff/cache),加速文件读写。
用“停车场”打个比方:free 是“空着的车位”,buff/cache 是“临时停了车但随时可走的”。你真要停车时,这些临时车会马上让位。所以 available(free + 可回收的 cache)才是你真正能用的。看到 free 很小别慌,看 available。
📌 swap 为什么一用就卡?swap 是把内存里的数据换到磁盘上,腾出内存。但磁盘比内存慢几千倍!一旦频繁 swap,等于把“闪电的内存”降级成“龟速的磁盘”,性能断崖式下跌。所以 swap 频繁使用 = 内存不够的报警信号。
swap 预警:swap 被频繁使用说明物理内存不足,系统会把内存换到磁盘,性能急剧下降。
| 指标 | 含义 |
|---|---|
%util |
磁盘繁忙度,接近 100% = 磁盘瓶颈 |
await |
I/O 平均等待时间,高=响应慢 |
r/s w/s |
每秒读/写次数 |
关注:带宽是否打满、TIME_WAIT 连接是否异常堆积。
htop 是日常首选,彩色直观,可直接在界面里搜索、杀进程、调优先级。
遇到"系统卡",按这个顺序自顶向下定位:
典型误判:CPU id 很高但系统很卡 → 看 wa,多半是磁盘 I/O 瓶颈,不是 CPU 问题。
接上 project.md 里装的 nginx,深入它的配置。Nginx 是最主流的 Web 服务器/反向代理。
Nginx 读作 "engine-x",是一个高性能的 Web 服务器 + 反向代理。它的核心价值:用户的所有请求先到 Nginx,由它决定怎么处理。
两个角色:
📌 用"代购"和"前台"区分:
一句话:正向代理藏客户端,反向代理藏服务器。Nginx 是后者。
明明应用自己也能监听端口,为什么前面要套一层 Nginx?
| 痛点 | Nginx 解决方式 |
|---|---|
| 后端应用直接暴露公网不安全 | Nginx 当唯一入口,隐藏后端 |
| 应用只能跑在某端口(如 3000) | Nginx 监听 80/443,转发给 3000 |
| 想跑多个网站 | 一个 Nginx 用 server_name 区分多站点 |
| HTTPS 证书每个应用都配麻烦 | Nginx 统一做 SSL 终结 |
| 一台后端扛不住 | Nginx 负载均衡分发到多台 |
| 静态文件让应用处理太浪费 | Nginx 直接返回静态文件,快得多 |
📌 核心思想:把"通用的网络层杂活"(HTTPS、缓存、限流、负载均衡)从应用里剥离,交给专门的 Nginx 做。应用只管业务逻辑。这是关注点分离。
Nginx 配置是层层嵌套的块(block):
三层关系:
http — 全局设置(所有站点共享)server — 一个站点(一个域名/端口组合),可以有多个location — 站点内不同 URL 路径的处理规则,可以有多个📌 像三层抽屉:http 是整个柜子的通用规则,server 是一个抽屉(一个网站),location 是抽屉里的分隔格(不同路径分开处理)。请求来了,先找哪个 server(按域名+端口),再找哪个 location(按路径)。
📌 available 和 enabled 为什么分两个目录?这是个聪明设计,呼应 04 章的软链接:sites-available 是"所有写好的站点配置仓库",sites-enabled 放软链接,只链接当前要启用的。想临时停用一个站点,删软链接即可,配置文件还留着,不用删。
📌 为什么要 proxy_set_header?因为请求经 Nginx 转发后,后端看到的"客户端"是 Nginx(127.0.0.1),看不到真实用户。这几行把原始信息(域名、真实 IP)塞进请求头传给后端,否则后端日志里全是 Nginx 的 IP。
默认轮询(round-robin)依次分发;可改 least_conn(发给连接最少的)等策略。
📌 什么是"SSL 终结"?HTTPS 加解密很耗 CPU。让 Nginx 统一处理加解密(在 Nginx 这里"终结"加密),它和后端之间走普通 HTTP(内网安全)。后端应用完全不用管证书和加密,省心又省 CPU。
📌 为什么改配置后先 nginx -t 再 reload?这是血泪经验:配置写错时直接 reload,Nginx 可能加载失败导致整个网站挂掉。nginx -t 先做语法检查,通过了再 reload 才安全。呼应 10 章 fstab 的 mount -a 思想——危险操作前先验证。
📌 reload 和 restart 区别?呼应 09 章:reload 是平滑重载,老连接继续服务、新连接用新配置,用户无感;restart 是先停后起,会瞬间断开所有连接。生产环境优先 reload。
全部串联了前面学的:服务(09)、端口(07)、日志、防火墙(11)。
第 7 节配 HTTPS 需要证书。生产上证书从哪来?Let's Encrypt 提供免费证书,certbot 工具自动申请、配置、续期,一条命令搞定。
📌 certbot 怎么证明"这域名是你的"?它用 ACME 协议做验证:certbot 在你服务器上放一个特定文件,Let's Encrypt 通过 http://你的域名/.well-known/... 来访问这个文件——能访问到,就证明你确实控制这个域名和服务器,于是签发证书。所以申请前域名必须先解析到本机、80 端口要能被外网访问(呼应 07 DNS + 11 防火墙放行 80)。
📌 为什么有效期只有 90 天?短有效期是安全设计:万一私钥泄露,危害窗口短。但人工每 90 天换一次太麻烦,所以 certbot 装好后会自动加一个 systemd timer(呼应 09 章)定期续期,全程无人值守。certbot --nginx 还会自动帮你改好 nginx 配置(加 ssl_certificate、配 80→443 跳转),省去手写。
现代部署的核心。理解 Docker 前先回顾 06 进程章——容器本质就是"被隔离的进程"。
经典痛点:"在我电脑上能跑,到服务器就挂了"。原因是两边的依赖版本、环境变量、系统库不一致。
Docker 把应用和它的整个运行环境(依赖、库、配置)打包成一个标准盒子,这个盒子在任何装了 Docker 的机器上跑起来都一模一样。
📌 用"集装箱"理解(Docker 的 logo 就是装满集装箱的鲸鱼):以前运货,散装货物在轮船/火车/卡车间换装时各种麻烦。有了标准集装箱,里面装什么不管,外面尺寸统一,任何运输工具都能直接搬。Docker 就是软件界的集装箱——把应用标准化打包,任何机器都能直接运行。
| 概念 | 是什么 | 类比 |
|---|---|---|
| 镜像 Image | 只读模板,含应用+依赖+环境 | 菜谱 / 安装光盘 / class |
| 容器 Container | 镜像运行起来的实例 | 做好的菜 / 装好的系统 / 对象 |
| 仓库 Registry | 存放镜像的地方(Docker Hub) | 应用商店 / GitHub |
📌 镜像和容器的关系 = 类和对象:镜像是静态模板(只读),容器是它跑起来的实例。一个镜像能起任意多个容器,就像一个 class 能 new 出多个对象。改容器里的东西不影响镜像,删了容器再从镜像起一个又是干净的。
📌 为什么容器比虚拟机轻这么多?回顾 01 章的内核概念:
容器靠 Linux 内核的两个机制实现隔离(呼应 06 进程章):
所以本质上,容器就是宿主机上一个被隔离、被限额的普通进程,不是什么虚拟机。
📌 镜像的分层(layer):镜像不是一整块,而是一层层叠起来的(基础系统层 + 依赖层 + 应用层)。多个镜像能共享相同的底层,省空间。改动只在最上层,这也是为什么改一行代码重新构建很快——底层不变直接复用缓存。
📌 -p 8080:80 端口映射什么意思?呼应 07 网络章:容器内部 nginx 监听 80,但容器是隔离的,外面访问不到。-p 8080:80 在宿主机 8080 端口和容器 80 端口之间打通道——访问 宿主机IP:8080 就转到容器的 80。冒号左边是宿主机,右边是容器。
📌 docker exec -it web bash 为什么能"进入"容器?因为容器就是个进程,这条命令是在那个隔离环境里再起一个 bash 进程,让你像 SSH 进服务器一样在容器内部操作。-it = 交互式终端。
📌 为什么需要数据卷?容器是"用完即弃"的——删掉容器,里面写的数据全没了(像便利贴,撕了就没)。但数据库、上传的文件不能丢。数据卷把宿主机的目录挂进容器,数据实际存在宿主机上,容器删了数据还在。这本质就是 02 章的 mount 挂载思想。
Dockerfile 是构建镜像的"说明书",一行行写清楚怎么搭环境:
📌 RUN 和 CMD 的区别(容易混):RUN 在构建镜像时执行(如装依赖,结果固化进镜像);CMD 在容器启动时执行(如启动应用)。一个是"做菜时的准备",一个是"上桌时的动作"。
📌 为什么 COPY package.json 和 COPY . . 分两步?利用第 5 节说的分层缓存:依赖不常变,单独一层;源码常改,放最上层。改代码重新 build 时,依赖那层命中缓存不用重装,构建飞快。这是 Dockerfile 优化的核心技巧。
真实应用常是"网站 + 数据库 + 缓存"多个容器。用 docker-compose.yml 一个文件全描述、一条命令全启动:
📌 为什么要 Compose?手动一个个 docker run 还要配网络让它们互通,繁琐易错。Compose 把整套服务的定义写进一个文件,up 一下全起来、自动配好互联网络。呼应 09 章 systemd 的思想——把启动编排交给配置文件,而非手敲命令。
全程串联前面:进程隔离(06)、端口(07)、服务编排思想(09)、挂载(02)、性能(12)。
几个日常高频但前面没专门讲的系统技能:定时任务、vim、压缩归档、环境变量。
09 章讲过 systemd timer,但传统的 cron 更普及、更简单,至今广泛使用。
五个时间字段(核心):
常见例子:
📌 怎么记这五个字段顺序?从左到右"分时日月周",单位从小到大(分钟最小)。* 表示"每",*/5 表示"每隔5"。先把分钟定死(如 0),否则 * * * * * 会变成每分钟都跑。
📌 cron vs systemd timer 怎么选?cron 简单、一行搞定、适合个人脚本;systemd timer 功能强(能依赖服务、记日志、开机错过能补跑),适合正式服务。新手日常用 cron 足够。
服务器上改配置躲不开 vim(前面多次出现 vim /etc/...)。它有两个核心模式,理解了就不慌。
📌 为什么 vim 这么"反人类"?因为它诞生于没有鼠标、连方向键都未必有的年代,设计目标是"手不离主键盘区就能高效编辑"。它分模式:普通模式下按键是命令(移动、删除),插入模式下按键才是打字。新手卡住通常是忘了切模式。
最小够用命令集:
| 操作 | 按键 | 说明 |
|---|---|---|
| 进入插入模式 | i |
在光标前插入 |
| 回普通模式 | Esc |
退出编辑 |
| 保存退出 | :wq 或 ZZ |
write + quit |
| 不保存退出 | :q! |
强制退出丢弃改动 |
| 删除一行 | dd |
(普通模式) |
| 撤销 / 重做 | u / Ctrl+r |
|
| 搜索 | /关键词 回车 |
n 下一个 |
| 跳到行首/尾 | 0 / $ |
|
| 跳到文件首/尾 | gg / G |
|
| 显示行号 | :set nu |
📌 新手最常卡的两件事:① 打开就猛敲字却没反应——你在普通模式,先按 i。② 想退出退不出——按 Esc 再输 :q!(丢弃)或 :wq(保存)。记住"卡住就按 Esc"。
备份脚本里用过 tar -czf,这里系统讲。注意**"归档"和"压缩"是两件事**:
📌 归档 vs 压缩:tar 本来只"归档"——把一堆文件打包成一个 .tar 文件(像把散件装进一个箱子,不减体积)。gzip 才负责"压缩"(把箱子抽真空变小)。常见的 .tar.gz 就是先 tar 打包再 gzip 压缩,所以命令里 -c(打包) 和 -z(gzip) 一起用。
📌 怎么记 czf / xzf:c=create(打包)、x=extract(解压)、t=list(看),三选一;z=用 gzip;f=后面跟文件名(必须放最后)。打包用 c、解压用 x,其余不变。
前面多次提到 $PATH、.bashrc,这里集中讲清。环境变量是 shell 和程序共享的"全局设置"。
📌 PATH 到底是什么?回顾 01 章:你敲 ls,bash 怎么知道去哪找 ls 程序?它按 $PATH 里列的目录(用 : 分隔)逐个找,找到第一个 ls 就执行。这就是为什么自己写的程序要么放进 PATH 目录,要么写全路径 ./myprog——不在 PATH 里 bash 找不到。
📌 .bashrc、.profile、.bash_profile 区别?.bashrc 每开一个交互式终端就加载(最常改这个);.profile/.bash_profile 登录时加载一次。改了环境变量配置后必须 source 或重开终端才生效(呼应 05 章:用 ./ 跑改不了当前 shell)。
服务器时间不对会引发一连串怪问题:日志时间戳错乱、HTTPS 证书校验失败、定时任务(cron)在错误时间触发、分布式系统数据不一致。
📌 为什么服务器都用 UTC 或统一时区?跨地域的服务器如果各用本地时区,日志一对比就乱套(到底谁先谁后?)。运维惯例是服务器统一用 UTC,展示时再按用户所在时区转换。这样无论机器在哪,日志时间都能直接比较。
📌 什么是 NTP?Network Time Protocol,机器定期向时间服务器对表,自动校正几毫秒的偏差。不开 NTP,机器时钟会慢慢漂移,几个月后差好几分钟,证书、定时任务就出问题。System clock synchronized: yes 就是它在工作。
📌 两个时钟:系统时钟在内存里,开机时从硬件时钟(主板上有纽扣电池供电)读取初值,之后由内核维护。NTP 同步的是系统时钟,hwclock --systohc 再把它写回硬件,保证下次开机也准。
📌 解决什么痛点?呼应 06 章:你 SSH 上服务器跑一个耗时几小时的任务,网络一抖断线,shell 进程收到 HUP 信号,连带任务一起被杀。nohup & 能让单个命令活下来,但你想"重新连上去看它跑得怎样、继续操作"就做不到了。tmux 把会话留在服务器端——断线后会话还在后台跑,重连后一条命令就回到原样,光标、输出、多个窗口全在。
进入 tmux 后的快捷键(都先按前缀 Ctrl+b,松开再按下一个键):
| 按键 | 作用 |
|---|---|
Ctrl+b 然后 d |
detach 脱离会话(任务继续在后台跑) |
Ctrl+b 然后 c |
新建一个窗口 |
Ctrl+b 然后 数字 |
切到第 N 个窗口 |
Ctrl+b 然后 % / " |
左右 / 上下 分屏 |
Ctrl+b 然后 方向键 |
在分屏间跳 |
📌 典型用法:上服务器先敲 tmux,在里面跑任务,Ctrl+b d 脱离,关掉笔记本回家。第二天 tmux attach 连回来,任务还在跑、输出都在。这就是为什么运维跑长任务(数据迁移、编译、压测)几乎都在 tmux 里——会话的生命周期和你的 SSH 连接解绑了。screen 是更老的同类工具,思路一样。
前面 03 章学过 grep,附录速查表提了 sed/awk。这里系统讲三者分工——它们是 Linux 文本处理的核心,运维日志分析、批量改配置全靠它。
📌 打个比方,三剑客各管一摊,配合管道流水作业:
口诀:grep 挑行、sed 改字、awk 取列。一行日志进来,先 grep 挑出错误行,再 awk 取出其中的 IP 列,这就是它们的典型配合。
最高频的就是替换:
其他常用动作:
📌 为什么叫"流式"编辑器?sed 不像 vim 把整个文件读进来再编辑,而是逐行读入、处理、吐出,像流水线一样。所以它能处理超大文件不爆内存,也天然适合放进管道。
-i 是不可逆的,直接改原文件。养成习惯:先不带 -i 看输出对不对,确认了再加 -i,或者用 -i.bak 留个备份。
awk 把每行自动按空白分割成字段:$1 第一列、$2 第二列……$0 是整行,NF 是列数,NR 是行号。
带条件和计算(awk 其实是个小型编程语言):
📌 {} 前面的是条件,后面的是动作:$3>100 {print} 读作"当第3列>100时,打印"。不写条件就对每行都执行,不写动作默认 print。BEGIN{} 在处理前跑一次(打表头),END{} 在最后跑一次(出汇总)。
把三剑客和前面学的管道串起来,这是运维日常:
📌 这就是 Linux 哲学的高光时刻(呼应 03 管道章):没有一个"日志分析专用软件",但 grep + awk + sort + uniq 几个小工具一拼,就是一套强大的日志分析流水线。学会组合,胜过记一堆专用命令。
grep/sed/awk 的"匹配"能力都来自正则表达式(regex)——一套描述"文本模式"的通用语法。学一次,三个工具通用。
📌 正则在描述什么?普通搜索是"找字面量 abc";正则是"找符合某种规律的文本",比如"以数字开头""像邮箱的字符串""3 个连续的 a"。它用一些特殊符号当"占位规则",让你不必列举所有可能。
| 符号 | 含义 | 例子 |
|---|---|---|
. |
任意单个字符 | a.c 匹配 abc、a9c |
* |
前一项重复 0 次或多次 | ab* 匹配 a、ab、abbb |
+ |
前一项重复 1 次或多次 | ab+ 匹配 ab、abbb(不匹配 a) |
? |
前一项 0 次或 1 次(可选) | colou?r 匹配 color、colour |
^ |
行首 | ^# 匹配以 # 开头的行 |
$ |
行尾 | ;$ 匹配以分号结尾的行 |
[] |
字符集合(任选一个) | [0-9] 一个数字,[a-z] 一个小写字母 |
[^] |
取反集合 | [^0-9] 非数字 |
| |
或 | cat|dog 匹配 cat 或 dog |
{n,m} |
重复 n 到 m 次 | [0-9]{3} 恰好 3 个数字 |
() |
分组 | (ab)+ 匹配 ab、abab |
\ |
转义(让特殊符号变普通) | \. 匹配真正的点号 |
📌 为什么 grep 的 + 有时不灵?这是历史遗留:grep 默认用基础正则(BRE),+、?、|、() 被当普通字符,要写成 \+ 才有特殊含义。太反直觉,所以实践中一律加 -E 用扩展正则(ERE),符号直接用,和大多数语言的正则一致。记住:写正则没生效,先试试加 -E。
📌 grep -vE "^#|^$" 是查配置文件的神器:^# 注释行、^$ 空行,| 连起来,-v 反向排除。一条命令把几百行带满注释的配置文件,浓缩成几行真正生效的设置。
理解开机时发生了什么,能串联 01(内核)、06(进程)、09(systemd)三章,也是排查"开机失败""卡住"的基础。
| 阶段 | 干什么 | 呼应章节 |
|---|---|---|
| ① BIOS/UEFI | 主板固件,自检硬件(POST),找到启动磁盘 | — |
| ② GRUB | 引导程序,显示启动菜单,把内核加载进内存 | — |
| ③ 内核 kernel | 解压自身、初始化硬件驱动、挂载根文件系统 / |
01 内核 |
| ④ systemd | 内核启动的第一个进程(PID 1),按依赖关系拉起所有服务 | 06 PID1 · 09 服务 |
| ⑤ 登录 | systemd 启动登录服务,给出登录提示符 | — |
📌 一句话串起来:固件找到内核 → 内核准备好系统底座 → systemd 把所有服务一一拉起 → 你能登录用了。回顾 06 章——systemd 是 PID 1、所有进程的祖先,它的诞生就在第④步。
📌 鸡生蛋问题:内核是个文件,躺在磁盘上。但"读取磁盘上的文件"这件事,本来是内核才会干的——内核还没启动,谁来读内核?这就需要一个极小的、不依赖内核的程序先上场,它就是 GRUB。
GRUB 足够简单,能直接和 BIOS/磁盘打交道,把内核文件搬进内存并跳过去执行。它还能管理多系统启动(菜单里选 Ubuntu 还是 Windows)、传内核参数(比如进救援模式)。
systemd 用 target 描述系统要进入的状态(取代老的 runlevel):
| target | 含义 | 类比老 runlevel |
|---|---|---|
multi-user.target |
多用户命令行(服务器常用) | 3 |
graphical.target |
图形界面 | 5 |
rescue.target |
救援模式(最小环境,修复用) | 1 |
📌 服务器为什么常设 multi-user.target?图形界面吃内存、占资源,服务器没人坐在前面看屏幕,纯命令行就够。呼应 12 性能章——省下的资源全留给业务。
📌 开机变慢怎么查?systemd-analyze blame 直接列出每个服务的启动耗时,排在最前的就是元凶(常见是某个网络等待、磁盘挂载超时)。这把 09 章的服务管理和 12 章的性能排查连起来了。
cat/grep 通吃高频命令一页查。原理见对应正文章节。
| 命令 | 作用 |
|---|---|
ls -alh |
列目录(含隐藏、详细、可读大小) |
ls -lt / ls -lS |
按时间 / 大小排序 |
cd - / cd ~ |
回上个目录 / 回家目录 |
pwd |
当前路径 |
mkdir -p a/b/c |
递归建目录 |
cp -a src dst |
完整复制(保留属性) |
mv old new |
移动/重命名 |
rm -rf dir/ |
强制递归删除(危险) |
ln -s 目标 链接 |
创建软链接 |
find / -name "*.log" -size +10M |
按名+大小找文件 |
find . -mtime +30 -delete |
删 30 天前的文件 |
| 命令 | 作用 |
|---|---|
cat -n file |
输出+行号 |
less file |
分页(/ 搜索,q 退出) |
head -20 / tail -20 |
前/后 20 行 |
tail -f log |
实时追踪日志 |
grep -rn "词" 目录 |
递归搜内容+行号 |
grep -v "^#" |
排除注释行 |
grep -i |
忽略大小写 |
awk '{print $1}' |
取第 1 列 |
sed 's/旧/新/g' |
替换文本 |
| `sort | uniq -c` |
wc -l |
数行数 |
cut -d: -f1 |
按分隔符取列 |
| 写法 | 作用 |
|---|---|
cmd1 | cmd2 |
管道:前者输出给后者 |
> file / >> file |
覆盖 / 追加写 |
2> file |
重定向错误 |
> out 2>&1 |
正常+错误都进文件 |
&> out |
同上(bash 简写) |
2>/dev/null |
丢弃错误 |
| 命令 | 作用 |
|---|---|
chmod 755 file |
数字改权限 |
chmod u+x file |
符号改权限 |
chown user:group file |
改归属 |
chmod -R 644 dir/ |
递归改 |
id / whoami / groups |
查身份/组 |
useradd -m -s /bin/bash 用户 |
建用户 |
usermod -aG sudo 用户 |
加入 sudo 组 |
passwd 用户 |
改密码 |
su - 用户 / sudo -i |
切换用户 / 切 root |
权限数字:r=4 w=2 x=1 → 755=rwxr-xr-x,644=rw-r--r--,600=私密
| 命令 | 作用 |
|---|---|
ps aux |
所有进程快照 |
ps aux --sort=-%cpu | head |
CPU 占用排名 |
top / htop |
实时监控 |
pgrep -a 名字 |
按名找 PID |
kill PID / kill -9 PID |
礼貌终止 / 强杀 |
pkill 名字 / killall 名字 |
按名杀 |
cmd & |
后台运行 |
jobs / fg / bg |
作业控制 |
nohup cmd & |
退出终端仍运行 |
信号:15(TERM 礼貌) · 9(KILL 强杀) · 2(INT=Ctrl+C) · 1(HUP 重载)
| 命令 | 作用 |
|---|---|
ip a |
查 IP 和网卡 |
ip route |
路由表 |
ping -c4 host |
测连通 |
ss -tlnp |
查 TCP 监听端口+进程 |
ss -tlnp | grep :80 |
查 80 端口被谁占 |
curl -I url |
看响应头 |
curl -o file url |
下载 |
dig domain |
DNS 查询 |
ssh user@host -p 端口 |
远程登录 |
scp file user@host:/path |
远程复制(全量) |
rsync -avz 源/ user@host:/目标/ |
增量同步(备份首选) |
rsync -avn 源/ 目标/ |
演练,只看不改 |
tcpdump -i any port 80 -nn |
抓包排查 |
certbot --nginx -d 域名 |
申请免费 HTTPS 证书 |
常见端口:22(SSH) 80(HTTP) 443(HTTPS) 53(DNS) 3306(MySQL) 6379(Redis)
| 命令 | 作用 |
|---|---|
apt update |
刷新索引(装前必做) |
apt upgrade |
升级所有软件 |
apt install 包 |
安装 |
apt remove 包 / purge 包 |
卸载 / 连配置卸载 |
apt search 词 |
搜索 |
dpkg -i 包.deb |
装本地 deb |
dpkg -L 包 |
看包装了哪些文件 |
| 命令 | 作用 |
|---|---|
systemctl status 服务 |
查状态(最常用) |
systemctl start/stop/restart 服务 |
起/停/重启 |
systemctl reload 服务 |
平滑重载配置 |
systemctl enable --now 服务 |
开机自启+立即启动 |
systemctl is-active/is-enabled 服务 |
查运行/自启状态 |
systemctl list-units --type=service |
列所有服务 |
journalctl -u 服务 -f |
实时看服务日志 |
journalctl -u 服务 -n 50 |
看最近 50 行日志 |
systemctl daemon-reload |
改 unit 后重载 |
| 命令 | 作用 |
|---|---|
lsblk |
树状看磁盘分区 |
df -h |
各挂载点用量 |
du -sh dir/ |
目录占用大小 |
du -sh /* | sort -rh | head |
找最大目录 |
free -h |
内存/swap |
mount /dev/sdb1 /mnt |
挂载 |
blkid |
查 UUID |
mount -a |
测试 fstab |
pvs/vgs/lvs |
看 LVM 三层 |
| 命令 | 作用 |
|---|---|
ufw allow 22 |
放行端口(启用前必做) |
ufw enable / ufw status verbose |
启用 / 查规则 |
ufw default deny incoming |
入站默认拒绝 |
last / lastb |
登录成功 / 失败记录 |
who / w |
当前在线用户 |
find / -perm -4000 |
找 SUID 文件 |
| 命令 | 作用 |
|---|---|
top / htop |
全局监控 |
uptime |
看 load average |
free -h |
看 available 和 swap |
iostat -x 1 |
磁盘 I/O(看 %util、await) |
ss -s |
连接统计 |
journalctl -p err -n 50 |
最近错误日志 |
dmesg | tail |
内核消息(OOM 等) |
| 命令 | 作用 |
|---|---|
nginx -t |
检查配置语法(改完必做) |
systemctl reload nginx |
平滑重载(不断连接) |
ln -s sites-available/x sites-enabled/ |
启用站点 |
tail -f /var/log/nginx/error.log |
看错误日志 |
curl -I localhost |
本机测试访问 |
| 命令 | 作用 |
|---|---|
docker pull/images/rmi 镜像 |
拉取/列出/删镜像 |
docker run -d -p 8080:80 --name x 镜像 |
后台运行+端口映射 |
docker ps -a |
列容器(含已停止) |
docker stop/start/rm 容器 |
停/起/删容器 |
docker logs -f 容器 |
看容器日志 |
docker exec -it 容器 bash |
进容器内部 |
docker run -v 宿主:容器 ... |
挂数据卷 |
docker build -t 名:版 . |
按 Dockerfile 构建 |
docker compose up -d / down |
编排多容器 |
docker stats |
看资源占用 |
| 命令 | 作用 |
|---|---|
crontab -e / -l |
编辑/查看定时任务 |
vim:i 编辑 · Esc · :wq / :q! |
改配置必备 |
tar -czf x.tar.gz dir/ |
打包压缩 |
tar -xzf x.tar.gz -C /tmp |
解压到目录 |
export PATH="$PATH:/dir" |
加 PATH |
source ~/.bashrc |
重载配置生效 |
alias ll='ls -alh' |
设别名 |
tmux / tmux attach -t 名 |
终端复用:断线任务不挂 |
Ctrl+b d / Ctrl+b c |
tmux 脱离 / 新窗口 |
timedatectl set-timezone 区 |
改时区 |
logrotate -d /etc/logrotate.d/x |
演练日志轮转 |
| 命令 | 作用 |
|---|---|
grep -E "模式" file |
扩展正则搜索(推荐加 -E) |
grep -vE "^#|^$" 配置 |
排除注释和空行看有效配置 |
sed -i 's/旧/新/g' file |
批量替换(改文件,慎用) |
sed -n '5,10p' file |
只看 5~10 行 |
awk '{print $1}' file |
取第 1 列 |
awk -F: '{print $1}' f |
指定冒号分隔取列 |
awk '{s+=$2} END{print s}' |
某列求和 |
... | sort | uniq -c | sort -rn |
统计 Top 排行 |
正则核心:. 任意字符 · * 0+次 · + 1+次 · ^$ 行首尾 · [0-9] 字符集 · {n} 重复 n 次
性能定位:top 看全局 → CPU高看 ps、wa高看 iostat、内存看 free → 定位进程
先盖住答案自己答,再对照。重点考"为什么",不只是"是什么"。答案在每节末尾折叠区。
Q1. Linux 和 GNU/Linux 有什么区别?
Q2. 为什么用户程序不能直接操作硬件?这样设计有什么好处?
Q3. cat /proc/cpuinfo 读的是磁盘上的真实文件吗?为什么?
Q4. Windows 有 C: D: 盘,Linux 为什么只有一个 /?这样设计好在哪?
Q5. 权限 rwxr-xr-- 用数字表示是多少?为什么用 4/2/1 而不是 1/2/3?
Q6. passwd 命令为什么需要 SUID?它解决了什么矛盾?
Q7. 目录只有 r 没有 x 权限,能进去吗?能看到什么?
/proc 是虚拟文件系统,文件不占磁盘,是你读取的瞬间内核临时生成的实时数据。754。用 4/2/1(二进制位权)保证任意组合相加不冲突,4+1=5 只能是 r-x。/etc/shadow 是 600 root,只有 root 能写。SUID 让 passwd 运行时临时变身 root,只在这个受控程序里有改密码的权限,而不必把文件开放给所有人。x 才是"进入权",没有 x 即使有 r 也只能看到文件名列表,无法访问内容或 cd 进去。Q8. 脚本第一行 #!/bin/bash 叫什么?是给谁看的?
Q9. ./script.sh 里的 cd /tmp 为什么不影响当前终端?想让它影响该怎么运行?
Q10. 退出码为什么 0 表示成功、非 0 表示失败(和直觉相反)?
Q11. 为什么创建进程要 fork + exec 两步,而不是一步搞定?
Q12. kill 为什么叫 kill 却不一定是"杀"?kill -9 和 kill -15 有什么区别,该先用哪个?
Q13. 2>&1 里的 & 是干什么的?为什么 > out.txt 2>&1 顺序不能反?
Q14. apt update 和 apt upgrade 区别是什么?为什么装软件前要先 update?
./ 会 fork 出子 shell 执行脚本,cd 改的是子进程的目录,子进程退出就没了。想影响当前终端用 source script.sh(不开子进程)。if、&&、|| 能精确判断。-15(TERM) 是礼貌通知,进程可保存数据后退出;-9(KILL) 是内核直接拔插,拦不住但可能丢数据。先 15 后 9。& 表示"这是文件描述符不是文件名",&1 = fd 1 当前指向的地方。2>&1 复制 fd 1 此刻的指向(快照),所以必须先 > out.txt 把 1 接到文件,再 2>&1 让 2 跟上。Q15. systemd 的 enabled 和 active 有什么区别?部署服务时该用什么命令一步到位?
Q16. 改完 .service 文件后必须执行什么命令?为什么?
Q17. TCP 三次握手为什么是三次,不是两次或四次?
Q18. 启用 ufw 防火墙前,为什么务必先 ufw allow 22?
Q19. LVM 的 PV/VG/LV 三层分别是什么?它比传统分区强在哪?
Q20. 系统很卡但 top 显示 CPU 空闲(id 高),最该先看哪个指标?为什么?
Q21. free -h 显示 free 很小,是内存不够了吗?该看哪一列?
Q22. 磁盘满了,用什么命令逐层定位是哪个目录占的?
systemctl enable --now 服务。systemctl daemon-reload。systemd 缓存了 unit 配置,不重载就不知道你改了文件。default deny incoming 生效后默认拒绝所有入站,包括你正在用的 SSH,会瞬间把自己锁在门外。必须先放行 22 再启用。wa(iowait)。它高说明 CPU 在干等磁盘 I/O,CPU 本身是空的,加 CPU 没用,该换快盘或优化 I/O。available 列才是真正可用的。df -h 找满的分区 → du -sh /* | sort -rh | head 逐层往下钻定位大目录。Q23. 正向代理和反向代理有什么区别?Nginx 属于哪种?
Q24. Nginx 改完配置后为什么先 nginx -t 再 reload?reload 和 restart 有什么区别?
Q25. sites-available 和 sites-enabled 两个目录为什么要分开?启用站点本质做了什么?
Q26. 镜像和容器是什么关系?用编程概念怎么类比?
Q27. 容器为什么比虚拟机轻?它靠什么实现隔离?
Q28. docker run -p 8080:80 里冒号两边分别是什么?为什么需要它?
Q29. 容器删了数据就没了,怎么持久化?这本质是哪一章的什么机制?
Q30. Dockerfile 里 RUN 和 CMD 有什么区别?
Q31. cron 的五个时间字段从左到右是什么?*/5 * * * * 是什么意思?
Q32. vim 打开后猛敲字没反应,为什么?想退出退不出怎么办?
Q33. tar -czf 里的 c/z/f 各是什么?归档和压缩有何不同?
Q34. 硬链接和软链接有什么区别?删掉原文件后各自还能用吗?
Q35. ls -l 第 2 列那个数字是什么?它和 inode 有什么关系?
Q36. 磁盘 df -h 显示还有空间,创建文件却报 No space left,为什么?怎么查?
Q37. usermod -aG docker alice 里的 -a 不加会怎样?主组和附加组有什么区别?
Q38. grep、sed、awk 三者分工是什么?用一句口诀概括。
Q39. 系统从按下电源到能登录,经历哪几个阶段?systemd 在第几步出现?
Q40. 为什么需要 GRUB 这个引导程序,内核不能自己启动吗?
nginx -t 先做语法检查,避免配置写错直接 reload 导致整站挂掉(同 fstab 的 mount -a 思想)。reload 平滑重载,老连接不断;restart 先停后起会断所有连接。生产优先 reload。ln -s),停用删软链接即可,配置仍保留。-p 在两者间打通道,访问宿主机:8080 转到容器:80(呼应 07 端口)。-v 宿主机目录:容器目录,数据存宿主机,容器删了还在。本质就是 02 章的 mount 挂载思想。RUN 在构建镜像时执行(如装依赖,结果固化进镜像);CMD 在容器启动时执行(如启动应用)。*/5 * * * * = 每 5 分钟执行一次。i 进插入模式才能打字。退不出按 Esc 再输 :q!(丢弃)或 :wq(保存)。c=create 打包、z=用 gzip 压缩、f=指定文件名。归档是把多文件打成一个包(不减体积,tar 的本职),压缩才减体积(gzip 干的),.tar.gz 是先打包再压缩。ln 一次加1,删一个名字减1,减到 0 内核才真正回收磁盘数据(所以删文件本质是 unlink)。df -i 查 inode 使用率(df -h 查的是空间,两者要分开看)。-a 会用 docker 覆盖掉所有附加组(可能把 sudo 弄丢,失去管理员权限)。主组只有一个、决定新建文件的所属组;附加组可多个、用来获得额外权限。加组永远用 -aG。碎片时间快速过,看问题立刻在心里答。
| 问 | 答 |
|---|---|
| 权限 755 是什么? | rwxr-xr-x |
| r/w/x 数字? | 4/2/1 |
| 强杀进程信号? | 9 (SIGKILL) |
| 礼貌终止信号? | 15 (SIGTERM) |
| 查端口占用? | ss -tlnp |
| 查本机 IP? | ip a |
| 实时看日志? | tail -f 或 journalctl -u 服务 -f |
| 开机自启+启动? | systemctl enable --now |
| 装软件前先做? | apt update |
| 递归建目录? | mkdir -p |
| 丢弃错误输出? | 2>/dev/null |
| 内存真正可用看哪列? | available |
| 系统卡先看哪个 CPU 指标? | wa (iowait) |
| 找最大目录? | du -sh /* | sort -rh |
| SSH 默认端口? | 22 |
| HTTP/HTTPS 端口? | 80 / 443 |
| Nginx 改配置先做? | nginx -t 检查语法 |
| 启用 Nginx 站点? | 在 sites-enabled 建软链接 |
| 镜像 vs 容器? | 类 vs 对象(模板 vs 实例) |
| Docker 端口映射? | -p 宿主:容器 |
| 容器数据持久化? | -v 挂数据卷 |
| 进容器内部? | docker exec -it 容器 bash |
| cron 五字段顺序? | 分 时 日 月 周 |
| vim 退出不保存? | Esc → :q! |
| 打包压缩目录? | tar -czf x.tar.gz dir/ |
| 硬链接 vs 软链接? | 同 inode 别名 vs 存路径的独立文件 |
| 删原文件软链接会怎样? | 失效(死链接) |
| 空间没满却建不了文件? | inode 耗尽,df -i 查 |
| 加附加组命令? | usermod -aG 组 用户(必带 a) |
| 三剑客口诀? | grep 挑行 · sed 改字 · awk 取列 |
| 取文件第1列? | awk '{print $1}' |
| 文本批量替换? | sed -i 's/旧/新/g' |
| 改时区? | timedatectl set-timezone 区 |
| 开机五阶段? | BIOS→GRUB→内核→systemd→登录 |
| 查谁拖慢开机? | systemd-analyze blame |
| 增量同步文件? | rsync -avz 源/ 目标/ |
| rsync 比 scp 强在? | 只传变化部分(差量) |
| 网络抓包工具? | tcpdump -i any port X |
| 日志自动轮转? | logrotate(防撑爆磁盘) |
| SSH 断了任务不挂? | tmux(会话留服务端) |
| tmux 脱离会话? | Ctrl+b 然后 d |
| 免费 HTTPS 证书? | certbot / Let's Encrypt |
| grep 正则 + 不灵? | 加 -E 用扩展正则 |
| 正则行首/行尾? | ^ / $ |
完成下面任务,每个都用到多个知识点:
/etc 下最大的 5 个文件(提示:ls -lS / find + sort)/etc 打包压缩成带时间戳的 tar.gz(提示:tar + date)一个项目把前面知识串起来。从装软件到上线、加监控、做安全加固,全流程实操。每步标注用到哪节知识,完成后你就具备了基本运维能力。
涉及知识点:包管理(08) · 服务(09) · 脚本(05) · 进程(06) · 权限(04) · 网络(07) · 安全(11) · 存储(10)
检查点:
📌 串联:enable --now 同时设开机自启和立即启动(09);ss -tlnp 查监听端口(07)。
📌 串联:/var/www 属于可变数据目录(02);用 ls -l 看文件归属和权限(04)。
创建 ~/monitor.sh,监控 nginx 是否存活、记录系统资源:
逐处知识点:
pgrep nginx(06 按名查进程)systemctl start(09 拉起服务)free -h 取 available、df -h 取使用率(12 + 10)>> 追加写日志(03 重定向)chmod +x(04 权限)用 systemd timer 每分钟跑一次监控(替代传统 cron,对比见 15 章)。
创建 service 单元:
创建 timer 单元:
启用:
📌 串联:daemon-reload 让 systemd 识别新 unit(09);tail -f 实时追踪日志(03)。
检查点:确认 22/80/443 放行,其他端口拒绝。
📌 串联:先 allow 22 再 enable,避免把自己锁门外(11)。
模拟 nginx 挂掉,看监控能否自愈:
排查练习:
curl http://localhost 返回你的自定义页面systemctl is-enabled nginx 显示 enabled/var/log/mymonitor.log 每分钟新增一行ufw status 只放行 22/80/443把孤立的命令变成了一条完整的运维链路:
真实的服务器运维,核心就是这套流程的放大版。