首页 > 编程语言 >实验8.混合编程实现打印字符函数

实验8.混合编程实现打印字符函数

时间:2024-07-20 23:55:46浏览次数:14  
标签:字符 编程 文件目录 文件 打印 BUILD main DIR build

简介

实验:混合编程实现打印字符函数

仓库地址:https://gitee.com/caicunjun/tityos

代码

引导

省略

内核

main.c

// 文件: main.c
// 时间: 2024-07-19
// 来自: ccj
// 描述: 内核从此处开始


#include "print.h"

int main(void) {
    uint8_t i = 'A';
    while (i < 'z') {
        put_char(i);
        put_char('\n');
        i++;
    }

    while (1);
    return 0;
}

print.s

; 文件: print.s
; 时间: 2024-07-19
; 来自: ccj
; 描述: 打印函数接口定义


TI_GDT equ  0
RPL0  equ   0
SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0

[bits 32]
section .text
;---打印一个字符 begin---
global put_char
put_char:
    pushad                           ; 备份32位寄存器环境

    mov ax, SELECTOR_VIDEO	        ; 需要保证gs中为正确的视频段选择子,为保险起见,每次打印时都为gs赋值 不能直接把立即数送入段寄存器
    mov gs, ax

    ;---获取光标位置 begin---
    ;先获得高8位
    mov dx, 0x03d4  ; 索引寄存器
    mov al, 0x0e    ; 用于提供光标位置的高8位
    out dx, al
    mov dx, 0x03d5  ; 通过读写数据端口0x3d5来获得或设置光标位置
    in al, dx       ; 得到了光标位置的高8位
    mov ah, al

    ;再获取低8位
    mov dx, 0x03d4
    mov al, 0x0f
    out dx, al
    mov dx, 0x03d5
    in al, dx       ; 此时ax就是光标的位置
    mov bx, ax      ;将光标存入bx
    ;---获取光标位置 end---

    mov ecx, [esp + 36]       ; cl是获取要打印的字符 pushad压入4×8=32字节,加上主调函数的返回地址4字节,故esp+36字节
    cmp cl, 0xd               ; 回车是0x0d
    jz .is_carriage_return
    cmp cl, 0xa               ; 换行是0x0a
    jz .is_line_feed
    cmp cl, 0x8               ; 退格是8
    jz .is_backspace
    jmp .put_other            ; 正常写入

    ; 退格: 光标前一位显示为空,光标设置为前一位
.is_backspace:
    dec bx                     ; 减少1
    shl bx,1                   ; 左移1位,因为显示1个字符要2个字节,所以在内存偏移是bx*2
    mov byte [gs:bx], 0x20     ; 将0x20=32=空字符
    inc bx
    mov byte [gs:bx], 0x07     ; 0x07 黑底白字
    shr bx,1                   ; 右移1位,表示第几个字符
    jmp .set_cursor

    ; 输出其他的普通字符
.put_other:
    shl bx, 1                  ; 左移1位,因为显示1个字符要2个字节,所以在内存偏移是bx*2
    mov [gs:bx], cl            ; 打印cl
    inc bx
    mov byte [gs:bx],0x07      ; 0x07 黑底白字
    shr bx, 1                  ; 右移1位,表示第几个字符
    inc bx                     ; 下一个光标值
    cmp bx, 2000
    jl .set_cursor             ; 若光标值小于2000,表示未写到显存的最后,则去设置新的光标值,若超出屏幕字符数大小(2000)则换行处理

    ; 换行和回车
.is_line_feed:
.is_carriage_return:
    ;  以下5步=拿到光标所在行的行首位置 bx = bx - (bx % 80)
    xor dx, dx                 ; 被除数÷除数=商……余数 dx是被除数的高16位, ax是被除数的低16位
    mov ax, bx                 ; 所以被除数=bx=光标值
    mov si, 80                 ; si是一样的光标数=80
    div si                     ; 光标值/80
    sub bx, dx                 ; dx为余数 被除数-余数 = 光标值的行首

    add bx, 80                 ; 移动到下一行
    cmp bx, 2000               ; 如果超出屏幕字符数大小(2000)
    jl .set_cursor             ; 没有越界就设置光标 jump if less

    ; 滚屏 将屏幕的1~24行搬运到0~23行,再将第24行用空格填充
