1 type命令(harib16a)

大家好。笔者觉得“纸娃娃系统”最近越来越有操作系统的样子了,不知道大家是不是也有同感呢?今天我们的系统即将可以运行应用程序了哦,很激动人心吧!

昨天经过我们的努力,终于可以显示磁盘中的文件名和文件大小信息了,不过,这离运行应用程序还差得远。因此,接下来我们要讲解一下如何读取文件本身的内容。

在Windows的命令行中,有一个叫做type的命令,输入“type 文件名”就会显示出文件的内容。在Linux中有一个cat命令,功能基本上是一样的。

可能有人还不知道type命令,我们来演示一下。

也就是说,我们要在“纸娃娃系统”上也实现这样的功能。

■■■■■

那么到底应该如何读取文件呢?我们还是从观察磁盘映像入手寻找线索吧。

首先,我们再回头看看昨天讲过的文件信息的部分。这是harib15g的磁盘映像,和昨天看到的只有少许不同,没关系,我们就当是复习了。

1 type命令(harib16a) - 图1

运行type Makefile的结果

磁盘映像的内容

  1. +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F 0123456789ABCDEF

002600 48 41 52 49 42 4F 54 45 53 59 53 20 00 00 00 00 HARIBOTESYS …. 002610 00 00 00 00 00 00 18 74 FF 32 02 00 70 6C 00 00 …….t.2..pl.. 002620 49 50 4C 31 30 20 20 20 4E 41 53 20 00 00 00 00 IPL10 NAS …. 002630 00 00 00 00 00 00 59 7A 42 35 39 00 95 0B 00 00 ……YzB59….. 002640 4D 41 4B 45 20 20 20 20 42 41 54 20 00 00 00 00 MAKE BAT …. 002650 00 00 00 00 00 00 F6 10 81 30 3F 00 2E 00 00 00 ………0?….. 002660 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …………….

这里面有一个32个字节的数据结构:

  1. struct FILEINFO {
  2. unsigned char name[8], ext[3], type;
  3. char reserve[10];
  4. unsigned short time, date, clustno;
  5. unsigned int size;
  6. };

其中clustno这个成员,代表文件从磁盘上的哪个扇区开始存放,我们只把这个部分写出来看看。

  1. HARIBOTE.SYS 02 00 clustno = 0x0002
  2. IPL10.NAS 39 00 clustno = 0x0039
  3. MAKE.BAT 3F 00 clustno = 0x003f

原来如此……说啥呢,其实完全没看懂吧(苦笑)。好吧,接下来我们就来找一找文件的内容到底存放在磁盘上的哪个扇区。首先来看第一个文件HARIBOTE.SYS的位置,这个很简单,在0x004200。咦,你怎么知道的?其实看一下3.4节就明白了,这可不是超能力哟。

也就是说,貌似clustno = 0x0002就代表0x004200的意思,嗯嗯,我们再来看看其他的文件。

IPL10.NAS在哪里呢?我们在磁盘映像中找找看。HARIBOTE.SYS的大小差不多27KB,估摸着也就是在它27KB之后的位置附近吧。你看,找到了!在0x00b000这个位置。

1 type命令(harib16a) - 图2

0x00b000附近的样子

MAKE.BAT我们也找到了,在0x00bc00这个位置。

1 type命令(harib16a) - 图3

0x00bc00附近的样子

于是,我们把上面搜集到的线索总结一下。

  1. clustno = 0x0002 0x004200
  2. clustno = 0x0039 0x00b000
  3. clustno = 0x003f 0x00bc00

唔,这个里面有什么规律呢?首先,我们从0x0039和0x003f开始看,把它们相减,0x3f – 0x39 = 6,也就是说,culstno每增加6,磁盘映像中的位置就增加0xc00个字节,将0xc00除以6得到0x200,即512个字节。

如果能马上想到512个字节代表什么,那你真是个天才!没错,1个扇区的容量正好是512个字节。也就是说,clustno的值相差1,在磁盘映像中文件的位置就相差1个扇区,即512个字节。如果以clustno = 0x0002为起点出发倒推的话,笔者认为clustno = 0x0000应该就相当于0x003e00这个位置。

如果当真如此,那我们就可以总结出下面这个公式。

  1. 磁盘映像中的地址 = clustno * 512 + 0x003e00

那么这个公式是否正确呢?我们来试算一下。

0x0002 * 512 + 0x003e00 = 0x000400 + 0x003e00 = 0x004200(正确)

0x0039 * 512 + 0x003e00 = 0x007200 + 0x003e00 = 0x00b000(正确)

0x003f * 512 + 0x003e00 = 0x007e00 + 0x003e00 = 0x00bc00(正确)

哇,成功了!看来这个公式可行。

■■■■■

有了上面的知识,接下来只要将文件的内容逐字节读取出来并显示在屏幕上就可以了。好,我们来写代码了哦。

