• 30天自制操作系统(第21天)


    21.1 攻克难题——字符串显示API

    显示单个字符时,用 [CS:ECX] 的方式特意指定了 CS(代码段寄存器),因此可以成功读取 msg的内容。但在显示字符串时,由于无法指定段地址,程序误以为是 DS而从完全错误的内存地址中读取了内容。hrb_api并不知道代码段的起始位置位于内存的哪个地址,但cmd_app应该知道,因为当初设置这个代码段的正是cmd_app。
    1. int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline){
    2. (中略)
    3. if (finfo != 0) {
    4. /* 找到了与字符串相同的文件 */
    5. p = (char *) memman_alloc_4k(memman, finfo->size);
    6. /*这里*/ *((int *) 0xfe8) = (int) p;
    7. file_loadfile(finfo->clustno, finfo->size, p, fat, (char *) (ADR_DISKIMG + 0x003e00));
    8. set_segmdesc(gdt + 1003, finfo->size - 1, (int) p, AR_CODE32_ER);
    9. farcall(0, 1003 * 8);
    10. memman_free_4k(memman, (int) p, finfo->size);
    11. cons_newline(cons);
    12. return 1;
    13. }
    14. return 0;
    15. }
    16. //根据输入参数打印数据 eax为当前字符 ebx为字符串 ecx长度
    17. void hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax){
    18. /*这里*/int cs_base = *((int *) 0xfe8);
    19. struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);
    20. if(edx == 1)
    21. cons_putchar(cons, eax&0xff, 1);
    22. else if(edx == 2)
    23. /*这里*/cons_putstr0(cons, (char*)ebx+cs_base);
    24. else if(edx == 3)
    25. /*这里*/cons_putstr1(cons, (char*)ebx+cs_base, ecx);
    26. return;
    27. }

    21.2 用C语言编写应用程序

    按照之前章节的理解,都是从汇编程序中输入了提前设定的字符才能进行打印,若将其整合成一个函数,当调用该函数时,便可打印岂不是更方便?函数api_putchar就是将输入参数c写入寄存器,并调用INT 0x40,该中断执行文件console.c中的hrb_api函数进行字符打印(详情看20.6节)。
    按照文件分工,hello3.c文件调用了a_nask.nas文件中的函数,需要在Makefile文件中添加如下代码(文件格式请参考30天自制操作系统(第1-3天)中的2.6节):

    hello3.bim : hello3.obj a_nask.obj Makefile
        $(OBJ2BIM) @$(RULEFILE) out:hello3.bim map:hello3.map hello3.obj a_nask.obj

    hello3.hrb : hello3.bim Makefile
        $(BIM2HRB) hello3.bim hello3.hrb 0

    1. /* hello3.c */
    2. void api_putchar(int c);
    3. void HariMain(void)
    4. {
    5. api_putchar('h');
    6. api_putchar('e');
    7. api_putchar('l');
    8. api_putchar('l');
    9. api_putchar('o');
    10. return;
    11. }
    12. /* a_nask.nas */
    13. _api_putchar: ; void api_putchar(int c);
    14. MOV EDX,1
    15. MOV AL,[ESP+4] ; c
    16. INT 0x40
    17. RET

    21.3 保护操作系统(1

    需要为应用程序提供专用的内存空间,并且告诉它们 别的地方不许碰哦 ”。要做到这一点,可以创建应用程序专用的数据段,并在应用程序运行期间,将 DS SS 指向该段地址。
      操作系统用代码段 ……2 * 8
      操作系统用数据段 ……1 * 8
      应用程序用代码段 ……1003 * 8
      应用程序用数据段 ……1004 * 8
    1. int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline){
    2. (中略)
    3. char name[18], *p, *q;
    4. (中略)
    5. if (finfo != 0) {
    6. /* 找到了与字符串相同的文件 */
    7. p = (char *) memman_alloc_4k(memman, finfo->size);
    8. /*这里*/q = (char *) memman_alloc_4k(memman, 64*1024);
    9. *((int *) 0xfe8) = (int) p;
    10. file_loadfile(finfo->clustno, finfo->size, p, fat, (char *) (ADR_DISKIMG + 0x003e00));
    11. set_segmdesc(gdt + 1003, finfo->size - 1, (int) p, AR_CODE32_ER);
    12. /*这里*/set_segmdesc(gdt + 1004, 64*1024 - 1, (int) q, AR_DATA32_RW);
    13. (中略)
    14. /*这里*/start_app(0, 1003 * 8, 64 * 1024, 1004 * 8);
    15. memman_free_4k(memman, (int) p, finfo->size);
    16. /*这里*/memman_free_4k(memman, (int) q, 64*1024);
    17. cons_newline(cons);
    18. return 1;
    19. }
    20. return 0;
    21. }
    1. ;void start_app(int eip, int cs, int esp, int ds);
    2. ;操作系统栈的ESP保存在0xfe4这个地址,以便从应用程序返回操作系统时使用
    3. _start_app:
    4. PUSHAD ;将832位寄存器压入栈,即8*(32/8)=32字节
    5. MOV EAX,[ESP+36] ; 应用程序用EIP
    6. MOV ECX,[ESP+40] ; 应用程序用CS
    7. MOV EDX,[ESP+44] ; 应用程序用ESP
    8. MOV EBX,[ESP+48] ; 应用程序用DS/SS
    9. MOV [0xfe4],ESP ; 操作系统用ESP
    10. CLI ; 在切换过程中禁止中断请求
    11. MOV ES,BX
    12. MOV SS,BX
    13. MOV DS,BX
    14. MOV FS,BX
    15. MOV GS,BX
    16. MOV ESP,EDX ; ESP为应用程序
    17. STI ; 切换完成后恢复中断请求
    18. PUSH ECX ; 用于far-CALL的PUSH(cs=1003*8)
    19. PUSH EAX ; 用于far-CALL的PUSH(eip=0)
    20. CALL FAR [ESP] ; 调用应用程序
    21. ; 应用程序结束后返回此处
    22. MOV EAX,1*8 ; 操作系统用DS/SS
    23. CLI ; 再次进行切换,禁止中断请求
    24. MOV ES,AX
    25. MOV SS,AX
    26. MOV DS,AX
    27. MOV FS,AX
    28. MOV GS,AX
    29. MOV ESP,[0xfe4] ; 切换成操作系统ESP
    30. STI ; 切换完成后恢复中断请求
    31. POPAD ; 恢复之前保存的寄存器值
    32. RET

    21.4 对异常的支持

    要想强制结束程序,只要在中断号 0x0d 中注册一个函数即可,这是因为在x86架构规范中,当应用程序试图破坏操作系统,或者试图违背操作系统的设置时,就会自动产生 0x0d 中断,因此该中断也被称为 异常”。写一个与 _asm_inthandler20函数大同小异的_asm_inthandler0d函数,与_asm_inthandler20的主要区别在于增加了STI/CLI这样控制中断请求禁止、恢复的指令和根据inthandler0d的结果来执行强制结束应用程序的操作
    1. _asm_inthandler0d:
    2. STI
    3. PUSH ES
    4. PUSH DS
    5. PUSHAD
    6. MOV AX,SS
    7. CMP AX,1*8
    8. JNE .from_app
    9. ; 当操作系统活动时产生中断的情况和之前差不多
    10. MOV EAX,ESP
    11. PUSH SS ; 保存中断时的SS
    12. PUSH EAX ; 保存中断时的ESP
    13. MOV AX,SS
    14. MOV DS,AX
    15. MOV ES,AX
    16. CALL _inthandler0d
    17. ADD ESP,8
    18. POPAD
    19. POP DS
    20. POP ES
    21. ADD ESP,4 ; 在INT 0x0d中需要这句
    22. IRETD
    23. .from_app:
    24. ; 当应用程序活动时产生中断
    25. CLI
    26. MOV EAX,1*8
    27. MOV DS,AX ; 先仅将DS设定为操作系统用
    28. MOV ECX,[0xfe4] ; 操作系统的ESP
    29. ADD ECX,-8
    30. MOV [ECX+4],SS ; 保存产生中断时的SS
    31. MOV [ECX ],ESP ; 保存产生中断时的ESP
    32. MOV SS,AX
    33. MOV ES,AX
    34. MOV ESP,ECX
    35. STI
    36. CALL _inthandler0d
    37. CLI
    38. CMP EAX,0
    39. JNE .kill
    40. POP ECX
    41. POP EAX
    42. MOV SS,AX ; 将SS恢复为应用程序用
    43. MOV ESP,ECX ; 将ESP恢复为应用程序用
    44. POPAD
    45. POP DS
    46. POP ES
    47. ADD ESP,4 ; INT 0x0d需要这句
    48. IRETD
    49. .kill:
    50. ; 将应用程序强制结束
    51. MOV EAX,1*8 ; 操作系统用的DS/SS
    52. MOV ES,AX
    53. MOV SS,AX
    54. MOV DS,AX
    55. MOV FS,AX
    56. MOV GS,AX
    57. MOV ESP,[0xfe4] ; 强制返回到start_app时的ESP
    58. STI ; 切换完成后恢复中断请求
    59. POPAD ; 恢复事先保存的寄存器值
    60. RET
  • 相关阅读:
    java计算机毕业设计图书管理系统演示录像 源代码+数据库+系统+lw文档
    面向接口编程实践之aspnetcoreapi的抽象
    我摊牌了!真正的灰度队列实现方案!全网你都搜不到!
    C++之多态以及文件处理
    Go语言高并发编程——互斥锁、条件变量
    Java求数组中的重复数字
    多水站送水桶装水配送员公众号H5开发
    ​力扣解法汇总792. 匹配子序列的单词数
    SpringBoot整合RabbitMQ实现消息的发送与接收,确认消息,延时消息
    第五章 树 14 AcWing 1552. AVL树的根
  • 原文地址:https://blog.csdn.net/Amnesiac_seven/article/details/136297138