操作系统实现-进入内核

博客网址:www.shicoder.top
微信:18223081347
欢迎加群聊天 :452380935

这一次我们正式进入内核,编写相关的内核代码,也就是kernel代码

数据类型定义

因为我们在内核中会使用一些数据,因此先提前定义一些数据类型

#define EOF -1

#define NULL ((void *)0) // 空指针
#define EOS '\0' // 字符串结尾

#define bool _Bool
#define true 1
#define false 0

#define _packed __attribute__((packed)) // 用于定义特殊的结构体 不对齐

typedef unsigned int size_t;
typedef char int8;
typedef short int16;
typedef int int32;
typedef long long int64;
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned long long u64;

typedef u32 time_t;
typedef u32 idx_t;

输入输出

我们知道,在操作系统启动的时候,刚开始都是黑乎乎的界面,然后光标闪烁等,那么这个是怎么实现的呢,一般这种都是通过向一些寄存器写入一些值和和获取一些值实现,因此就需要用一些输入输出函数

首先是四个函数

extern u8 inb(u16 port); // 输入1个字节 从port端口中读一个字节
extern u16 inw(u16 port); // 输入2个字节  从port端口中读2个字节

extern void outb(u16 port,u8 value); // 输出1个字节 将value值输入到port端口中
extern void outw(u16 port,u16 value); // 输出2个字节 将value值输入到port端口中

我们采用汇编实现

global inb ; 将inb导出
inb:
    ; 栈帧保存
    push ebp
    mov ebp, esp

    xor eax, eax ;清空
    mov edx, [ebp + 8] ;port [ebp + 8]就是传入进来的port
    in al, dx ;将dx所指向的端口,读取一个字放在al,也就是从port端口读一个字节

    jmp $+2 ;延迟
    jmp $+2 ;延迟
    jmp $+2 ;延迟

    leave ; 恢复栈帧
    ret
global outb ; 将outb导出
outb:
    ; 栈帧保存
    push ebp
    mov ebp, esp

    mov edx, [ebp + 8] ;port [ebp + 8]就是传入进来的port
    mov eax, [ebp + 12] ; value 参数入栈是从右往左 所以value地址更高
    out dx, al ;将al的8比特输出到dx的端口号

    jmp $+2 ;延迟
    jmp $+2 ;延迟
    jmp $+2 ;延迟

    leave ; 恢复栈帧
    ret

global inw ; 将inw导出
inw:
    ; 栈帧保存
    push ebp
    mov ebp, esp

    xor eax, eax ;清空
    mov edx, [ebp + 8] ;port [ebp + 8]就是传入进来的port
    in ax, dx ;将dx所指向的端口,读取2个字放在ax

    jmp $+2 ;延迟
    jmp $+2 ;延迟
    jmp $+2 ;延迟

    leave ; 恢复栈帧
    ret

global outw ; 将outw导出
outw:
    ; 栈帧保存
    push ebp
    mov ebp, esp

    mov edx, [ebp + 8] ;port [ebp + 8]就是传入进来的port
    mov eax, [ebp + 12] ; value 参数入栈是从右往左 所以value地址更高
    out dx, ax ;将ax的2个字输出到dx的端口号

    jmp $+2 ;延迟
    jmp $+2 ;延迟
    jmp $+2 ;延迟

    leave ; 恢复栈帧
    ret

我们在kernel中测试下获取光标的位置,相关的寄存器有以下几个

  • CRT 地址寄存器 0x3D4
  • CRT 数据寄存器 0x3D5
  • CRT 光标位置 – 高位 0xE
  • CRT 光标位置 – 低位 0xF

比如我们把光标高位位置给地址寄存器,那么就可以通过数据寄存器得到和设置光标位置的高位值

// - CRT 地址寄存器 0x3D4
// - CRT 数据寄存器 0x3D5
// - CRT 光标位置 - 高位 0xE
// - CRT 光标位置 - 低位 0xF

#define CRT_ADDR_REG 0x3d4
#define CRT_DATA_REG 0x3d5