.roll_screen:
    cld
    mov ecx, 960                ; 一共有2000-80=1920个字符要搬运,共1920*2=3840字节.一次搬4字节,共3840/4=960次
    mov esi, 0xb80a0            ; 第1行行首偏移 0xb800 + 0xa0(160)
    mov edi, 0xb8000            ; 第0行行首偏移
    rep movsd                   ; 把esi(第1行行首偏移)开始的ecx*4(3840)字节复制到edi(第0行行首偏移)

    mov ebx, 3840               ; 最后一行行首的偏移为1920*2=3840
    mov ecx, 80                 ; 一行是80字符(160字节),每次清空1字符(2字节),一行需要移动80次
.cls:
    mov word [gs:ebx], 0x0720   ; 0x20是空格 0x07 黑底白字
    add ebx, 2
    loop .cls
    mov bx,1920                 ; 将光标值重置为1920,最后一行的首字符.

    ; 设置光标位置为bx
.set_cursor:
    ;1 设置高8位
    mov dx, 0x03d4              ; 索引寄存器
    mov al, 0x0e                ; 用于提供光标位置的高8位
    out dx, al
    mov dx, 0x03d5              ; 通过读写数据端口0x3d5来获得或设置光标位置
    mov al, bh
    out dx, al

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

.put_char_done:
    popad                       ; 恢复32位寄存器环境
    ret
;---打印一个字符 end---


编译

Makefile

BUILD_DIR   = build
TARGET     ?= ${BUILD_DIR}/kernel

AS = nasm
CC = gcc
LD = ld

# 头文件目录
INCDIRS   := lib/kernel \
# 遍历头文件目录并且加上-I 前缀 include -> -I include
INCLUDE   := $(patsubst %, -I %, $(INCDIRS))

# 源文件目录
SRCDIRS   := kernel \
			 lib/kernel

# 遍历源文件目录里的.s文件
SFILES    := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.s))
# 遍历源文件目录里的.c文件
CFILES    := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))

# 去除源文件的目录
SFILENDIR := $(notdir  $(SFILES))
CFILENDIR := $(notdir  $(CFILES))

# 遍历源文件并添加上build/前缀  xxx.s -> build/xxx.o
SOBJS	  := $(patsubst %, ${BUILD_DIR}/%, $(SFILENDIR:.s=.o))
# 遍历源文件并添加上build/前缀  xxx.c -> build/xxx.o
COBJS     := $(patsubst %, ${BUILD_DIR}/%, $(CFILENDIR:.c=.o))
OBJS      := $(SOBJS) $(COBJS)

# 重要!!! 指定.c文件的目录
VPATH	  := $(SRCDIRS)

# 编译s文件时的配置
ASFLAGS = -f elf
# 编译c文件时的配置
CFLAGS = -Wall -m32 -fno-stack-protector -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes
# 链接时的配置
LDFLAGS =  -m elf_i386 -Ttityos.lds -e main -Map $(BUILD_DIR)/kernel.map

# 链接全部.o文件
$(TARGET).bin : $(OBJS)
	$(LD) $(LDFLAGS) $^ -o $@
# 编译全部的.s
$(SOBJS) : ${BUILD_DIR}/%.o : %.s
	$(AS) $(ASFLAGS) $< -o $@
# 编译全部.c文件
$(COBJS) : ${BUILD_DIR}/%.o : %.c
	$(CC) $(CFLAGS) $(INCLUDE) -o $@ $<

.PHONY : mk_dir bootloader clean all kernel

mk_dir: #创建build目录
	if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fi

bootloader: #编译启动内核的文件
	nasm -I boot/include/ -o $(BUILD_DIR)/mbr.bin boot/mbr.s
	nasm -I boot/include/ -o $(BUILD_DIR)/loader.bin boot/loader.s
	dd if=/home/c/tityos/${BUILD_DIR}/mbr.bin of=/home/c/tityos/hd60M.img bs=512 count=1 conv=notrunc
	dd if=/home/c/tityos/${BUILD_DIR}/loader.bin of=/home/c/tityos/hd60M.img bs=512 count=3 seek=2 conv=notrunc

kernel: ${BUILD_DIR}/kernel.bin
	dd if=$(BUILD_DIR)/kernel.bin of=/home/c/tityos/hd60M.img bs=512 count=200 seek=9 conv=notrunc


clean: #删除build目录里的全部文件
	cd $(BUILD_DIR) && rm -f  ./*

# 创建build目录。编译启动内核的文件。
all: mk_dir bootloader kernel


tityos.lds

SECTIONS{
	. = 0xc0001500;
	.text :
	{
		build/main.o /*重要!必须指定mian为最开头*/
		*(.text)
	}
	.rodata ALIGN(4) : {*(.rodata*)}
	.data ALIGN(4)   : { *(.data) }
	__bss_start = .;
	.bss ALIGN(4)  : { *(.bss)  *(COMMON) }
	__bss_end = .;
}

