《操作系统真象还原》第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下增加:(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)释放锁。
参考博客:
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库