【读书笔记-《30天自制操作系统》-19】Day20
本篇的内容围绕系统调用展开。为了让应用程序能够调用操作系统功能,引入了系统调用以及API的概念。首先实现了显示单个字符的API,让应用程序通过传递地址的方式进行调用;接下来又改进为通过中断的方式进行调用。在此基础上继续实现了显示字符串的API。

1. 显示单个字符的API:传递地址方式调用
应用程序调用操作系统功能,称为系统调用(system call);而API是指应用程序与操作系统接口(application program interface)。
首先来实现显示单个字符的API。上一篇中显示字符的程序,我们改写成一个函数cons_putchar:
void cons_putchar(struct CONSOLE *cons, int chr, char move)
{char s[2];s[0] = chr;s[1] = 0;if (s[0] == 0x09) { /* tab */for (;;) {putfonts8_asc_sht(cons->sht, cons->cur_x, cons->cur_y, COL8_FFFFFF, COL8_000000, " ", 1);cons->cur_x += 8;if (cons->cur_x == 8 + 240) {cons_newline(cons);}if (((cons->cur_x - 8) & 0x1f) == 0) {break; }}} else if (s[0] == 0x0a) { cons_newline(cons);} else if (s[0] == 0x0d) { } else { putfonts8_asc_sht(cons->sht, cons->cur_x, cons->cur_y, COL8_FFFFFF, COL8_000000, s, 1);if (move != 0) {cons->cur_x += 8;if (cons->cur_x == 8 + 240) {cons_newline(cons);}}}return;
}
使用cons_putchar就可以输出单个字符。这样在应用程序中只需要调用cons_putchar就可以了。使用类似于下面的代码:
[BITS 32]MOV AL,'A'CALL (cons_putchar函数地址)fin:HLTJMP fin
在汇编语言中,CALL指令与JMP指令类似,区别在于调用CALL指令时,为了能够在执行RET时正确返回,需要先将返回的目标地址PUSH到栈中。应用程序无法知道操作系统中函数的地址,因此这里不能在应用程序中直接调用cons_putchar函数,需要查到cons_putchar函数的地址后填写到应用程序的代码中。
又因为cons_putchar是C语言函数,将需要显示的字符通过写入寄存器的方式来进行参数传递,函数无法直接接收。因此这里还需要通过一个汇编语言函数将寄存器的值推入栈,再调用cons_putchar函数进行显示。
调用cons_putchar函数,需要传入cons变量的地址。应用程序当然无法知道这个地址,需要操作系统传递。这里将这个地址放在0x0fec中,在asm_cons_putchar函数中从0x0fec地址中获取。
void console_task(struct SHEET *sheet, unsigned int memtotal)
{
……struct CONSOLE cons;char cmdline[30];cons.sht = sheet;cons.cur_x = 8;cons.cur_y = 28;cons.cur_c = -1;*((int *) 0x0fec) = (int) &cons;
……
_asm_cons_putchar:PUSH 1AND EAX,0xff ; EAX高位置0,EAC置为存入字符编码的状态PUSH EAXPUSH DWORD [0x0fec] ; 从0x0fec中读取cons地址并PUSHCALL _cons_putchar ; 调用cons_putchar显示字符ADD ESP,12 ; 丢弃栈中的数据RET
到这里还没有完成,应用程序还不知道asm_cons_putchar的地址,无法对其进行调用。先运行make,会生成bootpack.map的文件,用文本编辑器打开,可以找到如下的行:
0x00000BE3 : _asm_cons_putchar
这就是asm_cons_putchar函数的地址,可以将其填入到应用程序中:
[BITS 32]MOV AL,'A'CALL 0xbe3fin:HLTJMP fin
这样就可以运行应用程序了。在命令行中输入hlt并回车,却发现QEMU出错关闭了。
这是什么原因呢?
原来应用程序在对API执行CALL指令时需要加上段号。我们给应用程序设置的段号为1003,而操作系统的段位2,不能使用普通的CALL,而应使用fat-CALL。与far-JMP指令一样,fat-CALL指令需要指定段号和偏移量。相应地,RET指令也需要使用RETF来代替。
[BITS 32]MOV AL,'A'CALL 2*8:0xbe3fin:HLTJMP fin
_asm_cons_putchar:PUSH 1AND EAX,0xff ; EAX高位置0,EAC置为存入字符编码的状态PUSH EAXPUSH DWORD [0x0fec] ; 从0x0fec中读取cons地址并PUSHCALL _cons_putchar ; 调用cons_putchar显示字符ADD ESP,12 ; 丢弃栈中的数据RETF
这样修改之后再运行hlt,就可以正常显示了:

应用程序完成字符显示之后就进入了HLT循环,相当于就这样卡住了。是否能让其回到操作系统继续执行其他命令呢?
我们可以将HLT部分改成RET。前文使用JMP来运行应用程序,这里也需要改成CALL。
由于CALL调用的程序位于不同的段,其实是far-CALL,因此对应地也要使用RETF。首先编写一个函数farcall:
_farcall: ;void farcall(int eip, int cs);CALL FAR [ESP + 4] ;eip, csRET
改写调用应用程序的部分如下:
void cmd_hlt(struct CONSOLE *cons, int *fat)
{struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;struct FILEINFO *finfo = file_search("HLT.HRB", (struct FILEINFO *) (ADR_DISKIMG + 0x002600), 224);struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT;char *p;if (finfo != 0) {p = (char *) memman_alloc_4k(memman, finfo->size);file_loadfile(finfo->clustno, finfo->size, p, fat, (char *) (ADR_DISKIMG + 0x003e00));set_segmdesc(gdt + 1003, finfo->size - 1, (int) p, AR_CODE32_ER);farcall(0, 1003 * 8);// 用CALL代替JMPmemman_free_4k(memman, (int) p, finfo->size);} else {putfonts8_asc_sht(cons->sht, 8, cons->cur_y, COL8_FFFFFF, COL8_000000, "File not found.", 15);cons_newline(cons);}cons_newline(cons);return;
}
不过重新编译之后,asm_cons_putchar函数的地址发生了变化,对应修改应用程序中的地址:
[BITS 32]MOV AL,'A'CALL 2*8:0xbe8RETF
这样运行结束后就可以退回到操作系统了。还可以显示一个简单的字符串:
[BITS 32]MOV AL,'h'CALL 2*8:0xbe8MOV AL,'e'CALL 2*8:0xbe8MOV AL,'l'CALL 2*8:0xbe8MOV AL,'l'CALL 2*8:0xbe8MOV AL,'o'CALL 2*8:0xbe8RETF

2. 显示单个字符的API:中断方式调用
如上面所展示的,如果修改了操作系统的代码,相应函数的地址发生了变化,用这种通过地址调用的方式就总要修改应用程序的代码,这样会很麻烦。
为了解决这个问题,我们回想一下前面介绍过的中断处理函数。IDT中最多可以注册256个函数,出了IRQ0-15以外,还有很多空闲,我们可以从中找一个注册asm_cons_putchar函数。比如选择0x40号:
void init_gdtidt(void)
{
……/* IDT设置 */set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32);set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32);set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);set_gatedesc(idt + 0x40, (int) asm_cons_putchar, 2 * 8, AR_INTGATE32);……
}
这样只需要使用INT 40指令就可以调用asm_cons_putchar函数了,应用程序的代码可以修改如下:
[BITS 32]MOV AL,'h'INT 0x40MOV AL,'e'INT 0x40MOV AL,'l'INT 0x40MOV AL,'l'INT 0x40MOV AL,'o'INT 0x40RETF
使用INT指令来调用asm_cons_putchar函数,会被视为中断处理,会自动调用CLI来禁止中断请求,而实际上这里并非中断,我们应该允许此时接收中断,因此在开头使用STI允许中断请求;返回指令也需要使用IRETD:
_asm_cons_putchar:STIPUSH 1AND EAX,0xff PUSH EAXPUSH DWORD [0x0fec] CALL _cons_putcharADD ESP,12 ; IRETD
这样修改下来,应用程序就不用总是随着操作系统的修改而修改了。
3. 显示字符串的API
既然已经实现了显示单个字符的API,我们还可以更进一步,实现显示字符串的API,毕竟显示字符串的函数应用更多。显示字符串的API一般有两种实现方式:一种是依次显示字符串中的字符,直到’\0’结束;另一种是指定显示字符串的长度。
void cons_putstr0(struct CONSOLE *cons, char *s)
{for (; *s != 0; s++) {cons_putchar(cons, *s, 1);}return;
}void cons_putstr1(struct CONSOLE *cons, char *s, int l)
{int i;for (i = 0; i < l; i++) {cons_putchar(cons, s[i], 1);}return;
}
这样前文显示字符串的部分都可以用以上的函数进行替换了。
有了显示字符串的函数,如何变成API呢?还是采用注册中断函数的方法。不过中断号毕竟还是有限的,如果每个函数都注册一个中断号,中断号还是很容易就被占满的。这里我们可以仿照BIOS的调用,通过传入不同的功能号,只用一个INT就可以选择调用多个不同的函数了。
- 功能号1: 显示单个字符(AL = 字符编码)
- 功能号2: 显示字符串函数0(EBX = 字符串地址)
- 功能号3: 显示字符串1(EBX = 字符串地址,ECX = 字符串长度)
asm_cons_putcha修改为新的函数:
_asm_hrb_api:STIPUSHAD ; 用于保存寄存器的PUSHPUSHAD ; 用于向hrb_api传值的PUSHCALL _hrb_apiADD ESP,32POPADIRETD
处理函数hrb_api:
void hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);if (edx == 1) {cons_putchar(cons, eax & 0xff, 1);} else if (edx == 2) {cons_putstr0(cons, (char *) ebx);} else if (edx == 3) {cons_putstr1(cons, (char *) ebx, ecx);}return;
}
这样应用程序通过传入不同的功能号调用asm_hrb_api函数,hrb_api就会根据传入参数来选择不同的函数。再把IDT中的0x40号函数修改成asm_hrb_api函数就可以了。
参数变了,应用程序也需要进行改写一下:
[INSTRSET "i486p"]
[BITS 32]MOV EDX,2MOV EBX,msgINT 0x40RETF
msg:DB "hello",0
运行结果如下:

