第28天 文件操作与文字显示

第28天 文件操作与文字显示

2020.5.8

1. alloca(1)(harib25a)

  • 编写一个求素数的应用程序sosu.c:

    #include <stdio.h>
    #include "apilib.h"
    
    #define MAX		1000
    
    void HariMain(void)
    {
        char flag[MAX], s[8];
        int i, j;
        for (i = 0; i < MAX; i++) {
            flag[i] = 0;
        }
        for (i = 2; i < MAX; i++) {
            if (flag[i] == 0) {
                /* 没有标记的为素数 */
                sprintf(s, "%d ", i);
                api_putstr0(s);
                for (j = i * 2; j < MAX; j += i) {
                    flag[j] = 1;	/* 给它的倍数做上标记 */
                }
            }
        }
        api_end();
    }
    
    • 这个应用程序求1000以内的素数。
  • 在suso目录下生成一个精简的OS磁盘映像并用VMware运行:

  • 修改一下MAX的宏定义,改成求10000以内的素数,并另存为新的应用程序sosu2.c。

    • sosu2.c需要在栈中保存很多变量,光flag[10000]就大概需要10KB的空间,因此在Makefile中指定栈大小改成了11k。
  • make run后,出现了一条警告“Warning: can’t link __alloca”。不管它,运行sosu2.hrb试试:

    • 产生了一般保护性中断!
  • 警告在提示:缺少一个叫__alloca的函数。

    • 使用的C语言编译器规定:如果栈中的变量超过4KB,需要调用__alloca这个函数。这个函数的功能是根据OS的规格来获取栈的空间。在Windows或者Liunx中,如果不调用这个函数,而是仅对ESP进行减法运算的话,貌似无法成功获得内存空间。小于4KB时,只要对ESP进行减法运算即可。
    • 不过在此OS中,对栈的管理并没有什么特殊的设计,因此也用不着去调用__alloca函数,可C语言编译器并不是此OS专用的,于是又会擅自去调用这个函数了。
  • 为了解决上述问题,需要编写一个__alloca函数,只对ESP进行减法运算,而不做其他多余的操作。

  • 其实,不用__alloca函数也可以运行,因为可以调用OS的9号API,使用api_malloc函数申请内存空间(笑)。编写应用程序sosu3.c:

    #include <stdio.h>
    #include "apilib.h"
    
    #define MAX		10000
    
    void HariMain(void)
    {
        char *flag, s[8];
        int i, j;
        api_initmalloc();
        flag = api_malloc(MAX);  /*申请10000字节的内存空间*/
        for (i = 0; i < MAX; i++) {
            flag[i] = 0;
        }
        for (i = 2; i < MAX; i++) {
            if (flag[i] == 0) {
                sprintf(s, "%d ", i);
                api_putstr0(s);
                for (j = i * 2; j < MAX; j += i) {
                    flag[j] = 1;	
                }
            }
        }
        api_end();
    }
    
  • make后用VMware运行:

2. alloca(2)(harib25b)

  • 思前想后,虽然能够使用api_malloc申请内存空间,但是__alloca函数还是要写。编写__alloca函数(apilib目录下的alloca.nass):

    [FORMAT "WCOFF"]
    [INSTRSET "i486p"]
    [BITS 32]
    [FILE "alloca.nas"]
    
            GLOBAL	__alloca
    
    [SECTION .text]
    
    __alloca:
            ADD		EAX,-4
            SUB		ESP,EAX
            JMP		DWORD [ESP+EAX]		; 代替RET
    
    • alloca.nas的归类有点难分,所以暂时现将它放在apilib目录下,虽然它不是个API。
  • 详细讲解__alloca函数:

    • __alloca函数会在下述情况下被C语言的程序调用(采用near-CALL的方式):
      • 要执行的操作:从栈中分配EAX个字节的内存空间(ESP-=EAX)。
      • 要遵守的规则:不能改变ECX、EDX、EBX、EBP、ESI、EDI的值(可以临时改变,但是要使用PUSH/POP来复原)
    • 根据上述描述,于是编写出了第一版错误的alloca:
      SUB     ESP,EAX
      RET
      
      • 这个程序是无法运行的,因为RET返回的地址保存在ESP中,而ESP的值在这里被改变了,于是读取了错误的返回地址。注意:RET相当于POP EIP
    • 接着又编写了第二版错误的alloca:
      SUB     ESP,EAX  
      JMP     DWORD [ESP+EAX]     ;代替RET
      
      • JMP的目标地址从[ESP]变成了[ESP+EAX],ESP+EAX的值刚好是减法运算之前的ESP值,也就是正确的地址。
      • RET指令相当于POP EIP,而POP EIP又相当于下面两条指令:
        MOV     EIP,[ESP]       ;没有这个指令,用JMP [ESP]代替。
        ADD     ESP,4
        
        • 也就是说刚刚忘记给ESP+4了。
    • 编写第三版错误的alloca:
      SUB     ESP,EAX  
      JMP     DWORD [ESP+EAX]     ;代替RET  
      ADD     ESP,4
      
      • 第三版错误的原因是ADD指令的位置,将ADD指令放在了JMP指令的后面,所以ADD指令不会被执行。
    • 编写第四版正确的alloca:
      SUB     ESP,EAX  
      ADD     ESP,4
      JMP     DWORD [ESP+EAX-4]     ;代替RET  
      
      • 用这个程序直接作为alloca.nas是完全没有问题的。
    • 编写第五版正确的alloca:
      ADD		EAX,-4
      SUB		ESP,EAX
      JMP		DWORD [ESP+EAX]		; 代替RET
      
      • 和第四版大同小异,不多精简了一点儿。
  • make后用VMware重新运行sosu2.hrb,这次成功输出,没有产生一般保护性中断。

  • 这样的话sosu2.hrb和sosu3.hrb在运行结果上没有任何区别,看一下文件大小:

    • sosu2.hrb:1484字节
    • sosu3.hrb:1524字节
    • 虽然差别不大,但是还是sosu2.hrb小一点。既然小一点,那么把winhelo也从栈中分配空间吧,不再用malloc了。
    • 修改winhelo.c:
      #include "apilib.h"
      
      void HariMain(void)
      {
          int win;
          char buf[150 * 50];
          win = api_openwin(buf, 150, 50, -1, "hello");
          for (;;) {
              if (api_getkey(1) == 0x0a) {
                  break;
              }
          }
          api_end();
      }
      
      • 在Makefile中设定STACK = 8k,因为buf大概需要7.5KB的空间。
      • make后可以成功运行应用程序winhelo,要知道修改前有7664KB。之所以对文件大小这样苛刻,是因为担心磁盘空间不够(后面还要支持汉字字库),因此应用程序能小就小。
  • 顺便把winhelo2.c也改了:

    #include "apilib.h"
    
    void HariMain(void)
    {
        int win;
        char buf[150 * 50];
        win = api_openwin(buf, 150, 50, -1, "hello");
        api_boxfilwin(win,  8, 36, 141, 43, 3);
        api_putstrwin(win, 28, 28, 0, 12, "hello, world");
        for (;;) {
            if (api_getkey(1) == 0x0a) {
                break; 
            }
        }
        api_end();
    }
    
    • 把Makefile中的STACK设置为8K。
  • 比较一下winhelo[23]?前后的大小:

    winhelo(buf[]) winhelo2(buf[]) winhelo3(malloc方式)
    改良前 7664b 7808b 359b(未改良)
    改良后 174b 315b 359b(未改良)