#define CRT_CURSOR_H 0xe
#define CRT_CURSOR_L 0xf
void kernel_init()
{
    outb(CRT_ADDR_REG,CRT_CURSOR_H);
    u16 pos = inb(CRT_DATA_REG) << 8;
    outb(CRT_ADDR_REG,CRT_CURSOR_L);
    pos |= inb(CRT_DATA_REG); // 到这里,pos值为240,通过qemu也可以看到,光标在第4行,每行80字符
    u8 data = inb(CRT_DATA_REG);

    // 比如想把光标位置改为160
    outb(CRT_ADDR_REG,CRT_CURSOR_H);
    outb(CRT_DATA_REG,0);
    outb(CRT_ADDR_REG,CRT_CURSOR_L);
    outb(CRT_DATA_REG,160); // 到这里,就可以看到光标在第3行开始处
}

操作系统实现-进入内核

字符串函数实现

我们在C语言中,使用过很多字符串函数,比如

char *strcpy(char *dest, const char *src);
char *strcat(char *dest, const char *src);
size_t strlen(const char *str);
int strcmp(const char *lhs, const char *rhs);
char *strchr(const char *str, int ch);
char *strrchr(const char *str, int ch);
int memcmp(const void *lhs, const void *rhs, size_t count);
void *memset(void *dest, int ch, size_t count);
void *memcpy(void *dest, const void *src, size_t count);
void *memchr(const void *ptr, int ch, size_t count);

下面是其实现的代码

char *strcpy(char *dest, const char *src)
{
    char *ptr = dest;
    while (true)
    {
        *ptr++ = *src;
        if (*src++ == EOS)
            return dest;
    }
}

char *strcat(char *dest, const char *src)
{
    char *ptr = dest;
    while (*ptr != EOS)
    {
        ptr++;
    }
    while (true)
    {
        *ptr++ = *src;
        if (*src++ == EOS)
        {
            return dest;
        }
    }
}

size_t strlen(const char *str)
{
    char *ptr = (char *)str;
    while (*ptr != EOS)
    {
        ptr++;
    }
    return ptr - str;
}

int strcmp(const char *lhs, const char *rhs)
{
    while (*lhs == *rhs && *lhs != EOS && *rhs != EOS)
    {
        lhs++;
        rhs++;
    }
    return *lhs < *rhs ? -1 : *lhs > *rhs;
}

char *strchr(const char *str, int ch)
{
    char *ptr = (char *)str;
    while (true)
    {
        if (*ptr == ch)
        {
            return ptr;
        }
        if (*ptr++ == EOS)
        {
            return NULL;
        }
    }
}

char *strrchr(const char *str, int ch)
{
    char *last = NULL;
    char *ptr = (char *)str;
    while (true)
    {
        if (*ptr == ch)
        {
            last = ptr;
        }
        if (*ptr++ == EOS)
        {
            return last;
        }
    }
}

int memcmp(const void *lhs, const void *rhs, size_t count)
{
    char *lptr = (char *)lhs;
    char *rptr = (char *)rhs;
    while (*lptr == *rptr && count-- > 0)
    {
        lptr++;
        rptr++;
    }
    return *lptr < *rptr ? -1 : *lptr > *rptr;
}

void *memset(void *dest, int ch, size_t count)
{
    char *ptr = dest;
    while (count--)
    {
        *ptr++ = ch;
    }
    return dest;
}

void *memcpy(void *dest, const void *src, size_t count)
{
    char *ptr = dest;
    while (count--)
    {
        *ptr++ = *((char *)(src++));
    }
    return dest;
}

void *memchr(const void *str, int ch, size_t count)
{
    char *ptr = (char *)str;
    while (count--)
    {
        if (*ptr == ch)
        {
            return (void *)ptr;
        }
        ptr++;
    }
}

基础显卡驱动

我们知道比如在显示器显示 hello,world\n,那么显示器就会先输出一句 hello,world,然后换行,这一次就是实现这个操作,其实可以想下,换行,不就是设置一下光标位置嘛,那不就是第二个部分输入输出的样例吗,下面来实现吧,同时注意有以下寄存器

  • CRT 地址寄存器 0x3D4
  • CRT 数据寄存器 0x3D5
  • CRT 光标位置 – 高位 0xE
  • CRT 光标位置 – 低位 0xF
  • CRT 显示开始位置 – 高位 0xC
  • CRT 显示开始位置 – 低位 0xD

