【读书笔记-《30天自制操作系统》-19】Day20

news2024/9/24 3:27:11

本篇的内容围绕系统调用展开。为了让应用程序能够调用操作系统功能,引入了系统调用以及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:
	HLT
	JMP	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	1
		AND		EAX,0xff	; EAX高位置0,EAC置为存入字符编码的状态
		PUSH	EAX
		PUSH	DWORD [0x0fec]	;0x0fec中读取cons地址并PUSH
		CALL	_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	0xbe3

fin:
	HLT
	JMP	fin

这样就可以运行应用程序了。在命令行中输入hlt并回车,却发现QEMU出错关闭了。

这是什么原因呢?

原来应用程序在对API执行CALL指令时需要加上段号。我们给应用程序设置的段号为1003,而操作系统的段位2,不能使用普通的CALL,而应使用fat-CALL。与far-JMP指令一样,fat-CALL指令需要指定段号和偏移量。相应地,RET指令也需要使用RETF来代替。

[BITS 32]
	MOV AL,'A'
	CALL	2*8:0xbe3

fin:
	HLT
	JMP	fin
_asm_cons_putchar:
	PUSH	1
	AND		EAX,0xff	; EAX高位置0,EAC置为存入字符编码的状态
	PUSH	EAX
	PUSH	DWORD [0x0fec]	;0x0fec中读取cons地址并PUSH
	CALL	_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, cs
	RET

改写调用应用程序的部分如下:

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代替JMP
		memman_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:0xbe8
	RETF

这样运行结束后就可以退回到操作系统了。还可以显示一个简单的字符串:

[BITS 32]
		MOV		AL,'h'
		CALL    2*8:0xbe8
		MOV		AL,'e'
		CALL    2*8:0xbe8
		MOV		AL,'l'
		CALL    2*8:0xbe8
		MOV		AL,'l'
		CALL    2*8:0xbe8
		MOV		AL,'o'
		CALL    2*8:0xbe8
		RETF

在这里插入图片描述
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		0x40
		MOV		AL,'e'
		INT		0x40
		MOV		AL,'l'
		INT		0x40
		MOV		AL,'l'
		INT		0x40
		MOV		AL,'o'
		INT		0x40
		RETF

使用INT指令来调用asm_cons_putchar函数,会被视为中断处理,会自动调用CLI来禁止中断请求,而实际上这里并非中断,我们应该允许此时接收中断,因此在开头使用STI允许中断请求;返回指令也需要使用IRETD:

_asm_cons_putchar:
		STI
		PUSH	1
		AND		EAX,0xff	
		PUSH	EAX
		PUSH	DWORD [0x0fec]	
		CALL	_cons_putchar
		ADD		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:
		STI
		PUSHAD	; 用于保存寄存器的PUSH
		PUSHAD	; 用于向hrb_api传值的PUSH
		CALL	_hrb_api
		ADD		ESP,32
		POPAD
		IRETD

处理函数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,2
		MOV		EBX,msg
		INT		0x40
		RETF
msg:
		DB	"hello",0

运行结果如下:
在这里插入图片描述
并没有显示出任何内容?这是什么原因呢?下一篇我们来继续解决,敬请期待。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2126666.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【CanMV K230 AI视觉】人脸姿态(脸部朝向)

【CanMV K230 AI视觉】人脸姿态&#xff08;脸部朝向&#xff09; 人脸姿态&#xff08;脸部朝向&#xff09; &#xff08;动态测试效果可以去下面网站自己看。&#xff09; B站视频链接&#xff1a;已做成合集 抖音链接&#xff1a;已做成合集 人脸姿态&#xff08;脸部朝向…

基于yolov8的工程车辆挖掘机叉车卡车检测系统python源码+onnx模型+评估指标曲线+精美GUI界面

【算法介绍】 基于YOLOv8的工程车辆&#xff08;如挖掘机、叉车、卡车&#xff09;检测系统是一种利用先进深度学习技术的智能监控系统。该系统集成了YOLOv8算法&#xff0c;该算法以其高效的检测速度和准确的识别能力著称&#xff0c;特别适用于实时视频分析场景。 该系统通…

