第七步 实现打印函数

文章目录

  • 前言
  • 一、如何设计我们的打印函数?
  • 二、实践检验!


查看系列文章点这里: 操作系统真象还原

前言

  现在接力棒意见交到内核手中啦,只不过我们的内核现在可谓是一穷二白啥都没有,为了让我们设计的内核能被看见被使用,那首先就得有在屏幕输出信息的能力。因此,我们就先来实现打印函数,它的功能就是在屏幕上输出字符信息。


一、如何设计我们的打印函数?

  首先,我们知道在屏幕上输出信息,其实都是操控显卡。我们在前面的步骤当中,都是通过直接操控显存来往屏幕上输出信息的,虽然这个方法简单,但是不用我说,大家也能知道这个方法局限性很大。

  所以,我们需要实现一个函数,使其满足我们自己设计的操作系统的需要。也就是要实现字符串、数字以及基本的控制字符(回车。换行、退格)的输,并且要在一行输出满了的情况下,自己换行输出。

  具体怎么实现呢?显卡除了显存,还有端口,也就是显卡用的寄存器。我们需要通过端口来控制显卡的一些行为(设置光标位置等等)或者获取一些信息(光标位置等等),进而将我们要输出的内容卸载正确的显存位置。是不是听起来很简单,就是换了种方式写显存而已,没什么难的。

  在实现的思路上,我们先实现单个字符的输出,然后再实现字符串和数字的输出,相信大家能理解为什么这样做,接下来我们就来看看具体怎么实现。

二、实践检验!

  现在我们的 code 目录下新建 lib 文件夹,存储我们自己实现的函数,在 lib 下在建 kernel 文件夹,存储内核将来会用到的函数,我们的打印函数就放在其中。

  在开始之前,我们先给C语言中的数据类型起个别名,方便我们能清楚知道我们定义的变量是多少位的,是有符号还是无符号的。在 lib 下新建 stdint.h 文件,其内容如下:

#ifndef _LIB_STDINT_H
#define _LIB_STDINT_Htypedef signed char          int8_t;
typedef signed short int     int16_t;
typedef signed int           int32_t;
typedef signed long long int int64_t;typedef unsigned char          uint8_t;
typedef unsigned short         uint16_t;
typedef unsigned int           uint32_t;
typedef unsigned long long int uint64_t;#endif

  print.h

#ifndef __LIB_KERNEL_PRINT_H
#define __LIB_KERNEL_PRINT_H
#include "stdint.h"
void put_char(uint8_t char_in_ascii);
void put_str(char* message);
void put_int(uint32_t num);
#endif

  print.S

