这本书讲 Linux 的骨架:内核、进程、内存、文件系统、命令行。我读的时候最有用的不是记住每条命令,而是搞清楚”敲一条命令时,系统里发生了什么”。这篇记录我留下的部分。

三层结构

Linux 分为三层:用户进程、内核、硬件。

Linux分层

内核在硬件之上,管理硬件,是硬件系统和应用程序之间通信的接口。

进程指计算机中运行的所有程序,组成最顶层,叫做用户空间(user space)。

用户进程和内核最根本的区别:内核运行在内核模式(kernel mode),用户进程运行在用户模式。内核模式下不受限制,可以访问处理器和内存的任何部分。只有内核能访问的空间叫内核空间(kernel space),用户进程能访问的叫用户空间。一般来说,用户进程的影响范围被限制在用户空间内。

内核在管四件事

  • 进程:决定哪个进程可以使用 CPU
  • 内存:内存分配,管理进程间的共享内存与空闲内存
  • 设备驱动程序:硬件系统和进程之间的接口
  • 系统调用和支持:进程通过系统调用和内核通信

进程管理:上下文切换

CPU 同时运行多个进程,靠的是轮流执行,叫多任务执行。CPU 从一个进程切换到另一个进程,叫上下文切换(context switch)。完整过程:

  1. CPU 停止 A 进程,由内核接管 CPU 控制权
  2. 内核记录当前 CPU 和内存的状态信息,恢复进程时使用
  3. 内核执行上个时间段内被挂起的任务(输入输出、磁盘读写)
  4. 内核从准备就绪的进程中选择下一个
  5. 内核为 B 进程准备 CPU 与内存
  6. 内核把新进程的执行时间段通知 CPU
  7. 内核将 CPU 切换至用户模式,把控制权移交给新进程

内核在什么时候运行?在上下文切换的时间段间隙中运行。

内存管理:MMU 和页表

内核在上下文切换过程中管理内存,要保证:

  • 内核自己的专有内存空间
  • 每个用户进程的专有空间
  • 用户进程之间的共享空间
  • 某些用户进程的内存空间是只读的
  • 交换空间(swap space):用磁盘扩展出比物理内存更大的可用空间

进程不直接访问物理内存。MMU(内存管理单元)截获进程的访问请求,通过内存映射表把虚拟地址转换成实际物理地址。这张表由内核维护,用页表(page table)实现。

系统调用:fork 和 exec

有些事用户进程自己做不了,比如打开、读取、写文件,这些操作都要通过系统调用(system call)请内核完成。

两个最基础的调用:

  • fork():内核创建一个和当前进程几乎一样的副本
  • exec(program):内核启动 program 来替换当前进程

除了 init,Linux 里所有用户进程都是通过 fork() 启动的。多数情况下,你还可以用 exec() 启动新进程。

在终端输入 ls 时发生的事:shell 调用 fork() 创建一个 shell 副本,这时候有了两个一样的 shell 进程,然后副本调用 exec(ls) 来运行 ls。

内核还提供虚拟设备,比如随机数生成器(/dev/random)。它技术上不一定要放在内核里,但实现难度上,放在内核里要小得多。

用户

Linux 继承了 UNIX 的用户概念。早期还没有 PC 这个概念时,一台设备接多个终端,多个用户共用。

内核用用户 ID 管理用户,每个用户进程都有一个所有者。管理员用户即 root,拥有超级权限。

root 权限很高,但 root 也运行在用户模式,不是内核模式。

命令行的背后

Linux 默认 shell 是最初 Bourne Shell 的增强版,叫 bash(Bourne-again shell)

标准输入输出

进程从输入流读数据,向输出流写数据。

终端里只输入 cat,cat 就从标准输入流读数据,运行 cat 的终端就成了标准输入。这时候你输入任意字符都会回显。

Ctrl+D 终止当前终端的标准输入并结束命令;Ctrl+C 终止进程本身,不管有没有输入输出。

标准输出同理。cat 运行时标准输出和终端建立连接,所以能看到回显。

标准输入和标准输出简写为 stdin 和 stdout。很多命令和 cat 一样,不指定输入文件就从标准输入读。输出则有些命令会直接写到文件。

通配符展开

shell 中 * 代表任意字符和数字。Shell 根据通配符匹配文件,把命令中的参数替换成实际的文件名,这个过程叫展开

  • at* 展开为所有以 at 开头的文件名
  • *at 展开为所有以 at 结尾的文件名
  • *at* 展开为所有包含 at 的文件名

? 匹配任意一个字符,比如 b?at 匹配 boat 和 brat。

如果没有匹配的文件名,shell 就不展开。
不想展开可以用引号包起来:'*'

Linux 中 . 代表当前目录(./mydir/),.. 代表上级目录(cd ../../usr)。

常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 打印
echo Hello there

# 查看文件内容
cat /etc/passwd
cat file1 file2

# 列表
ls -l
ls -a

# 复制 / 移动 / 删除
cp file file1
cp file1 .... fileN dir
mv file file1
mv file1 .... fileN dir
touch file
rm file
mkdir dir
rmdir dir # 删除空文件夹

# 搜索
grep -i pattern file # 不区分大小写
grep -v pattern file # 反转匹配,输出所有不匹配的行
grep -E pattern file # 启用正则,等价于 egrep
grep root /etc/passwd # 在 passwd 文件中查找 root
find dir -name file -print # 在 dir 目录下按名字查找
locate file # 按索引查找,快,但索引定期更新,新文件可能查不到

# 查看
head file
tail file
less file # more 的增强版:空格下一页、B 上一页、Q 退出、/word 向下搜、?word 向上搜
pwd # 显示当前目录
diff file1 file2 # 比较文件
file file # 显示文件详细信息
sort -r file # 按字母数字排序,-r 反向

# 重定向
command > file # 输出重定向到文件(覆盖)
command >> file # 追加到文件末尾

环境变量和 PATH

通过 $ 使用声明过的变量。

环境变量和 Shell 变量的区别:Shell 变量只能被当前 shell 使用,export 后变成环境变量,可以传给子进程。

1
2
3
STUFF=blah
echo $STUFF
export STUFF

PATH 决定你在任意目录敲命令时,shell 去哪里找可执行文件。把目录加进 PATH:

1
PATH=$PATH:dir

命令行编辑

一些比箭头更好用的快捷键:

1
2
3
4
5
6
7
8
9
10
Ctrl+B  左移
Ctrl+F 右移
Ctrl+P 上一条命令
Ctrl+N 下一条命令
Ctrl+A 光标到行首
Ctrl+E 光标到行尾
Ctrl+W 删除前一个词
Ctrl+U 删除从光标到行首
Ctrl+K 删除从光标到行尾
Ctrl+Y 粘贴已删除的文本

目录结构

Linux目录结构

  • /bin:基础命令的可执行文件,大部分是 C 编译器创建的二进制文件
  • /dev:设备文件
  • /etc:系统配置文件
  • /home:个人目录
  • /lib:可执行文件使用的代码库
  • /proc:当前运行的进程和内核信息
  • /sys:类似 /proc,暴露内核与硬件信息
  • /sbin:系统管理命令,很多需要 root 运行
  • /tmp:临时文件,很多系统启动时会清空
  • /usr:存放许多 Linux 系统文件
  • /var:运行时数据,如系统日志、用户信息、缓存

命令背不下来没关系,用到再查。搞清楚命令背后谁在干活,比记住命令本身有用。