第七步 实现打印函数

文章目录

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


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

前言

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


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

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

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

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

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

二、实践检验!

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

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

#ifndef _LIB_STDINT_H
#define _LIB_STDINT_H

typedef 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 0

section .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_VIDEO
    mov 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, 0x03d4
    mov al, 0x0f
    out dx, al
    mov dx, 0x03d5
    in al, dx

    ; 寄存器 bx 存储着光标的(线性)坐标
    mov bx, ax

    ; -------------------------------------------------------
    ; 检索要打印的字符,32字节的寄存器空间 + 4字节的调用者返回地址
    ; -------------------------------------------------------
    mov ecx, [esp+36]

    ; -------------------------------------------------------
    ; 处理要打印的字符
    ; 1.对控制字符进行特殊处理,并打印普通可见字符
    ; 2.如果可见字符超出屏幕(cmp bx, 2000),则添加回车处理操作
    ; -------------------------------------------------------
    cmp cl, 0xd
    jz .is_carriage_return ; 回车符
    cmp cl, 0xa          
    jz .is_line_feed       ; 换行符
    cmp cl, 0x8
    jz .is_backspace       ; 退格键
    jmp .put_other         ; 其它字符

; ------------------------
; 处理退格键
; ------------------------
.is_backspace:
    ; 光标坐标减1,相当于光标向左移动
    dec bx
    ; 光标是字符的坐标,而一个字符占据 2 字节,所以通过光标向视频内存写入数据时,光标需要乘以 2
    shl bx, 1

    mov byte [gs:bx], 0x20  ; 指定字符:空格->覆盖原有字符实现擦除
    inc bx                  ; 加一指向设置属性的地址
    mov byte [gs:bx], 0x07  ; 指定属性:黑屏白字

    ; 恢复 bx 值,使其重新为光标位置,而不是光标的内存地址
    shr bx, 1
    jmp .set_cursor  ;处理光标的位置

; ------------------------
; 处理可见字符
; ------------------------
.put_other:
    shl bx, 1
    mov [gs:bx], cl
    inc bx
    mov byte [gs:bx], 0x07
    shr bx, 1
    inc bx          ; 将光标指向下一个待打印的位置
    cmp bx, 2000
    jl .set_cursor  ; 若光标值小于2000表示还可以显示,否则执行换行处理

; -----------------------------------
; 处理回车符和换行符(统一看做回车换行符)
; -----------------------------------
; "\n" --- 将光标移动到下一行的开头
.is_line_feed:
; "\r" --- 将光标移动到同一行的开头
.is_carriage_return:
    ; 16位除法,求模80的结果
    xor dx, dx
    mov ax, bx
    mov si, 80
    div si
    ; 减去余数,即回到行首
    sub bx, dx
    ; 加80,即到了下一行
    add bx, 80
    ; 如果光标超出了屏幕范围(即指令jl的结果为假),则滚动屏幕
    cmp bx, 2000
    jl .set_cursor

; ------------------------
; 滚屏
; ------------------------
.roll_screen:
    ; 将第 1 行到第 24 行的内容覆盖到第 0 行到第 23 行
    cld                 ; 将eflags寄存器中方向标志位DF清0
    mov ecx, 960        ; ((2000-80)*2)/4=960
    mov esi, 0xc00b80a0 ; 第 0 行开始
    mov edi, 0xc00b8000 ; 第 1 行开始
    rep movsd           ; 每次复制 4 字节

    ; 清除当前屏幕的最后一行,填充为白空格(0x0720)
    mov ebx, 3840 ; 1920*2 = 3840
    mov ecx, 80
.cls:
    mov word [gs:ebx], 0x0720
    add ebx, 2
    loop .cls
    ; 更新光标位置信息->指向最后一行的开头
    mov bx, 1920

; ------------------------
; 更新图形卡中的光标位置信息
; ------------------------
.set_cursor:
    ; 设置高 8 位
    mov dx, 0x03d4
    mov al, 0x0e
    out dx, al

    mov dx, 0x03d5
    mov al, bh
    out dx, al

    ; 设置低 8 位
    mov dx, 0x03d4
    mov al, 0x0f
    out dx, al

    mov dx, 0x03d5
    mov al, bl
    out dx, al

.put_char_end:
    popad  ; 恢复之前压入栈的 8 个寄存器
    ret    ; 执行完函数流程,返回

; ============================================================
; put_str: 通过 put_char 来打印以 0 字符结尾的字符串
; ============================================================
global put_str
put_str:
    ; -----------------------------------
    ; 备份寄存器,准备参数(字符串的起始地址)
    ; -----------------------------------
    push ebx
    push ecx
    xor ecx, ecx       ; 清空
    mov ebx, [esp+12]  ; 备份寄存器的8个字节 + 调用者返回地址的4个字节

