JOS工具链平台迁移:从Linux到Windows

写在前面

在写这篇文章的时候,我已经把MIT 6.828的课程实验都做完了,相关的文章也会陆续更新。但是由于写文章把事情讲清楚的难度确实比做实验的难度大很多,所以更新速度可能会比较慢,还请谅解。
后续我决定在这个课程实验的基础上继续开发这个操作系统,添加一些自己想做的功能,并从此把项目改名为QOS
新项目的Github仓库链接:https://github.com/QZero233/QOS

QOS的第一步是把整个编译+运行的工具链从Linux平台迁移到Windows平台。想这么做的主要原因是在虚拟机里面写代码有点太别扭了,平时我会在上课的时候写写代码,上课的时候一般笔记本电脑不连电源,额外开一个虚拟机写代码太耗电了QAQ。另一方面,这也是一个逼自己去学习JOS构建过程的好机会。

准备编译运行环境

这里列一下在Windows下编译和运行JOS需要的工具:

  • GNU Make 与 Linux指令
    在Linux下,我们敲make qemu,其中的make就是GNU Make。
    但是官方并没有实现Windows平台下的GNU Make。所幸,GnuWin32项目里面有make,GnuWin32里面同时还包含了其他许多Linux下有,但是Windows下没有的指令,例如seddd,这些在编译时都会需要用到。
    我们可以在这个链接处下载GnuWin32的所有软件。下载完成后把bin目录加入PATH环境变量即可。
  • dd指令
    dd是Linux下读写文件的工具指令,尽管GnuWin32里面有dd,但是那里的dd不支持输入文件是/dev/zero的情况,这里我们使用另外一个版本的dd,可以从这个链接处下载。这个版本的dd支持我们使用输入文件是/dev/zero,它在识别到之后会自动填充0。
  • i386-elf-toolchain
    我们需要在Windows下编译出i386的elf格式可执行文件,使用常规的gcc肯定就做不到了,这里需要使用一个跨平台的编译工具链,叫做i386-elf-toolchain,可以在这个仓库里下载。
    需要注意的是,这里有2个东西,分别是binutils和gcc,分别是一些工具(例如objdump)和gcc编译器,这两个都需要下载,并且在下载解压后需要把两个文件夹的内容合并一下,否则可能会出现编译汇编语言的时候提示不支持mov指令的奇怪错误,具体可以见下面这个官方的possible issue:
    在这里插入图片描述
  • perl
    JOS中有些功能是通过perl脚本实现的,所以需要安装perl并把可执行文件添加到PATH里面。

修改Makefile

这是本次迁移工作的重头戏,这其中最难办的地方在于:JOS的Makefile里面使用了比较多的Linux Shell指令,但是有些语法在Windows上会有差异,甚至于不支持,所以这部分工作需要浏览所有的Makefile,把其中使用Linux Shell的逻辑使用Windows Cmd来实现。
不过好在JOS的Makefile与特定平台的耦合度并不高,大部分编译指令都是使用了$(CC)之类的变量来代替的,所以一般看到以$(V)$(CC)之类的开头的指令可以放心跳过。
具体的更改可以见Github仓库的这两个commit:0cbd987f
这里只挑其中的难点出来分析一下。

GNU Makefile变量语法

在GNU Makefile里会遇到大量的变量使用,比如$(CC),甚至于$($*)这种奇怪的语法。其实Makefile里的变量引用可以就直接原地做替换,不用纠结这个变量引用是不是被双引号包围着的,或者是不是在一个变量引用运算符里。这些都不用考虑,只把它当成一个简单的字符替换即可。
例如,$($@),首先是$@,网上查找可以知道,$@的表示目标文件的名字

部分符号:
$@
表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,“$@“就是匹配于目标中模式定义的集合。
$%
仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a (bar.o)”,那么,”$%“就是"bar.o”,“$@“就是"foo.a”。如果目标不是函数库文件(Unix下是[.a],Windows下是 [.lib]),那么,其值为空。
$<
依赖目标中的第一个目标名字。如果依赖目标是以模式(即”%“)定义的,那么”$<“将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
$?
所有比目标新的依赖目标的集合。以空格分隔。
$^
所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。
$+
这个变量很像”$^“,也是所有依赖目标的集合。只是它不去除重复的依赖目标。
$*
这个变量表示目标模式中”%“及其之前的部分。如果目标是"dir/a.foo.b”,并且目标的模式是"a.%.b",那么,“$“的值就是"dir /a.foo”。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么"$“也就不能被推导出,但是,如果目标文件的后缀是 make所识别的,那么”$“就是除了后缀的那一部分。例如:如果目标是"foo.c”,因为".c"是make所能识别的后缀名,所以,"$“的值 就是"foo”。这个特性是GNU make的,很有可能不兼容于其它版本的make,所以,你应该尽量避免使用”$“,除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不 能识别的,那么”$"就是空值。

