一切皆文件
一切皆文件
我们已经提供了完整的文件系统, 用户程序已经可以读写普通的文件了. 想想我们在AM上运行的打字游戏, 读入按键/查询时钟/更新屏幕其实也是用户程序的合理需求, 操作系统也需要提供支持. 一种最直接的方式, 就是为每个功能单独提供一个系统调用, 用户程序通过这些系统调用, 就可以直接使用相应的功能了. 然而这种做法却存在不少问题:
首先, 设备的类型五花八门, 其功能更是数不胜数,
要为它们分别实现系统调用来给用户程序提供接口, 本身就已经缺乏可行性了;
此外, 由于设备的功能差别较大, 若提供的接口不能统一, 程序之间的交互就会变得困难.
我们在上一小节中提到, 文件的本质就是字节序列. 事实上, 计算机系统中到处都是字节序列(如果只是无序的字节集合, 计算机要如何处理?), 我们可以轻松地举出很多例子:
内存是以字节编址的, 天然就是一个字节序列, 因而我们之前使用的ramdisk作为字节序列也更加显而易见了
管道(shell命令中的
|
)是一种先进先出的字节序列, 本质上它是内存中的一个队列缓冲区磁盘也可以看成一个字节序列:
我们可以为磁盘上的每一个字节进行编号, 例如第x柱面第y磁头第z扇区中的第n字节,
把磁盘上的所有字节按照编号的大小进行排列, 便得到了一个字节序列
socket(网络套接字)也是一种字节序列, 它有一个缓冲区, 负责存放接收到的网络数据包,
上层应用将socket中的内容看做是字节序列, 并通过一些特殊的文件操作来处理它们
操作系统的一些信息可以以字节序列的方式暴露给用户, 例如CPU的配置信息
操作系统提供的一些特殊的功能, 如随机数生成器, 也可以看成一个无穷长的字节序列
甚至一些非存储类型的硬件也可以看成是字节序列:
我们在键盘上按顺序敲入按键的编码形成了一个字节序列,
显示器上每一个像素的内容按照其顺序也可以看做是字节序列...
既然文件就是字节序列, 那很自然地, 上面这些五花八门的字节序列应该都可以看成文件. Unix就是这样做的, 因此有"一切皆文件"(Everything is a file)的说法. 这种做法最直观的好处就是为不同的事物提供了统一的接口: 我们可以使用文件的接口来操作计算机上的一切, 而不必对它们进行详细的区分: 例如 nanos-lite/Makefile
中通过管道把各个shell工具的输入输出连起来, 生成文件记录表
以十六进制的方式查看磁盘上的内容
查看CPU的配置信息
而
则会将urandom中的内容包含到源文件中: 由于urandom是一个长度无穷的字节序列, 提交一个包含上述内容的程序源文件将会令一些检测功能不强的Online Judge平台直接崩溃.
"一切皆文件"的抽象使得我们可以通过标准工具很容易完成一些在Windows下不易完成的工作, 这其实体现了Unix哲学的部分内容: 每个程序采用文本文件作为输入输出, 这样可以使程序之间易于合作. GNU/Linux继承自Unix, 也自然继承了这种优秀的特性. 为了向用户程序提供统一的抽象, Nanos-lite也尝试将IOE抽象成文件.
首先当然是来看输出设备. 串口已经被抽象成stdout
和stderr
了, 我们无需担心. 至于VGA, 程序为了更新屏幕, 只需要将像素信息写入VGA的显存即可. 于是, Nanos-lite需要做的, 便是把显存抽象成文件. 显存本身也是一段存储空间, 它以行优先的方式存储了将要在屏幕上显示的像素. Nanos-lite和Navy-apps约定, 把显存抽象成文件/dev/fb
(fb为frame buffer之意), 它需要支持写操作和lseek, 以便于用户程序把像素更新到屏幕的指定位置上.
除此之外, 用户程序还需要获得屏幕大小的信息, 然后才能决定如何更好地显示像素内容. Nanos-lite和Navy-apps约定, 屏幕大小的信息通过/proc/dispinfo
文件来获得, 它需要支持读操作. /proc/dispinfo
内容的一个例子如下:
需要注意的是, /dev/fb
和/proc/dispinfo
都是特殊的文件, 文件记录表中有它们的文件名, 但它们的实体并不在ramdisk中. 因此, 我们需要在fs_read()
和fs_write()
的实现中对它们进行"重定向", 以fs_write()
为例:
最后我们来看输入设备. 输入设备有键盘和时钟, 我们需要把它们的输入包装成事件. 一种简单的方式是把事件以文本的形式表现出来, 我们定义以下事件, 一个事件以换行符\n
结束:
t 1234
: 返回系统启动后的时间, 单位为毫秒;kd RETURN
/ku A
: 按下/松开按键, 按键名称全部大写, 使用AM中定义的按键名
我们采用文本形式来描述事件有两个好处, 首先文本显然是一种字节序列, 这使得事件很容易抽象成文件; 此外文本方式使得用户程序可以容易可读地解析事件的内容. Nanos-lite和Navy-apps约定, 上述事件抽象成文件/dev/events
, 它需要支持读操作, 用户程序可以从中一次读出一个输入事件. 需要注意的是, 由于时钟事件可以任意时刻进行读取, 我们需要优先处理按键事件, 当不存在按键事件的时候, 才返回时钟事件, 否则用户程序将永远无法读到按键事件.
运行仙剑奇侠传
我们的框架代码已经把SDLPAL移植到Navy-apps中了. 移植的主要工作就是把应用层之下提供给仙剑奇侠传的所有API重新实现一遍, 因为这些API大多都依赖于操作系统提供的运行时环境, 我们需要根据Navy-apps提供的运行时环境重写它们. 主要包括以下三部分内容:
C标准库
浮点数
SDL库
Navy-apps中的newlib已经提供了C标准库的功能, 我们无需额外移植. 关于浮点数的移植工作, 我们会在PA5中再来讨论, 目前先忽略它. 为了移植SDL库相关的代码, Navy-apps把时钟, 键盘, 显示的功能封装成NDL(NJU DirectMedia Layer)多媒体库, 其中封装了我们之前实现的/dev/fb
和/dev/events
的读写. 为了用NDL的API来替代原来SDL的相应功能, 移植工作需要对SDLPAL进行了少量修改, 包括去掉了声音, 修改了和按键相关的处理, 把我们关心的与NDL相关的功能整理到hal/hal.c
中, 一些我们不必关心的实现则整理到unused/
目录下. 框架代码已经把这些移植工作都做好了, 目前你不需要编写额外的代码来进行移植.
Last updated
Was this helpful?