; 通过调用 put_char 实现该函数
.goon:
    mov cl, [ebx]
    cmp cl, 0
    jz .str_over  ; 判断是不是到了结尾

    push ecx
    call put_char
    add esp, 4
    inc ebx
    loop .goon

.str_over:
    pop ecx
    pop ebx
    ret


; ====================================================================
; put_int: 打印栈中的数字(put_int_buffer 用作缓冲区,用于存储转换后的结果)
; ====================================================================
global put_int
put_int:
    pushad
    mov ebp, esp      ; 获取esp的值,通过esp来访问栈
    mov eax, [ebp+36] ; 32字节的寄存器 + 4字节的调用者返回地址
    mov edx, eax

    mov edi, 7        ; 指定 put_int_buffer 中初始的偏移量
    mov ecx, 8        ; 待计算的位数(32/4=8)
    mov ebx, put_int_buffer  ; EBX代表缓冲区的基地址

; ------------------------------------------
; 将字符(32位数中的每4位)转换为相应的ASCII值
; ------------------------------------------
.16based_4bits:
    and edx, 0x0000000F
    cmp edx, 9
    jg .is_A2F
    add edx, '0'
    jmp .store
.is_A2F:
    sub edx, 10
    add edx, 'A'
.store:
    mov [ebx+edi], dl
    dec edi
    shr eax, 4
    mov edx, eax
    loop .16based_4bits

; ------------------------
; 去掉多余的 0
; ------------------------
.ready_to_print:
    inc edi ; 使 edi 重新指向最高位

.skip_prefix_0:
    ; 如果所有位都是 0,做特殊处理
    cmp edi, 8
    je .full0

.detect_prefix_0:
    mov cl, [put_int_buffer+edi]
    inc edi
    cmp cl, '0'
    je  .skip_prefix_0
    dec edi
    jmp .put_each_num

.full0:
    mov cl, '0'

.put_each_num:
    push ecx
    call put_char
    add esp, 4
    inc edi
    mov cl, [put_int_buffer+edi]
    cmp edi, 8
    jl .put_each_num
    popad
    ret

  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;
}

  结果如下所示:

在这里插入图片描述


  持续更新中!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

uniapp微信小程序在ios端返回不显示弹窗的bug解决

这个问题其实是因为返回页面的时候弹的太快了导致的解决办法&#xff1a; 其实就是返回页面的弹窗加个延迟就好啦

电脑同时配置两个版本mysql数据库常见问题

1.配置时&#xff0c;要把bin中的mysql.exe和mysqld.exe 改个名字&#xff0c;不然两个版本会重复&#xff0c;当然&#xff0c;在初始化数据库的时候&#xff0c;如果时57版本的&#xff0c;就用mysql57(已经改名的)和mysqld57 代替 mysql 和 mysqld 例如 mysql -u root -p …

golang通过go-aci适配神通数据库

1. go-aci简介 go-aci是神通数据库基于ACI(兼容Oracle的OCI)开发的go语言开发接口&#xff0c;因此运行时需要依赖ACI驱动和ACI库的头文件。支持各种数据类型的读写、支持参数绑定、支持游标范围等操作。 2. Linux部署步骤 2.1. Go安装&#xff1a; 版本&#xff1a;1.9以上…

[数据集][目标检测]吸烟检测数据集VOC+YOLO格式1449张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;1449 标注数量(xml文件个数)&#xff1a;1449 标注数量(txt文件个数)&#xff1a;1449 标注…

深度学习之基于Pytorch框架手写数字识别

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 手写数字识别是数字图像处理领域的一个经典问题&#xff0c;也是深度学习技术的一个常用应用场…

初识C语言——第二十八天

代码练习1&#xff1a; 用函数的方式实现9*9乘法表 void print_table(int n) {int i 0;int j 0;for (i 1; i< n; i){for (j 1; j< i; j){printf("%d*%d%-3d ", i, j, i * j);}printf("\n");}}int main() {int n 0;scanf("%d", &a…

【加密与解密(第四版)】第十六章笔记

第十六章 脱壳技术 16.1 基础知识 壳的加载过程&#xff1a;保存入口参数、获取壳本身需要使用的API地址、解密原程序各个区块的数据、IAT的初始化、重定位项的处理、HOOK API、跳转到程序原入口点 手动脱壳步骤&#xff1a;查找真正的入口点、抓取内存映像文件、重建PE文件&…

pod介绍之 容器分类与重启策略

