从零写OS(二十五):TTY 终端输入

到目前为止,进程只能输出,没有办法接收用户输入。SYS_READ 形同虚设,键盘按了也没反应。 这一章实现 TTY——Unix 对终端设备的最初抽象,让进程能以行为单位从串口读取输入。 TTY 是什么 TTY 原本是 teletypewriter(电传打字机)的缩写。在 Unix 里,它泛指"终端"——一个字符设备,既能接收键盘输入,又能输出文字。 现代 Linux 里的 /dev/tty、/dev/pts/0 都是 TTY 的后代。我们这里用串口 COM1 模拟一个最简单的 TTY。 整体架构 用户键盘输入 ↓ 串口硬件触发 IRQ4 isr_handler → tty_recv(c) ← 回显 + 写入 ring buffer ↓ 遇到 '\n' line_ready = 1 ↓ 进程 syscall SYS_READ(fd=0) tty_read(buf, len) ← 把一行搬到用户 buf ↓ 用户程序处理输入 三个关键组件: Ring Buffer:固定大小的环形缓冲区,存放还未被读走的字符 行规程(line discipline):积累字符,遇 \n 才通知"行就绪" IRQ4 中断处理:从串口读一个字节,交给 TTY Ring Buffer 环形缓冲区是一个固定数组,加上读指针和写指针: [ _ _ _ _ h e l l o \n _ _ ] ↑ ↑ rx_read rx_write rx_len = 6 写字符:rx_buf[rx_write] = c; rx_write = (rx_write + 1) % TTY_BUF_SIZE; rx_len++ ...

May 14, 2026 · 2 min · 大飞

从零写OS(十三):Shell,把所有东西串起来

前十二章,内核从一个 512 字节的 Bootloader 长成了有进程调度、文件系统、VFS 的微型系统。但用户还没有办法和它交互。 这一章做 Shell——一个可以敲命令操作文件的命令行。 先把概念搞清楚 Shell 是普通进程 Shell 不是内核的一部分。它是一个运行在用户态的普通进程,通过系统调用和内核打交道,和 ls、cat 这些程序的地位完全一样。 真实系统里,Shell 用 fork + exec 启动外部命令。我们这里简化:四条命令直接内嵌在 Shell 进程里,不做 fork/exec。 tokenize:命令行的第一步 用户输入一行字符串,Shell 要把它拆成命令名和参数: "write hello.txt world" ↓ tokenize argv[0] = "write" argv[1] = "hello.txt" argv[2] = "world" 做法是遍历字符串,遇到空格就写入 \0 截断,记录每段的起始地址。不需要任何库函数,20 行搞定。 串口 I/O 键盘输入通过串口读取(x86 端口 0x3F8)。QEMU 的 -serial mon:stdio 把宿主机终端直接映射到串口,你在终端敲的每个字符都会被 in 指令读到。 实现了什么 四条命令: 命令 功能 write <文件> <内容> 创建文件并写入内容 read <文件> 读取文件内容并打印 ls 列出根目录所有文件 help 打印帮助 关键代码 readline:读一行,支持 backspace static int readline(char *buf, int maxlen) { int i = 0; while (i < maxlen - 1) { char c = serial_getchar(); if (c == '\r' || c == '\n') { serial_print("\r\n"); break; } if (c == 127 || c == '\b') { if (i > 0) { i--; serial_print("\b \b"); } continue; } buf[i++] = c; char echo[2] = {c, 0}; serial_print(echo); // 回显给用户 } buf[i] = '\0'; return i; } \b \b 是终端删除字符的标准做法:退格、打空格覆盖、再退格。 ...

May 6, 2026 · 2 min · 大飞
京ICP备14031575号-3