控制字符 八进制 十六进制 描述 NUL 0 0x00 在输入时忽略,不保存在输入缓冲中 ENQ 5 0x05 传送应答消息 BEL 7 0x07 从键盘发声响 BS 10 0x08 将光标移向左边一个字符位置处;若光标已经处在左边沿,则无动作 HT 11 0x09 将光标移到下一个制表位;若右侧已经没有制表位,则移到右边缘处 LF 12 0x0A 此代码导致一个回车或换行操作 VT 13 0x0B 作用如LF FF 14 0x0C 作用如LF CR 15 0x0D 将光标移到当前行的左边缘处 SO 16 0x0E 使用由 SCS 控制序列设计的 G1 字符集 SI 17 0x0F 选择 G0 字符集,由 ESC 序列选择 XON 21 0x11 使终端重新进行传输 XOFF 23 0x13 使中断除发送 XOFF 和 XON 以外,停止发送其它所有代码 CAN 30 0x18 如果在控制序列期间发送,则序列不会执行而立刻终止,同时会显示出错字符 SUB 32 0x1A 作用同 CAN ESC 33 0x1B 产生一个控制序列 DEL 177 0x7F 在输入时忽略 不保存在输入缓冲中

#define CRT_ADDR_REG 0x3D4 // CRT(6845)索引寄存器
#define CRT_DATA_REG 0x3D5 // CRT(6845)数据寄存器

#define CRT_START_ADDR_H 0xC // 显示内存起始位置 - 高位
#define CRT_START_ADDR_L 0xD // 显示内存起始位置 - 低位
#define CRT_CURSOR_H 0xE     // 光标位置 - 高位
#define CRT_CURSOR_L 0xF     // 光标位置 - 低位

#define MEM_BASE 0xB8000              // 显卡内存起始位置
#define MEM_SIZE 0x4000               // 显卡内存大小
#define MEM_END (MEM_BASE + MEM_SIZE) // 显卡内存结束位置
#define WIDTH 80                      // 屏幕文本列数
#define HEIGHT 25                     // 屏幕文本行数
#define ROW_SIZE (WIDTH * 2)          // 每行字节数 一个字符由2个字节控制 ,一个是ascii,一个是样式
#define SCR_SIZE (ROW_SIZE * HEIGHT)  // 屏幕字节数

#define ASCII_NUL 0x00
#define ASCII_ENQ 0x05
#define ASCII_BEL 0x07 // \a
#define ASCII_BS 0x08  // \b
#define ASCII_HT 0x09  // \t
#define ASCII_LF 0x0A  // \n
#define ASCII_VT 0x0B  // \v
#define ASCII_FF 0x0C  // \f
#define ASCII_CR 0x0D  // \r
#define ASCII_DEL 0x7F

static u32 screen; // 记录当前显示器开始的内存位置
static u32 pos;    // 记录当前光标内存位置
static u32 x, y;   // 当前光标坐标

// 删除后,会在那里显示一个类似橡皮擦的样式光标
static u8 attr = 7;        // 字符样式
static u16 erase = 0x0720; // 空格 07是字符,20是样式

// 获得当前显示器的位置
static void get_screen()
{
    outb(CRT_ADDR_REG, CRT_START_ADDR_H); // 显示内存起始位置高地址
    screen = inb(CRT_DATA_REG) << 8;      // 显示内存起始位置值的高8位
    outb(CRT_ADDR_REG, CRT_START_ADDR_L); // 显示内存起始位置低地址
    screen |= inb(CRT_DATA_REG);          // 显示内存起始位置值的低8位

    screen <> 9) & 0xff); // 因为screen获得时候,是左移1位,然后再移8位是高地址
    outb(CRT_ADDR_REG, CRT_START_ADDR_L);                  // 显示内存起始位置低地址
    outb(CRT_DATA_REG, ((screen - MEM_BASE) >> 1) & 0xff); // 因为screen获得时候,是左移1位
}

// 获得当前光标位置
static void get_cursor()
{
    outb(CRT_ADDR_REG, CRT_CURSOR_H); // 光标内存起始位置高地址
    pos = inb(CRT_DATA_REG) << 8;     // 光标内存起始位置值的高8位
    outb(CRT_ADDR_REG, CRT_CURSOR_L); // 光标内存起始位置低地址
    pos |= inb(CRT_DATA_REG);         // 光标内存起始位置值的低8位
    pos <> 1;
    x = delta % WIDTH;
    y = delta / WIDTH;
}