本次的bootpack.c节选

  1. void console_task(struct SHEET *sheet, unsigned int memtotal)
  2. {
  3. (中略)
  4. char s[30], cmdline[30], *p; /*这里! */
  5. (中略)
  6. for (;;) {
  7. io_cli();
  8. if (fifo32_status(&task->fifo) == 0) {
  9. (中略)
  10. } else {
  11. (中略)
  12. if (256 <= i && i <= 511) { /*键盘数据(通过任务A) */
  13. if (i == 8 + 256) {
  14. (中略)
  15. } else if (i == 10 + 256) {
  16. /* Enter */
  17. (中略)
  18. if (strcmp(cmdline, "mem") == 0) {
  19. (中略)
  20. } else if (strcmp(cmdline, "cls") == 0) {
  21. (中略)
  22. } else if (strcmp(cmdline, "dir") == 0) {
  23. (中略)
  24. /*从此开始*/ } else if (cmdline[0] == 't' && cmdline[1] == 'y' && cmdline[2] == 'p' && cmdline[3] == 'e' && cmdline[4] == ' ') {
  25. /* type命令*/
  26. /*准备文件名*/
  27. for (y = 0; y < 11; y++) {
  28. s[y] = ' ';
  29. }
  30. y = 0;
  31. for (x = 5; y < 11 && cmdline[x] != 0; x++) {
  32. if (cmdline[x] == '.' && y <= 8) {
  33. y = 8;
  34. } else {
  35. s[y] = cmdline[x];
  36. if ('a' <= s[y] && s[y] <= 'z') {
  37. /*将小写字母转换成大写字母 */
  38. s[y] -= 0x20;
  39. }
  40. y++;
  41. }
  42. }
  43. /*寻找文件*/
  44. for (x = 0; x < 224; ) {
  45. if (finfo[x].name[0] == 0x00) {
  46. break;
  47. }
  48. if ((finfo[x].type & 0x18) == 0) {
  49. for (y = 0; y < 11; y++) {
  50. if (finfo[x].name[y] != s[y]) {
  51. goto type_next_file;
  52. }
  53. }
  54. break; /*找到文件*/
  55. }
  56. type_next_file:
  57. x++;
  58. }
  59. if (x < 224 && finfo[x].name[0] != 0x00) {
  60. /*找到文件的情况*/
  61. y = finfo[x].size;
  62. p = (char *) (finfo[x].clustno * 512 + 0x003e00 + ADR_DISKIMG);
  63. cursor_x = 8;
  64. for (x = 0; x < y; x++) {
  65. /*逐字输出*/
  66. s[0] = p[x];
  67. s[1] = 0;
  68. putfonts8_asc_sht(sheet, cursor_x, cursor_y, COL8_FFFFFF,
  69. COL8_000000, s, 1);
  70. cursor_x += 8;
  71. if (cursor_x == 8 + 240) { /*到达最右端后换行*/
  72. cursor_x = 8;
  73. cursor_y = cons_newline(cursor_y, sheet);
  74. }
  75. }
  76. } else {
  77. /*没有找到文件的情况*/
  78. putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, "File not found.", 15);
  79. cursor_y = cons_newline(cursor_y, sheet);
  80. }
  81. /*到此结束*/ cursor_y = cons_newline(cursor_y, sheet);
  82. } else if (cmdline[0] != 0) {
  83. (中略)
  84. }
  85. (中略)
  86. } else {
  87. (中略)
  88. }
  89. }
  90. (中略)
  91. }
  92. }
  93. }

■■■■■

这次的代码比较长,我们还是来稍微讲解一下。

首先看type命令那一段开头的地方,我们这次没有使用strcmp(cmdline,“type”),而是用了之前那种很难读的写法。其实笔者也想用strcmp来着,不过在这里用strcmp的话,当输入“type make.bat”时,就会输出“Bad command.”了,这可不行啊。这是由于strcmp会比较字符串的长度,因此仅开头5个字符一致是不会被判定相等的。

接下来的程序中(“准备文件名”的地方),首先将s[0~10]这11个字节用空格的字符编码填充,然后读取cmdline[5~]并复制到s[0~],在复制的同时,将其中的小写字母转换为大写字母。随后,当遇到句点时,则可以断定接下来的部分为扩展名,于是将复制的目标改为s[8~]。经过这样的转换,我们就得到了和磁盘内格式相同的文件名。

“寻找文件”这一段中,我们在磁盘中寻找与所输入的文件名相符的文件。如果成功找到指定文件,则用break跳出for循环;如果找不到,则会在x到达224或者finfo[x].name[0]为0x00时结束循环。

因此,当for循环结束后,我们就可以判断出到底是找到文件跳出for循环的,还是没有找到文件而结束for循环的,从而决定是显示指定的文件内容,还是显示“File not found.”的错误信息。

■■■■■

我们来“make run”,然后“type make.bat”。

1 type命令(harib16a) - 图4

type make.bat

哇,运行成功,撒花。显示出来的内容是不是正确呢?我们可以用文本编辑器打开make.bat来确认一下,果然是一模一样呢。

如果我们输入“type haribote.sys”会怎么样呢?程序会将机器语言的文件强行用文本方式显示出来,于是就变成下面这样的乱码。

1 type命令(harib16a) - 图5

type haribote.sys的话……

玩得一时兴起,我们再来试试看“type ipl10.nas”,笔者认为一定会显示出ipl10的程序源代码,但结果却显示出下面这样乱七八糟的字符。

1 type命令(harib16a) - 图6

type ipl10.nas

为……为啥会这样?不过我们仔细看看的话,其中还是有“load error”、RESB、DB之类正确显示的部分嘛。

哦,对了!我们忘记处理换行和制表符的字符编码了,怪不得会变成这个样子呢。好,接下来我们就来搞定它。