《操作系统真象还原》第8章

步骤:

1.实现assert断言

2.实现字符串操作函数

3.位图bitmap函数的实现

4.内存管理系统


1.实现assert断言

实现开关中断:

在interrupt.c的第14行添加:

在interrupt.c的末尾添加:

复制代码
 1 /* 开中断并返回开中断前的状态 */
 2 enum intr_status intr_enable(){
 3     enum intr_status old_status;
 4     if (INTR_ON==intr_get_status()){
 5         old_status=INTR_ON;
 6         return old_status;
 7     }
 8     else{
 9         old_status=INTR_OFF;
10         asm volatile("sti");       // 开中断,sti指令将IF位置1
11         return old_status;
12     }
13 }
14 
15 /* 关中断并返回关中断前的状态 */
16 enum intr_status intr_disable(){
17     enum intr_status old_status;
18     if (INTR_ON==intr_get_status()){
19         old_status=INTR_ON;
20         asm volatile("cli":::"memory");
21         return old_status;
22     }
23     else{
24         old_status=INTR_OFF;
25         return old_status;
26     }
27 }
28 
29 /* 将中断状态设置为status */
30 enum intr_status intr_set_status(enum intr_status status){
31     return status&INTR_ON?intr_enable():intr_disable();
32 }
33 
34 /* 获取当前中断状态 */
35 enum intr_status intr_get_status(){
36     uint32_t eflags=0;
37     GET_EFLAGS(eflags);
38     return (EFLAGS_IF&eflags)?INTR_ON:INTR_OFF;
39 }
复制代码

将interrupt.h修改为:

复制代码
 1 #ifndef __KERNEL_INTERRUPT_H
 2 #define __KERNEL_INTERRUPT_H
 3 #include "stdint.h"
 4 typedef void* intr_handler;
 5 void idt_init(void);
 6 
 7 /* 定义中断的两种状态:
 8  * INTR_OFF值为0,表示关中断
 9  * INTR_ON值为1,表示开中断 */
10 enum intr_status{           //中断状态
11     INTR_OFF,
12     INTR_ON
13 };
14 
15 enum intr_status intr_get_status(void);
16 enum intr_status intr_set_status(enum intr_status);
17 enum intr_status intr_enable(void);
18 enum intr_status intr_disable(void);
19 #endif
复制代码

实现ASSERT:

编写kernel/debug.h:

复制代码
 1 #ifndef __KERNEL_DEBUG_H
 2 #define __KERNEL_DEBUG_H
 3 void panic_spin(char* filename,int line,const char* func,const char* condition);
 4 
 5 /**************************** __VA_ARGS__ *******************************
 6  * __VA_ARGS__是预处理器所支持的专用标识符。
 7  * 代表所有与省略号相对应的参数。
 8  * "..."表示定义的宏,其参数可变。*/
 9 #define PANIC(...) panic_spin(__FILE__,__LINE__,__func__,__VA_ARGS__)