[bits 32]
section .data
put_int_buffer dq 0section .text;定义视频段的选择子
TI_GDT equ 0
RPL0 equ 0
SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0; ============================================================
; put_char: 从光标处打印堆栈中的字符
; ============================================================
global put_char
put_char:; ------------------------; 备份32位寄存器(共八个); ------------------------pushad; ------------------------; 为gs安装正确的选择子; ------------------------mov ax, SELECTOR_VIDEOmov gs, ax; -------------------------------; 从显卡寄存器中获取光标位置(16位); -------------------------------; 高8位mov dx, 0x03d4  ;指定索引寄存器mov al, 0x0e    ;指定子功能:获取光标高8位out dx, al mov dx, 0x03d5  ;指定读写数据寄存器(端口)in al, dx       mov ah, al; 低8位mov dx, 0x03d4mov al, 0x0fout dx, almov dx, 0x03d5in al, dx; 寄存器 bx 存储着光标的(线性)坐标mov bx, ax; -------------------------------------------------------; 检索要打印的字符,32字节的寄存器空间 + 4字节的调用者返回地址; -------------------------------------------------------mov ecx, [esp+36]; -------------------------------------------------------; 处理要打印的字符; 1.对控制字符进行特殊处理,并打印普通可见字符; 2.如果可见字符超出屏幕(cmp bx, 2000),则添加回车处理操作; -------------------------------------------------------cmp cl, 0xdjz .is_carriage_return ; 回车符cmp cl, 0xa          jz .is_line_feed       ; 换行符cmp cl, 0x8jz .is_backspace       ; 退格键jmp .put_other         ; 其它字符; ------------------------
; 处理退格键
; ------------------------
.is_backspace:; 光标坐标减1,相当于光标向左移动dec bx; 光标是字符的坐标,而一个字符占据 2 字节,所以通过光标向视频内存写入数据时,光标需要乘以 2shl bx, 1mov byte [gs:bx], 0x20  ; 指定字符:空格->覆盖原有字符实现擦除inc bx                  ; 加一指向设置属性的地址mov byte [gs:bx], 0x07  ; 指定属性:黑屏白字; 恢复 bx 值,使其重新为光标位置,而不是光标的内存地址shr bx, 1jmp .set_cursor  ;处理光标的位置; ------------------------
; 处理可见字符
; ------------------------
.put_other:shl bx, 1mov [gs:bx], clinc bxmov byte [gs:bx], 0x07shr bx, 1inc bx          ; 将光标指向下一个待打印的位置cmp bx, 2000jl .set_cursor  ; 若光标值小于2000表示还可以显示,否则执行换行处理; -----------------------------------
; 处理回车符和换行符(统一看做回车换行符)
; -----------------------------------
; "\n" --- 将光标移动到下一行的开头
.is_line_feed:
; "\r" --- 将光标移动到同一行的开头
.is_carriage_return:; 16位除法,求模80的结果xor dx, dxmov ax, bxmov si, 80div si; 减去余数,即回到行首sub bx, dx; 加80,即到了下一行add bx, 80; 如果光标超出了屏幕范围(即指令jl的结果为假),则滚动屏幕cmp bx, 2000jl .set_cursor; ------------------------
; 滚屏
; ------------------------
.roll_screen:; 将第 1 行到第 24 行的内容覆盖到第 0 行到第 23 行cld                 ; 将eflags寄存器中方向标志位DF清0mov ecx, 960        ; ((2000-80)*2)/4=960mov esi, 0xc00b80a0 ; 第 0 行开始mov edi, 0xc00b8000 ; 第 1 行开始rep movsd           ; 每次复制 4 字节; 清除当前屏幕的最后一行,填充为白空格(0x0720)mov ebx, 3840 ; 1920*2 = 3840mov ecx, 80
.cls:mov word [gs:ebx], 0x0720add ebx, 2loop .cls; 更新光标位置信息->指向最后一行的开头mov bx, 1920; ------------------------
; 更新图形卡中的光标位置信息
; ------------------------
.set_cursor:; 设置高 8 位mov dx, 0x03d4mov al, 0x0eout dx, almov dx, 0x03d5mov al, bhout dx, al; 设置低 8 位mov dx, 0x03d4mov al, 0x0fout dx, almov dx, 0x03d5mov al, blout dx, al.put_char_end:popad  ; 恢复之前压入栈的 8 个寄存器ret    ; 执行完函数流程,返回; ============================================================
; put_str: 通过 put_char 来打印以 0 字符结尾的字符串
; ============================================================
global put_str
put_str:; -----------------------------------; 备份寄存器,准备参数(字符串的起始地址); -----------------------------------push ebxpush ecxxor ecx, ecx       ; 清空mov ebx, [esp+12]  ; 备份寄存器的8个字节 + 调用者返回地址的4个字节; 通过调用 put_char 实现该函数
.goon:mov cl, [ebx]cmp cl, 0jz .str_over  ; 判断是不是到了结尾push ecxcall put_charadd esp, 4inc ebxloop .goon.str_over:pop ecxpop ebxret; ====================================================================
; put_int: 打印栈中的数字(put_int_buffer 用作缓冲区,用于存储转换后的结果)
; ====================================================================
global put_int
put_int:pushadmov ebp, esp      ; 获取esp的值,通过esp来访问栈mov eax, [ebp+36] ; 32字节的寄存器 + 4字节的调用者返回地址mov edx, eaxmov edi, 7        ; 指定 put_int_buffer 中初始的偏移量mov ecx, 8        ; 待计算的位数(32/4=8)mov ebx, put_int_buffer  ; EBX代表缓冲区的基地址; ------------------------------------------
; 将字符(32位数中的每4位)转换为相应的ASCII值
; ------------------------------------------
.16based_4bits:and edx, 0x0000000Fcmp edx, 9jg .is_A2Fadd edx, '0'jmp .store
.is_A2F:sub edx, 10add edx, 'A'
.store:mov [ebx+edi], dldec edishr eax, 4mov edx, eaxloop .16based_4bits; ------------------------
; 去掉多余的 0
; ------------------------
.ready_to_print:inc edi ; 使 edi 重新指向最高位.skip_prefix_0:; 如果所有位都是 0,做特殊处理cmp edi, 8je .full0.detect_prefix_0:mov cl, [put_int_buffer+edi]inc edicmp cl, '0'je  .skip_prefix_0dec edijmp .put_each_num.full0:mov cl, '0'.put_each_num:push ecxcall put_charadd esp, 4inc edimov cl, [put_int_buffer+edi]cmp edi, 8jl .put_each_numpopadret

  main.c