(摘自https://www.cnblogs.com/ydxt/p/5642331.html
所以这个语句首先会被理解为$(xxx),其中xxx是目标文件的名字,然后才是引用名字为xxx的变量的值。

与或表达式的巧妙利用

注意看这条语句
CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector)
这条语句按照注释的意思,是如果gcc支持-fno-stack-protector这个选项,那么就添加到CFLAGS里面。这条语句的底层逻辑可以理解为一个AND语句,AND语句左边是$(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1,如果这个语句执行正确,那么这条语句的值就是true,那么这时候就需要执行AND语句右边的那条语句,也就是echo -fno-stack-protector。相反,如果左边的语句执行错误,那么右边的语句就没必要执行了。
可以使用这个技巧来让shell输出-fno-stack-protector,如果gcc支持这个选项。
在Windows下也可以使用同样的技巧。

文件夹创建

在Windows下,也是用mkdir来创建文件夹,与Linux相比,主要有3个区别:

  1. 它没有-p选项,如果有一级目录不存在,那么它会自动创建。
  2. 如果目标目录存在,那么它会报错。
  3. Windows里面的路径必须用反斜杠,使用斜杠会报错

对于其中的第二点,解决办法为使用一个Makefile提供的subst函数来实现字符串替换。由于涉及到对所有mkdir指令的替换,并且经过观察,JOS的mkdir指令创建的目录都是@D,所以我们只需要定义一个变量,把这条语句写入变量里就好,具体操作是在env.mk里面加入这样一条语句

MAKE_DIR_D = @if not exist $(subst /,\,$(@D)) (mkdir $(subst /,\,$(@D)))

对于第三点,同样需要进行额外处理。因为在make里面,报错就意味着停止执行了,这是我们不希望看到的,对此,有2种解决办法:
一种是对所有的mkdir加个判断,如果目录存在就不创建了,目前我是这样解决的,如上面的代码块所示,只需要加入一个判断即可。另一种办法则是使用expr || true来屏蔽掉语句的报错,即

MAKE_DIR_D = mkdir $(subst /,\,$(@D)) || true

我没有采用这个方法,原因是即使makefile能继续执行,但是错误信息还是会输出,有点影响观感
(后面查到可以通过在语句后面添加 2 > nul的方式来屏蔽掉错误输出,但是没有实际尝试了)

文件夹的删除

Windows同样有rm指令,但是用法有点不太一样,具体而言,如果文件夹不是空的,那么不能像rm -rf一样删除文件夹。如果需要删除非空文件夹需要使用rd /s /q来做到,但是使用这条指令时,如果目标文件夹不存在就会报错,对此只能使用上面提到的或一个true的小技巧了,最终可以写成这样:

rd /s /q dir_to_delete || true

QEMU配置修改

在最新版的QEMU里面,是不支持使用-net user -net nic,model=e1000 -redir这样的语法来创建虚拟网卡的,取而代之的,是要通过-device创建一个前端,再使用-netdev创建一个后端。其中,前端是对模拟器里的程序可见的设备接口,例如一个e1000网卡;后端指的是用来处理流入该设备的数据的具体方式,例如使用user模式的网络来处理。
在查阅官方文档和借助kimi ai 的帮助下,可以得到下面这条和旧版QEMU配置项等价的配置项:

QEMUOPTS += -netdev user,id=net0,hostfwd=tcp::$(PORT7)-:7,hostfwd=tcp::$(PORT80)-:80,hostfwd=udp::$(PORT7)-:7
QEMUOPTS += -device e1000,netdev=net0
QEMUOPTS += -object filter-dump,id=f1,netdev=net0,file=qemu.pcap

具体的可以参考下面这几个官方文档:
https://www.qemu.org/docs/master/system/invocation.html#sec-005finvocation
https://www.qemu.org/docs/master/system/device-emulation.html
https://www.qemu.org/docs/master/system/devices/net.html

修改fsformat.c

fsformat.c是用于生成适合于JOS的磁盘镜像文件的,要把这个程序迁移到Windows上面,需要做2个修改:

  • Windows不支持mmap
    JOS里面的fsformat实现使用到了Linux的mmap函数,用于把文件映射到内存,但是Windows下并没有功能完全相同的系统调用。
    对此,可以使用一个比较简单的解决方案:在内存中分配一块内存,然后fsformat对这块内存操作,最后把这块内存写入到文件里。
  • Windows默认打开文件不是以二进制模式打开
    这是一个大坑!!
    最开始是发现使用read函数读文件的时候,读有些文件总会在固定的位置报错,后来查资料发现是需要在open函数里面添加O_BINARY这个flag
    在这里插入图片描述
    后来又发现文件系统的镜像文件坏掉了,经过排查终于确定是写镜像文件时没使用BINARY模式导致的
    在这里插入图片描述
    具体的区别可以见这篇文章

其它小坑

最后再记录一下遇到的一个小坑:Jetbrains全家桶的terminal是不会自动更新PATH环境变量的。也就是说,如果打开了terminal,再修改环境变量,即使后面再新开一个terminal,新开的terminal的PATH环境变量依然是旧的,除非重启系统。

  • 25
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值