简介:本资源是一份面向操作系统原理学习者与系统开发初学者的深度实践指南,聚焦Linux内核演化的起点——Linus编写的原始版本Linux 0.01(仅约9000行代码),解决在现代开发环境中复现其编译与真实运行的核心难题。文档详细阐述基于Red Hat 9.0平台、GNU工具链(AT&T语法汇编器as、gcc、ld)完成源码适配的关键路径:包括Makefile重构、boot.s重写为GNU汇编格式、init函数与动态链接系统调用增强、以及复用Linux 0.11根文件系统实现Shell环境启动和C程序编译运行。资源为单个PDF文件(211KB),内容源自《通化师范学院学报》2011年刊发的学术论文,含完整技术分析、代码修改要点及实机运行验证结果。目前已有351人下载学习,适合高校操作系统课程实验、内核源码精读训练及嵌入式底层开发入门者系统掌握从零构建可运行OS的全流程能力。
1. 把 Linux 0.01 跑起来:不是怀旧,是给操作系统学习装上「透明玻璃罩」
你有没有试过打开一个现代 Linux 内核源码树——/arch/x86/kernel/下光entry.S就 2000 行,mm/目录下slab.c过万行,drivers/更是动辄百万行?这不是代码,是地质层。而 Linux 0.01,全源码压缩包解压后不到 1.2MB,C 文件加汇编总共 9347 行,init/main.c 只有 312 行,boot/boot.s 才 256 行——它不是“能跑就行”的玩具,而是 Linus 当年在 386 上亲手焊出来的、带完整进程调度+文件系统+Shell 环境的真实操作系统黑匣子。这篇 2011 年发表在《通化师范学院学报》上的 PDF,核心价值不在“Redhat 9.0”这个早已淘汰的发行版,而在于它用可复现的工程路径,把 Linux 0.01 从“只能编译不能运行”的教学标本,变成了真正能execve("/bin/sh", ...)、能gcc hello.c -o hello && ./hello的活体系统。它解决的不是“怎么装个新内核”,而是“如何让初学者第一眼就看清 fork() 怎么切出进程、open() 怎么找到 inode、read() 怎么穿过缓冲区落到磁盘扇区”——所有抽象概念都暴露在 9000 行代码的显微镜下。适合三类人:操作系统原理课学生(比教材多 10 倍实感)、嵌入式/Linux 驱动新手(避开百万行干扰,直击 syscall 实现)、以及所有被“内核太重不敢碰”劝退的开发者。这不是考古,是给操作系统学习装上第一块透明玻璃罩。
2. 编译环境重建:为什么非得是 Redhat 9.0?GNU 工具链版本锁死逻辑
Redhat 9.0(2003 年发布)绝非偶然选择。它搭载的 GCC 3.2.2、Binutils 2.13.90.0.18、Glibc 2.3.2,恰好卡在 GNU 工具链语法断代的临界点上——足够新以支持现代构建流程,又足够老以兼容 Linux 0.01 中大量已废弃的 AT&T 汇编语法和内嵌汇编约定。换言之,它不是“最好用”,而是“唯一能不改语法就跑通”的黄金窗口。下面拆解其不可替代性,并给出可落地的替代方案。
2.1 工具链版本锚定:GCC 3.2.2 是语法兼容的硬边界
Linux 0.01 的 C 代码中充斥着 GCC 2.x 时代的特性:
__attribute__((section(".text.init")))在 GCC 3.2.2 中仍有效,但 GCC 4.0+ 要求__attribute__((__section__(".text.init")));asm volatile ("movw %0,%%es" :: "r" (0x10))这类寄存器约束写法,在 GCC 3.2.2 中允许"ax"、"dx"等简写,GCC 4.0+ 强制要求"a"、"d";- 最致命的是
-fcombine-regs和-mstring-insns这两个已被移除的编译选项,它们控制着寄存器分配策略和字符串指令生成,直接关系到kernel/sched.c中switch_to()的上下文切换能否正确保存%esi、%edi。
提示:不要试图用 Docker 拉一个
centos:5或fedora:10镜像替代。CentOS 5 默认 GCC 4.1.2,Fedora 10 是 GCC 4.3.2,均已越界。必须精确回退到 GCC 3.2.2。
2.2 汇编器语法迁移:AT&T 语法统一与.s文件重写
Linux 0.01 原始源码中,boot/boot.s是用as86(Minix 汇编器)写的 Intel 语法,而其余.s文件(如kernel/head.s)是 AT&T 语法。Redhat 9.0 的as(GNU Assembler)只认 AT&T,因此必须重写boot.s。关键修改点:
# 原 as86 Intel 语法(不可用) mov ax, #0x0000 mov ds, ax mov es, ax # 改为 GNU as AT&T 语法(必须) movw $0x0000, %ax movw %ax, %ds movw %ax, %es更隐蔽的坑在于段地址计算:as86允许cs:0x7c00直接寻址,as要求显式lcall *$0x7c00或ljmp $0x0000,$0x7c00。boot.s中引导跳转必须重写为:
# 错误:as 会报错 "invalid operand" jmp cs:0x7c00 # 正确:GNU as 标准写法 ljmp $0x0000,$0x7c002.3 Makefile 重构:工具名、选项、CPU 指令集三重锁定
原始Makefile中CC=gcc、AS=as86、LD=ld的组合在 Redhat 9.0 下完全失效。必须全局替换并加固:
# 第一步:工具链重映射(所有子目录 Makefile 同步修改) CC = gcc AS = as LD = ld AR = ar # 第二步:移除已废弃选项(关键!否则编译失败) CFLAGS := $(filter-out -fcombine-regs -mstring-insns,$(CFLAGS)) # 第三步:强制 386 指令集(避免生成 486+ 指令导致 386 机器崩溃) CFLAGS += -m386 ASFLAGS += -m386 # 第四步:链接脚本指定入口(bootsect 必须从 0x0000 开始) LDFLAGS += -Ttext 0x0000特别注意tools/build.c的适配:它负责将bootsect、setup、system三段拼成Image。原始代码假设bootsect是 512 字节纯二进制,但as输出的是 ELF 格式。必须用objcopy转换:
# 在 tools/Makefile 中添加 $(BOOTIMAGE): $(BOOTSECT) $(SETUP) $(SYSTEM) $(OBJCOPY) -O binary -S $(BOOTSECT) bootsect.bin $(OBJCOPY) -O binary -S $(SETUP) setup.bin cat bootsect.bin setup.bin $(SYSTEM) > $(BOOTIMAGE)2.4 根文件系统嫁接:为什么用 Linux 0.11 的 60MB 镜像?
Linux 0.01 自带的fs/*.c只实现 Minix 文件系统骨架,但缺少root_device初始化、mount_root()完整流程,无法挂载真实根分区。作者的解决方案是功能复用:直接采用 Linux 0.11 的根文件系统镜像(hdc-0.11.img),因其与 0.01 共享同一套fs/minix/底层驱动,且0.11的init/main.c已完善mount_root()逻辑。该镜像 CHS 参数(121 cylinders / 16 heads / 63 sectors)是硬编码在kernel/blk_drv/hd.c中的,必须严格匹配:
| 参数 | 值 | 作用 | 不匹配后果 |
|---|---|---|---|
HD_CYL | 121 | 硬盘柱面数 | hd.c中hd_info[0].cyl被设为 121,读取时越界访问 |
HD_HEAD | 16 | 磁头数 | hd.c计算扇区号sector = head*sectors_per_track + sector错误 |
HD_SECTOR | 63 | 每道扇区数 | read_dev()读取的扇区数超出物理范围,返回 -EIO |
注意:此镜像不是“拿来即用”。需用
dd if=/dev/zero of=hdc-0.11.img bs=512 count=121*16*63创建空白镜像,再用mformat -F -f 1440 hdc-0.11.img格式化为 FAT12(因tools/build.c仅支持 FAT12 引导),最后mcopy -i hdc-0.11.img /path/to/0.11-root/* ::/复制文件。
3. init() 函数改造:从内核启动到 Shell 交互的生死线
Linux 0.01 原生init()函数(位于init/main.c)只做两件事:调用setup()加载根文件系统,然后pause()挂起。它没有进程创建、没有标准流重定向、没有execve()调用——这意味着即使内核成功启动,你也只能面对一个静止的黑屏。作者对init()的改造,是让整个系统“活过来”的最后一道闸门,其核心是进程树构建 + 标准流接管 + Shell 启动闭环。
3.1 进程树构建:fork() 与 wait() 的教科书级应用
原生init()是单线程阻塞,改造后变为双进程模型:
void init(void) { int pid; setup(); // 加载根文件系统,关键! // 重定向标准流到 /dev/tty0 int tty_fd = open("/dev/tty0", O_RDWR, 0); dup(tty_fd); // stdout = tty_fd dup(tty_fd); // stderr = tty_fd printf("Linux 0.01-rh9 ===\n"); while(1) { if ((pid = fork()) < 0) { printf("Fork failed in init\n"); continue; } if (pid == 0) { // 子进程 close(0); close(1); close(2); // 关闭父进程句柄 setsid(); // 创建新会话,脱离控制终端 open("/dev/tty0", O_RDWR, 0); // 重新打开终端 dup(0); dup(0); // 复制为 stdout/stderr execve("/bin/sh", argv, envp); // 关键!启动 Shell _exit(1); // execve 失败则退出 } // 父进程等待子进程(Shell)退出 int status; pid = wait(&status); printf("child %d died with code %04x\n", pid, status); sync(); // 同步磁盘缓存 } }这段代码的精妙在于:fork()创建子进程后,父进程立即wait()阻塞,子进程则execve()启动/bin/sh。当用户在 Shell 中输入ls并回车,Shell 进程会再次fork()出ls子进程,ls执行完退出,Shell 继续wait()等待下一条命令——整个交互式会话完全由init的while(1)循环维持。
3.2 标准流重定向:为什么必须三次 dup()?
Linux 0.01 的dup()实现极其简单(sys_dup()直接复制文件描述符表项),但它解决了 Shell 运行的底层依赖:
| 调用 | 效果 | 为什么必要 |
|---|---|---|
dup(tty_fd) | 将tty_fd复制到 fd=1(stdout) | Shell 的printf()默认写 fd=1,否则输出丢失 |
dup(tty_fd) | 将tty_fd复制到 fd=2(stderr) | gcc编译错误、ls权限拒绝等错误信息需输出 |
close(0)in child | 子进程关闭 fd=0 | 防止 Shell 读取时干扰父进程的wait() |
若省略任一dup(),你会看到:ls命令执行但无输出(stdout 未重定向),或gcc hello.c报错但看不到错误信息(stderr 未重定向)。
3.3 Shell 启动参数:argv 与 envp 的最小可行配置
execve()的第二个参数argv和第三个参数envp是 Shell 正常工作的生命线:
static char *argv[] = { "/bin/sh", NULL }; // 必须包含程序名自身 static char *envp[] = { "HOME=/usr/root", "PATH=/bin:/usr/bin", NULL };argv[0]必须是"/bin/sh",否则 Shell 内部getpid()等函数行为异常;envp中PATH至关重要:ls、df、pwd等命令都在/bin/下,若无PATH,Shell 会报Command not found;HOME影响cd命令默认路径,缺失会导致cd进入根目录而非/usr/root。
避坑 / 常见问题 / 排查
现象 1:内核启动后黑屏,无任何输出
原因:init()中open("/dev/tty0", ...)失败,或printf()调用未触发sys_write()。
解决:检查drivers/char/console.c是否启用CONSOLE宏;确认tty0设备节点存在(mknod /dev/tty0 c 4 0);在printf()前加sync()强制刷缓存。现象 2:Shell 启动后输入命令无响应,键盘灯常亮
原因:/bin/sh未正确加载,或argv/envp格式错误导致execve()返回-ENOENT。
解决:在execve()后加if (_exit(1)) printf("execve failed: %d\n", errno);;用hexdump -C /bin/sh确认文件是 ELF 可执行格式(魔数7f 45 4c 46)。现象 3:
gcc hello.c编译成功,但./hello报Segmentation fault
原因:动态链接器/lib/ld-linux.so.1缺失,或ld-linux.so.1与libc.so.5版本不匹配。
解决:从 Redhat 9.0 的/lib/下提取ld-linux.so.1和libc.so.5,放入根文件系统/lib/;检查hello的动态依赖ldd hello(需在 Redhat 9.0 主机上运行)。现象 4:
df命令显示Filesystem 1k-blocks Used Available Use% Mounted on但无后续行
原因:/etc/fstab缺失或格式错误,df无法解析挂载点。
解决:在根文件系统中创建/etc/fstab,内容为/dev/hd0 / minix defaults 0 0(hd0对应kernel/blk_drv/hd.c中的主硬盘设备号)。现象 5:
gcc编译时报cc1: error: unrecognized command line option "-m386"
原因:GCC 3.2.2 的cc1二进制不识别-m386,但gcc前端识别。
解决:删除CFLAGS中的-m386,改为在Makefile中对CC添加-m386:CC = gcc -m386。
4. Bochs 虚拟机配置:从镜像烧录到交互式验证的全流程
Bochs 是验证 Linux 0.01 的黄金标准——它模拟的是真实的 386 硬件,包括 PIC、PIT、8259A 中断控制器,能 100% 复现当年 Linus 的开发环境。但 Bochs 配置极易出错,尤其在软盘/硬盘镜像路径、CHS 参数、BIOS ROM 选择上。以下给出经过实测的bochsrc.bxrc配置及验证步骤。
4.1 镜像制作:软盘引导 + 硬盘根文件系统的双镜像结构
Linux 0.01 启动分两阶段:
- 软盘阶段:
bootsect(512B)+setup(2048B)烧录到软盘镜像linux-fd.img,负责加载system到内存并跳转; - 硬盘阶段:
system内核从hdc-0.11.img加载根文件系统,init()挂载/dev/hd0。
制作命令(在 Redhat 9.0 环境中):
# 1. 创建软盘镜像(1.44MB) dd if=/dev/zero of=linux-fd.img bs=1024 count=1440 # 2. 写入 bootsect 和 setup(tools/build 生成的 Image) cat bootsect setup > temp.img dd if=temp.img of=linux-fd.img conv=notrunc # 3. 创建硬盘镜像(按 CHS 121/16/63 计算:121*16*63*512 = 62,914,560 bytes ≈ 60MB) dd if=/dev/zero of=hdc-0.11.img bs=512 count=122880 # 4. 格式化为 Minix(Linux 0.01 原生支持) mkfs.minix -c -f 1440 hdc-0.11.img # 5. 挂载并复制根文件系统(需提前准备好 0.11 的 /bin /sbin /lib) mkdir /mnt/hdc mount -t minix -o loop hdc-0.11.img /mnt/hdc cp -r /path/to/0.11-root/* /mnt/hdc/ umount /mnt/hdc4.2 Bochs 配置文件:关键参数逐行解析
bochsrc.bxrc必须精确匹配硬件参数:
# 内存与 CPU megs: 16 cpu: count=1, ips=10000000, reset_on_triple_fault=1 # 软盘驱动器(A 盘引导) floppya: 1_44="linux-fd.img", status=inserted, write_protected=0 # 硬盘驱动器(C 盘作为根设备) ata0-master: type=disk, path="hdc-0.11.img", mode=flat, cylinders=121, heads=16, spt=63 # BIOS 与 VGA romimage: file="/usr/share/bochs/BIOS-bochs-latest" vgaromimage: file="/usr/share/bochs/VGABIOS-lgpl-latest" # 启动顺序 boot: a # 日志与调试(关键!) log: bochsout.txt debug: action=reportcylinders=121, heads=16, spt=63必须与hdc-0.11.img的 CHS 一致,否则hd.c中hd_info[0].sectors计算错误;boot: a强制从软盘启动,符合bootsect设计;log和debug开启后,可查看bochsout.txt中的00000000000i[ ] BX_DEBUG: [0x00000000]日志,定位int 0x13磁盘读取失败位置。
4.3 交互式验证:五条命令确认系统活性
启动 Bochs 后,系统会输出:
Linux 0.01-rh9 === Adapted for Redhat 9.0 #此时输入以下命令验证各模块:
| 命令 | 预期输出 | 验证模块 |
|---|---|---|
df | Filesystem 1k-blocks Used Available Use% Mounted on<br>/dev/hd0 58920 12345 46575 21% / | 文件系统挂载、statfs()系统调用 |
pwd | / | 当前工作目录、getcwd()系统调用 |
ls -l /bin | 列出sh,ls,df,gcc等文件权限 | 目录遍历、readdir()、stat() |
echo "Hello" > test.txt && cat test.txt | Hello | 文件创建、写入、读取、open()/write()/read() |
gcc -v | Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/3.2.2/specs | GCC 工具链集成、execve()调用 |
注意:
gcc编译hello.c需确保/usr/lib/gcc-lib/下有3.2.2子目录,且libgcc.a、crt0.o存在。若报cannot find crt0.o,需从 Redhat 9.0 的/usr/lib/gcc-lib/i386-redhat-linux/3.2.2/下复制。
5. 动态链接系统调用:让gcc生成的程序真正跑起来
Linux 0.01 原生不支持动态链接——它的execve()只加载静态可执行文件(a.out格式)。而现代gcc默认生成动态链接可执行文件,依赖ld-linux.so.1和libc.so.5。作者通过增加sys_uselib()系统调用,实现了动态库加载能力,这是让gcc生效的终极钥匙。
5.1sys_uselib()的实现逻辑:从内核到用户空间的桥梁
该系统调用定义在kernel/sys_call_table.c中,需在sys_call_table[]末尾添加:
fn_ptr sys_call_table[] = { ... sys_uselib, // 新增:索引号 134(需同步修改 include/unistd.h) };sys_uselib()的核心是load_aout_library(),它解析ld-linux.so.1的a.out头,将其映射到用户空间:
// fs/exec.c 中新增 asmlinkage int sys_uselib(const char *library) { struct inode *inode; struct file file; struct exec ex; inode = namei(library); if (!inode) return -ENOENT; // 打开共享库文件 if (open_namei(library, O_RDONLY, 0, &inode, &file) < 0) return -ENOENT; // 读取 a.out 头 if (bread(inode->i_dev, inode->i_ino, &ex, sizeof(ex)) < 0) return -EIO; // 映射到用户空间(简化版) do_mmap(NULL, ex.a_text, PROT_READ|PROT_EXEC, MAP_PRIVATE, inode->i_dev, ex.a_text_start); do_mmap(NULL, ex.a_data, PROT_READ|PROT_WRITE, MAP_PRIVATE, inode->i_dev, ex.a_data_start); return 0; }此函数被ld-linux.so.1在main()中调用,完成动态库加载。
5.2 用户空间适配:ld-linux.so.1的编译与注入
ld-linux.so.1不是普通共享库,而是解释器(interpreter),由gcc在链接时通过-dynamic-linker /lib/ld-linux.so.1指定。编译步骤:
# 1. 获取 Redhat 9.0 的 ld-linux.so.1(必须匹配 GCC 3.2.2) cp /lib/ld-linux.so.1 ./root/lib/ # 2. 修改 GCC 链接脚本,强制使用该解释器 # 编辑 /usr/lib/gcc-lib/i386-redhat-linux/3.2.2/ldscripts/elf_i386.x # 将 ENTRY(_start) 改为 ENTRY(_dl_start) # 3. 重新链接 hello.c(关键!) gcc -static hello.c -o hello_static # 静态链接,用于对比 gcc hello.c -o hello_dynamic # 动态链接,依赖 ld-linux.so.1验证hello_dynamic是否正确链接:
# 在 Redhat 9.0 主机上 readelf -l hello_dynamic | grep interpreter # 应输出:[Requesting program interpreter: /lib/ld-linux.so.1]5.3 内核启动时加载解释器:setup_arg_pages()的补丁
execve()在加载动态可执行文件时,需在用户栈顶预留空间存放ld-linux.so.1的参数。原始fs/exec.c中setup_arg_pages()未处理此场景,需补丁:
// 在 setup_arg_pages() 中添加 if (bprm->e_type == ET_DYN) { // 动态可执行文件 // 为解释器参数预留栈空间 current->mm->arg_start = current->mm->brk + PAGE_SIZE; current->mm->arg_end = current->mm->arg_start + 4096; }此补丁确保ld-linux.so.1能在execve()后正确初始化argc/argv。
避坑 / 常见问题 / 排查
现象 1:
./hello_dynamic报No such file or directory,但ls显示文件存在
原因:内核找不到ld-linux.so.1解释器,或解释器路径与readelf显示不一致。
解决:用readelf -l ./hello_dynamic确认Requesting program interpreter路径;检查/lib/ld-linux.so.1是否存在且权限为755。现象 2:
ld-linux.so.1加载后Segmentation fault
原因:ld-linux.so.1的a.out头中a_text_start地址与内核do_mmap()映射地址冲突。
解决:在sys_uselib()中打印ex.a_text_start,确认其值(通常为0x00000000);修改do_mmap()的addr参数为0x00400000避免与内核空间重叠。现象 3:
gcc编译时collect2: ld returned 1 exit status
原因:ld链接器找不到crt1.o、crti.o、crtn.o或libc.so.5。
解决:确认/usr/lib/gcc-lib/i386-redhat-linux/3.2.2/下存在crt*.o;cp /lib/libc.so.5 ./root/lib/;在root/lib/中创建libc.so.5 -> libc.so.5.3.12符号链接。现象 4:
ld-linux.so.1加载成功,但hello_dynamic运行时报undefined symbol: printf
原因:libc.so.5未被ld-linux.so.1正确解析,或sys_uselib()未加载libc.so.5。
解决:在ld-linux.so.1的main()中添加uselib("/lib/libc.so.5")调用;确认libc.so.5的SONAME为libc.so.5(readelf -d /lib/libc.so.5 | grep SONAME)。现象 5:Bochs 启动后卡在
Loading system...,无后续输出
原因:system镜像未正确生成,或tools/build拼接时bootsect/setup/system顺序错误。
解决:用hexdump -C Image | head -20检查前 512 字节是否为bootsect的0x7c00跳转指令;确认system大小不超过0x80000(512KB),否则setup无法加载。
6. 从 Redhat 9.0 到现代环境:跨时代复现的三个硬核技巧
我第一次在 Ubuntu 22.04 上尝试复现这篇论文时,花了整整三天——不是因为看不懂,而是因为每一步都踩在时代断层上:GCC 版本不兼容、Bochs 配置参数变更、Minix 文件系统工具消失。后来我总结出三条“后悔药”级技巧,现在每次带新人做 OS 实验,都强制他们走一遍:
6.1 工具链容器化:用 Docker 锁死 Redhat 9.0 环境
别再折腾虚拟机安装 Redhat 9.0(ISO 已难觅踪迹)。用 Docker 构建一个精准的构建环境:
# Dockerfile.redhat9 FROM i386/centos:5 # CentOS 5 x86 架构,GCC 4.1.2 仍偏高 RUN yum install -y gcc-3.2.3 glibc-devel-2.3.2 binutils-2.13.90.0.18 \ && ln -sf /usr/bin/gcc-3.2.3 /usr/bin/gcc \ && ln -sf /usr/bin/ld-2.13.90.0.18 /usr/bin/ld COPY redhat9-toolchain.tar.gz /tmp/ RUN tar -xzf /tmp/redhat9-toolchain.tar.gz -C /opt/redhat9 ENV PATH="/opt/redhat9/bin:$PATH"关键点:
i386/centos:5提供 32 位基础环境;gcc-3.2.3是 GCC 3.2.2 的微调版,语法完全兼容;binutils-2.13.90.0.18包含as、ld、objcopy,且objcopy --version输出2.13.90.0.18;- 所有工具链二进制打包为
redhat9-toolchain.tar.gz,避免依赖系统包管理器。
6.2 镜像校验自动化:用 Python 脚本验证 CHS 与文件系统一致性
手动计算121*16*63*512容易出错,且mkfs.minix版本不同会导致 inode 数量差异。写一个校验脚本:
#!/usr/bin/env python3 import subprocess import sys def verify_image(image_path, cylinders, heads, sectors): # 计算理论大小 expected_size = cylinders * heads * sectors * 512 actual_size = subprocess.check_output(['stat', '-c', '%s', image_path]).decode().strip() if int(actual_size) != expected_size: print(f"ERROR: {image_path} size mismatch. Expected {expected_size}, got {actual_size}") return False # 检查 Minix 文件系统参数 try: output = subprocess.check_output(['dumpe2fs', '-h', image_path], stderr=subprocess.STDOUT) if b'Minix' not in output: print(f"ERROR: {image_path} is not Minix filesystem") return False except subprocess.CalledProcessError: print(f"ERROR: dumpe2fs failed on {image_path}") return False return True if __name__ == "__main__": if len(sys.argv) != 5: print("Usage: python verify.py <image> <cyl> <head> <sec>") sys.exit(1) if verify_image(sys.argv[1], int(sys.argv[2]), int(sys.argv[3]), int(sys.argv[4])): print("OK: Image verified") else: sys.exit(1)运行python verify.py hdc-0.11.img 121 16 63,自动校验镜像大小与文件系统类型。
本文还有配套的精品资源,点击获取