#include "print.h"
int main(void){while(1){put_str("I am kernel!");put_char('\n');put_int(0x66666);put_char('\n');}return 0;
}

  结果如下所示:

在这里插入图片描述


  持续更新中!

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

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

相关文章

数据防泄露解决方案分享

在当今高度数字化和互联的商业环境中&#xff0c;数据防泄密已成为企业保护财产、维护客户隐私和遵守合规要求的重要一环。数据防泄密不仅关乎企业的经济利益&#xff0c;更涉及用户个人信息安全、商业机密保护以及国家安全等核心问题。能做好数据防泄露&#xff0c;对于提升企…

启动docker报错:Failed to listen on Docker Socket for the API.

说明&#xff1a; 1、安装部署docker完成后&#xff0c;启动docker报错&#xff1a;Failed to listen on Docker Socket for the API&#xff0c;如下图所示&#xff1a; 2、将SocketGroupdocker更改成&#xff1a;SocketGrouproot即可 一、解决方法&#xff1a; 1、执行命令…

舵机(结构,原理,控制方法)

介绍 舵机&#xff0c;全称为伺服马达&#xff08;Servo Motor&#xff09;&#xff0c;是一种能够精确控制角度或位置的电动机。它广泛应用于模型制作、机器人技术、工业自动化等领域。舵机通过接收控制信号&#xff0c;将其转化为机械运动&#xff0c;从而实现精确的控制。 …

代码随想录算法训练营第三天| 203.移除链表元素、 707.设计链表、 206.反转链表

203.移除链表元素 题目链接&#xff1a; 203.移除链表元素 文档讲解&#xff1a;代码随想录 状态&#xff1a;没做出来&#xff0c;做题的时候定义了一个cur指针跳过了目标val遍历了一遍链表&#xff0c;实际上并没有删除该删的节点。 错误代码&#xff1a; public ListNode re…

文档解析新纪元:TextIn产品体验与解决难题的深度剖析

前言 在数字化浪潮席卷各行各业的今天&#xff0c;作为一名数据分析师&#xff0c;每天我都需要处理和分析大量的文档。然而&#xff0c;传统的文档解析工具在面对我的专业需求时&#xff0c;往往显得力不从心。 我的工作常常涉及到各种格式的文档&#xff0c;包括PDF、Word、…

在洁净实验室设计装修中怎么选择合适实验室家具?

在现代科学研究和技术开发中&#xff0c;洁净实验室装修设计成为了确保实验准确性和安全性的重要因素。洁净实验室需要提供一个无尘、无菌、受控的环境&#xff0c;而在洁净实验室装修设计这个过程中&#xff0c;如何选择合适的实验室家具就显得尤为重要&#xff0c;因为它直接…

Web前端一套全部清晰 ⑨ day5 CSS.4 标准流、浮动、Flex布局

我走我的路&#xff0c;有人拦也走&#xff0c;没人陪也走 —— 24.5.24 一、标准流 标准流也叫文档流&#xff0c;指的是标签在页面中默认的排布规则&#xff0c;例如:块元素独占一行&#xff0c;行内元素可以一行显示多个。 二、浮动 作用: 让块级元素水平排列。 属性名:floa…

练习题(2024/5/22)

1N 皇后 II n 皇后问题 研究的是如何将 n 个皇后放置在 n n 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#xff0c;返回 n 皇后问题 不同的解决方案的数量。 示例 1&#xff1a; 输入&#xff1a;n 4 输出&#xff1a;2 解释&#xff1a;如上…

民国漫画杂志《时代漫画》第18期.PDF

