简介
实验:混合编程实现打印字符函数
仓库地址: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