10 /***********************************************************************/
11 
12 #ifdef NDEBUG
13     #define ASSERT(CONDITION) ((void)0)
14 #else
15     #define ASSERT(CONDITION) \
16     if (CONDITION){} \
17     /* 符号#让编译器将宏的参数转化为字符串字面量 */ \
18     else {PANIC(#CONDITION);}
19 #endif /* __NDEBUG */
20 
21 #endif /* __KERNEL_DEBUG_H */
复制代码

编写kernel/debug.c:

复制代码
 1 #include "debug.h"
 2 #include "print.h"
 3 #include "interrupt.h"
 4 
 5 /* 打印文件名、行号、函数名、条件并使程序悬停 */
 6 void panic_spin(char* filename,     \
 7         int line,            \
 8         const char* func      \
 9         const char* condition) \
10 {
11     intr_disable();   // 因为有时候会单独调用panic_spin,所以在此处关中断
12     put_str("\n\n\n!!!!! error !!!!!\n");
13     put_str("filename:");put_str(filename);put_str("\n");
14     put_str("ilne:0x");put_int(line);put_str("\n");   
15     put_str("function:");put_str((char*)func);put_str("\n");
16     put_str("condition:");put_str((char*)condition);put_str("\n");
17     while (1);
18 }
复制代码

为了测试ASSERT,咱们在main.c中测试一下:

复制代码
 1 #include "print.h"
 2 #include "init.h"
 3 #include "debug.h"
 4 int main(void)
 5 {    
 6   put_str("Welcome,\nI am kernel!\n");
 7   init_all();
 8   ASSERT(1==2);
 9   //asm volatile("sti");   // 为演示中断处理,在此临时开中断
10   while(1);
11   return 0;
12 }
复制代码

接下来通过makefile来编译,在bochs/下vim makefile:

复制代码
 1 BUILD_DIR = ./build
 2 ENTRY_POINT = 0xc0001500
 3 AS = nasm
 4 CC = gcc
 5 LD = ld
 6 LIB = -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -I device/
 7 ASFLAGS = -f elf
 8 CFLAGS = -Wall -m32 -fno-stack-protector $(LIB) -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes
 9 LDFLAGS =  -m elf_i386 -Ttext $(ENTRY_POINT) -e main -Map $(BUILD_DIR)/kernel.map
10 OBJS = $(BUILD_DIR)/main.o $(BUILD_DIR)/init.o $(BUILD_DIR)/interrupt.o \
11       $(BUILD_DIR)/timer.o $(BUILD_DIR)/kernel.o $(BUILD_DIR)/print.o \
12       $(BUILD_DIR)/debug.o
13 ##############     c代码编译     ###############
14 $(BUILD_DIR)/main.o: kernel/main.c lib/kernel/print.h \
15         lib/stdint.h kernel/init.h
16     $(CC) $(CFLAGS) $< -o $@
17 
18 $(BUILD_DIR)/init.o: kernel/init.c kernel/init.h lib/kernel/print.h \
19         lib/stdint.h kernel/interrupt.h device/timer.h
20     $(CC) $(CFLAGS) $< -o $@
21 
22 $(BUILD_DIR)/interrupt.o: kernel/interrupt.c kernel/interrupt.h \
23         lib/stdint.h kernel/global.h lib/kernel/io.h lib/kernel/print.h
24     $(CC) $(CFLAGS) $< -o $@
25 
26 $(BUILD_DIR)/timer.o: device/timer.c device/timer.h lib/stdint.h\
27         lib/kernel/io.h lib/kernel/print.h
28     $(CC) $(CFLAGS) $< -o $@
29 
30 $(BUILD_DIR)/debug.o: kernel/debug.c kernel/debug.h \
31         lib/kernel/print.h lib/stdint.h kernel/interrupt.h
32     $(CC) $(CFLAGS) $< -o $@
33 
34 
35 ##############    汇编代码编译    ###############
36 $(BUILD_DIR)/kernel.o: kernel/kernel.S
37     $(AS) $(ASFLAGS) $< -o $@
38 
39 $(BUILD_DIR)/print.o: lib/kernel/print.S
40     $(AS) $(ASFLAGS) $< -o $@
41 
42 ##############    链接所有目标文件    #############
43 $(BUILD_DIR)/kernel.bin: $(OBJS)
44     $(LD) $(LDFLAGS) $^ -o $@
45 
46 .PHONY : mk_dir hd clean all
47 
48 mk_dir:
49     if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fi
50 
51 hd:
52     dd if=$(BUILD_DIR)/kernel.bin \
53            of=hd60M.img \
54            bs=512 count=200 seek=9 conv=notrunc
55 
56 clean:
57     cd $(BUILD_DIR) && rm -f  ./*
58 
59 build: $(BUILD_DIR)/kernel.bin
60 
61 all: mk_dir build hd
复制代码

注意:自己根据书本手敲代码是很容易打错的,因为要涉及空格键和Tab键的正确使用。如果你是直接复制的makefile代码,也大概率会因为文章编辑原因导致Tab不正确出现以下现象:

这张图片是if/dd if/cd前面是空格而非tab,你需要将空格删除后敲入一个tab。

这个原因和上方类似,解决方案就是删除第16行前面的空格,再敲入tab。

最后正确的代码如下图:

已经不再飘红了,而且第二张图片中15、16行是对齐的,第16行的-o是标红的。这样应该就可以了。

对了,记得检查全部的路径是否正确。

在bochs中运行检验:

make all
bin/bochs -f bochsrc.disk

 效果如图所示:

perfect!


2.实现字符串操作函数

感觉看C语言就比汇编亲切多了,下面的实现也都挺简单的,我就直接cv了:)

①lib/string.c:

复制代码
  1 #include "string.h"
  2 #include "debug.h"
  3 #include "global.h"
  4 
  5 void memset(void* dst_,uint8_t value,uint32_t size)
  6 {
  7     ASSERT(dst_ != NULL);
  8     uint8_t* dst = (uint8_t*) dst_;
  9     while((size--) > 0)
 10         *(dst++) = value;
 11     return;
 12 }
 13 
 14 void memcpy(void* dst_,const void* src_,uint32_t size)
 15 {
 16     ASSERT(dst_ != NULL && src_ != NULL);
 17     uint8_t* dst = dst_;
 18     const uint8_t* src = src_;
 19     while((size--) > 0)
 20         *(dst++) = *(src++);
 21     return;
 22 }
 23 
 24 int memcmp(const void* a_,const void* b_, uint32_t size)
 25 {
 26     const char* a = a_;
 27     const char* b = b_;
 28     ASSERT(a != NULL || b != NULL);
 29     while((size--) > 0)
 30     {
 31         if(*a != *b)    return (*a > *b) ? 1 : -1;
 32        ++a,++b;
 33     }
 34     return 0;
 35 }
 36 
 37 char* strcpy(char* dsc_,const char* src_)
 38 {
 39     ASSERT(dsc_ != NULL && src_ != NULL);
 40     char* dsc = dsc_;
 41     while((*(dsc_++) = *(src_++) ));
 42     return dsc;     
 43 }
 44 
 45 uint32_t strlen(const char* str)
 46 {
 47     ASSERT(str != NULL);
 48     const char* ptr = str;
 49     while(*(ptr++));
 50     return (ptr - str - 1);
 51 }
 52 
 53 int8_t strcmp(const char* a,const char* b)
 54 {
 55     ASSERT(a != NULL && b != NULL);
 56     while(*a && *a == *b)
 57     {
 58         a++,b++;
 59     }   
 60     return (*a < *b) ? -1 : (*a > *b) ;
 61 }
 62 
 63 char* strchr(const char* str,const char ch)
 64 {
 65     ASSERT(str != NULL);
 66     while(*str)
 67     {
 68         if(*str == ch)    return (char*)str;
 69         ++str;
 70     } 
 71     return NULL;
 72 }
 73 
 74 char* strrchr(const char* str,const uint8_t ch)
 75 {
 76     ASSERT(str != NULL);
 77     char* last_chrptr = NULL;
 78     while(*str != 0)
 79     {
 80         if(ch == *str)    last_chrptr = (char*)str;
 81         str++;
 82     }
 83     return last_chrptr;   
 84 }
 85 
 86 char* strcat(char* dsc_,const char* src_)
 87 {
 88     ASSERT(dsc_ != NULL && src_ != NULL);
 89     char* str = dsc_;
 90     while(*(str++));
 91     str--;
 92     while((*(str++) = *(src_++)) != 0);
 93     return dsc_;
 94 }
 95 
 96 uint32_t strchrs(const char* str,uint8_t ch)
 97 {
 98     ASSERT(str != NULL);
 99     uint32_t ch_cnt = 0;
100     while(*str)
101     {
102         if(*str == ch) ++ch_cnt;
103         ++str;
104     }
105     return ch_cnt;
106 }
复制代码

②lib/string.h:

复制代码
 1 #ifndef __LIB_STRING_H
 2 #define __LIB_STRING_H
 3 
 4 #include "stdint.h"
 5 #define NULL 0
 6 
 7 void memset(void* dst_,uint8_t value,uint32_t size);
 8 void memcpy(void* dst_,const void* src_,uint32_t size);
 9 int memcmp(const void* a_,const void* b_, uint32_t size);
10 char* strcpy(char* dsc_,const char* src_);
11 uint32_t strlen(const char* str);
12 int8_t strcmp(const char* a,const char* b);
13 char* strchr(const char* str,const char ch);
14 char* strrchr(const char* str,const uint8_t ch);
15 char* strcat(char* dsc_,const char* src_);
16 uint32_t strchrs(const char* str,uint8_t ch);
17 #endif
复制代码

③makefile:

OBJS下增加:$(BUILD_DIR)/string.o

$(BUILD_DIR)/main.o下增加:lib/string.h

以及增加:


 3.位图bitmap函数的实现:

感觉也是好理解的,但自己写的话bitmap_scan函数大概实现方法会和书本很不一样。。。

①lib/kernel/bitmap.h:

复制代码
 1 #ifndef __LIB_KERNEL_BITMAP_H
 2 #define __LIB_KERNEL_BITMAP_H
 3 #include "global.h"
 4 #define BITMAP_MASK 1
 5 #define bool int
 6 #define false 0
 7 #define true 1
 8 
 9 struct bitmap {
10    uint32_t btmp_bytes_len;
11 /* 在遍历位图时,整体上以字节为单位,细节上是以位为单位,所以此处位图的指针必须是单字节 */
12    uint8_t* bits;
13 };
14 
15 void bitmap_init(struct bitmap* btmp);
16 bool bitmap_scan_test(struct bitmap* btmp, uint32_t bit_idx);
17 int bitmap_scan(struct bitmap* btmp, uint32_t cnt);
18 void bitmap_set(struct bitmap* btmp, uint32_t bit_idx, int8_t value);
19 #endif
复制代码

似乎又发现了书中的一点纰漏,C语言中是不带bool类型的,所以我们要把bool宏定义成int,才能大胆地使用。

②lib/kernel/bitmap.c:

复制代码
 1 #include "bitmap.h"
 2 #include "stdint.h"
 3 #include "string.h"
 4 #include "print.h"
 5 #include "interrupt.h"
 6 #include "debug.h"
 7 
 8 /* 将位图btmp初始化 */
 9 void bitmap_init(struct bitmap* btmp){
10     memset(btmp->bits,0,btmp->btmp_bytes_len);
11 }
12 
13 /* 判断bit_idx位是否为1,若为1,则返回true,否则返回false */
14 bool bitmap_scan_test(struct bitmap* btmp,uint32_t bit_idx){
15     uint32_t byte_idx=bit_idx/8;   // 向下取整用于索引数组下标
16     uint32_t bit_off=bit_idx%8;   // 取余用于索引数组内的值
17     return (btmp->bits[byte_idx]&(BITMAP_MASK<<bit_off));
18 }
19 
20 /* 在位图中申请连续cnt个位,成功则返回其起始位下标,失败则返回-1 */
21 int bitmap_scan(struct bitmap* btmp,uint32_t cnt){
22     uint32_t idx_byte=0;   // 用于记录空闲位所在的字节
23     /* 逐字节比较,蛮力法 */
24     while ((0xff==btmp->bits[idx_byte]) && (idx_byte<btmp->btmp_bytes_len)){
25     // 1表示已分配,若为0xff,说明该字节内已无空闲位,向下一字节继续找
26     idx_byte++;
27     }
28 
29     ASSERT(idx_byte<btmp->btmp_bytes_len);
30     if (idx_byte==btmp->btmp_bytes_len) {   // 若内存池中找不到可用空间
31         return -1;
32     }
33 
34     /* 若在位图数组范围内的某字节内找到了空闲位
35      * 在该字节内逐位比对,返回空闲位索引 */
36     int idx_bit=0;
37     /* 和btmp->bits[idx_byte]这个字节逐位比对 */
38     while ((uint8_t)(BITMAP_MASK<<idx_bit)&(btmp->bits[idx_byte])){
39     idx_bit++;
40     }
41 
42     int bit_idx_start=idx_byte*8+idx_bit;   // 空闲位在位图内的下标
43     if (cnt==1){
44         return bit_idx_start;
45     }
46     uint32_t bit_left=(btmp->btmp_bytes_len*8-bit_idx_start);
47     // 记录还有多少位可以判断
48     uint32_t next_bit=bit_idx_start+1;
49     uint32_t count=1;   // 用于记录找到的空闲位的个数
50 
51     bit_idx_start=-1;
52     while (bit_left-->0){
53     if (!bitmap_scan_test(btmp,next_bit)){
54         count++;
55     }else{
56         count=0;
57     }
58     if (count==cnt){
59         bit_idx_start=next_bit-cnt+1;
60         break;
61     }
62     next_bit++;
63     }
64     return bit_idx_start;
65 }
66 
67 /* 将位图btmp的bit_idx位设置为value */
68 void bitmap_set(struct bitmap* btmp,uint32_t bit_idx,int8_t value){
69     ASSERT((value==0) || (value==1));
70     uint32_t byte_idx=bit_idx/8;
71     uint32_t bit_off=bit_idx%8;
72 
73     /* 将1移位进行操作 */
74     if (value){
75     btmp->bits[byte_idx]|=(BITMAP_MASK<<bit_off);
76     }
77     else{
78     btmp->bits[byte_idx]&=~(BITMAP_MASK<<bit_off);
79     }
80 }
复制代码

4.内存管理系统

内存池规划:

①kernel/memory.h:

复制代码
 1 #ifndef __KERNEL_MEMORY_H
 2 #define __KERNEL_MEMORY_H
 3 #include "stdint.h"
 4 #include "bitmap.h"
 5 
 6 /* 虚拟地址池,用于虚拟地址管理 */
 7 struct virtual_addr{
 8     struct bitmap vaddr_bitmap;   // 虚拟地址用到了位图结构
 9     uint32_t vaddr_start;   // 虚拟地址起始地址
10 };
11 
12 extern struct pool kernel_pool,user_pool;
13 void mem_init(void);
14 #endif
复制代码

②kernel/memory.c:

复制代码
 1 #include "memory.h"
 2 #include "stdint.h"
 3 #include "print.h"
 4 
 5 #define PG_SIZE 4096
 6 
 7 /********************** 位图地址 **********************/
 8 #define MEM_BITMAP_BASE 0xc009a000
 9 
10 /* 0xc0000000是内核从虚拟地址3G起。0x100000意指跨过低端1MB内存,使虚拟地址在逻辑上连续 */
11 #define K_HEAP_START 0xc0100000
12 
13 /* 内存池结构,生成两个实例用于管理内核内存池和用户内存池 */
14 struct pool{
15     struct bitmap pool_bitmap;   // 本内存池用到的位图结构,用于管理物理内存
16     uint32_t phy_addr_start;   // 本内存池所管理物理内存的起始地址
17     uint32_t pool_size;   // 本内存池字节容量
18 };
19 
20 struct pool kernel_pool,user_pool;   // 生成内核内存池和用户内存池
21 struct virtual_addr kernel_vaddr;   // 此结构用来给内核分配虚拟地址
22 
23 /* 初始化内存池 */
24 static void mem_pool_init(uint32_t all_mem){
25     put_str("   mem_pool_init start\n");
26     uint32_t page_table_size=PG_SIZE*256;
27 
28     uint32_t used_mem=page_table_size+0x100000;
29     uint32_t free_mem=all_mem-used_mem;
30     uint16_t all_free_pages=free_mem/PG_SIZE;
31 
32     uint16_t kernel_free_pages=all_free_pages/2;
33     uint16_t user_free_pages=all_free_pages-kernel_free_pages;
34 
35     /* 为简化位图操作,余数不处理,不用考虑越界检查,但会丢失部分内存 */
36     uint32_t kbm_length=kernel_free_pages/8;   // kernel bitmap的长度。1位管理1页,单位为字节
37     uint32_t ubm_length=user_free_pages/8;   // user bitmap的长度
38 
39     uint32_t kp_start=used_mem;   // KernelLPool,内核内存池的起始地址
40     uint32_t up_start=kp_start+kernel_free_pages*PG_SIZE;   // UserPool,用户内存池的起始地址
41 
42     kernel_pool.phy_addr_start=kp_start;
43     user_pool.phy_addr_start=up_start;
44 
45     kernel_pool.pool_size=kernel_free_pages*PG_SIZE;
46     user_pool.pool_size=user_free_pages*PG_SIZE;
47 
48     kernel_pool.pool_bitmap.btmp_bytes_len=kbm_length;
49     user_pool.pool_bitmap.btmp_bytes_len=ubm_length;
50 
51 
52     /************** 内核内存池和用户内存池位图 ***************
53      * 位图是全局的数据,长度不固定。
54      * 全局或静态的数组需要在编译时知道其长度,
55      * 而我们需要根据总内存大小算出需要多少字节,
56      * 所以改为指定一块内存来生成位图。
57      * ******************************************************/
58     // 内核使用的最高地址是0xc009f000,这是主线程的站地址
59     // 32MB内存占用的位图是2KB,内核内存池位图先定在MEM_BITMAP_BASE(0xc009a000)处
60     kernel_pool.pool_bitmap.bits=(void*)MEM_BITMAP_BASE;
61 
62     /* 用户内存池的位图紧跟在内核内存池位图之后 */
63     user_pool.pool_bitmap.bits=(void*)(MEM_BITMAP_BASE+kbm_length);
64 
65     /***************** 输出内存池信息 ***********************/
66     put_str("   kernel_pool_bitmap_start:");
67     put_int((int)kernel_pool.pool_bitmap.bits);
68     put_str("   kernel_pool_phy_addr_start:");
69     put_int((int)kernel_pool.phy_addr_start);
70     put_str("\n");
71     put_str("   user_pool_bitmap_start:");
72     put_int((int)user_pool.pool_bitmap.bits);
73     put_str("   user_pool_phy_addr_strar:");
74     put_int((int)user_pool.phy_addr_start);
75     put_str("\n");
76 
77     /*将位图置0*/
78     bitmap_init(&kernel_pool.pool_bitmap);
79     bitmap_init(&user_pool.pool_bitmap);
80 
81     /* 下面初始化内核虚拟地址位图,按实际物理内存大小生成数组 */
82     kernel_vaddr.vaddr_bitmap.btmp_bytes_len=kbm_length;
83     // 用于维护内核堆的虚拟地址,所以要和内核内存池大小一致
84     
85     /* 位图的数组指向一块未使用的内存,目前定位在内核内存池和用户内存池之外 */
86     kernel_vaddr.vaddr_bitmap.bits=(void*)(MEM_BITMAP_BASE+kbm_length+ubm_length);
87 
88     kernel_vaddr.vaddr_start=K_HEAP_START;
89     bitmap_init(&kernel_vaddr.vaddr_bitmap);
90     put_str("   mem_pool_init done\n");
91 }
92 
93 /* 内存管理部分初始化入口 */
94 void mem_init(){
95     put_str("mem_init start\n");
96     uint32_t mem_bytes_total=(*(uint32_t*)(0xb00));
97     mem_pool_init(mem_bytes_total);   // 初始化内存池
98     put_str("mem_init done\n");
99 }
复制代码

③makefile:

OBJS下增加:(BUILDDIR)/memory.o(BUILD_DIR)/bitmap.o

$(BUILD_DIR)/main.o下增加:kernel/memory.h

以及增加:

④kernel/main.c、init.c:

就不放出来了,自己修改试试看。

运行结果图如下:

成功打印出了内存池信息。


分配页内存:

在学习这部分内容前我又回到之前的分页部分复习了下,感觉复习后再看这里会好一些。

开工!

①kernel/memory.h:

复制代码
 1 #ifndef __KERNEL_MEMORY_H
 2 #define __KERNEL_MEMORY_H
 3 #include "stdint.h"
 4 #include "bitmap.h"
 5 
 6 /* 内存池标记,用于判断用哪个内存池 */
 7 enum pool_flags{
 8     PF_KERNEL=1,   // 内核内存池
 9     PF_USER=2   // 用户内存池
10 };
11 
12 #define PG_P_1  1   // 存在位
13 #define PG_P_0  0
14 #define PG_RW_R 0   // R/W位,读/执行
15 #define PG_RW_W 2   //        读/写/执行
16 #define PG_US_S 0   // U/S位,系统级
17 #define PG_US_U 4   //        用户级
18 
19 struct virtual_addr{
20     struct bitmap vaddr_bitmap;
21     uint32_t vaddr_start;
22 };
23 
24 extern struct pool kernel_pool,user_pool;
25 void mem_init(void);
26 void* vaddr_get(enum pool_flags pf,uint32_t pg_cnt);
27 uint32_t* pte_ptr(uint32_t vaddr);
28 uint32_t* pde_ptr(uint32_t vaddr);
29 void* palloc(struct pool* m_pool);
30 void page_table_add(void* _vaddr,void* _page_phyaddr);
31 void* malloc_page(enum pool_flags pf,uint32_t pg_cnt);
32 void* get_kernel_pages(uint32_t pg_cnt);
33 void mem_pool_init(uint32_t all_mem);
34 #endif
复制代码

②kernel/memory.c:

复制代码
  1 #include "memory.h"
  2 #include "bitmap.h"
  3 #include "stdint.h"
  4 #include "global.h"
  5 #include "debug.h"
  6 #include "print.h"
  7 #include "string.h"
  8 
  9 #define PG_SIZE 4096
 10 
 11 /************** 位图地址 *****************/
 12 #define MEM_BITMAP_BASE 0xc009a000
 13 
 14 /* 0xc0000000是内核从虚拟地址3G起,0x100000意指跨过低端1MB内存,使虚拟地址在逻辑上连续 */
 15 #define K_HEAP_START 0xc0100000
 16 
 17 #define PDE_IDX(addr) ((addr & 0xffc00000)>>22)   // 得到PDX
 18 #define PTE_IDX(addr) ((addr & 0x003ff000)>>12)   // 得到PTX
 19 
 20 /* 内存池结构,生成两个实例用于管理内核内存池和用户内存池 */
 21 struct pool{
 22     struct bitmap pool_bitmap;   // 本内存池用到的位图结构,用于管理物理内存
 23     uint32_t phy_addr_start;   // 本内存池所管理物理内存的起始地址
 24     uint32_t pool_size;   // 本内存池字节容量
 25 };
 26 
 27 struct pool kernel_pool,user_pool;   // 生成内核内存池和用户内存池
 28 struct virtual_addr kernel_vaddr;   // 此结构用来给内核分配虚拟地址
 29 
 30 /* 在pf表示的虚拟内存池中申请pg_cnt个虚拟页
 31  * 成功则返回虚拟页的起始地址,失败则返回NULL */
 32 void* vaddr_get(enum pool_flags pf,uint32_t pg_cnt){
 33     int vaddr_start=0,bit_idx_start=-1;
 34     uint32_t cnt=0;
 35     if (pf==PF_KERNEL){
 36     bit_idx_start=bitmap_scan(&kernel_vaddr.vaddr_bitmap,pg_cnt);
 37     if (bit_idx_start==-1){
 38         return NULL;
 39     }
 40     while (cnt<pg_cnt){
 41         bitmap_set(&kernel_vaddr.vaddr_bitmap,bit_idx_start+cnt++,1);
 42     }
 43     vaddr_start=kernel_vaddr.vaddr_start+bit_idx_start*PG_SIZE;
 44     }else{
 45     //用户内存池,将来实现用户进程再补充
 46     }
 47     return (void*)vaddr_start;
 48 }
 49 
 50 /* 得到虚拟地址vaddr对应的pte指针 */
 51 uint32_t* pte_ptr(uint32_t vaddr){
 52     /* 先访问到页表自己 + 
 53      * 再用页目录项pde作为pte的索引访问到页表 +
 54      * 再用页表项pte作为页内偏移 */
 55     uint32_t* pte=(uint32_t*)(0xffc00000+\
 56         ((vaddr & 0xffc00000)>>10)+\
 57         PTE_IDX(vaddr)*4);
 58     return pte;
 59 }
 60 
 61 /* 得到虚拟地址vaddr对应的pde指针 */
 62 uint32_t* pde_ptr(uint32_t vaddr){
 63     /* 0xfffff用来访问到页表本身所在的地址 */
 64     uint32_t* pde=(uint32_t*)((0xfffff000)+PDE_IDX(vaddr)*4);
 65     return pde;
 66 }
 67 
 68 /* 在m_pool指向的物理内存池中分配1个物理页,
 69  * 成功则返回页框的物理地址,失败则返回NULL */
 70 void* palloc(struct pool* m_pool){
 71     /* 扫描或设置位图要保证原子操作 */
 72     int bit_idx=bitmap_scan(&m_pool->pool_bitmap,1);   // 找一个物理页面
 73     if (bit_idx==-1){
 74         return NULL;
 75     }
 76     bitmap_set(&m_pool->pool_bitmap,bit_idx,1);   // 将此位bit_idx置为1
 77     uint32_t page_phyaddr=((bit_idx*PG_SIZE)+m_pool->phy_addr_start);
 78     return (void*)page_phyaddr;
 79 }
 80 
 81 /* 页表中添加虚拟地址_vaddr与物理地址_page_phyaddr的映射 */
 82 void page_table_add(void* _vaddr,void* _page_phyaddr){
 83     uint32_t vaddr=(uint32_t)_vaddr,page_phyaddr=(uint32_t)_page_phyaddr;
 84     uint32_t* pde=pde_ptr(vaddr);
 85     uint32_t* pte=pte_ptr(vaddr);
 86 
 87     /***************************** 注意 ****************************
 88      * 执行*pte,会访问到空的pde。所以确保pde创建完成后才嫩执行*pte,
 89      * 否则会引发page_fault。因此在*pde为0时,pte只能在下面else语句块的*pde后面。
 90      **************************************************************/
 91     /* 先在页目录内判断目录项的P位,若为1,则表示该表已存在 */
 92     if (*pde & 0x00000001){   //P位,此处判断目录项是否存在。若存在
 93     ASSERT(!(*pte & 0x00000001));
 94 
 95     if(!(*pte & 0x00000001)){
 96         *pte=(page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
 97     }else{   // 理论上不会执行到这里
 98         PANIC("pte repeat");
 99         *pte=(page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
100     }
101     }else{   // 页目录项不存在,所以要先创建页目录项再创建页表项
102         /* 页表中用到的页框一律从内核空间分配 */
103        uint32_t pde_phyaddr=(uint32_t)palloc(&kernel_pool);
104 
105        *pde=(pde_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
106 
107        /* 分配到的物理页地址pde_phyaddr对应的物理内存清0,
108     * 避免里面的陈旧数据变成了页表项,从而让页表混乱。
109     * 访问到pde对应的物理地址,用pte取高20位即可。
110     * 因为pte基于该pde对应的物理地址内再寻址,
111     * 把低12位置0便是该pde对应的物理页的起始 */
112        memset((void*)((int)pte & 0xfffff000),0,PG_SIZE);
113 
114        ASSERT(!(*pte & 0x00000001));
115        *pte=(page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
116     }
117 }
118 
119 /* 分配pg_cnt个页空间,成功则返回起始虚拟地址,失败则返回NULL */
120 void* malloc_page(enum pool_flags pf,uint32_t pg_cnt){
121     ASSERT(pg_cnt>0 && pg_cnt<3840);
122     /************ malloc_page 的原理是三个动作的合成:**********
123      * 1.通过vaddr_get在虚拟地址内存池中的申请虚拟地址
124      * 2.通过palloc在物理内存池中申请物理页
125      * 3.通过page_table_add将以上得到的虚拟地址和物理地址在页表中完成映射
126      **********************************************************/
127     void* vaddr_start=vaddr_get(pf,pg_cnt);
128     if (vaddr_start==NULL){
129         return NULL;
130     }
131    
132     uint32_t vaddr=(uint32_t)vaddr_start,cnt=pg_cnt;
133     struct pool* mem_pool=(pf & PF_KERNEL)?&kernel_pool:&user_pool;
134 
135     /* 因为虚拟地址是连续的,但物理地址可以是不连续的,所以逐个做映射 */
136     while (cnt-->0){
137     void* page_phyaddr=palloc(mem_pool);
138     if (page_phyaddr==NULL){    // 申请物理内存失败,将已申请的虚拟地址和物理页全部回滚,在将来完成内存回收时再补充
139         return NULL;
140     }
141     page_table_add((void*)vaddr,page_phyaddr);   // 在页表中做映射
142     vaddr+=PG_SIZE;   // 下一个虚拟页
143     }
144     return vaddr_start;
145 }
146 
147 /* 从内核物理内存池中申请1页内存,成功则返回其虚拟地址,失败则返回NULL */
148 void* get_kernel_pages(uint32_t pg_cnt){
149     void* vaddr=malloc_page(PF_KERNEL,pg_cnt);
150     if (vaddr!=NULL){   // 若分配的地址不为空,将页框清0
151     memset(vaddr,0,pg_cnt*PG_SIZE);
152     }
153     return vaddr;
154 }
155 
156 /* 初始化内存池 */
157 void mem_pool_init(uint32_t all_mem){
158     put_str("   mem_pool_init start\n");
159     uint32_t page_table_size=PG_SIZE*256;
160 
161     uint32_t used_mem=page_table_size+0x100000;
162     uint32_t free_mem=all_mem-used_mem;
163     uint16_t all_free_pages=free_mem/PG_SIZE;
164 
165     uint16_t kernel_free_pages=all_free_pages/2;
166     uint16_t user_free_pages=all_free_pages-kernel_free_pages;
167 
168     /* 为简化位图操作,余数不处理,不用考虑越界检查,但会丢失部分内存 */
169     uint32_t kbm_length=kernel_free_pages/8;   // kernel bitmap的长度。1位管理1页,单位为字节
170     uint32_t ubm_length=user_free_pages/8;   // user bitmap的长度
171 
172     uint32_t kp_start=used_mem;   // KernelLPool,内核内存池的起始地址
173     uint32_t up_start=kp_start+kernel_free_pages*PG_SIZE;   // UserPool,用户内存池的起始地址
174 
175     kernel_pool.phy_addr_start=kp_start;
176     user_pool.phy_addr_start=up_start;
177 
178     kernel_pool.pool_size=kernel_free_pages*PG_SIZE;
179     user_pool.pool_size=user_free_pages*PG_SIZE;
180 
181     kernel_pool.pool_bitmap.btmp_bytes_len=kbm_length;
182     user_pool.pool_bitmap.btmp_bytes_len=ubm_length;
183 
184 
185     /************** 内核内存池和用户内存池位图 ***************
186      * 位图是全局的数据,长度不固定。
187      * 全局或静态的数组需要在编译时知道其长度,
188      * 而我们需要根据总内存大小算出需要多少字节,
189      * 所以改为指定一块内存来生成位图。
190      * ******************************************************/
191     // 内核使用的最高地址是0xc009f000,这是主线程的站地址
192     // 32MB内存占用的位图是2KB,内核内存池位图先定在MEM_BITMAP_BASE(0xc009a000)处
193     kernel_pool.pool_bitmap.bits=(void*)MEM_BITMAP_BASE;
194 
195     /* 用户内存池的位图紧跟在内核内存池位图之后 */
196     user_pool.pool_bitmap.bits=(void*)(MEM_BITMAP_BASE+kbm_length);
197 
198     /***************** 输出内存池信息 ***********************/
199     put_str("   kernel_pool_bitmap_start:");
200     put_int((int)kernel_pool.pool_bitmap.bits);
201     put_str("   kernel_pool_phy_addr_start:");
202     put_int((int)kernel_pool.phy_addr_start);
203     put_str("\n");
204     put_str("   user_pool_bitmap_start:");
205     put_int((int)user_pool.pool_bitmap.bits);
206     put_str("   user_pool_phy_addr_strar:");
207     put_int((int)user_pool.phy_addr_start);
208     put_str("\n");
209 
210     /*将位图置0*/
211     bitmap_init(&kernel_pool.pool_bitmap);
212     bitmap_init(&user_pool.pool_bitmap);
213 
214     /* 下面初始化内核虚拟地址位图,按实际物理内存大小生成数组 */
215     kernel_vaddr.vaddr_bitmap.btmp_bytes_len=kbm_length;
216     // 用于维护内核堆的虚拟地址,所以要和内核内存池大小一致
217 
218     /* 位图的数组指向一块未使用的内存,目前定位在内核内存池和用户内存池之外 */
219     kernel_vaddr.vaddr_bitmap.bits=(void*)(MEM_BITMAP_BASE+kbm_length+ubm_length);
220 
221     kernel_vaddr.vaddr_start=K_HEAP_START;
222     bitmap_init(&kernel_vaddr.vaddr_bitmap);
223     put_str("   mem_pool_init done\n");
224 }
225 
226 /* 内存管理部分初始化入口 */
227 void mem_init(){
228     put_str("mem_init start\n");
229     uint32_t mem_bytes_total=(*(uint32_t*)(0xb00));
230     mem_pool_init(mem_bytes_total);   // 初始化内存池
231     put_str("mem_init done\n");
232 }
复制代码

感觉函数前面不能写static,也就是不要用静态函数,否则make all时会报错。

③kernel/main.c:

复制代码
 1 #include "print.h"
 2 #include "init.h"
 3 #include "memory.h"
 4 int main(void)
 5 {    
 6     put_str("Welcome,\nI am kernel!\n");
 7     init_all();
 8   
 9     void* addr=get_kernel_pages(3);
10     put_str("\n get_kernel_page start vaddr is ");
11     put_int((uint32_t)addr);
12     put_str('\n');
13 
14     while(1);
15     return 0;
16 }
复制代码

④kernel/init.c:

复制代码
 1 #include "init.h"
 2 #include "print.h"
 3 #include "interrupt.h"
 4 #include "../device/timer.h"   //相对路径
 5 #include "memory.h"
 6 
 7 /*负责初始化所有模块*/
 8 void init_all(){
 9     put_str("init_all\n");
10     idt_init();   // 初始化中断
11     timer_init(); // 初始化PIT
12     mem_init();
13 }
复制代码

⑤makefile:

复制代码
 1 BUILD_DIR = ./build
 2 ENTRY_POINT = 0xc0001500
 3 AS = nasm
 4 CC = gcc
 5 LD = ld
 6 LIB = -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -I device/
 7 ASFLAGS = -f elf
 8 CFLAGS = -Wall -m32 -fno-stack-protector $(LIB) -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes
 9 LDFLAGS =  -m elf_i386 -Ttext $(ENTRY_POINT) -e main -Map $(BUILD_DIR)/kernel.map
10 OBJS = $(BUILD_DIR)/main.o $(BUILD_DIR)/init.o $(BUILD_DIR)/interrupt.o \
11       $(BUILD_DIR)/timer.o $(BUILD_DIR)/kernel.o $(BUILD_DIR)/print.o \
12       $(BUILD_DIR)/debug.o $(BUILD_DIR)/string.o $(BUILD_DIR)/memory.o \
13       $(BUILD_DIR)/bitmap.o
14 ##############     c代码编译     ###############
15 $(BUILD_DIR)/main.o: kernel/main.c lib/kernel/print.h \
16         lib/stdint.h kernel/init.h lib/string.h kernel/memory.h
17     $(CC) $(CFLAGS) $< -o $@
18 
19 $(BUILD_DIR)/init.o: kernel/init.c kernel/init.h lib/kernel/print.h \
20         lib/stdint.h kernel/interrupt.h device/timer.h kernel/memory.h
21     $(CC) $(CFLAGS) $< -o $@
22 
23 $(BUILD_DIR)/interrupt.o: kernel/interrupt.c kernel/interrupt.h \
24         lib/stdint.h kernel/global.h lib/kernel/io.h lib/kernel/print.h
25     $(CC) $(CFLAGS) $< -o $@
26 
27 $(BUILD_DIR)/timer.o: device/timer.c device/timer.h lib/stdint.h\
28         lib/kernel/io.h lib/kernel/print.h
29     $(CC) $(CFLAGS) $< -o $@
30 
31 $(BUILD_DIR)/debug.o: kernel/debug.c kernel/debug.h \
32         lib/kernel/print.h lib/stdint.h kernel/interrupt.h
33     $(CC) $(CFLAGS) $< -o $@
34 
35 $(BUILD_DIR)/string.o: lib/string.c lib/string.h \
36     kernel/debug.h kernel/global.h
37     $(CC) $(CFLAGS) $< -o $@
38 
39 $(BUILD_DIR)/memory.o: kernel/memory.c kernel/memory.h \
40     lib/stdint.h lib/kernel/bitmap.h kernel/debug.h lib/string.h
41     $(CC) $(CFLAGS) $< -o $@
42 
43 $(BUILD_DIR)/bitmap.o: lib/kernel/bitmap.c lib/kernel/bitmap.h \
44     lib/string.h kernel/interrupt.h lib/kernel/print.h kernel/debug.h
45     $(CC) $(CFLAGS) $< -o $@
46 
47 ##############    汇编代码编译    ###############
48 $(BUILD_DIR)/kernel.o: kernel/kernel.S
49     $(AS) $(ASFLAGS) $< -o $@
50 
51 $(BUILD_DIR)/print.o: lib/kernel/print.S
52     $(AS) $(ASFLAGS) $< -o $@
53 
54 ##############    链接所有目标文件    #############
55 $(BUILD_DIR)/kernel.bin: $(OBJS)
56     $(LD) $(LDFLAGS) $^ -o $@
57 
58 .PHONY : mk_dir hd clean all
59 
60 mk_dir:
61     if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fi
62 
63 hd:
64     dd if=$(BUILD_DIR)/kernel.bin \
65            of=hd60M.img \
66            bs=512 count=200 seek=9 conv=notrunc
67 
68 clean:
69     cd $(BUILD_DIR) && rm -f  ./*
70 
71 build: $(BUILD_DIR)/kernel.bin
72 
73 all: mk_dir build hd
复制代码

看看结果:

吼吼,xdm成啦!


在本章的内存管理部分,我们重点看看memory.c中的代码实现:

首先我们需要知道,内存分为用户内存和内核内存,用内存池分别管理它们。内存池中包含位图(每一位管理一个页框)、物理内存起始地址、内存池字节数(大小)、互斥锁。

当然,还有一个管理内核虚拟地址的数据结构,拥有位图和虚拟内存起始地址。

1.内存初始化——mem_init()

(1)初始化内存池——mem_pool_init()——内核/用户内存位图的初始化+内核/用户物理内存的起始地址和大小;内核虚拟内存初始化。

其中,内核物理地址=页表总大小(256页*4KB)+0x100000,用户物理地址=内核物理地址+内核页框总大小。内核虚拟地址为K_HEAP_START=0xc0100000。

2.从内核物理内存池中申请若干页内存——get_kernel_pages(pg_cnt),返回虚拟地址:

(1)因为访问到共享资源,获取锁。

(2)申请pg_cnt个页框——malloc_page()——考虑到可能申请多个页,这些页在虚拟地址上是连续的,而物理地址上却不一定连续,因此:

  ①得到虚拟内存起始地址vaddr_get():位图置1,虚拟内存起始地址增加;返回虚拟地址。

  ②在物理内存池中逐一分配物理页palloc():位图置1,物理内存起始地址增加;返回物理地址。

  ③将虚拟地址与物理页框作映射page_table_add()。(当然,要借助页目录和页表)

(3)释放锁。


参考博客:

posted @   Hell0er  阅读(177)  评论(0编辑  收藏  举报
编辑推荐:
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示