C2 Magic 附工具下载,供学习使用

最近&#xff0c;我们进行了一次安全演练&#xff0c;想要模拟一些复杂的攻击场景并测试我们的防御能力。这时&#xff0c;我想到了一款开源工具&#xff0c;它在处理抗沙箱后门启动和隐蔽ShellCode调用方面表现得非常出色。这款工具的设计理念是为了帮助安全团队更好地应对高级…

使用QT界面运行roslaunch,roslaunch,roscore等

QT通过界面运行rosrun,roslaunch,roscore等 QT 运行roslaunch加入ui界面修改cmakelist运行 使用qt界面运行rosrun&#xff0c;roscore,roslaunch等方法一方法二方法三 QT 运行roslaunch 首先需要使用QT安装好ROS插件&#xff0c;并且配置好环境&#xff0c;这个在之前的文章已…

nvm ls-remote: N/A

背景&#xff1a; 项目因为node版本问题运行失败&#xff0c;在彻底删除node后再重新安装 问题描述&#xff1a; 原因分析&#xff1a; 可能是因为终端不能获取镜像包 解决办法&#xff1a; 【方法一】 输入&#xff1a; step1. export NVM_NODEJS_ORG_MIRRORIndex of …

数据处理与统计分析篇-day01-Linux基础与环境搭建

day01-Linux基础 计算机简介 概述 电子计算机, 电脑, PC, Computer, 就是由 软件 硬件组成的 电子设备. 组成 计算机硬件 CPU(运算器, 控制器) 存储器(内存, 外存) 输入设备 输出设备 计算机软件 系统软件: 充当 用户 和 计算机硬件之间的 桥梁的. PC端: windows, Linu…

Elasticsearch 使用误区之五——单次请求获取大量数据

在使用 Elasticsearch 进行数据查询时&#xff0c;很多开发者、读者会遇到这样的问题&#xff1a;一次性检索大量数据&#xff0c;导致查询速度缓慢、网络延迟增加&#xff0c;甚至影响系统的整体性能。 单次获取过多数据不仅增加了网络传输的负担&#xff0c;还会使查询过程复…

Vue 中的 Web Workers:提升性能与流畅度

大家可能都听到过 Web Workers&#xff0c;那究竟如何使用呢&#xff1f;可以往下了解一下。 1. 什么是 Web Workers&#xff1f; Web Workers 是现代浏览器提供的一种机制&#xff0c;允许我们在主线程之外运行 JavaScript 脚本&#xff0c;避免阻塞 UI 渲染和用户交互操作。…

verilog vscode 与AI 插件

Verilog 轻量化开发环境 背景 笔者常用的开发环境 VIAVDO, 体积巨大&#xff0c;自带编辑器除了linting 能用&#xff0c;编辑器几乎不能用&#xff0c;仿真界面很友好&#xff0c;但是速度比较慢。Sublime Text, 非常好用的编辑器&#xff0c;各种插件使用verilog 非常方便…

深入理解Java虚拟机:Jvm总结-Java内存区域与内存溢出异常

第二章 Java内存区域与内存溢出异常 2.1 意义 对于C、C程序开发来说&#xff0c;程序员需要维护每一个对象从开始到终结。Java的虚拟自动内存管理机制&#xff0c;让java程序员不需要手写delete或者free代码&#xff0c;不容易出现内存泄漏和内存溢出问题&#xff0c;但是如果…

CSGHub携手Nvidia NIM、阿里计算巢打造企业级私有化部署解决方案

强强联合 人工智能与大数据的迅速发展&#xff0c;大模型的推理应用和资产管理已成为企业数字化转型的重要组成部分&#xff0c;企业正寻求高效、安全的AI模型部署解决方案。为应对日益增长的计算需求和复杂的数据管理挑战&#xff0c;CSGHub、Nvidia和阿里云计算巢强强联手&a…

Frozen CLIP: A Strong Backbone for Weakly Supervised Semantic Segmentation

摘要 弱监督语义分割在图像级标签方面取得了巨大的成就。最近的几种方法使用CLIP模型生成伪标签来训练单个分割模型&#xff0c;而没有尝试将CLIP模型作为主干&#xff0c;直接分割具有图像级标签的对象。在本文中&#xff0c;我们提出了 WeCLIP&#xff0c;一个基于 CLIP 的单…

【笔记】自动驾驶预测与决策规划_Part1_自动驾驶决策规划简介

自动驾驶决策规划简介 0、前言1、自动驾驶概述1.1 预测&#xff08;Prediction&#xff09;1.2 决策&#xff08;Decision Making&#xff09;1.3 规划&#xff08;Planning&#xff09; 2、自动驾驶历史和背景3、自动驾驶级别和分类4、预测决策规划的重要性4.1 预测的重要性4.…

环境搭建---部署rabbitmq集群

rabbitmq下载&#xff1a;https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.8.34/rabbitmq-server-generic-unix-3.8.34.tar.xz erlang下载&#xff1a;https://github.com/erlang/otp/releases/download/OTP-24.3.4.1/otp_src_24.3.4.1.tar.gz 配置主机名 …

MySQL原理之UUID主键分析,插入或更新语法分析

文章目录 1 MySQL不能用UUID做主键1.1 前言1.2 mysql和程序实例1.2.1 准备工作1.2.2 开始测试1.2.3 程序写入结果1.2.4 效率测试结果 1.3 使用uuid和自增id的索引结构对比1.3.1 自增id1.3.2 uuid 1.4 自增id缺点1.5 雪花算法 2 插入或更新2.1 on duplicate key2.1.1 定义2.1.2 …

git版本问题Your branch is behind ‘origin/dev‘by 2 commits,

git版本问题 一个不小心点击了版本的修改&#xff0c;于是就进入了翻滚中&#xff0c;回不来了 遇事还是不要慌&#xff0c;出现这个问题&#xff0c;如果那些你不需要&#xff0c;只是需要回到某一个版本&#xff0c;那么就是需要 git reset --hard origin/master 上面这就…

Vue3入门 - 登录功能开发(Vue3+ts+Pinia+Element Plus)

Vue3中实现登录功能&#xff0c;通常涉及到创建一个表单&#xff0c;用户输入用户名和密码&#xff0c;然后将信息发送到后端进行验证&#xff0c;得到响应结果后作出相应操作。 一、创建项目 这里他用pnpm进行项目的创建的&#xff0c;所以需要事先全局安装pnpm&#xff08;在…

神经网络的非线性激活

文章目录 一、神经网络的非线性激活是什么二、非线性激活常用函数三、非线性激活的实际演示 一、神经网络的非线性激活是什么 神经网络的非线性激活函数的主要作用是引入非线性变换&#xff0c;从而使网络能够学习和逼近复杂的函数关系。在神经网络中&#xff0c;线性变换&…

[产品管理-4]:NPDP新产品开发 - 2 - 战略 - 制定企业经营战略目标的结构化方法与工具

目录 一、SWOT分析工具 1、SWOT分析工具概述 2、SWOT分析与企业战略目标制定的关系 3、SWOT分析在企业战略目标制定中的应用实例 4、SWOT分析的改进与应用建议 二、P E S T L E 分 析&#xff1a;外部环境分析 2.1 概述 1. 政治因素&#xff08;Political&#xff09; …

COCOS:(飞机大战08)子弹和飞机添加碰撞器和刚体

做两个物体的碰撞有2种方式&#xff1a;碰撞检测和触发检测 这里子弹不能和飞机使用碰撞检测&#xff0c;因为会影响到敌机的运动&#xff0c;所有选择使用触发检测 从预制体Prefabs文件中&#xff0c;将子弹Bullet1和Bullet2拖到Canvas下 选中子弹&#xff0c;添加组件&#…