老PC从U盘启动裸机内核,到底行不行?

介绍 在上一篇文章里,我们从引导扇区开始写了一个会打招呼的裸机“内核”,在QEMU里跑得很开心,最后把“拿真机开刀”的几个坑留了下来。这一篇就来填坑,回答一个具体的问题:老PC到底能不能从U盘启动我们自己写的裸机内核? 先把答案放这:能。大约2003年以后、BIOS里有USB启动选项的机器基本都可以。但上一篇那个镜像直接dd进U盘,在不少真机上会翻车——不是BIOS压根不认这个U盘,就是读盘读出一堆垃圾。这篇文章给引导程序做两个小升级,把这两个坑都填平。 U盘启动的原理:BIOS的“模拟大法” 先想一个问题:BIOS又不懂文件系统,也没有什么“USB启动协议”,它是怎么从U盘启动的?答案是模拟(emulation):BIOS用自己内置的USB驱动把U盘伪装成一块普通硬盘(USB-HDD模式)或软盘(USB-FDD模式),然后照常提供INT 13h读盘服务、照常执行“搬运第一个扇区到0x7C00”的老流程。对我们的引导程序来说,U盘“看起来”就是一块硬盘。 听上去很美,但两个坑正是藏在这套模拟里: 步骤 升级一:改用INT 13h扩展读(LBA) 治CHS的办法很直接:不用CHS。1990年代中期之后的BIOS都提供“INT 13h扩展”,可以用LBA(逻辑块地址)直接说“从第几个扇区开始读几个”,完全绕开几何参数。顺便理清计数方式:内核在磁盘的“第2个扇区”,用CHS的说法是柱面0、磁头0、扇区2(CHS的扇区从1数),换成LBA就是LBA 1(LBA从0数,LBA 0是引导扇区自己)。 用法分两步:先用功能41h探测扩展是否可用,可用的话就用功能42h读。42h的参数不再塞寄存器,而是填一个16字节的“参数包”(Disk Address Packet): 注意我们保留了上一篇的CHS读法作为兜底(use_chs分支):万一碰上真·上古BIOS不支持扩展(多见于软盘模拟模式),就退回老办法,两头都照顾到。 升级二:给引导扇区塞一张分区表 MBR的512字节其实是有规定格局的:前446字节归引导代码,接下来64字节是4个分区表项(每个16字节),最后2字节是魔数0xAA55。我们的引导代码离446字节还远,正好把分区表补上,专治坑二: 这个分区基本上是“演给BIOS看”的:它从LBA 2048开始,而我们的内核放在LBA 1到16——藏在分区开始之前的空隙里(现代分区工具管这段叫MBR gap,GRUB也把自己的一部分塞在这里,我们算是蹭了个正规位置)。分区里并没有真的FAT文件系统,BIOS也不会去检查;讲究的话,你可以之后真的把它格式化成FAT放点文件,两不耽误。 完整引导程序…

给吃灰的老x86 PC写一个裸机内核

介绍 家里角落里是不是也有一台吃灰的老电脑?装Windows跑不动,装Linux都嫌它配置寒酸。小编最近给它找了份新工作:不装任何操作系统,直接跑我自己写的“内核”。 本文从按下电源键的那一刻讲起:BIOS怎么把控制权交给我们的代码、怎么写一个引导扇区、怎么从16位实模式切换到32位保护模式、最后用C写一个能在屏幕上打印的迷你内核。全部代码加起来不到两百行,先在QEMU里跑通,再用U盘拿真机开刀。 先说明一下:这里的“内核”是带引号的——它不会调度进程也不会管理内存,只会在屏幕上打招呼。但麻雀虽小,从上电到你自己的C代码接管整台机器,这条链路它可是完整走了一遍。 从按下电源键说起 老PC用的是传统BIOS启动(Legacy Boot),这套流程几十年没变过: 所以“写内核”的第一步,其实是写好这512字节的引导扇区。也正因为这套流程简单直接,老电脑反而是最好的裸机实验台:自带BIOS中断可以用,自带VGA文本模式可以打印,连显卡驱动都省了。(现代UEFI机器是另一套玩法,本文不涉及。) 准备工具 QEMU用来在动真机之前把代码调通——毕竟裸机上没有调试器,只有黑屏和不黑屏两种状态。本文的代码小编都在QEMU 6.2上实际跑过,文中的输出和截图都是真实运行结果。 步骤 第一步:先写个会打招呼的引导扇区 先来体验一下“机器归你了”的感觉。新建hello.asm: 编译成纯二进制,直接扔给QEMU: QEMU窗口里,SeaBIOS的启动信息后面会跟着一行我们的问候: 就这么二十来行汇编,没有操作系统、没有libc,屏幕上就有字了——打印的脏活是BIOS的INT 10h中断帮我们干的。 第二步:完整的引导程序——读盘、进保护模式 打招呼只是开胃菜。一个真正的引导程序还要干两件事:把内核从磁盘读进内存(BIOS只帮你搬第一个512字节),以及把CPU从16位实模式切到32位保护模式(不然C编译器生成的32位代码没法跑)。完整的boot.asm如下: 几个值得说明的地方: 第三步:真正的“内核”——C语言登场 在继续之前,先理清一个容易迷糊的点:C代码是什么时候、在哪里被编译的?答案是——在你的开发机上、制作镜像的时候。GCC把kernel.c提前编译成32位x86机器码,链接成不带任何格式头的纯机器码文件kernel.bin,再拼进磁盘镜像。老电脑上从头到尾都没有编译器,更没有什么“运行C程序的环境”——引导程序只是把第2扇区开始的那串字节搬到0x8000,然后一个call跳过去。对CPU来说那就是一串普通指令,它根本不知道、也不关心这些指令是C编译出来的还是手写的汇编。 整条流水线画出来是这样: 知道了这一点,内核的结构就好理解了:一个汇编入口加一个C文件。为什么还要一个汇编入口?因为C编译器并不保证kernel_main会被放在二进制文件的哪个位置,而引导程序是“盲跳”到0x8000的——所以我们用链接顺序保证kernel_entry.asm排在最前面,让0x8000处的第一条指令正好是call…

