第七步 实现打印函数

news2024/11/24 17:44:21

文章目录

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


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

前言

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


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

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

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

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

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

二、实践检验!

  现在我们的 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;
}

  结果如下所示:

在这里插入图片描述


  持续更新中!

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

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

相关文章

fyne网格包裹布局

fyne网格包裹布局 与之前的网格布局一样&#xff0c;网格环绕布局以网格模式创建元素排列。但是&#xff0c;此网格没有固定数量的列&#xff0c;而是为每个单元格使用固定大小&#xff0c;然后将内容流到显示项目所需的行数。 layout.NewGridWrapLayout(size) 您可以使用其中…

Mac维护神器CleanMyMac X成为你的苹果电脑得力助手

在数字化时代&#xff0c;Mac电脑已成为众多用户的首选。然而&#xff0c;随着频繁的使用和数据量的日益增长&#xff0c;许多Mac用户面临着系统杂乱、存储空间不足以及隐私保护等问题。幸运的是&#xff0c;"CleanMyMac X"这款优化和清理工具应运而生&#xff0c;它…

「网络流浅谈」最大流的应用

更好的阅读体验 二分图匹配 考虑如何将二分图匹配问题&#xff0c;转化为流网络。设置 1 1 1 个汇点和源点&#xff0c;从源点向二分图一侧的每一个点连边&#xff0c;从另一侧向汇点连边&#xff0c;边权均为 1 1 1&#xff0c;二分图中的边也全部加入&#xff0c;权值设为…

【Linux取经路】基于信号量和环形队列的生产消费者模型

文章目录 一、POSIX 信号量二、POSIX 信号量的接口2.1 sem_init——初始化信号量2.2 sem_destroy——销毁信号量2.3 sem_wait——等待信号量2.4 sem_post——发布信号量 三、基于环形队列的生产消费者模型3.1 单生产单消费模型3.2 多生产多消费模型3.3 基于任务的多生产多消费模…

构建健壮的机器学习大数据平台:任务实现与数据治理的关键

随着数据驱动决策成为现代企业的核心&#xff0c;构建安全、可靠且可扩展的大数据平台变得至关重要。这样的平台不仅需要支持复杂的机器学习任务&#xff0c;还需要在数据质量、合规性和分发方面提供严格的控制。本文旨在探讨构建大型企业机器学习大数据平台时需要考虑的关键要…

项目如何有效做资源管理?易趋项目管理软件让资源管理可视化

在项目管理的过程中&#xff0c;有效的资源管理能够确保资源得到合理的分配和使用&#xff0c;避免资源的浪费和冗余&#xff0c;进而提高整体工作效率、确保项目的成功&#xff1b;同时降低组织的运营成本。 但在项目推进过程中&#xff0c;项目经理总会面临各种资源管理的难…

基于Tensorflow卷积神经网络人脸识别公寓人员进出管理系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 随着科技的快速发展和智能化水平的提高&#xff0c;公寓管理面临着越来越多的挑战。传统的公寓…

HTML静态网页成品作业(HTML+CSS)——我的家乡云南保山介绍网页(3个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有3个页面。 二、作品演示 三、代…

轻松同步:将照片从三星手机传输到iPad的简便方法

概括 想要在新 iPad 上查看三星照片吗&#xff1f;但是&#xff0c;如果您不知道如何将照片从三星手机传输到 iPad&#xff0c;则无法在 iPad 上查看图片。为此&#xff0c;本文分享了 7 个有用的方法&#xff0c;以便您可以使用它们在不同操作系统之间轻松发送照片。现在&…

leetcode-盛水最多的容器-109

题目要求 思路 1.正常用双循环外循环i从0开始&#xff0c;内循环从height.size()-1开始去计算每一个值是可以的&#xff0c;但是因为数据量太大&#xff0c;会超时。 2.考虑到超时&#xff0c;需要优化一些&#xff0c;比如第一个选下标1&#xff0c;第二个选下标3和第一个选下…

【笔记】从零开始做一个精灵龙女-素模阶段

事前准备 1.在ps标记好位置先&#xff0c;斜方肌&#xff0c;腰线&#xff0c;耻骨&#xff0c;膝盖&#xff0c;脚 2.导入素模&#xff0c;对好位置 软选择 1.原画上半身很短&#xff0c;所以这里把上半身做的也短一些 选择上半身的点-软选择-衰减调整-箭头调整 如果要调整…

mysql数据库innodb体系结构(一、内存结构 与二、物理存储结构)

文章目录 InnoDB存储引擎结构图innoDB体系结构一、内存结构1.Buffer Pool2.Change Pool3.Log Buffer 二、物理存储结构1.系统表空间2.独立表空间3.Redo日志1、redo 日志 4.Undo日志1、undo 日志 回滚段中的UNDO日志分为两种&#xff1a;UNDO 日志存储结构 InnoDB存储引擎结构图…

Flat Ads获广东电视台报道!CEO林啸:助力更多企业实现业务全球化增长

近日,在广州举行的第四届全球产品与增长展会(PAGC2024)上,Flat Ads凭借其卓越的一站式全球化营销和创新的变现方案大放异彩,不仅吸引了众多业界目光,同时也在展会上斩获了备受瞩目的“金帆奖”,展现了其在全球化营销推广领域的卓越实力和专业服务。 在大会现场,Flat Ads的CEO林…

差分约束题解

目录 注意点&#xff1a; 思路&#xff1a; SPFA和Dij的不同点&#xff1a; Dij: SPFA: AC代码&#xff1a; 扩展&#xff1a; 题目链接&#xff1a;【模板】差分约束 - 洛谷 注意点&#xff1a; 注意这一题不能用Dij&#xff0c;只能用SPFA 因为这样子才可以得出这个不…

【简单介绍下近邻算法】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

全域运营是割韭菜吗?常见套路有哪些?

随着全域运营赛道的全面开启&#xff0c;全域运营服务商和全域运营系统的数量迅速增加&#xff0c;持续激发赛道活力的同时&#xff0c;也让一些试图用全域运营割韭菜的人有了可趁之机。 值得庆幸的是&#xff0c;由于当前全域运营赛道刚兴起不久&#xff0c;因此&#xff0c;割…

Raylib 绘制自定义字体的一种套路

Raylib 绘制自定义字体是真的难搞。我的需求是程序可以加载多种自定义字体&#xff0c;英文中文的都有。 我调试了很久成功了&#xff01; 很有用的参考&#xff0c;建议先看一遍&#xff1a; 瞿华&#xff1a;raylib绘制中文内容 个人笔记&#xff5c;Raylib 的字体使用 - …

Nginx - 健康检查终极指南:探索Upstream Check模块

文章目录 概述upstream_check_module模块安装和配置指南模块安装步骤基本配置示例详细配置说明检查类型和参数常见问题及解决方案 SSL检查和DNS解析功能SSL检查配置示例和说明配置示例 DNS解析配置示例和说明配置示例 结合实际应用场景的高级配置示例综合SSL检查与DNS解析 总结…

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

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

一键恢复安卓手机数据:3个快速简便的解决方案!

安卓手机作为我们不可或缺的数字伙伴&#xff0c;承载着大量珍贵的个人和工作数据。然而&#xff0c;随着我们在手机上进行各种操作&#xff0c;不可避免地会遇到一些令人头痛的问题&#xff0c;比如意外删除文件、系统故障或其他不可预见的情况&#xff0c;导致重要数据的丢失…