// 设置当前光标位置
static void set_cursor()
{
    outb(CRT_ADDR_REG, CRT_CURSOR_H); // 光标内存起始位置高地址
    outb(CRT_DATA_REG, ((pos - MEM_BASE) >> 9) & 0xff);
    outb(CRT_ADDR_REG, CRT_CURSOR_L); // 光标内存起始位置低地址
    outb(CRT_DATA_REG, ((pos - MEM_BASE) >> 1) & 0xff);
}

void console_clear()
{
    screen = MEM_BASE;
    pos = MEM_BASE;
    x = y = 0;
    set_cursor();
    set_screen();

    // 清空 让屏幕全为空格
    u16 *ptr = (u16 *)MEM_BASE;
    while (ptr < (u16 *)MEM_END)
    {
        *ptr++ = erase;
    }
}

void console_init()
{

    // 相当于screen为第二行开始的地方,意思就是我们只能从显示器第二行开始看,第一行就看不到了
    // screen = 80 * 2 + MEM_BASE;
    // set_screen();
    // get_screen();
    // 比如设置光标为124, 第一行的后半截,124/2=62
    // pos = 124 + MEM_BASE;
    // set_cursor();

    console_clear();
}
// 超过屏幕显示大小,向上滚屏,也就是把最上面一行去掉
static void scroll_up()
{
    if (screen + SCR_SIZE + ROW_SIZE < MEM_END)
    {
        u32 *ptr = (u32 *)(screen + SCR_SIZE);
        for (size_t i = 0; i < WIDTH; i++)
        {
            *ptr++ = erase;
        }
        screen += ROW_SIZE;
        pos += ROW_SIZE;
    }
    // 超过,感觉是直接重头开始
    else
    {
        memcpy((void *)MEM_BASE, (void *)screen, SCR_SIZE);
        pos -= (screen - MEM_BASE);
        screen = MEM_BASE;
    }
    set_screen();
}
static void command_lf()
{
    if (y + 1 < HEIGHT)
    {
        y++;
        pos += ROW_SIZE;
        return;
    }
    scroll_up();
}

static void command_bs()
{
    if (x)
    {
        x--;
        pos -= 2;
        *(u16 *)pos = erase;
    }
}

static void command_cr()
{
    pos -= (x << 1);
    x = 0;
}

static void command_del()
{

    *(u16 *)pos = erase;
}

void console_write(char *buf, u32 count)
{
    char ch;
    while (count--)
    {
        ch = *buf++;
        switch (ch)
        {
        case ASCII_NUL:
            break;
        case ASCII_ENQ:
            break;
        case ASCII_BEL: // \a
            break;
        case ASCII_BS: // \b
            command_bs();
            break;
        case ASCII_HT: // \t
            break;
        case ASCII_LF: // \n
            command_lf();
            command_cr();
            break;
        case ASCII_VT: // \v
            break;
        case ASCII_FF: // \f
            command_lf();
            break;
        case ASCII_CR: // \r
            command_cr();
            break;
        case ASCII_DEL:
            command_del();
            break;
        default:
            if (x >= WIDTH)
            {
                x -= WIDTH;
                pos -= ROW_SIZE;
                command_lf();
            }
            *((char *)pos) = ch;
            pos++;
            *((char *)pos) = attr;
            pos++;

            x++;
            break;
        }
    }
    set_cursor();
}

下面简单测试下吧,kernel主函数如下

char message[] = "hello system...\n";
void kernel_init()
{
    console_init();
    u32 count = 20;
    while (count--)
    {
        console_write(message, sizeof(message) - 1);
    }
}

操作系统实现-进入内核

可以看到打印了20次,且每次都换行了,成功啦

Original: https://www.cnblogs.com/shilinkun/p/16261108.html
Author: 小坤学习园
Title: 操作系统实现-进入内核

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/607193/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Linux 配置Git

    前言:请各大网友尊重本人原创知识分享,谨记本人博客: 南国以南i 一、用git –version命令检查是否已经安装 二、下载git源码并解压 wget https:/…

    Linux 2023年6月14日
    096
  • Java对象序列化和反序列化

    Java类的序列化和反序列化 序列化:指将对象转换为字节序列的过程,也就是将对象的信息转换成文件保存。 反序列化:将字节序列转换成目标对象的过程,也就是读取文件,并转换为对象。 几…

    Linux 2023年6月14日
    0107
  • sql server 增删改(查太多了)

    delete(删除) 使用 delete语句删除表中数据。 delete from 表名 [where where_definition] 如果不使用where子句,将删除表中所有…

    Linux 2023年6月7日
    087
  • 008 Linux 文件查找 find

    在 Linux 系统,find 毫无疑问是最强的文件查找工具。find 一般会与其他命令结合,将查找到的结果作为参数传入到后置命令中,进行删除、统计、复制迁移等操作。 find /…

    Linux 2023年5月27日
    0102
  • Spring事务基础入门及AOP陷阱分析

    更新说明: 2021-12-28 6.2.1 章节更新了代理说明图,调整了说明顺序,修复了错误说明 转载请注明出处: https://www.cnblogs.com/qnlcy/p…

    Linux 2023年6月6日
    084
  • JQ 实现对比两个文本的差异并高亮显示差异部分

    利用jq对比两段文本的差异,差异的内容用不同颜色表示出来。 在线参考demo:http://incaseofstairs.com/jsdiff/ 项目地址:https://gith…

    Linux 2023年6月7日
    0114
  • Linux 0.11源码阅读笔记-中断过程

    Linux 0.11源码阅读笔记-中断过程 是什么中断 中断发生时,计算机会停止当前运行的程序,转而执行中断处理程序,然后再返回原被中断的程序继续运行。中断包括硬件中断和软件中断,…

    Linux 2023年5月27日
    0117
  • IDEA远程部署项目到Docker

    最近在写东西部署到服务器,结构是springboot工程配合docker部署。但是每次部署都3个步骤: 部署次数一多,我就怀疑人生了。就在找有没有IDEA远程部署Docker的方案…

    Linux 2023年6月7日
    089
  • 学习一下 JVM (二) — 学习一下 JVM 中对象、String 相关知识

    一、JDK 8 版本下 JVM 对象的分配、布局、访问(简单了解下) 1、对象的创建过程 (1)前言Java 是一门面向对象的编程语言,程序运行过程中在任意时刻都可能有对象被创建。…

    Linux 2023年6月11日
    0112
  • 数据库日志

    数据库日志: 首先,在MySQL中默认只开启了错误日志 MySQL中的日志文件: 错误日志(errorlog): 一般查询日志(general log) 慢查询日志(slow qu…

    Linux 2023年6月6日
    097
  • 常见开发模型-敏捷开发与瀑布开发模型详解

    引言 在学习软件工程的时候接触过一些软件工程开发模型的相关概念,其中,印象比较深刻的就是瀑布模型和敏捷开发模型。这两种模型在日常的软件开发中都是非常常用的,但是它们也有比较大的区别…

    Linux 2023年6月7日
    0136
  • JAVA环境变量配置

    java环境配置 下载jdk地址如下: http://www.oracle.com/technetwork/java/javase/downloads/index.html 下载安…

    Linux 2023年6月7日
    0119
  • [CentOS7]redis设置开机启动,设置密码

    简介 上篇文章介绍了如何安装redis,但每次重启服务器之后redis不会自启,这里将介绍如何进行自启设置,以及如何设置redis的密码,进行密码验证登陆。 上篇文章: Cento…

    Linux 2023年5月28日
    0110
  • Greenplum数据库基本操作命令大全-完善ing

    Greenplum数据库基本操作命令大全 select * from pg_stat_activity; datid –16388 datname –hfamlgpdbs &a…

    Linux 2023年6月11日
    0106
  • Docker-资源限制(Cgroup)

    Docker-资源限制 1.Cgroup简介 _cgroups,是一个非常强大的linux内核工具,他不仅可以限制被namespace隔离起来的资源,还可以为资源设置权重、计算使用…

    Linux 2023年6月13日
    083
  • Redis集群的节点通信原理

    Redis集群搭建中,数据如何在节点分布的原理,下面来介绍一下节点之间是如何进行通信(节点握手) 一、基础通讯原理 1、维护集群的元数据的两种方案介绍及对比 在分布式存储中需要提供…

    Linux 2023年5月28日
    0153
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球