时代漫画18.PDF: https://url03.ctfile.com/f/1779803-1248612707-27e56b?p9586 (访问密码: 9586) 《时代漫画》的杂志在1934年诞生了&#xff0c;截止1937年6月战争来临被迫停刊共发行了39期。 ps:资源来源网络&#xff01;

春秋CVE-2022-23906

简介 CMS Made Simple v2.2.15 被发现包含通过上传图片功能的远程命令执行 (RCE) 漏洞。此漏洞通过精心制作的图像文件被利用。 正文 1.进入靶场2.进入登录界面&#xff0c;弱口令admin/123456 3.进入后台&#xff0c;文件上传点 4.上传一句话木马图片 5.复制图片&#xf…

细胞冻存——让你的细胞“长生不老”

《星际穿越》电影中提到漫长的太空旅程中&#xff0c;宇航员可以进入休眠水床休眠&#xff0c;并自行设定唤醒时间。在《异形》《深空失忆》《三体》等科幻作品中&#xff0c;都出现此类技术。《三体》中&#xff0c;休眠后来成为人类最普遍的一项技术。技术上的人类低温休眠&a…

OpenCV:入门(五)

图像梯度 图像梯度计算的是图像变化的速度。对于图像的边缘部分&#xff0c;其灰度值变化较大&#xff0c;梯度值也 较大&#xff1b;相反&#xff0c;对于图像中比较平滑的部分&#xff0c;其灰度值变化较小&#xff0c;相应的梯度值也较小。一般情 况下&#xff0c;图像梯度计…

中文信息期刊投稿邮箱

《中文信息》杂志是国家新闻出版总署批准的国家级刊物&#xff08;月刊&#xff09;&#xff0c;国内外公开发行&#xff0c;大十六开印刷。本刊主要反映我国中文信息处理的学术水平&#xff0c;重点刊登科技、经济、教育等领域的基础理论、科研与应用技术的学术论文&#xff0…

第2天 搭建安全拓展_小迪网络安全笔记

1.常见搭建平台脚本使用: 例如 phpstudy IIS Nginx(俗称中间件): 什么是中间件: 中间件是介于应用系统和系统软件之间的一类软件&#xff0c;它使用系统软件所提供的基础服务&#xff08;功能&#xff09;&#xff0c;衔接网络上应用系统的各个部分或不同的应用&#…

vue2引入brand.vue和brand-add-or-update.vue后重启项目报错解决方案

最近在用粒谷商城项目练手&#xff0c;学习到P59时引入品牌两个vue文件&#xff0c;重启(npm run dev)项目报错: ERROR Failed to compile with 2 errors 12:11:59Th…

【论文阅读】Rank-DETR(NIPS‘23)

paper:https://arxiv.org/abs/2310.08854 code:https://github.com/LeapLabTHU/Rank-DETR

微信小程序视频怎么保存到本地

你是否遇到过在微信小程序中发现了精彩的视频&#xff0c;却不知道如何将其保存到本地的困扰&#xff1f;别担心&#xff0c;这篇文章将为您揭示2024年最新的保存方法&#xff0c;让您轻松下载和保留这些珍贵的视频内容。不管您是使用安卓设备还是苹果设备&#xff0c;我们都为…

学生手机管理方案

咱们现在的学生&#xff0c;手机几乎成了标配。所以问题就来了&#xff0c;怎么管理这些手机&#xff0c;让手机在课堂上不成为学习的干扰&#xff1f; 先得搞明白&#xff0c;手机在学生手里上该扮演什么角色。手机确实能帮学生查资料、交流学习&#xff0c;甚至写作业&#x…

Log360:护航安全,远离暗网风险

暗网有时候就像是一个神秘的地下世界&#xff0c;是互联网的隐蔽角落&#xff0c;没有任何规则。这是一个被盗数据交易、网络犯罪分子策划下一步攻击的地方。但仅仅因为它黑暗&#xff0c;不意味着你要对潜在的威胁视而不见。 暗网 这就是ManageEngine Log360的用武之地&…

【Oracle篇】rman工具实用指南:常用命令详解与实践(第二篇,总共八篇)

&#x1f4ab;《博主介绍》&#xff1a;✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ &#x1f4ab;《擅长领域》&#xff1a;✌️擅长Oracle、MySQL、SQLserver、阿里云AnalyticDB for MySQL(分布式数据仓库)、Linux&#xff0c;也在扩展大数据方向的知识面✌️…