运行

start.sh

#/bin/bash
# 文件: start.sh
# 描述: 启动bochs
# 时间: 2024-07-19
# 来自: ccj


set -x

bin/bochs -f bochsrc.disk

在这里插入图片描述

标签:字符,编程,文件目录,文件,打印,BUILD,main,DIR,build
From: https://blog.csdn.net/laidene/article/details/140569409

相关文章

  • 字符的统计 387、389、383、242、49
    387.字符串中的第一个唯一字符解法一、哈希映射计算每个字符出现的次数,然后再遍历,与数组里记录次数进行比对顺便一提哈希表数据结构耗时很大,数组计数哈希思想快很多(是不是桶排来着classSolution{publicstaticintfirstUniqChar(Strings){intlen=......
  • 语法错误:源代码字符串不能包含空字节
    我正在尝试运行pythonmanage.pymakemigrations并引发以下异常。请注意-1-我在models.py中有自己的User类2-两个模型类,一个在应用程序内部,另一个文件在应用程序外部。外部models.py是根据我的数据库架构生成的类。我使用以下命令自动生成文件。pythonmana......
  • 第七章 元类编程
    目录8.1元类的基本概念什么是元类?8.2自定义元类自定义元类的创建8.3元类的应用场景自动注册单例模式8.4使用元类实现接口检查8.5动态修改类8.6元类的嵌套使用8.7元类与装饰器的结合示例代码:8.8元类的实际应用案例案例1:实现ORM框架中的模型类自动注册案......
  • C++面向对象编程的一个核心概念--RAII
    RAII是"ResourceAcquisitionIsInitialization"(资源获取即初始化)的缩写,它是C++编程中的一种编程技术,用于管理资源的生命周期。RAII是C++面向对象编程的一个核心概念,它利用对象的构造函数和析构函数来自动管理资源,如内存、文件句柄、线程、互斥锁等。RAII的主要原则包括:1.*......
  • 如何使用C++中的字符串类(如std::string)
    在C++中,std::string 类是标准模板库(StandardTemplateLibrary,STL)的一部分,它提供了对字符串的灵活处理。std::string 使得字符串的存储、操作、比较、查找等任务变得更加方便和高效。下面将介绍如何使用 std::string 类。1.包含头文件要使用 std::string,首先需要包含......
  • Python; Django 添加字符到路径名导致操作系统错误 22
    我一直在尝试让django渲染我创建的模板。起初它说模板不存在,但是一旦我修复了错误,它现在就会向路径添加字符,并且因此找不到模板。路径应该是:C:\\Users\\ABC\\Desktop\\science_crowd\\Lightweight_Django\\placeholder\\home.html但是错误说:它找不到:C:\\Us......
  • C. 字符序列
    简要题意:有一个函数\(f(c,s)=cs_1cs_2cs_3\cdotscs_nc\)。给出操作序列\(c_i\),每次操作使\(s=f(c_i,s)\)(\(s\)开始为空串),求最后的字符串中有多少个本质不同的子序列。数据范围:\(n\le500\)。首先我们可以考虑一个简化经典问题,已知一个字符串,求本质不同的子序列数量。因为......
  • 编程AI工具
    通义灵码https://tongyi.aliyun.com/lingmaSourcegraphhttps://sourcegraph.com/Tabinehttps://www.tabnine.com/CodeGeeX免费并且开源模型https://codegeex.cn/开源模型:https://github.com/THUDM/CodeGeeXCodeiumhttps://codeium.com/GithubCopilotthttps://gith......
  • 2024“钉耙编程”中国大学生算法设计超级联赛(1)结题报告1 2 8
    1001循环位移字符串哈希将a展开*2对于每个长度为len_a的序列进行一次hash存储并将其插入set中对于b进行一次哈希对于每个长度为len_a的连续子串进行一次查询点击查看代码#include<bits/stdc++.h>usingnamespacestd;//22222constintN=5e6+10;constintp1......
  • 在pyspark(python)中将json字符串扩展到多列
    我需要将Json对象(b列)扩展到多列。从此表中,A列B列id1[{a:1,b:'letter1'}]id2[{a:1,b:'letter2',c:3,d:4}]对......