用NFSRoot和TFTP启动你的嵌入式设备

介绍 最近在学习嵌入式 Linux 设备驱动开发。刚开始学习时,一个比较大的痛点是:总要想办法把编译好的二进制文件搬到设备上。对于内核驱动或用户态程序这类较小的文件,用 scp 之类的工具就能解决;但到了学习后期,涉及到修改内核源代码、甚至要替换 SD 卡上的内核镜像时,可用的手段就比较有限了——除非你能忍受每次改完代码都把内核重新烧写到 SD 卡或其他存储介质上。 经过一番搜索,我发现了一种很适合嵌入式开发的启动方式:用 TFTP 加载内核,用 NFSRoot 挂载根文件系统(RootFS)。这种方式可以大大减少把二进制文件拷贝到存储介质的次数。 以我家里的网络为例(配置如下图):我用笔记本进行开发,网络中还有一块嵌入式开发板和一台服务器。服务器走有线连接,网络比较稳定。借助 NFSRoot 和 TFTP,我可以把开发板的 RootFS、内核镜像和 dtb 等文件都放在服务器上。开发板上电后,会先从服务器读取内核到内存中的指定地址,再用它启动系统;内核加载过程中,又会把服务器上的 RootFS 挂载为根目录,完成启动。 这样的设置意味着:开发板启动后的“硬盘”已经不在开发板上,而是在服务器上了!如果用终端或…

OProfile尝。。鲜??

最近使用了一个叫OProfile的用于程序性能测试的开源项目,在这里记录一下使用感受和心得。 什么是OProfile? OProfile诞生于2002年,已经有着18年的历史了。该项目主要用于Linux系统的性能剖析,采用记录CPU事件(events)的方式,最终给使用者提供程序或系统的性能统计数据。 下载安装 OProfile可以从这个网站下载,小编用的是写这篇时最新的1.4.0版本。下载完成后用如下简单命令编译安装: 编译前记得先装好libpopt和binutils的开发包(Debian系对应的是libpopt-dev和binutils-dev)。如果不想折腾源码,Debian系的系统也可以直接sudo apt install oprofile,只是版本会旧一些。 因为主要用于监测Linux系统性能,所以当然只能在Linux上编译啦。 小编测试环境使用的是当下流行的树莓派3B+,编译器使用的是GCC 10。 使用 老版本的OProfile用的是opcontrol加内核模块那一套,1.0之后已经彻底废弃了。现在的主力是三个命令:operf(采样)、opreport(看报告)和opannotate(标注到源码)。它们底层用的是内核的perf_events接口,不需要额外加载内核模块,普通用户就能剖析自己的程序。 准备一个“有问题”的程序 为了试用,我们先写一个自带热点的小程序——经典的朴素矩阵乘法(matmul.c): 编译时记得带上-g,这样后面才能把采样对应回源码;优化照常开-O2,毕竟我们要剖析的是“真实”的程序: 用operf采样 就这么简单,不需要任何参数。采样数据会写进当前目录下的oprofile_data/里,opreport等工具默认也从这里读取。默认采样的事件是CPU_CYCLES,也就是“这段时间CPU的周期都花在哪了”。 用opreport看报告 先看整体情况(以下输出为摘录,具体数字在不同环境下会不一样,下同): 再用-l细化到函数级别: 99.6%的周期都花在mat_mul里——热点一目了然。 用opannotate对应到源码 可以看到绝大部分采样都落在最内层那一行乘加上。这一步能工作的前提,就是前面编译时带上的那个-g。…

.emacs.d/

DotEmacsDotD The directory ~/.emacs.d/ is a standard location for additional per-user Emacs-specific files. Various packages store information in this directory. Since it is located in the home directory…