并没有显示出任何内容?这是什么原因呢?下一篇我们来继续解决,敬请期待。
相关文章:
【读书笔记-《30天自制操作系统》-19】Day20
本篇的内容围绕系统调用展开。为了让应用程序能够调用操作系统功能,引入了系统调用以及API的概念。首先实现了显示单个字符的API,让应用程序通过传递地址的方式进行调用;接下来又改进为通过中断的方式进行调用。在此基础上继续实现了显示字符…...
Kubernetes服务注册与发现
Kubernetes服务注册与发现 1、服务注册2、服务发现2.1 DNS服务发现2.2 环境变量(较少使用)💖The Begin💖点点关注,收藏不迷路💖 在Kubernetes中,服务注册与发现确保了Pod间的高效通信。 1、服务注册 当创建Service时,其信息被存储在Kubernetes的ETCD数据库中。Pod…...
【 html+css 绚丽Loading 】000047 玄武流转盘
前言:哈喽,大家好,今天给大家分享htmlcss 绚丽Loading!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏关注哦 💕…...
线程池原理及改造
目录 一 线程池执行原理 二 线程池改造(一) 三 线程池改造(二) 一 线程池执行原理 首先我们先了解一下线程池里面几个参数: 第一个是核心线程数,第二个是线程池最大线程数。(线程池里面的线程分为核心线程和非核心线程,既然核心…...
彻底理解mysql Buffer Pool (拓展)
彻底理解Buffer Pool (拓展) 一、Buffer Pool 的内存管理策略对数据库性能的影响 内存分配与回收:Buffer Pool 在申请内存时,需要考虑操作系统的内存分配策略。如果分配不合理,可能导致内存碎片,影响性能…...
信号量(二值信号量和计数信号量)和互斥量
信号量 信号量(Semaphore) 是一种实现任务间通信的机制, 可以实现任务之间同步或临界资源的互斥访问, 常用于协助一组相互竞争的任务来访问临界资源。 在多任务系统中, 各任务之间需要同步或互斥实现临界资源的保护&a…...
结构型模式-python版
在21种设计模式中, 结构型设计模式有7种, 分别是: 适配器模式代理模式桥接模式享元模式外观模式组合模式装饰器模式 下面逐一简要介绍: 1 适配器模式 适配器(Adapter)设计模式是一种结构型设计模式&…...
Java重修笔记 第五十四天 坦克大战(二)常用的绘图方法、画出坦克图形
常用的绘图方法 1.设置当前画笔的颜色,可多次调用 public abstract void setColor(Color c) 参数:c -颜色 2. 画一条直线 public abstract void drawLine(int x1, int y1, int x2, int y2) 参数:x1 - 第一个点的 x坐标。 y1 - 第一点的 y坐…...
OpenAI澄清:“GPT Next”不是新模型。
不,”GPT Next” 并不是OpenAI的下一个重要项目。 本周早些时候,OpenAI 日本业务的负责人长崎忠男在日本 KDDI 峰会上分享了一场演讲,似乎在暗示一个名为 “GPT Next” 的新模型即将出现。 但OpenAI的一位发言人已向Mashable证实࿰…...
<<编码>> 第 10 章 逻辑与开关(Logic and Switches) 示例电路
串联电路 info::操作说明 鼠标单击开关切换开合状态 需要两个开关同时闭合才能接通电路 primary::在线交互操作链接 https://cc.xiaogd.net/?startCircuitLinkhttps://book.xiaogd.net/code-hlchs-examples/assets/circuit/code-hlchs-ch10-01-series-circuit.txt 并联电路 in…...
深入浅出 Ansible 自动化运维:从入门到实战
在现代 IT 运维中,自动化是提升效率、降低错误率的关键。Ansible 作为一款流行的自动化工具,凭借其简洁的语法和强大的功能,成为了运维工程师的得力助手。本文将深入探讨 Ansible 的核心概念、实际应用以及一些实用的技巧,帮助你在…...
一句话描述设计模式
最近在看设计模式,其描述抽象程度令人欲罢不能,始终不得其意。于是尝试用一句话总结了一下,常规的就不说了,只是举了个例子。 单例模式 Spring中的单例bean使用了双重锁机制 工厂模式 Spring中的BeanFactory是简单工厂模式Bea…...
【Linux】Ubuntu 22.04 shell实现MySQL5.7 tar 一键安装
参考 https://blog.csdn.net/qq_35995514/article/details/134350572?spm1001.2014.3001.5501 在原作者基础上做了修改,加了一个删除原有mysql 的脚本 文章目录 一、安装下载**my.cnf 配置文件** 二、执行安装**install_mysql.sh 安装脚本**本机免密脚本 ssh_keyge…...
SQL Server开启网络访问
目前工作中很少用到SQL Server了,最近需要测试几个表,需要搭建一个SQL Server数据库服务,这里做个总结吧。 安装这里就不做详细介绍了,本文只介绍如何开启SQL Server网络访问。 1、云服务器安全组设置 如果是搭建在云服务器上&a…...
el-input设置type=‘number‘和v-model.number的区别
el-input设置typenumber’与设置.number修饰符的区别 1. 设置type‘number’ 使用el-input时想收集数字类型的数据,我们首先会想到typenumber,设置完type为number时会限制我们输入的内容只能为数字,不能为字符/汉字等非数字类型的数值&…...
6.第二阶段x86游戏实战2-理解程序流程
免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动! 本次游戏没法给 内容参考于:微尘网络安全 工具下载: 链接:https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…...
Netty笔记01-Netty的基本概念与用法
文章目录 1. 概述1.1 Netty 是什么?1.2 Netty 的特点1.3 Netty 的作者1.4 Netty 的地位1.5 Netty 的优势1.6 Netty 的工作原理1.7 Netty 的应用场景1.8 Netty 的重要组件 2. 第一个程序2.1 目标2.2 服务器端2.3 客户端2.4 流程梳理💡 提示 1. 概述 1.1 …...
OpenHarmony鸿蒙( Beta5.0)RTSPServer实现播放视频详解
鸿蒙开发往期必看: 一分钟了解”纯血版!鸿蒙HarmonyOS Next应用开发! “非常详细的” 鸿蒙HarmonyOS Next应用开发学习路线!(从零基础入门到精通) “一杯冰美式的时间” 了解鸿蒙HarmonyOS Next应用开发路…...
QT使用事件事件和绘制事件实现简易时钟
这个时钟实现的底层原理主要是利用 Qt 的绘图机制和定时器。首先,设置固定大小的窗口,创建定时器并连接到槽函数,定时器每秒钟触发一次,触发窗口重绘。在paintEvent函数中,使用QPainter进行绘图,绘制圆形表…...
kubeadm方式安装k8s
一、安装环境 环境准备:(有阿里云)centos7 k8s-master 192.168.1.11 k8s-node1 192.168.1.22 k8s-node2 192.168.1.33 二、前期准备 在k8s-master主机 [rootk8s-master ~]# vim /etc/hosts…...
告别上位机:用STM32的CAN总线直接对话Maxon EPOS4驱动器(附完整通信代码)
STM32直连Maxon EPOS4:CAN总线电机控制实战指南 在机器人关节控制、智能小车驱动等高精度运动控制场景中,Maxon EPOS4系列驱动器凭借其卓越性能成为工业级首选。但传统依赖PC上位机(如EPOS Studio)的调试方式,严重制约…...
VSCode + Modelsim 搭建Verilog开发环境:除了语法检查,还能这样玩?
VSCode与ModelSim深度集成:打造高效Verilog开发工作流 在数字电路设计领域,Verilog作为硬件描述语言的标准之一,其开发效率直接影响项目进度。传统开发模式中,工程师需要在多个工具间频繁切换——编辑器用于编码,Model…...
【路径规划】基于A星算法实现图结构中的多机器人路径规划附matlab代码
✅作者简介:热爱科研的Matlab仿真开发者,擅长毕业设计辅导、数学建模、数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。🍎完整代码获取 定制创新 论文复现点击:Matlab科研工作室👇 关注我领取海量m…...
福特押注五款新车型,含电动车与Bronco,欲重振欧洲市场
福特计划未来三年内在欧洲推出五款全新乘用车,以重振其在欧洲市场日渐式微的品牌形象。这一"福特欧洲乘用车新纪元"计划涵盖一款全新的多能源Bronco SUV、一款小型纯电掀背车、一款纯电SUV,以及两款多能源跨界SUV,所有车型均专为欧…...
STM32F103驱动ST7567 LCD屏:手把手教你移植U8g2库(SPI接口,附完整工程)
STM32F103驱动ST7567 LCD屏:从零开始移植U8g2库实战指南 当你第一次拿到一块ST7567驱动的LCD屏时,可能会被各种引脚定义和初始化代码搞得晕头转向。本文将带你从硬件连接到软件移植,一步步完成U8g2库在STM32F103上的适配过程。不同于简单的代…...
Windows HEIC缩略图终极解决方案:3步解锁苹果照片完美预览
Windows HEIC缩略图终极解决方案:3步解锁苹果照片完美预览 【免费下载链接】windows-heic-thumbnails Enable Windows Explorer to display thumbnails for HEIC/HEIF files 项目地址: https://gitcode.com/gh_mirrors/wi/windows-heic-thumbnails 还在为iPh…...
【实战】Latex|在保留ACM-Reference-Format格式的前提下,实现参考文献按引用顺序排列
1. 问题背景与核心痛点 当你使用ACM官方模板撰写论文时,参考文献格式要求必须采用ACM-Reference-Format样式。这个格式有个让人头疼的特性:它会强制按作者姓氏字母顺序排列参考文献,而不是按照文中实际引用顺序。想象一下,你精心设…...
Perplexity学校信息检索的“黑箱”终于被打开:基于37所样本校实测的响应延迟、召回率与可信度三维评估报告
更多请点击: https://codechina.net 第一章:Perplexity学校信息检索的“黑箱”终于被打开:基于37所样本校实测的响应延迟、召回率与可信度三维评估报告 实测方法论:三维度穿透式评估框架 我们对全国37所高校(含985/2…...
MRI绕组结构设计及均匀度优化算法【附算法】
✨ 长期致力于MRI、均匀度、球面谐波、目标场、主被动匀场、优化算法、超导磁体、线性规划、非线性规划研究工作,擅长数据搜集与处理、建模仿真、程序编写、仿真设计。 ✅ 专业定制毕设、代码 ✅ 如需沟通交流,点击《获取方式》 (1࿰…...
ARM中断机制深度解析:从硬件原理到实战调试与RTOS应用
1. 项目概述:从一行代码到硬件响应“ARM体系架构处理器的中断程序分析”这个标题,对于很多嵌入式开发者和系统软件工程师来说,就像一把钥匙。它指向了连接软件逻辑与硬件实时响应的核心枢纽。我处理过太多因为中断没玩明白而导致的系统“玄学…...