目录 一 pod 基础概念介绍 1&#xff0c;pod 是什么 2&#xff0c;Pod使用方式 3&#xff0c;如何解决一个pod 多容器通信 4&#xff0c;pod 组成 5&#xff0c; k8s 中的 pod 二 pause容器 1&#xff0c;pause容器 是什么 2&#xff0c;pause容器作用 3&#xff…

Android studio关闭自动更新

Windows下&#xff1a; 左上角file - setting - Appearance & Behavier - system setting - update - 取消勾选

汉明码(海明码)的计算的规则

一.汉明码的由来 1.汉明码&#xff08;Hamming Code&#xff09;&#xff0c;是在电信领域的一种线性调试码&#xff0c;以发明者理查德卫斯里汉明的名字命名。汉明码在传输的消息流中插入验证码&#xff0c;当计算机存储或移动数据时&#xff0c;可能会产生数据位错误&#x…

vivado2020.2创建hls仿真工程实现led闪烁

下载vivado2020.2后会有这个出现在桌面 点击进入创建工程&#xff0c;这里注意不要有前面的\我再复制的时候复制错了导致创建失败 按f光标就会跳转到下一个f开头的函数处&#xff0c;要查找其他函数也同理 生成了一个synthesis summary文件 找到目录下生成的.v文件 an 点…

U-Mail邮件系统为用户提供更加安全的数据保护机制

据外媒报道&#xff0c;近日美国国家安全委员会泄露了其成员的近1万封电子邮件和密码&#xff0c;暴露了政府组织和大公司在内的2000家公司。其中包括美国国家航空航天局和特斯拉等。报道称该漏洞于3月7日被研究人员发现&#xff0c;通过该漏洞攻击者能够访问对web服务器操作至…

等保三级-MySQL 加固

1、身份鉴别 要求&#xff1a;建议身份密码登录&#xff0c;身份标识具有唯一性&#xff0c;身份鉴别信息具有复杂度要求&#xff0c;密码长度最少为8位&#xff0c;密码由数字、字母大小写、特殊符号组成、并设置定期更换&#xff0c;更换时间最长位90天 &#xff08;1&#…

asp.net core接入prometheus

安装prometheus和Grafana 参考之前的文章->安装prometheus和Grafana教程 源代码 dotnet源代码 新建.net core7 web项目 修改Program.cs using Prometheus;namespace PrometheusStu01;public class Program {public static void Main(string[] args){var builder We…

airflow2.7.3 + celery + redis + mysql 安装部署测试

集群环境&#xff1a; ​ 3台 centos 7.9 (dp95、dp96、dp97) python3.8 ​ dp96&#xff1a;mysql8.0.36(mysql8.0离线安装) ​ dp95\dp96\dp97&#xff1a;celery 集群(Celery安装测试) 安装目标&#xff1a; airflow2.7.3 mysql celery redis WebserverSchedulerwo…

Java 商品入库系统 案例

测试类 package 练习.商品入库系统;import java.util.ArrayList; import java.util.Scanner; public class Test {public static final int Enrool 1;public static final int Search 2;public static final int Delect 3;public static final int Exit 4;public static…

交换机部分综合实验

实验要求 1.内网IP地址使用172.16.0.0/16 2.sw1和sW2之间互为备份; 3.VRRP/mstp/vlan/eth-trunk均使用; 4.所有pc均通过DHcP获取Ip地址; 5.ISP只配置IP地址; 6.所有电脑可以正常访问IsP路由器环回 实验拓扑 实验思路 1.给交换机创建vlan&#xff0c;并将接口划入vlan 2.在SW1和…

java实现List对象转geojson文本返回前端

1.业务需求 查询带有经纬度数据的list列表&#xff0c;将其转为geojson格式给前端。 2.GeoJson格式说明 GeoJSON是一种对各种地理数据结构进行编码的格式&#xff0c;基于Javascript对象表示法(JavaScript Object Notation, 简称JSON)的地理空间信息数据交换格式。GeoJSON对…

3D透视图模型转模型变形?---模大狮模型网

3D建模是数字艺术和设计领域中的重要技术&#xff0c;它可以为我们带来丰富多彩的视觉体验和创意表达。在本文中&#xff0c;我们将探讨一个引人注目的话题&#xff1a;3D透视图中模型转换是否会导致变形?通过深入探讨这个问题&#xff0c;我们希望能够帮助您更好地理解在3D建…

131. 面试中关于架构设计都需要了解哪些内容?

文章目录 一、社区系统架构组件概览1. 系统拆分2. CDN、Nginx静态缓存、JVM本地缓存3. Redis缓存4. MQ5. 分库分表6. 读写分离7. ElasticSearch 二、商城系统-亿级商品如何存储三、对账系统-分布式事务一致性四、统计系统-海量计数六、系统设计 - 微软1、需求收集2、顶层设计3、…