8.1 makefile
8.2 实现 assert 断言
8.2.1 实现开、关中断的函数
kernel/interrupt.c
补充了获取中断状态和设置中断的函数
#include "io.h"
#include "interrupt.h"
#include "stdint.h"
#include "global.h"
#include "print.h"#define IDT_DESC_CNT 0x21 //支持的中断数目
#define PIC_M_CTRL 0x20 // 主片的控制端口是 0x20
#define PIC_M_DATA 0x21 // 主片的数据端口是 0x21
#define PIC_S_CTRL 0xa0 // 从片的控制端口是 0xa0
#define PIC_S_DATA 0xa1 // 从片的数据端口是 0xa1#define EFLAGS_IF 0x00000200 //eflags 寄存器if位为1
#define GET_EFLAGS(EFLAG_VAR) asm volatile("pushfl; popl %0":"=g"(EFLAG_VAR));// 中断门描述符结构体
struct gate_desc{uint16_t func_offset_low_word;uint16_t selector;uint8_t dcount;uint8_t attribute;uint16_t func_offset_high_word;
};//静态函数声明 非必须
static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, intr_handler function);
static struct gate_desc idt[IDT_DESC_CNT]; // idt 是中断描述符表
// 声明引用定义在 kernel.S 中的中断处理函数入口数组
extern intr_handler intr_entry_table[IDT_DESC_CNT];
char *intr_name[IDT_DESC_CNT]; //保存异常的名字
/*定义中断处理程序数组,在kernel.asm中定义的intrXXentry只是中断处理程序的入口,最终调用的是 ide_table 中的处理程序*/
intr_handler idt_table[IDT_DESC_CNT];/*初始化可编程中断处理器 8259A*/
static void pic_init(void) {/*初始化主片 */ outb (PIC_M_CTRL, 0x11); // ICW1: 边沿触发,级联 8259, 需要 ICW4 outb (PIC_M_DATA, 0x20); // ICW2: 起始中断向量号为 0x20 // 也就是 IR[0-7] 为 0x20 ~ 0x27 outb (PIC_M_DATA, 0x04); // ICW3: IR2 接从片outb (PIC_M_DATA, 0x01); // ICW4: 8086 模式, 正常 EOI /*初始化从片 */ outb (PIC_S_CTRL, 0x11); // ICW1: 边沿触发,级联 8259, 需要 ICW4 outb (PIC_S_DATA, 0x28); // ICW2: 起始中断向量号为 0x28 // 也就是 IR[8-15]为 0x28 ~ 0x2F outb (PIC_S_DATA, 0x02); // ICW3: 设置从片连接到主片的 IR2 引脚outb (PIC_S_DATA, 0x01); // ICW4: 8086 模式, 正常 EOI /*打开主片上 IR0,也就是目前只接受时钟产生的中断 */ outb (PIC_M_DATA, 0xfe); //1111 1110 feoutb (PIC_S_DATA, 0xff); put_str(" pic_init done\n"); }/*创建中断门描述符*/
static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, intr_handler function) { p_gdesc->func_offset_low_word = (int32_t)function & 0x0000FFFF;p_gdesc->selector = SELECTOR_K_CODE;p_gdesc->dcount = 0;p_gdesc->attribute = attr;p_gdesc->func_offset_high_word = ((uint32_t)function & 0xFFFF0000) >> 16;
}
/*初始化中断描述符表*/
static void idt_desc_init(void){int i;for(i = 0; i < IDT_DESC_CNT; ++i){make_idt_desc(&idt[i], IDT_DESC_ATTR_DPL0, intr_entry_table[i]);}put_str(" idt_desc_init done\n");
}/*通用的中断处理函数*/static void general_intr_handler(uint8_t vec_nr){if(vec_nr == 0x27 || vec_nr == 0x2f){//IRQ7和IRQ5会产生伪中断,无需处理//0x2f是从片 8259A 上的最后一个 IRQ 引脚,保留项return;}put_str("int vector : 0x");put_int(vec_nr);put_char(' ');put_str(intr_name[vec_nr]);put_char('\n');}/*完成一般中断处理函数注册及异常名称注册*/
static void exception_init(void){int i;for(i = 0; i < IDT_DESC_CNT; ++i){/*idt_table中的函数是在进入中断后根据中断向量好调用的见kernel/kernel.asm 的 call[idt_table + %1*4]*/idt_table[i] = general_intr_handler;//默认为general_intr_handler//以后会有 register_handler注册具体的处理函数intr_name[i] = "unknown"; //先统一为 "unknown"}intr_name[0] = "#DE Divide Error";intr_name[1] = "#DB Debug Exception";intr_name[2] = "NMI Interrupt";intr_name[3] = "#BP Breakpoint Exception";intr_name[4] = "#OF Overflow Exception";intr_name[5] = "#BR BOUND Range Exceeded Exception";intr_name[6] = "#UD Invalid Opcode Exception";intr_name[7] = "#NM Device Not Available Exception";intr_name[8] = "#DF Double Fault Exception";intr_name[9] = "Coprocessor Segment Overrun"; intr_name[10] = "#TS Invalid TSS Exception";intr_name[11] = "#NP Segment Not Present";intr_name[12] = "#SS Stack Fault Exception";intr_name[13] = "#GP General Protection Exception"; intr_name[14] = "#PF Page-Fault Exception";// intr_name[15] 第 15 项是 intel 保留项,未使用intr_name[16] = "#MF x87 FPU Floating-Point Error";intr_name[17] = "#AC Alignment Check Exception"; intr_name[18] = "#MC Machine-Check Exception";intr_name[19] = "#XF SIMD Floating-Point Exception";intr_name[0x20] = "#CLOCK";}/*获取当前中断状态*/
enum intr_status intr_get_status(void){uint32_t eflags = 0;GET_EFLAGS(eflags);return (EFLAGS_IF & eflags) ? INTR_ON : INTR_OFF;
}
/*设置中断状态*/
enum intr_status intr_set_status(enum intr_status status){return (status & INTR_ON) ? intr_enable():intr_disable();
}/*开中断,返回之前的状态*/
enum intr_status intr_enable(){enum intr_status old_status;if(INTR_ON == intr_get_status()){old_status = INTR_ON;}else{old_status = INTR_OFF;asm volatile("cli":::"memory");//关中断 cli 将IF置1}return old_status;
}
/*关闭中断*/
enum intr_status intr_disable(void){enum intr_status old_status;if(INTR_ON == intr_get_status()){old_status = INTR_ON;asm volatile("cli" : : : "memory");}else{old_status = INTR_OFF;}return old_status;
}/*完成有关中断的所有初始化工作*/void idt_init(){put_str("idt_init start\n");idt_desc_init(); // 初始化中断描述符表exception_init(); //异常名和中断处理函数初始化pic_init(); //初始化 8259A/*加载 idt*/uint64_t idt_operand = ((sizeof(idt) - 1) | ((uint64_t)((uint32_t)idt << 16))); asm volatile("lidt %0"::"m"(idt_operand));put_str("idt_init done\n");}
kernel / interrupt.h
#ifndef __KERNEL_INTERRUPT_H
#define __KERNEL_INTERRUPT_H#include "stdint.h"
typedef void* intr_handler;enum intr_status{INTR_OFF,INTR_ON
};void idt_init();enum intr_status intr_get_status(void);
enum intr_status intr_set_status(enum intr_status);
enum intr_status intr_enable(void);
enum intr_status intr_disable(void);#endif
kernel / debug.h
#ifndef __KERNEL_DEBUG_H
#define __KERNEL_DEBUG_Hvoid panic_spin(char* filename, int line, const char* func, const char* condition);
/************* __VA_ARGV__ ********** __VA_ARGS__ 是预处理器所支持的专用标识符* 代表所有与省略号相对应的参数* "..."表示定义的宏其参数可变。*/
#define PANIC(...) panic_spin(__FILE__, __LINE__, __func__, __VA_ARGS__)
/*********************************************/#ifdef NDEBUG#define ASSERT(CONDITION) ((void)0)#else#define ASSERT(CONDITION) \if(CONDITION){}else{ \/*符号#让编译器将宏的参数转化为字符串字面量 */ \PANIC(#CONDITION); \}#endif /*__NDEBUG*/
#endif /*__KERNEL_DEBUG_H*/
kernel / debug.c
#include "debug.h"
#include "print.h"
#include "interrupt.h"/*打印文件名、行号、函数名、条件并使程序悬停*/
void panic_spin(char *filename, int line, const char *func, const char *cons )
{intr_disable();//有时候会单独调用 panic_spin , 所以在此处关中断put_str("\n\n\n!!!!! error !!!!!\n");put_str("filename:");put_str(filename);put_str("\n");put_str("line:0x");put_int(line);put_str("\n");put_str("function:");put_str((char*)func);put_str("\n");put_str("condition:");put_str((char*)cons);put_str("\n");while(1);
}
kernel / main.c
#include "print.h"
#include "init.h"
#include "debug.h"
int main(void){put_str("I am kernel\n");init_all();ASSERT(1==2);//asm volatile("sti");while(1);return 0;
}
运行结果:
8.3 实现字符串操作函数
lib / string.h
#ifndef __LIB_STRING_H
#define __LIB_STRING_H#include "stdint.h"/*将dst_起始的size个字节置为value*/void memset(void *dst_, uint8_t value, uint32_t size);/*将src_起始的size个字节复制到 dst_ */void memcpy(void *dst_, const void *src, uint32_t size);/*连续比较以地址a_和地址b_开头的size个字节.相等则返回 0, a>b return 1, a<b return -1;*/
int memcmp(const void *a_, const void *b_, uint32_t size);/*src_ 复制到 dst_*/
char *strcpy(char *dst_, const char *src_);/*返回字符串长度*/
uint32_t strlen(const char *str){;
/*比较两个字符串,若a_中的字符大于b_中的字符调用1,相等返回0,否则返回-1*/
int8_t strcmp(const char *a, const char *b);/*左到右找str中首次出现的 ch 的地址*/
char *strchr(const char *str, const uint8_t ch);/*从后往前找字符串str中首次出现的ch的地址*/
char *strrchr(const char *str, const uint8_t ch);/*将字符串 src 拼接到dst_后,返回拼接的字符串地址*/
char *strcat(char *dst_, const char *str_);/*在字符串str中查找字符ch出现的次数*/
uint32_t strchrs(const char *str, uint8_t ch);#endif /*__LIB_STRING_H*/
lib / string.c
#include "string.h"
#include "global.h"
#include "debug.h"/*将dst_起始的size个字节置为value*/void memset(void *dst_, uint8_t value, uint32_t size){ASSERT(dst_ != NULL);uint8_t *dst = (uint8_t*)dst_;while(size-- > 0){*dst++ = value;}return;}/*将src_起始的size个字节复制到 dst_ */void memcpy(void *dst_, const void *src, uint32_t size){ASSERT(dst_ != NULL && src_ != NULL);uint8_t *dst = dst_;const uint8_t *src = src_;while(size-- > 0){*dst++ = *src++;}}/*连续比较以地址a_和地址b_开头的size个字节.相等则返回 0, a>b return 1, a<b return -1;*/
int memcmp(const void *a_, const void *b_, uint32_t size)
{const char *a = a_;const char *b = b_;ASSERT(a != NULL || b != NULL);while(size-- > 0){if(*a != *b){return (*a > *b) ? 1: -1;}++a;++b;}return 0;
}/*src_ 复制到 dst_*/
char *strcpy(char *dst_, const char *src_)
{ASSERT(dst_ != NULL && src_ != NULL);char *r = dst_;while((*dst_ ++ = *src_ ++));return r;
}/*返回字符串长度*/
uint32_t strlen(const char *str){ASSERT(str != NULL);const char *p = str;while(*p++);return (p - str - 1);
}
/*比较两个字符串,若a_中的字符大于b_中的字符调用1,相等返回0,否则返回-1*/
int8_t strcmp(const char *a, const char *b)
{ASSERT(a != NULL && b != NULL);while(*a != 0 && *a == *b){++a;++b;}//if(*a < *b) return -1;//if(*a > *b) return 1; else return 0;return (*a < *b) ? -1 : (*a > *b);
}/*左到右找str中首次出现的 ch 的地址*/
char *strchr(const char *str, const uint8_t ch)
{ASSERT(str != NULL);while(*str != 0){if(*str == ch){return (char *)str;//需要强制转化为返回值类型。}++str;}return NULL;
}/*从后往前找字符串str中首次出现的ch的地址*/
char *strrchr(const char *str, const uint8_t ch)
{ASSERT(str != NULL);const char *last_char = NULL;while(*str != 0){if(*str == ch){last_char = str;}++str;}return (char *)last_char;
}/*将字符串 src 拼接到dst_后,返回拼接的字符串地址*/
char *strcat(char *dst_, const char *str_)
{ASSERT(dst_ != NULL && src_ != NULL);char *str = dst_;while(*str++);--str;while((*str++ = *src_++));return dst_;
}/*在字符串str中查找字符ch出现的次数*/
uint32_t strchrs(const char *str, uint8_t ch){ASSERT(str != NULL);uint32_t ch_cnt = 0;const char *p = str;while(*p != 0){if(*p == ch){++ch_cnt;}++p;}return ch_cnt;
}
8.4 位图 bitmap 及其函数的实现
8.4.1 位图简介
一个字节有8位,所以位图的一个字节对应8个资源单位。假设是管理内存,每一位都将表示实际物理内存中的 4KB。也就是 1页。如果某位为0,就是可以分配,如果某位为1,就是不可分配。
8.4.2 位图的定义和实现
lib / kernel / bitmap.h
#ifndef __LIB_KERNEL_BITMAP_H
#define __LIB_KERNEL_BITMAP_H
#include "global.h"
#define BITMAP_MASK 1
struct bitmap{uint32_t btmp_bytes_len;/*遍历位图时候是以字节为单位,微操是位。所以此处的位图指针必须是单字节*/uint8_t *bits;
};
void bitmap_init(struct bitmap *btmp);
bool bitmap_scan_test(struct bitmap *btmp, uint32_t bit_idx);
int bitmap_scan(struct bitmap *btmp, uint32_t cnt);
void bitmap_set(struct bitmap *btmp, uint32_t bit_idx, int8_t value);
lib/ kernel/ bitmap.c
#include "bitmap.h"
#include "stdint.h"
#include "string.h"
#include "print.h"
#include "interrupt.h"
#include "debug.h"/*初始化 btmp 位图*/
void bitmap_init(struct bitmap *btmp)
{memset(btmp->bits, 0, btmp->btmp_bytes_len);
}bool bitmap_scan_test(struct bitmap *btmp, uint32_t bit_idx)
{uint32_t byte_idx = bit_idx / 8;//向下取整用于数组索引。uint32_t bit_odd = bit_idx % 8; //取余用索引数组内的位return (btmp->bits[byte_idx] & (BITMAP_MASK << bit_odd));
}int bitmap_scan(struct bitmap *btmp, uint32_t cnt)
{uint32_t idx_byte = 0;/*先字节比较*/while((0xff == btmp->bits[idx_byte]) && (idx_byte < btmp->btmp_bytes_len)){//该字节无空位,去下一个字节++idx_byte;}ASSERT(idx_byte < btmp->btmp_bytes_len);if(idx_byte == btmp->btmp_bytes_len){ //找不到可用空间return -1;}//某字节有空位,则依次查找int idx_bit = 0;while((uint8_t)(BITMAP_MASK << idx_bit & btmp->bits [idx_byte]){++idx_bit;}int bit_idx_start = idx_byte * 8 + idx_bit;if(cnt == 1){return bit_idx_start;}uint32_t bit_left = (btmp->btmp_bytes_len * 8 - bit_idx_start);//记录还有多少位可以判断uint32_t next_bit = bit_idx-start + 1;uint32_t count = 1;bit_idx_start = -1;while (bit_left -- > 0){if(!(bitmap_scan_test(btmp, next_bit))){count++;}else{count = 0;}if(count == cnt){bit_idx_start = next_bit - cnt + 1;break;}next_bit++;}return bit_idx_start;
}
/*将位图bit_idx设置为value*/
void bitmap_set(struct bitmap *btmp, uint32_t bit_idx, int8_t value)
{ASSERT((value == 0) || (value == 1));uint32_t byte_idx = bit_idx / 8;uint32_t bit_odd = bit_idx % 8;if(value){btmp->bits[byte_idx] != (BITMAP_MASK << bit_odd);}else{btmp->bits[byte_idx] &= (BITMAP_MASK << bit_odd);}
}
8.5 内存管理系统
8.5.1 内存池规划
分页机制下有了虚拟、物理这两种地址,们本节所讨论的就是有关这两类地址的内存池规划问题。
创建虚拟内存地址池和物理内存地址池
内存的规划
分为内核内存池和用户内存池。
内存池中的内存单位大小是 4KB.
所以任务都有各自的 4GB,需要为所有任务维护它们自己的虚拟地址池,一个任务一个。
kernel / memory.h
#ifndef __KERNEL_MEMORY_H
#define __KERNEL_MEMORY_H#include "stdint.h"
#include "bitmap.h"/*虚拟地址池,用于虚拟地址管理*/
struct virtual_addr{struct bitmap vaddr_bitmap; // 虚拟地址用到的位图结构uint32_t vaddr_start; // 虚拟地址起始地址
};
extern struct pool kernel_pool, user_pool;
void mem_init(void);#endif
kernel / memory.c
#include "memory.h"
#include "stdint.h"
#include "print.h"#define PG_SIZE 4096/*************** 位图地址 ******************* 因为0xc009f00 是内核主线程栈顶, 0xc009e000 是内核主线程的 pcb* 一个页框大小的位图可表示 128MB 内存,位图位置安排在地址 0xc009a000,* 这样本系统最大支持 4个页框的位图,即 512 MB
*/
#define MEM_BITMAP_BASE 0xc009a000/* 0xc0000000 是内核从虚拟地址 3G 起,0x100000 指跨过低端 1MB 内存,使虚拟地址在逻辑上连续。*/
#define K_HEAP_START 0xc0100000
/* 内存池结构,生成两个实例,用于管理内核和用户内存池*/
struct pool{struct bitmap pool_bitmap; //本内存池用到的位图结构,用于管理物理内存uint32_t phy_addr_start; // 本内存池所管理物理内存的起始地址uint32_t pool_size; // 本内存池字节容量
};
struct pool kernel_pool, user_pool; // 生成内核内存池和用户内存池
struct virtual_addr kernel_vaddr; // 此结构用来给内核分配虚拟地址/*初始化内存池*/
static void mem_pool_init(uint32_t all_mem)
{put_str(" mem_pool_init start\n");uint32_t page_table_size = PG_SIZE * 256;/*页表大小 = 页目录表(1页) + 第0和第768页目录框指向同一个页表(第1个页表)+第 769~1022个页目录项共指向 254 个页表(254页),共 256 个页框*/uint32_t used_mem = page_table_size + 0x100000;//0x100000 为低端 1MB 内存。uint32_t free_mem = all_mem - used_mem;uint16_t all_free_pages = free_mem / PG_SIZE;/*1页为4KB,不管总内存是不是 4k 的倍数,*对于以页为单位的内存分配策略,不足一页的内存不用考虑了*/uint16_t kernel_free_pages = all_free_pages / 2; uint16_t user_free_pages = all_free_pages - kernel_free_pages;/* 为简化位图操作,余数不处理,坏处是这样做会丢内存。好处是不用做内存的越界检查,因为位图表示的内存少于实际物理内存*/uint32_t kbm_length = kernel_free_pages / 8; // Kernel BitMap 的长度,位图中的一位表示一页,以字节为单位uint32_t ubm_length = user_free_pages / 8;// User BitMap 的长度//Kernel pool start,内核内存池的起始地址。uint32_t kp_start = used_mem;//User Pool start, 用户内存池的起始地址uint32_t up_start = kp_start + kernel_free_pages * PG_SIZE;kernel_pool.phy_addr_start = kp_start;user_pool.phy_addr_start = up_start;kernel_pool.pool_size = kernel_free_pages * PG_SIZE;user_pool.pool_size = user_free_pages * PG_SIZE;kernel_pool.pool_bitmap.btmp_bytes_len = kbm_length;user_pool.pool_bitmap.btmp_bytes_len = ubm_length;/********* 内核内存池和用户内存池位图 ************ 位图是全局的数据,长度不固定。* 全局或静态的数组需要在编译时知道其长度,* 而我们需要根据总内存大小算出需要多少字节,* 所以改为指定一块内存来生成位图。***********************************************///内核使用的最高 0xc009f000,这是主线程的栈地址//(内核的大小预计为70KB左右)//32MB内存占有的位图为 2KB//内核内存池的位图先定在 MEM_BITMAP_BASE (0xc009a000) 处kernel_pool.pool_bitmap.bits = (void*)MEM_BITMAP_BASE;/* 用户内存池的位图紧跟在内核内存池位图之后 */user_pool.pool_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length);/********************输出内存池信息**********************/ put_str(" kernel_pool_bitmap_start:");put_int((int)kernel_pool.pool_bitmap.bits); put_str(" kernel_pool_phy_addr_start:"); put_int(kernel_pool.phy_addr_start);put_str("\n"); put_str("user_pool_bitmap_start:");put_int((int)user_pool.pool_bitmap.bits);put_str(" user_pool_phy_addr_start:");put_int(user_pool.phy_addr_start);put_str("\n");/* 将位图置于 0*/bitmap_init(&kernel_pool.pool_bitmap);bitmap_init(&user_pool.pool_bitmap); put_str("bitmap_init2\n");/* 下面初始化内核虚拟地址的位图,按实际物理内存大小生成数组。*/ kernel_vaddr.vaddr_bitmap.btmp_bytes_len = kbm_length; // 用于维护内核堆的虚拟地址,所以要和内核内存池大小一致/* 位图的数组指向一块未使用的内存,目前定位在内核内存池和用户内存池之外*/kernel_vaddr.vaddr_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length + ubm_length);kernel_vaddr.vaddr_start = K_HEAP_START;bitmap_init(&kernel_vaddr.vaddr_bitmap);put_str(" mem_pool_init done\n");
}/*内存管理部分初始化入口*/
void mem_init()
{put_str("mem_init start\n");uint32_t mem_bytes_total = (*(uint32_t *)(0xb00));mem_pool_init(mem_bytes_total);// 初始化内存池put_str("mem_init done\n");
}
还有一点:
这里的 total_mem_bytes 一定要放在这个位置上,恰好是 地址 0xb000。
因为 mem_init() 中要用到这个地址。上面存放的是内存大小。
loader.asm
%include "boot.inc"section loader vstart=LOADER_BASE_ADDRGDT_BASE:dd 0x00000000dd 0x00000000
CODE_DESC:dd 0x0000FFFFdd DESC_CODE_HIGH4
DATA_STACK_DESC:dd 0x0000FFFFdd DESC_DATA_HIGH4
VIDEO_DESC:dd 0x80000007dd DESC_VIDEO_HIGH4
GDT_SIZE equ $-GDT_BASE
GDT_LIMIT equ GDT_SIZE-1
times 60 dq 0;预留60个描述符的空位
;选择子
SELECTOR_CODE equ (0x0001 << 3) + TI_GDT + RPL0
SELECTOR_DATA equ (0x0002 << 3) + TI_GDT + RPL0
SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0;============= 读取内存数
total_mem_bytes dd 0 ;4 保存内存容量
;gdt 指针
gdt_ptr dw GDT_LIMITdd GDT_BASEards_buf times 244 db 0
ards_nr dw 0
/* ...略 */
kernel / init.c
#include "init.h"
#include "print.h"
#include "interrupt.h"
#include "../device/timer.h"
#include "memory.h"/*负责初始化所有模块*/
void init_all()
{put_str("init_all\n");idt_init(); //初始化中断//timer_init();mem_init();
}
kernel / main.c
#include "print.h"
#include "init.h"
#include "debug.h"
int main(void){put_str("I am kernel\n");init_all();//ASSERT(1==2);//asm volatile("sti");while(1);return 0;
}
运行结果: