操作系统实现-进入内核

博客网址: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)

大家都在看

  • 安装Redis

    1、下载redis 2、解压缩、安装 安装完之后,可以执行以下make test,执行make test之前需要先安装tcl 3、配置 redis服务后台启动 找到: 修改为: 4…

    Linux 2023年5月28日
    093
  • 调度器简介

    内核中用来安排进程执行的模块称为调度器(scheduler),它可以切换进程状态(process state)。例如执行、可中断睡眠、不可中断睡眠、退出、暂停等。 调度器是CPU中…

    Linux 2023年6月7日
    078
  • cobbler

    cobbler cobbler cobbler简介 cobbler工作原理 cobbler的作用 cobbler服务端部署 cobbler简介 Cobbler是一个Linux服务器…

    Linux 2023年6月6日
    0102
  • 使用 Powershell 删除N天前的文件

    在 Linux 中删除N天前的文件可以使用以下命令: find /path/to -maxdepth 1 -name "filename" -mtime +1 …

    Linux 2023年6月14日
    082
  • python 对文件操作

    实现对文件的简单操作 #!/usr/bin/env python -*- coding:utf-8 -*- def Find(class_name): class_list = […

    Linux 2023年6月13日
    0132
  • Spring5 学习笔记

    学习地址: B站-动力节点 个人代码: GitHub Spring 概述 1.1 Spring 简介 Spring Framework 是一个使用Java开发的、轻量级的、开源框架…

    Linux 2023年6月14日
    095
  • 自动升级shell

    make_version.sh ./make_version.sh 第一次提示”y/N” 表示接下来的操作是手动(y)还是自动(N); 自动(N)会为镜像自…

    Linux 2023年5月28日
    0107
  • [转帖]shell 学习之正则、别名以及管道重定向

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Linux 2023年5月28日
    0100
  • Shell alias命令详解:给命令设置别名

    给命令设置别名,你可以把它当作命令的”小名”,但是这样做有什么意义呢? 比如笔者刚接触 Linux 时,使用的编辑器是 Vi,但是现在 Vim 的功能明显比…

    Linux 2023年5月28日
    066
  • ELK 脚本自动化删除索引

    kibana有自带接口,可通过自带的API接口 通过传参来达到删除索引的目的。 删除15天前的索引 curl -XDELETE "http://10.228.81.161…

    Linux 2023年6月8日
    081
  • CSS解决父级边框坍塌的问题

    首先在父级标签内添加如下 标签 然后在CSS中对该标签进行如下修饰: #clear{ clear:both; margin:0px; padding: 0px; } 优点:简单。缺…

    Linux 2023年6月13日
    0102
  • 每天一个 HTTP 状态码 204

    204 No Content 表示服务器成功地处理了客户端的请求,但是… 204 No Content 204 No Content 表示服务器成功地处理了客户端的请求…

    Linux 2023年6月7日
    0104
  • Daydream Controller手柄数据的解析

    参考: How I hacked Google Daydream controller How I hacked Google Daydream controller (Part …

    Linux 2023年6月7日
    091
  • python 练习题:接收一个或多个数并计算乘积

    以下函数允许计算两个数的乘积,请稍加改造,变成可接收一个或多个数并计算乘积 def product(x, y): return x * y python;gutter:true; …

    Linux 2023年6月8日
    098
  • docker –link容器互联

    服务器版本 docker软件版本 CPU架构 CentOS Linux release 7.4.1708 (Core) Docker version 20.10.12 x86_64…

    Linux 2023年6月7日
    0100
  • Linux动静分离与Rewrite

    一、动静分离 1.1 单台机器动静分离 1、创建NFS挂载点(NFS服务端) mkdir /static vim /etc/exports /static 172.16.1.0/2…

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