计算机图形学 01:点、向量与矩阵怎样表达几何
点先旋转再平移,和先平移再旋转,为什么会落在不同位置?若把矩阵相乘的顺序写反,程序通常仍能编译,物体也可能仍在画面里。仅凭“看起来动了”无法判断几何变换正确。 取点 p=(1,0,0)p=(1,0,0)p=(1,0,0),绕 Z 轴旋转 90°,再平移 (1,2,0)(1,2,0)(1,2,0)。结果应为 (1,3,0)(1,3,0)(1,3,0);反过来执行,结果是 (−2,2,0)(-2,2,0)(−2,2,0)。这两个可手算的结果将成为累计代码的检查样本。第 00 篇检查图像数组,本篇检查进入渲染器之前的几何数据。 三个数字相同,含义未必相同 点表示位置,向量表示位移或方向。例如点 A 与点 B 的差 B−AB-AB−A 是从 A 指向 B 的位移;从 A 出发加上位移 v,得到点 A+vA+vA+v。两个位置相加通常不是需要的几何操作,除非同时说明所取的仿射组合,例如中点 (A+B)/2(A+B)/2(A+B)/2。 坐标依赖参考系。选择原点 O 与三个基向量 ex,ey,eze_x,e_y,e_zex,ey,ez 后,点的坐标 (x,y,z)(x,y,z)(x,...
计算机图形学 00:从一个像素到一张图像
一张图左右颠倒,错误可能只是一行数组索引;颜色发暗,原因也可能发生在写文件之前。只检查程序退出码,不能判断这两类错误。图形学实验的第一个可用结果,是一张能追查每个色块来源的图,以及一组独立于画面观感的检查。 这个实验生成一张 16×16 的非对称小图。左上、右上、左下、右下分别是红、绿、蓝、黄,内部有黑红交替的竖条纹,绿色分量向下分四级增加,坐标 (3,5) 放一个品红标记。它没有相机、光源或三维模型,全部颜色由整数坐标直接决定。 图为程序输出的 PNG 转换版,以最近邻方式放大显示。放大的方块用于检查样本排列,不能据此推断真实显示器的发光结构。原始数据也可下载:diagnostic.ppm。 像素数组先确定什么 用 (x,y) 给一个样本编号,需要先规定原点与方向。这里原点是图像左上角,横坐标向右增加,纵坐标向下增加,合法范围为 0≤x<16、0≤y<16。这些是图像存储约定,不代表今后的三维世界也必须让 Y 向下。 一个 RGB 样本保存红、绿、蓝三个通道。本篇每个通道是 0 到 255 的整数码值,0 表示相应通道的低端,255 表示高端。红色标记是 (255...
从零编写操作系统 30 - 综合演示与交付验证
前 29 篇已经分别证明了引导、内存、调度、用户态、文件系统、Shell、管道和窗口终端。单项实验通过仍留下两个问题:这些机制能否在同一次启动中连续工作,系统重启后能否得到同样的结果? 最终实验把答案压进一条固定操作链:列出 FAT16 根目录,读取文件,通过管道统计字节,从磁盘加载 DEMO.EXE,并发运行两个计数进程和一个越界进程。越界进程被终止后,两个计数进程必须继续增长;所有子进程退出后,Shell 还要执行下一条命令并完整释放资源。相同场景随后经历一次同进程 hard reset 和一次新 QEMU 进程复跑。 补齐最终场景缺少的两个入口 Day 29 的 Shell 能打开已知文件,却不能询问卷上有哪些文件;Day 19 的 DEMO 则以内嵌 ELF 运行,尚未走 FAT16 装载路径。Day 30 只补这两个缺口,不重写已经通过的机制。 最终镜像增加 syscall 12: 12345678#define SYS_READDIR 12ustruct fat_dirent { char name[13]; uint8_t attribute...
从零编写操作系统 29 - 窗口终端:把用户态 Shell 接进图形界面
Day 28 的窗口能接收字符,却还只是把字符追加到内核数组。Day 23 的 Shell 则已经在 Ring 3 中运行,能从 FAT16 加载命令、等待子进程和连接管道,但输出只能到文本控制台。把两者接起来,关键不是再写一套命令解释器,而是保持 Shell 只认识 fd 0/1/2,让窗口成为终端字节流的生产者和消费者。 本篇新增一个固定的图形终端窗口。键盘字符进入 64 字节输入队列,Shell 输出进入另一条 64 字节队列,UI 每轮只消费一小批字符,再处理鼠标和重绘。这样既能复用原来的用户态程序,也能用真实的满队列阻塞检查长输出期间的调度与界面响应。 复用 Shell,先固定身份证据 mbr-window-shell.img 使用图形版 stage2,但 FAT16 分区直接来自 Day 23 的 fat16-shell.bin。启动 Shell 的路径没有绕过文件系统: 123456FAT16 /SHELL.EXE → file_open + file_pread → elf_load_source → user_space_build_stack → p...
从零编写操作系统 28 - 窗口管理:Z 序、焦点与拖动
Day 27 已经把鼠标字节变成坐标、按键和边沿,但屏幕上仍然只有一个直接重绘的字符终端。窗口管理要再回答三件事:重叠位置显示谁,点击交给谁,拖动过程中谁持续拥有指针。 本篇实现一个固定双窗口管理器。窗口表保存几何和文本,Z 序单独保存遮挡关系;命中从顶向底查找,绘制从底向顶执行。左键按下标题栏后建立拖动捕获,键盘字符只写入焦点窗口。每次状态变化仍重建完整 320×200 画面,以最小机制先验证所有权和遮挡恢复。 窗口表与 Z 序承担不同职责 两个窗口的内容放在固定表中: 1234567891011121314struct demo_window { int32_t x, y; uint32_t width, height; uint8_t body_color; char name; char text[8]; uint32_t text_length;};struct demo_window windows[2] = { {20, 40, 150, 100, 1, 'A',...
从零编写操作系统 27 - 鼠标输入:共享控制器、三字节包与级联中断
Day 26 已经能在 Mode 13h 中重建完整画面,但画面仍然没有指针。接入鼠标也不只是再读一个端口:PS/2 键盘和鼠标共享控制器输出缓冲区,鼠标包可能在任意字节处损坏,IRQ12 还要经过从片和主片两级 8259A。 本篇为固定的 QEMU PC 增加标准三字节 PS/2 鼠标。中断入口只读一次 0x60,依据控制器状态把字节分到键盘或鼠标队列;任务上下文负责组包、符号扩展、Y 轴转换、按键状态和坐标裁剪。实验会填满 64 字节鼠标队列,注入坏包和溢出位,并分别漏掉从片、主片 EOI,观察 IRQ12 为什么停在第一轮。 0x60 只有一个消费者 8042 兼容控制器的状态端口是 0x64,数据端口是 0x60。状态 bit 0 表示输出缓冲区有数据,bit 5 表示该字节来自辅助设备。键盘和鼠标并没有两份独立数据寄存器。 如果 IRQ1 入口和 IRQ12 入口各自无条件执行一次 inb(0x60),先运行的处理程序可能拿走另一设备的字节,后运行的一方只能读到下一字节或空缓冲。来源信息也会随着读取消失。读取动作必须收敛到同一个函数: 1234567891011121...
从零编写操作系统 26 - 字体与后备缓冲:从字符网格重绘一帧
Day 25 已经能把颜色值写入 0xA0000,但那些矩形没有可恢复的内容模型。窗口被遮住再移开时,单靠显存无法知道原位置应该重画什么;文本发生滚动时,逐像素搬运也会把终端语义和显示细节绑在一起。 本篇把两层状态拆开。64,000 字节离屏画面由 16 个独立物理页持有,绘图函数通过 surface 访问它;终端另外保存 40×25 个字符单元、颜色和光标。折行与滚动先修改字符网格,渲染阶段再用固定 8×8 字体生成像素,最后把完整画面复制到 Mode 13h 显存。 专项实验覆盖第 8 页分配前失败、INT32_MIN 坐标、UINT32_MAX 尺寸、完全屏外和右下跨界矩形。GDB 对照字形 A 的八行数据,重新计算 64,000 字节帧哈希,并确认 surface 释放后空闲页数恢复。 一帧大于现有堆的单次上限 Mode 13h 的一帧需要: 1320 × 200 × 1 byte = 64,000 bytes Day 12 的 kmalloc 以单个 4 KiB 物理页为 arena,不跨页拼接对象。扣除块头和尾部哨兵后,单次请求上限为 4,076 字节。直接写 km...
从零编写操作系统 25 - 从字符到像素:接入 Mode 13h
前 24 篇一直把 0xB8000 当作屏幕:一个字符占两个字节,内核写入字符码和颜色,VGA 文本模式负责把它显示出来。窗口系统需要另一种输出契约。内核必须能描述一个像素的位置,并且知道这段内存的宽、高、步长和颜色格式。 本篇增加独立的图形启动镜像。第二阶段引导器在实模式设置 BIOS Mode 13h,回读确认后发布 BootInfo v2;内核验证 0xA0000、320×200、每行 320 字节和 8 位索引色,再通过同一个有界像素函数绘制整帧、矩形与边框。历史文本镜像继续使用 BootInfo v1,所以已有 Shell、管道和早期启动实验不需要假装理解新的字段语义。 专项实验同时读取串口、GDB 符号、物理页位图和 64,001 个显存字节,并保存 QEMU 截图。强制失败镜像则证明模式设置未通过时不会进入图形内核。 BIOS 调用必须留在实模式 Mode 13h 是一个适合教学的最小图形入口:320×200 个像素,每个像素占一个字节,一帧共 64,000 字节。标准 VGA 图形窗口从物理地址 0xA0000 开始。像素 (x, y) 的地址为: 10xA00...
从零编写操作系统 24 - 管道:让两个进程通过字节流协作
第 23 篇的 Shell 已经能启动 cat 和 wc,却只能让它们各自连接终端。管道要解决的问题,是把一个进程的 fd 1 接到另一个进程的 fd 0,同时让双方在速度不一致、提前退出和创建失败时都能结束。 本篇加入一个 64 字节有界管道和 pipe 系统调用。Shell 现在可以执行一条两段流水线: 12# cat readme.txt | wc27 1479 README.TXT 有 1479 字节,远大于缓冲区。这个样本会反复经历写满、阻塞、读出和唤醒,不能靠一次复制碰巧通过。专项实验还覆盖读端早退、第二个子进程创建失败、坏用户指针、对象耗尽和重复回收。 管道不只是一段共享内存 环形缓冲只能保存字节。一个可用的管道还要回答四个问题: 缓冲为空时,读者应该等待还是收到 EOF? 缓冲已满时,写者怎样让出 CPU? 哪一次 close 才表示一侧真的消失? Shell 在哪一步关闭自己的端点,失败时按什么顺序回收? 本篇的完整路径如下: 12345678910Shell fd 3 ──► read endpoint ─┐ ...
从零编写操作系统 23 - 用户态 Shell:从磁盘加载并执行命令
第 22 篇已经把 FAT16 文件和终端收进同一套文件描述符接口,但当时的用户程序仍然是内核里的字节数组。键盘能向 fd 0 交付字符,文件能从 fd 3 往后读取,这两条路径还没有被一个用户态程序连起来。 本篇加入一个常驻的 CPL3 Shell。它从终端读取一行,把命令名转换为 FAT16 8.3 文件名,调用 spawn_file 从磁盘加载 ELF,再用 wait 等待指定子进程。cat 和 echo 也运行在用户态,只依赖上一篇的 fd 系统调用。 实验使用 QEMU HMP sendkey 把按键送进模拟 i8042,然后经过 IRQ1、扫描码解析、终端队列和 read(0, ...) 到达 Shell。正常命令、未知命令、损坏 ELF、子进程页故障与解析边界都通过同一个交互镜像验证。 Shell 只负责编排用户态能力 命令解释器很容易被写成内核里的一个大 switch:内核直接识别 cat、读 FAT、往 VGA 打印。这样虽然能得到命令行,却绕过了前面已经建立的进程、系统调用和句柄边界。 当前的 Shell 只做四件事:读取一行、分割参数、创建子进程、等待子进...



