第五部分:命令执行全景 (41-50 回)

导言


Linux 操作系统凭借其开源特性与强大性能,在计算机领域占据重要地位。Linux 0.11 作为 Linux 发展早期的经典版本,其源代码蕴含着操作系统核心功能的基础设计思想。

详情见品读 Linux 0.11 核心代码

一、输入阶段:命令的获取与缓冲


1.1 键盘输入处理机制

当用户在键盘上按下一个按键时,硬件会触发 0x21 号中断,进而调用keyboard_interrupt中断处理函数。此时键盘控制器发送的扫描码会经历三重处理:

  • 扫描码转换:通过键盘映射表转换为对应的 ASCII 码
  • 队列存储:字符被存入tty_read_q原始输入队列
  • 终端处理copy_to_cooked函数对字符进行规范处理,如退格删除、换行转换等,处理后的字符存入secondary规范队列

这两个关键队列的分工如下:

1
tty_read_q (原始队列)  ←  扫描码 → ASCII转换  →  secondary (规范队列)

1.2 命令读取与阻塞控制

shell 通过read系统调用从secondary队列获取字符,这一过程包含精巧的阻塞机制:

  • secondary队列为空时,调用进程会进入阻塞状态
  • 内核通过修改进程state字段为非TASK_RUNNING状态实现阻塞
  • sleep_on函数将进程放入等待队列,wake_up函数在数据到来时唤醒进程

字符在终端队列间的流动路径为:

1
secondary队列  →  tty_read读取  →  tty_write写入write_q  →  shell读取

二、解析执行:命令的翻译与执行准备


2.1 命令语法分析

shell 对输入的命令字符串进行词法与语法分析:

  • 识别管道符|、重定向符>等特殊符号
  • 分割命令参数,构建参数列表
  • 生成cmd结构体描述命令执行所需信息

ls -la | grep log为例,语法分析会识别出两个命令节点和一个管道操作。

2.2 管道机制的底层实现

管道操作的核心是文件描述符重定向:

  1. 管道创建:通过pipe系统调用创建匿名管道文件,本质是一块共享内存

  2. 描述符重定向:关闭左边进程的 stdout dup 管道写端文件描述符

  • 关闭右边进程的 stdindup 管道读端文件描述符
  1. 子进程执行:通过fork创建子进程,execve加载目标程序

管道的本质可以理解为:

1
2
3
4
5
进程A stdout ────┬─────────┐
▼ │
管道文件(内存) │
▲ │
进程B stdin ────┴─────────┘

三、数据读取:从文件系统到内存的交互


3.1 文件系统寻址过程

当命令需要读取文件时:

  • inode 查找:从根目录开始,按路径分量逐层查找 inode 节点
  • 块地址映射:通过bmap函数将逻辑块号转换为物理块地址
  • 目录项缓存:使用 dentry 缓存加速路径查找

3.2 硬盘数据读取流程

数据从硬盘到内存的传输经历多层处理:

  1. 缓冲区操作
  • getblk函数查找缓冲池,未命中时分配新缓冲块
    • bread函数触发实际硬盘读取操作
  1. 底层 IO 交互
  • ll_rw_block函数向硬盘发送读请求
    • do_hd_request函数处理硬盘请求队列
  1. 中断响应
  • 硬盘完成读取后触发中断,调用read_intr回调函数
    • 数据从硬盘控制器读取到缓冲区

数据流动的关键函数链为:

1
bread → ll_rw_block → do_hd_request → read_intr

四、信号处理:命令执行的异常控制


4.1 信号发送机制

当用户按下 Ctrl+C 时:

  • tty_intr函数检测到特殊字符,生成 SIGINT 信号
  • 信号被发送到当前进程组的所有进程
  • 信号通过进程描述符的signal位图记录

4.2 信号处理流程

内核在进程切换时检查信号:

  • do_signal函数遍历信号位图,查找处理函数
  • 信号处理有三种方式:
    • 忽略(如 SIGKILL 不可忽略)
    • 执行默认处理(如 SIGINT 终止进程)
    • 执行用户自定义处理函数
  • 处理完成后恢复进程执行

五、输出显示:结果的终端呈现


5.1 输出数据流转

命令执行结果的输出路径:

  1. write系统调用将数据写入tty_write_q输出队列
  2. tty_write函数从队列读取数据,调用con_write
  3. con_write函数通过显卡驱动将字符写入显示缓冲区

5.2 进程生命周期管理

命令执行完毕后:

  • 父进程通过wait系统调用等待子进程结束
  • 子进程释放资源,向父进程返回退出状态
  • shell 重置终端状态,等待下一条命令输入

全流程技术总结

一条命令的执行背后,是多个子系统的协同工作:

  1. 中断系统:处理键盘输入和硬盘 IO 完成事件
  2. 进程系统:创建子进程、管理进程状态转换
  3. 文件系统:实现文件寻址和数据块读取
  4. 终端系统:管理输入输出队列和字符显示

理解这一完整流程,有助于深入掌握 Linux 系统的核心工作机制。当我们在终端输入命令时,每一个字符都经历了从硬件中断到软件处理的复杂旅程,最终在屏幕上呈现出执行结果。这种多层抽象的设计思想,正是 Linux 系统强大生命力的源泉。


第五部分:命令执行全景 (41-50 回)