3. 文件操作API(harib25c)

  • 所谓文件操作API,就是可以指定文件,并能够自由读写文件内容的API。现在的OS还不能对磁盘进行写入操作,因此只要能够读取文件内容就可以了。

  • 一般的OS中,输入输出文件的API基本上都有如下的功能:

    • 打开:open
      • 打开和关闭API用来对要读写的文件进行打开和关闭的操作。一个文件必须先打开才能进行读写操作,因为在打开时,OS需要对读写的文件进行准备工作,关闭时也需要进行一些善后处理。
      • 打开文件时需要指定文件名,如果打开成功,OS返回文件句柄。在随后的操作中,只要提供这个文件的句柄就可以进行读写操作了,操作结束后将文件关闭。
    • 定位:seek
      • 定位API的功能是指定下次读取、写入命令需要操作的目标位于文件中的位置。
    • 读取:read
      • 读取和写入API需要指定需要读取(写入)的数据长度以及内存地址,文件的内容会被传送至内存。(写入操作时是由内存传至文件)
    • 写入:write
      • 同读取
    • 关闭:close
      • 同打开
  • 设计API:

    • 打开文件
      • EDX = 21
      • EBX = 文件名
      • 返回值EAX = 文件句柄(当OS返回0时,代表文件打开失败)
    • 关闭文件
      • EDX = 22
      • EAX = 文件句柄
    • 文件定位
      • EDX = 23
      • EAX = 文件句柄
      • ECX = 定位模式
        • 0:定位起点为文件开头
        • 1:定位起点为当前访问位置
        • 2:定位起点为文件末尾
      • EBX = 定位偏移量
    • 获取文件大小
      • EDX = 24
      • EAX = 文件句柄
      • ECX = 文件大小获取模式
        • 0:普通文件大小
        • 1:当前读取位置从文件开头算起的偏移量
        • 2:当前读取位置从文件末尾算起的偏移量
      • 返回值EAX = 文件大小
    • 文件读取
      • EDX = 25
      • EAX = 文件句柄
      • EBX = 缓冲区地址
      • ECX = 最大读取字节数
      • 返回值EAX = 本次读取到的字节数
  • 修改bootpack.h:

    struct TASK {
        int sel, flags; 
        int level, priority;
        struct FIFO32 fifo;
        struct TSS32 tss;
        struct SEGMENT_DESCRIPTOR ldt[2];
        struct CONSOLE *cons;
        int ds_base, cons_stack;
        struct FILEHANDLE *fhandle;
        int *fat;
    };  
    
    struct FILEHANDLE {
        char *buf;
        int size;
        int pos;
    };
    
    • 结构体TASK增加了成员fhandle和fat,是为了让hrb_api和cmd_app能够使用在console_task中声明的变量。
      • fhandle是一个指向FILEHANDLE的指针,用于存放应用程序打开文件的信息(应用程序可能打开不止一个文件)。
      • fat是指向文件配置表的指针。
    • 结构体FILEHANDLE,文件句柄:
    • 1
      点赞
    • 0
      收藏
      觉得还不错? 一键收藏
    • 0
      评论

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

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

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值