《操作系统真象还原》第12章
步骤:
1.系统调用的实现
2.让用户进程“说话”
3.完善堆内存管理
1.系统调用的实现
①修改kernel/interruput.c,其实就是修改一下常量IDT_DESC_CNT和idt_desc_init()函数:
1 #define IDT_DESC_CNT 0x81 // 目前总共支持的中断数
2
3 /* 初始化中断描述符表 */
4 static void idt_desc_init(void){
5 int i,lastindex=IDT_DESC_CNT-1;
6 for (i=0;i<IDT_DESC_CNT;i++){
7 make_idt_desc(&idt[i],IDT_DESC_ATTR_DPL0,intr_entry_table[i]);
8 }
9 /* 单独处理系统调用,系统调用对应的中断门dpl为3,
10 * 中断处理程序为单独的syscall_handler */
11 make_idt_desc(&idt[lastindex],IDT_DESC_ATTR_DPL3,syscall_handler);
12 put_str(" idt_desc_init done\n");
13 }
②增加lib/user/syscall.c程序:
1 #include "syscall.h"
2
3 /* 无参数的系统调用 */
4 #define _syscall0(NUMBER) ({ \
5 int retval; \
6 asm volatile ( \
7 "int $0x80" \
8 : "=a"(retval) \
9 : "a"(NUMBER) \
10 : "memory"); \
11 retval; \
12 })
13
14 /* 一个参数的系统调用 */
15 #define _syscall1(NUMBER,ARG1) ({ \
16 int retval; \
17 asm volatile ( \
18 "int $0x80" \
19 : "=a"(retval) \
20 : "a"(NUMBER),"b"(ARG1) \
21 : "memory"); \
22 retval; \
23 })
24
25 #define _syscall2(NUMBER,ARG1,ARG2) ({ \
26 int retval; \
27 asm volatile ( \
28 "int $0x80" \
29 : "=a"(retval) \
30 : "a"(NUMBER),"b"(ARG1),"c"(ARG2) \
31 : "memory"); \
32 retval; \
33 })
34
35 #define _syscall3(NUMBER,ARG1,ARG2,ARG3)({ \
36 int retval; \
37 asm volatile ( \
38 "int $0x80" \
39 : "=a"(retval) \
40 : "a"(NUMBER),"b"(ARG1),"c"(ARG2),"d"(ARG3) \
41 : "memory"); \
42 retval; \
43 })
44
45 uint32_t getpid(void)
46 {
47 return _syscall0(SYS_GETPID);
48 }
③lib/user/syscall.h:
1 #ifndef __LIB_USER_SYSCALL_H
2 #define __LIB_USER_SYSCALL_H
3 #include "stdint.h"
4 enum SYSCALL_NR{
5 SYS_GETPID
6 };
7 uint32_t getpid(void);
8 #endif
④修改kernel/kernel.S,在末尾增加0x80号中断处理例程:
1 ;;;;;;;;;;;;;;; 0x80号中断 ;;;;;;;;;;;;;
2 [bits 32]
3 extern syscall_table
4 section .text
5 global syscall_handler
6 syscall_handler:
7 ;1.保存上下文环境
8 push 0 ; ERROR_CODE
9 push ds
10 push es
11 push fs
12 push gs
13 pushad ; PUSHAD压入32位寄存器
14
15
16 push 0x80 ; VEC_NO
17
18 ; 2.为系统调用子功能传入参数
19 push edx
20 push ecx
21 push ebx
22
23 ; 3.调用子功能处理函数
24 call [syscall_table+eax*4]
25 add esp,12 ; 跨过上面3个参数
26
27 ; 4.将call调用后的返回值存入当前内核栈中eax的位置
28 mov [esp+8*4],eax
29 jmp intr_exit ; intr_exit返回,恢复上下文
⑤增加userprog/syscall-init.c:
1 #include "syscall-init.h"
2 #include "syscall.h"
3 #include "stdint.h"
4 #include "print.h"
5 #include "interrupt.h"
6 #include "thread.h"
7 #define syscall_nr 32
8 typedef void* syscall;
9 syscall syscall_table[syscall_nr];
10
11 /* 返回当前任务的pid */
12 uint32_t sys_getpid(void){
13 return running_thread()->pid;
14 }
15
16 /* 初始化系统调用 */
17 void syscall_init(void){
18 put_str("syscall_init start\n");
19 syscall_table[SYS_GETPID]=sys_getpid;
20 put_str("syscall_init done\n");
21 }
⑥增加userprog/syscall-init.h:
1 #ifndef __USERPROG_SYSCALL_INIT_H
2 #define __USERPROG_SYSCALL_INIT_H
3 #include "stdint.h"
4
5 uint32_t sys_getpid(void);
6 void syscall_init(void);
7
8 #endif
⑦修改thread/thread.c,增加分配pid的函数allocate_pid()并修改thread_init()从而能对pid上锁:
1 /* 分配pid */
2 static pid_t allocate_pid(void){
3 static pid_t next_pid=0;
4 lock_acquire(&pid_lock);
5 ++next_pid;
6 lock_release(&pid_lock);
7 return next_pid;
8 }
9
10 /* 初始化线程环境 */
11 void thread_init(void){
12 put_str("thread_init start\n");
13 list_init(&thread_ready_list);
14 list_init(&thread_all_list);
15 lock_init(&pid_lock);
16 /* 将当前main函数创建为线程 */
17 make_main_thread();
18 put_str("thread_init done\n");
19 }
⑧你可能会问pid是哪儿来的啊,当然是要在thread/thread.h中定义一下咯:
⑨还要修改kernel/main.c:

1 #include "print.h"
2 #include "init.h"
3 #include "thread.h"
4 #include "interrupt.h"
5 #include "console.h"
6 #include "ioqueue.h"
7 #include "process.h"
8 #include "syscall.h"
9 #include "syscall-init.h"
10
11 void k_thread_a(void*);
12 void k_thread_b(void*);
13 void u_prog_a(void);
14 void u_prog_b(void);
15 int prog_a_pid=0,prog_b_pid=0;
16
17 int main(void){
18 put_str("Welcome,\nI am kernel!\n");
19 init_all();
20
21 process_execute(u_prog_a,"user_prog_a");
22 process_execute(u_prog_b,"user_prog_b");
23
24 intr_enable();
25 console_put_str(" main_pid:0x");
26 console_put_int(sys_getpid());
27 console_put_char('\n');
28
29 thread_start("k_thread_a",31,k_thread_a,"argA ");
30 thread_start("k_thread_b",31,k_thread_b,"argB ");
31
32 while(1);
33 return 0;
34 }
35
36 void k_thread_a(void* arg){
37 char* para = arg;
38 console_put_str(" thread_a_pid:0x");
39 console_put_int(sys_getpid());
40 console_put_char('\n');
41 console_put_str(" prog_a_pid:0x");
42 console_put_int(prog_a_pid);
43 console_put_char('\n');
44 while(1);
45 }
46
47 void k_thread_b(void* arg){
48 char* para = arg;
49 console_put_str(" thread_b_pid:0x");
50 console_put_int(sys_getpid());
51 console_put_char('\n');
52 console_put_str(" prog_b_pid:0x");
53 console_put_int(prog_b_pid);
54 console_put_char('\n');
55 while(1);
56 }
57
58 void u_prog_a(void){
59 prog_a_pid=getpid();
60 while(1);
61 }
62
63 void u_prog_b(void){
64 prog_b_pid=getpid();
65 while(1);
66 }
我们将进程和线程的创建顺序换了,尽量使得在进程执行getpid()时线程还没将变量prog_[a/b]_pid输出,否则会导致打印的pid为=0。
对了,别忘记修改makefile。
你以为这就完了?让我们来看看结果:
嘶~是不是有一点哈人,猜猜为什么?
对了,我们还没有初始化syscall——在kernel/init.c中添加两句:
#include "syscall-init.h"
syscall_init(); // 初始化系统调用
哎,我看到的第一眼真的是差点吓死,还以为又出什么隐藏bug了。(作者不提我就不记得是吧。。。)对于这个问题,如果你在进程中调用了console_put_xxx()函数也会出现,因为在进程中gs=0,是不允许使用显存的记得吗。
修改后再看看运行效果:
从上到下显示为主线程的pid=0x1,线程pthread_a的pid=0x4,进程prog_a的pid=0x2,线程pthread_b的pid=0x5,进程prog_b的pid=0x3。根据pid的值可以看出,任务创建的顺序和代码顺序是一样的。
至于用栈传递参数。就不贴出来了,大家自己尝试一下吧:)
2.让用户程序“说话”
①修改lib/user/syscall.h:

1 #ifndef __LIB_USER_SYSCALL_H
2 #define __LIB_USER_SYSCALL_H
3 #include "stdint.h"
4 enum SYSCALL_NR{
5 SYS_GETPID,
6 SYS_WRITE
7 };
8 uint32_t getpid(void);
9 uint32_t write(char* str);
10 #endif
②修改lib/user/syscall.c:

1 #include "syscall.h"
2
3 /* 无参数的系统调用 */
4 #define _syscall0(NUMBER) ({ \
5 int retval; \
6 asm volatile ( \
7 "int $0x80" \
8 : "=a"(retval) \
9 : "a"(NUMBER) \
10 : "memory"); \
11 retval; \
12 })
13
14 /* 一个参数的系统调用 */
15 #define _syscall1(NUMBER,ARG1) ({ \
16 int retval; \
17 asm volatile ( \
18 "int $0x80" \
19 : "=a"(retval) \
20 : "a"(NUMBER),"b"(ARG1) \
21 : "memory"); \
22 retval; \
23 })
24
25 #define _syscall2(NUMBER,ARG1,ARG2) ({ \
26 int retval; \
27 asm volatile ( \
28 "int $0x80" \
29 : "=a"(retval) \
30 : "a"(NUMBER),"b"(ARG1),"c"(ARG2) \
31 : "memory"); \
32 retval; \
33 })
34
35 #define _syscall3(NUMBER,ARG1,ARG2,ARG3)({ \
36 int retval; \
37 asm volatile ( \
38 "int $0x80" \
39 : "=a"(retval) \
40 : "a"(NUMBER),"b"(ARG1),"c"(ARG2),"d"(ARG3) \
41 : "memory"); \
42 retval; \
43 })
44
45 /* 获取任务pid */
46 uint32_t getpid(void){
47 return _syscall0(SYS_GETPID);
48 }
49
50 /* 打印字符串str */
51 uint32_t write(char* str){
52 return _syscall1(SYS_WRITE,str);
53 }
③修改userprog/syscall-init.h:

1 #ifndef __USERPROG_SYSCALL_INIT_H
2 #define __USERPROG_SYSCALL_INIT_H
3 #include "stdint.h"
4
5 uint32_t sys_getpid(void);
6 uint32_t sys_write(char* str);
7 void syscall_init(void);
8
9 #endif
④修改userprog/syscall-init.c:

1 #include "syscall-init.h"
2 #include "syscall.h"
3 #include "stdint.h"
4 #include "print.h"
5 #include "thread.h"
6 #include "console.h"
7 #include "string.h"
8
9 #define syscall_nr 32
10 typedef void* syscall;
11 syscall syscall_table[syscall_nr];
12
13 /* 返回当前任务的pid */
14 uint32_t sys_getpid(void){
15 return running_thread()->pid;
16 }
17
18 /* 打印字符串str(未实现文件系统的版本)*/
19 uint32_t sys_write(char* str){
20 console_put_str(str);
21 return strlen(str);
22 }
23
24 /* 初始化系统调用 */
25 void syscall_init(void){
26 put_str("syscall_init start\n");
27 syscall_table[SYS_GETPID]=sys_getpid;
28 syscall_table[SYS_WRITE]=sys_write;
29 put_str("syscall_init done\n");
30 }
⑤增加lib/stdio.h:
1 #ifndef __LIB__STDIO_H
2 #define __LIB__STDIO_H
3 #include "stdint.h"
4
5 typedef void* va_list;
6 uint32_t vsprintf(char* str,const char* format,va_list ap);
7 uint32_t sprintf(char* buf,const char* format, ...);
8 uint32_t printf(const char* str, ...);
9
10 #endif
⑥增加lib/stdio.c:
1 #include "stdio.h"
2 #include "interrupt.h"
3 #include "global.h"
4 #include "string.h"
5 #include "syscall.h"
6 #include "print.h"
7
8 #define va_start(ap,v) ap=(va_list)&v // 把ap指向第一个固定参数v
9 #define va_arg(ap,t) *((t*)(ap +=4)) // ap指向下一个参数并返回其值
10 #define va_end(ap) ap = NULL // 清除ap
11
12 /* 将整形转换成字符 */
13 static void itoa(uint32_t value,char** buf_ptr_addr,uint8_t base)
14 {
15 uint32_t m=value%base;
16 uint32_t i=value/base; //除数为0即最高位了 输出即可 没到零继续即可
17 if (i){
18 itoa(i,buf_ptr_addr,base);
19 }
20 if (m < 10){
21 *((*buf_ptr_addr)++)=m+'0';
22 }else{
23 *((*buf_ptr_addr)++)=m+'A'-10;
24 }
25 }
26
27 uint32_t vsprintf(char* str,const char* format,va_list ap)
28 {
29 char* buf_ptr=str;
30 const char* index_ptr=format;
31 char index_char=*index_ptr;
32 int32_t arg_int;
33 char* arg_str;
34 while(index_char){
35 if(index_char!='%'){
36 *(buf_ptr++)= index_char;
37 index_char=*(++index_ptr);
38 continue;
39 }
40 index_char=*(++index_ptr);
41 switch (index_char){
42 case 's':
43 arg_str=va_arg(ap,char*);
44 strcpy(buf_ptr,arg_str);
45 buf_ptr+=strlen(arg_str);
46 index_char=*(++index_ptr);
47 break;
48 case 'x':
49 arg_int=va_arg(ap,int);
50 itoa(arg_int,&buf_ptr,16);
51 index_char=*(++index_ptr);
52 break;
53 case 'd':
54 arg_int=va_arg(ap,int);
55 if(arg_int<0){
56 arg_int=0-arg_int;
57 *(buf_ptr++)='-';
58 }
59 itoa(arg_int,&buf_ptr,10);
60 index_char=*(++index_ptr);
61 break;
62 case 'c':
63 *(buf_ptr++)=va_arg(ap,char);
64 index_char=*(++index_ptr);
65 }
66 }
67 return strlen(str);
68 }
69
70 uint32_t printf(const char* format, ...)
71 {
72 va_list args;
73 uint32_t retval;
74 va_start(args,format); // 使args指向format
75 char buf[1024]={0}; // 用于存储拼接后的字符串
76 retval=vsprintf(buf,format,args);
77 va_end(args);
78 write(buf);
79 return retval;
80 }
此时发现.h中声明的sprintf()并没在书中本章作具体实现,反正也没用到,可能到后面再实现吧。
⑦修改kernel/main.c:

1 #include "print.h"
2 #include "init.h"
3 #include "thread.h"
4 #include "interrupt.h"
5 #include "console.h"
6 #include "ioqueue.h"
7 #include "process.h"
8 #include "syscall.h"
9 #include "syscall-init.h"
10
11 void k_thread_a(void*);
12 void k_thread_b(void*);
13 void u_prog_a(void);
14 void u_prog_b(void);
15 int prog_a_pid=0,prog_b_pid=0;
16
17 int main(void){
18 put_str("Welcome,\nI am kernel!\n");
19 init_all();
20
21 process_execute(u_prog_a,"user_prog_a");
22 process_execute(u_prog_b,"user_prog_b");
23
24 console_put_str(" I'm main, my pid:0x");
25 console_put_int(sys_getpid());
26 console_put_char('\n');
27
28 intr_enable();
29 thread_start("k_thread_a",31,k_thread_a,"argA ");
30 thread_start("k_thread_b",31,k_thread_b,"argB ");
31
32 while(1);
33 return 0;
34 }
35
36 void k_thread_a(void* arg){
37 char* para=arg;
38 console_put_str(" I'm thread_a, my pid:0x");
39 console_put_int(sys_getpid());
40 console_put_char('\n');
41 while(1);
42 }
43
44 void k_thread_b(void* arg){
45 char* para=arg;
46 console_put_str(" I'm thread_b, my pid:0x");
47 console_put_int(sys_getpid());
48 console_put_char('\n');
49 while(1);
50 }
51
52 void u_prog_a(void){
53 char* name="prog_a";
54 printf(" I'm %s, my pid:%d%c",name,getpid(),'\n');
55 while(1);
56 }
57
58 void u_prog_b(void){
59 char* name="prog_b";
60 printf(" I'm %s, my pid:%d%c",name,getpid(),'\n');
61 while(1);
62 }
⑧修改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/ -I thread/ -I userprog/
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)/switch.o $(BUILD_DIR)/debug.o $(BUILD_DIR)/string.o \
13 $(BUILD_DIR)/memory.o $(BUILD_DIR)/bitmap.o $(BUILD_DIR)/thread.o \
14 $(BUILD_DIR)/list.o $(BUILD_DIR)/sync.o $(BUILD_DIR)/console.o \
15 $(BUILD_DIR)/keyboard.o $(BUILD_DIR)/ioqueue.o $(BUILD_DIR)/tss.o \
16 $(BUILD_DIR)/process.o $(BUILD_DIR)/syscall.o $(BUILD_DIR)/syscall-init.o \
17 $(BUILD_DIR)/stdio.o
18 ############## c代码编译 ###############
19 $(BUILD_DIR)/main.o: kernel/main.c lib/kernel/print.h \
20 lib/stdint.h kernel/init.h lib/string.h kernel/memory.h \
21 thread/thread.h kernel/interrupt.h device/console.h \
22 device/keyboard.h device/ioqueue.h userprog/process.h \
23 lib/user/syscall.h userprog/syscall-init.h lib/stdio.h
24 $(CC) $(CFLAGS) $< -o $@
25
26 $(BUILD_DIR)/init.o: kernel/init.c kernel/init.h lib/kernel/print.h \
27 lib/stdint.h kernel/interrupt.h device/timer.h kernel/memory.h \
28 thread/thread.h device/console.h device/keyboard.h userprog/tss.h \
29 userprog/syscall-init.h
30 $(CC) $(CFLAGS) $< -o $@
31
32 $(BUILD_DIR)/interrupt.o: kernel/interrupt.c kernel/interrupt.h \
33 lib/stdint.h kernel/global.h lib/kernel/io.h lib/kernel/print.h
34 $(CC) $(CFLAGS) $< -o $@
35
36 $(BUILD_DIR)/timer.o: device/timer.c device/timer.h lib/kernel/io.h \
37 lib/kernel/print.h kernel/interrupt.h thread/thread.h kernel/debug.h
38 $(CC) $(CFLAGS) $< -o $@
39
40 $(BUILD_DIR)/debug.o: kernel/debug.c kernel/debug.h \
41 lib/kernel/print.h lib/stdint.h kernel/interrupt.h
42 $(CC) $(CFLAGS) $< -o $@
43
44 $(BUILD_DIR)/string.o: lib/string.c lib/string.h \
45 kernel/debug.h kernel/global.h
46 $(CC) $(CFLAGS) $< -o $@
47
48 $(BUILD_DIR)/memory.o: kernel/memory.c kernel/memory.h \
49 lib/stdint.h lib/kernel/bitmap.h kernel/debug.h lib/string.h \
50 thread/sync.h thread/thread.h
51 $(CC) $(CFLAGS) $< -o $@
52
53 $(BUILD_DIR)/bitmap.o: lib/kernel/bitmap.c lib/kernel/bitmap.h kernel/global.h \
54 lib/string.h kernel/interrupt.h lib/kernel/print.h kernel/debug.h
55 $(CC) $(CFLAGS) $< -o $@
56
57 $(BUILD_DIR)/thread.o: thread/thread.c thread/thread.h \
58 lib/stdint.h lib/string.h kernel/global.h kernel/memory.h \
59 kernel/debug.h kernel/interrupt.h lib/kernel/print.h userprog/process.h
60 $(CC) $(CFLAGS) $< -o $@
61
62 $(BUILD_DIR)/list.o: lib/kernel/list.c lib/kernel/list.h \
63 kernel/interrupt.h lib/stdint.h kernel/debug.h
64 $(CC) $(CFLAGS) $< -o $@
65
66 $(BUILD_DIR)/sync.o: thread/sync.c thread/sync.h \
67 lib/stdint.h thread/thread.h kernel/debug.h kernel/interrupt.h
68 $(CC) $(CFLAGS) $< -o $@
69
70 $(BUILD_DIR)/console.o: device/console.c device/console.h \
71 lib/kernel/print.h thread/sync.h
72 $(CC) $(CFLAGS) $< -o $@
73
74 $(BUILD_DIR)/keyboard.o: device/keyboard.c device/keyboard.h \
75 lib/kernel/print.h lib/kernel/io.h kernel/interrupt.h \
76 kernel/global.h lib/stdint.h device/ioqueue.h
77 $(CC) $(CFLAGS) $< -o $@
78
79 $(BUILD_DIR)/ioqueue.o: device/ioqueue.c device/ioqueue.h \
80 kernel/interrupt.h kernel/global.h kernel/debug.h
81 $(CC) $(CFLAGS) $< -o $@
82
83 $(BUILD_DIR)/tss.o: userprog/tss.c userprog/tss.h \
84 kernel/global.h thread/thread.h lib/kernel/print.h
85 $(CC) $(CFLAGS) $< -o $@
86
87 $(BUILD_DIR)/process.o: userprog/process.c userprog/process.h \
88 lib/string.h kernel/global.h kernel/memory.h lib/kernel/print.h \
89 thread/thread.h kernel/interrupt.h kernel/debug.h device/console.h
90 $(CC) $(CFLAGS) $< -o $@
91
92 $(BUILD_DIR)/syscall.o: lib/user/syscall.c lib/user/syscall.h \
93 lib/stdint.h
94 $(CC) $(CFLAGS) $< -o $@
95
96 $(BUILD_DIR)/syscall-init.o: userprog/syscall-init.c userprog/syscall-init.h \
97 lib/user/syscall.h lib/stdint.h lib/kernel/print.h device/console.h \
98 thread/thread.h lib/string.h
99 $(CC) $(CFLAGS) $< -o $@
100
101 $(BUILD_DIR)/stdio.o: lib/stdio.c lib/stdio.h lib/stdint.h lib/string.h \
102 lib/user/syscall.h
103 $(CC) $(CFLAGS) $< -o $@
104
105 ############## 汇编代码编译 ###############
106 $(BUILD_DIR)/kernel.o: kernel/kernel.S
107 $(AS) $(ASFLAGS) $< -o $@
108
109 $(BUILD_DIR)/print.o: lib/kernel/print.S
110 $(AS) $(ASFLAGS) $< -o $@
111
112 $(BUILD_DIR)/switch.o: thread/switch.S
113 $(AS) $(ASFLAGS) $< -o $@
114
115 ############## 链接所有目标文件 #############
116 $(BUILD_DIR)/kernel.bin: $(OBJS)
117 $(LD) $(LDFLAGS) $^ -o $@
118
119 .PHONY : mk_dir hd clean all
120
121 mk_dir:
122 if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fi
123
124 hd:
125 dd if=$(BUILD_DIR)/kernel.bin \
126 of=/home/zbb/bochs/hd60M.img \
127 bs=512 count=200 seek=9 conv=notrunc
128
129 clean:
130 cd $(BUILD_DIR) && rm -f ./*
131
132 build: $(BUILD_DIR)/kernel.bin
133
134 all: mk_dir build hd
最后结果如图所示:
3.完善堆内存管理
①修改kernel/memory.h,增加头文件、结构体和函数声明:
1 #include "list.h"
2
3 /* 内存块 */
4 struct mem_block{
5 struct list_elem free_elem;
6 };
7
8 /* 内存块描述符 */
9 struct mem_block_desc{
10 uint32_t block_size; // 内存块大小
11 uint32_t blocks_per_arena; // arena中可容纳的内存块数
12 struct list free_list; // 空闲的mem_block组成的链表
13 };
14
15 #define DESC_CNT 7 // 内存块描述符个数
16
17 void* malloc_init(void);
18 void block_desc_init(struct mem_block_desc* desc_array);
19 void* sys_malloc(uint32_t size);
②修改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 #include "sync.h"
9 #include "interrupt.h"
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 struct lock lock; // 申请内存时互斥锁
26 };
27
28 /* 内存仓库 */
29 struct arena{
30 struct mem_block_desc* desc; // 此arena关联的mem_block_desc
31 /* large为true,cnt表示页框数
32 * 否则false,表示空闲的mem_block数 */
33 uint32_t cnt;
34 bool large;
35 };
36
37 struct mem_block_desc k_block_descs[DESC_CNT]; // 内核内存块描述符数组
38
39 struct pool kernel_pool,user_pool; // 生成内核内存池和用户内存池
40 struct virtual_addr kernel_vaddr; // 此结构用来给内核分配虚拟地址
41
42 /* 在pf表示的虚拟内存池中申请pg_cnt个虚拟页
43 * 成功则返回虚拟页的起始地址,失败则返回NULL */
44 static void* vaddr_get(enum pool_flags pf,uint32_t pg_cnt){
45 int vaddr_start=0,bit_idx_start=-1;
46 uint32_t cnt=0;
47 if (pf==PF_KERNEL){ // 内核内存池
48 bit_idx_start=bitmap_scan(&kernel_vaddr.vaddr_bitmap,pg_cnt);
49 if (bit_idx_start==-1){
50 return NULL;
51 }
52 while (cnt<pg_cnt){
53 bitmap_set(&kernel_vaddr.vaddr_bitmap,bit_idx_start+cnt++,1);
54 }
55 vaddr_start=kernel_vaddr.vaddr_start+bit_idx_start*PG_SIZE;
56 }else{ // 用户内存池
57 struct task_struct* cur=running_thread();
58 bit_idx_start=bitmap_scan(&cur->userprog_vaddr.vaddr_bitmap,pg_cnt);
59 if (bit_idx_start==-1){
60 return NULL;
61 }
62 while (cnt<pg_cnt){
63 bitmap_set(&cur->userprog_vaddr.vaddr_bitmap,bit_idx_start+cnt++,1);
64 }
65 vaddr_start=cur->userprog_vaddr.vaddr_start+bit_idx_start*PG_SIZE;
66
67 ASSERT((uint32_t)vaddr_start<(0xc0000000-PG_SIZE));
68 }
69 return (void*)vaddr_start;
70 }
71
72 /* 得到虚拟地址vaddr对应的pte指针 */
73 uint32_t* pte_ptr(uint32_t vaddr){
74 /* 先访问到页表自己 +
75 * 再用页目录项pde作为pte的索引访问到页表 +
76 * 再用页表项pte作为页内偏移 */
77 uint32_t* pte=(uint32_t*)(0xffc00000+\
78 ((vaddr & 0xffc00000)>>10)+\
79 PTE_IDX(vaddr)*4);
80 return pte;
81 }
82
83 /* 得到虚拟地址vaddr对应的pde指针 */
84 uint32_t* pde_ptr(uint32_t vaddr){
85 /* 0xfffff用来访问到页表本身所在的地址 */
86 uint32_t* pde=(uint32_t*)((0xfffff000)+PDE_IDX(vaddr)*4);
87 return pde;
88 }
89
90 /* 在m_pool指向的物理内存池中分配1个物理页,
91 * 成功则返回页框的物理地址,失败则返回NULL */
92 static void* palloc(struct pool* m_pool){
93 /* 扫描或设置位图要保证原子操作 */
94 int bit_idx=bitmap_scan(&m_pool->pool_bitmap,1); // 找一个物理页面
95 if (bit_idx==-1){
96 return NULL;
97 }
98 bitmap_set(&m_pool->pool_bitmap,bit_idx,1); // 将此位bit_idx置为1
99 uint32_t page_phyaddr=((bit_idx*PG_SIZE)+m_pool->phy_addr_start);
100 return (void*)page_phyaddr;
101 }
102
103 /* 页表中添加虚拟地址_vaddr与物理地址_page_phyaddr的映射 */
104 static void page_table_add(void* _vaddr,void* _page_phyaddr){
105 uint32_t vaddr=(uint32_t)_vaddr,page_phyaddr=(uint32_t)_page_phyaddr;
106 uint32_t* pde=pde_ptr(vaddr);
107 uint32_t* pte=pte_ptr(vaddr);
108
109 /***************************** 注意 ****************************
110 * 执行*pte,会访问到空的pde。所以确保pde创建完成后才嫩执行*pte,
111 * 否则会引发page_fault。因此在*pde为0时,pte只能在下面else语句块的*pde后面。
112 **************************************************************/
113 /* 先在页目录内判断目录项的P位,若为1,则表示该表已存在 */
114 if (*pde & 0x00000001){ //P位,此处判断目录项是否存在。若存在
115 ASSERT(!(*pte & 0x00000001));
116
117 if(!(*pte & 0x00000001)){
118 *pte=(page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
119 }else{ // 理论上不会执行到这里
120 PANIC("pte repeat");
121 *pte=(page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
122 }
123 }else{ // 页目录项不存在,所以要先创建页目录项再创建页表项
124 /* 页表中用到的页框一律从内核空间分配 */
125 uint32_t pde_phyaddr=(uint32_t)palloc(&kernel_pool);
126
127 *pde=(pde_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
128
129 /* 分配到的物理页地址pde_phyaddr对应的物理内存清0,
130 * 避免里面的陈旧数据变成了页表项,从而让页表混乱。
131 * 访问到pde对应的物理地址,用pte取高20位即可。
132 * 因为pte基于该pde对应的物理地址内再寻址,
133 * 把低12位置0便是该pde对应的物理页的起始 */
134 memset((void*)((int)pte & 0xfffff000),0,PG_SIZE);
135
136 ASSERT(!(*pte & 0x00000001));
137 *pte=(page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
138 }
139 }
140
141 /* 分配pg_cnt个页空间,成功则返回起始虚拟地址,失败则返回NULL */
142 void* malloc_page(enum pool_flags pf,uint32_t pg_cnt){
143 ASSERT(pg_cnt>0 && pg_cnt<3840);
144 /************ malloc_page 的原理是三个动作的合成:**********
145 * 1.通过vaddr_get在虚拟地址内存池中的申请虚拟地址
146 * 2.通过palloc在物理内存池中申请物理页
147 * 3.通过page_table_add将以上得到的虚拟地址和物理地址在页表中完成映射
148 **********************************************************/
149 void* vaddr_start=vaddr_get(pf,pg_cnt);
150 if (vaddr_start==NULL){
151 return NULL;
152 }
153
154 uint32_t vaddr=(uint32_t)vaddr_start,cnt=pg_cnt;
155 struct pool* mem_pool=(pf & PF_KERNEL)?&kernel_pool:&user_pool;
156
157 /* 因为虚拟地址是连续的,但物理地址可以是不连续的,所以逐个做映射 */
158 while (cnt-->0){
159 void* page_phyaddr=palloc(mem_pool);
160 if (page_phyaddr==NULL){ // 申请物理内存失败,将已申请的虚拟地址和物理页全部回滚,在将来完成内存回收时再补充
161 return NULL;
162 }
163 page_table_add((void*)vaddr,page_phyaddr); // 在页表中做映射
164 vaddr+=PG_SIZE; // 下一个虚拟页
165 }
166 return vaddr_start;
167 }
168
169 /* 在用户空间中申请4k内存,并返回其虚拟地址 */
170 void* get_user_pages(uint32_t pg_cnt){
171 lock_acquire(&user_pool.lock);
172 void* vaddr=malloc_page(PF_USER,pg_cnt);
173 memset(vaddr,0,pg_cnt*PG_SIZE);
174 lock_release(&user_pool.lock);
175 return vaddr;
176 }
177
178 /* 从内核物理内存池中申请1页内存,成功则返回其虚拟地址,失败则返回NULL */
179 void* get_kernel_pages(uint32_t pg_cnt){
180 void* vaddr=malloc_page(PF_KERNEL,pg_cnt);
181 if (vaddr!=NULL){ // 若分配的地址不为空,将页框清0
182 memset(vaddr,0,pg_cnt*PG_SIZE);
183 }
184 return vaddr;
185 }
186
187 /* 将地址vaddr与pf池中的物理地址关联,仅支持一页空间分配 */
188 void* get_a_page(enum pool_flags pf,uint32_t vaddr){
189 struct pool* mem_pool=pf & PF_KERNEL?&kernel_pool:&user_pool;
190 lock_acquire(&mem_pool->lock);
191
192 /* 先将虚拟地址对应的位图置1 */
193 struct task_struct* cur=running_thread();
194 int32_t bit_idx=-1;
195
196 /* 若当前是用户进程申请用户内存,就修改用户进程自己的虚拟地址位图 */
197 if (cur->pgdir!=NULL && pf==PF_USER){
198 bit_idx=(vaddr-cur->userprog_vaddr.vaddr_start)/PG_SIZE;
199 ASSERT(bit_idx>0);
200 bitmap_set(&cur->userprog_vaddr.vaddr_bitmap,bit_idx,1);
201
202 }else if (cur->pgdir==NULL && pf==PF_KERNEL){
203 /* 如果是内核线程申请内核内存,就修改kernel_vaddr. */
204 bit_idx=(vaddr-kernel_vaddr.vaddr_start)/PG_SIZE;
205 ASSERT(bit_idx>0);
206 bitmap_set(&kernel_vaddr.vaddr_bitmap,bit_idx,1);
207 }else {
208 PANIC("get_a_page:not allow kernel alloc userspace or user alloc kernelspace by get_a_page");
209 }
210
211 void* page_phyaddr=palloc(mem_pool);
212 if (page_phyaddr==NULL){
213 return NULL;
214 }
215 page_table_add((void*)vaddr,page_phyaddr);
216 lock_release(&mem_pool->lock);
217 return (void*)vaddr;
218 }
219
220 uint32_t addr_v2p(uint32_t vaddr){
221 uint32_t* pte=pte_ptr(vaddr);
222 /* (*pte)的值是页表所在的物理页框地址
223 * 去掉其低12位的页表项属性+虚拟地址vaddr的低12位 */
224 return ((*pte & 0xfffff000)+(vaddr & 0x00000fff));
225 }
226
227 /* 初始化内存池 */
228 static void mem_pool_init(uint32_t all_mem){
229 put_str(" mem_pool_init start\n");
230 uint32_t page_table_size=PG_SIZE*256;
231
232 uint32_t used_mem=page_table_size+0x100000;
233 uint32_t free_mem=all_mem-used_mem;
234 uint16_t all_free_pages=free_mem/PG_SIZE;
235
236 uint16_t kernel_free_pages=all_free_pages/2;
237 uint16_t user_free_pages=all_free_pages-kernel_free_pages;
238
239 /* 为简化位图操作,余数不处理,不用考虑越界检查,但会丢失部分内存 */
240 uint32_t kbm_length=kernel_free_pages/8; // kernel bitmap的长度。1位管理1页,单位为字节
241 uint32_t ubm_length=user_free_pages/8; // user bitmap的长度
242
243 uint32_t kp_start=used_mem; // KernelLPool,内核内存池的起始地址
244 uint32_t up_start=kp_start+kernel_free_pages*PG_SIZE; // UserPool,用户内存池的起始地址
245
246 kernel_pool.phy_addr_start=kp_start;
247 user_pool.phy_addr_start=up_start;
248
249 kernel_pool.pool_size=kernel_free_pages*PG_SIZE;
250 user_pool.pool_size=user_free_pages*PG_SIZE;
251
252 kernel_pool.pool_bitmap.btmp_bytes_len=kbm_length;
253 user_pool.pool_bitmap.btmp_bytes_len=ubm_length;
254
255
256 /************** 内核内存池和用户内存池位图 ***************
257 * 位图是全局的数据,长度不固定。
258 * 全局或静态的数组需要在编译时知道其长度,
259 * 而我们需要根据总内存大小算出需要多少字节,
260 * 所以改为指定一块内存来生成位图。
261 * ******************************************************/
262 // 内核使用的最高地址是0xc009f000,这是主线程的站地址
263 // 32MB内存占用的位图是2KB,内核内存池位图先定在MEM_BITMAP_BASE(0xc009a000)处
264 kernel_pool.pool_bitmap.bits=(void*)MEM_BITMAP_BASE;
265
266 /* 用户内存池的位图紧跟在内核内存池位图之后 */
267 user_pool.pool_bitmap.bits=(void*)(MEM_BITMAP_BASE+kbm_length);
268
269 /***************** 输出内存池信息 ***********************/
270 put_str(" kernel_pool_bitmap_start:");
271 put_int((int)kernel_pool.pool_bitmap.bits);
272 put_str(" kernel_pool_phy_addr_start:");
273 put_int((int)kernel_pool.phy_addr_start);
274 put_str("\n");
275 put_str(" user_pool_bitmap_start:");
276 put_int((int)user_pool.pool_bitmap.bits);
277 put_str(" user_pool_phy_addr_strar:");
278 put_int((int)user_pool.phy_addr_start);
279 put_str("\n");
280
281 /*将位图置0*/
282 bitmap_init(&kernel_pool.pool_bitmap);
283 bitmap_init(&user_pool.pool_bitmap);
284
285 lock_init(&kernel_pool.lock);
286 lock_init(&user_pool.lock);
287
288 /* 下面初始化内核虚拟地址位图,按实际物理内存大小生成数组 */
289 kernel_vaddr.vaddr_bitmap.btmp_bytes_len=kbm_length;
290 // 用于维护内核堆的虚拟地址,所以要和内核内存池大小一致
291
292 /* 位图的数组指向一块未使用的内存,目前定位在内核内存池和用户内存池之外 */
293 kernel_vaddr.vaddr_bitmap.bits=(void*)(MEM_BITMAP_BASE+kbm_length+ubm_length);
294
295 kernel_vaddr.vaddr_start=K_HEAP_START;
296 bitmap_init(&kernel_vaddr.vaddr_bitmap);
297 put_str(" mem_pool_init done\n");
298 }
299
300 /* 内存管理部分初始化入口 */
301 void mem_init(){
302 put_str("mem_init start\n");
303 uint32_t mem_bytes_total=(*(uint32_t*)(0xb00));
304 mem_pool_init(mem_bytes_total); // 初始化内存池
305 /* 初始化mem_block_desc数组descs,为malloc做准备 */
306 block_desc_init(k_block_descs);
307 put_str("mem_init done\n");
308 }
309
310 /* 为malloc做准备 */
311 void block_desc_init(struct mem_block_desc* desc_array){
312 uint16_t desc_idx,block_size=16;
313
314 /* 初始化每个mem_block_desc描述符 */
315 for (desc_idx=0;desc_idx<DESC_CNT;++desc_idx){
316 desc_array[desc_idx].block_size=block_size;
317
318 /* 初始化arena中的内存块数量 */
319 desc_array[desc_idx].blocks_per_arena=(PG_SIZE-sizeof(struct arena))/block_size;
320 list_init(&desc_array[desc_idx].free_list);
321
322 block_size*=2; // 更新为下一个内存块
323 }
324 }
325
326 /* 返回arena中第idx个内存块的地址 */
327 static struct mem_block* arena2block(struct arena* a,uint32_t idx){
328 return (struct mem_block*)((uint32_t)a+sizeof(struct arena)+idx*a->desc->block_size);
329 }
330
331 /* 返回内存块b所在的arena地址 */
332 static struct arena* block2arena(struct mem_block* b){
333 return (struct arena*)((uint32_t)b & 0xfffff000);
334 }
335
336 /* 在堆中申请size个字节内存 */
337 void* sys_malloc(uint32_t size){
338 enum pool_flags pf;
339 struct pool* mem_pool;
340 uint32_t pool_size;
341 struct mem_block_desc* descs;
342 struct task_struct* cur_thread=running_thread();
343
344 /* 判断用哪个内存池 */
345 if (cur_thread->pgdir==NULL){ // 若为内核线程
346 pf=PF_KERNEL;
347 pool_size=kernel_pool.pool_size;
348 mem_pool=&kernel_pool;
349 descs=k_block_descs;
350 }else { // 若为用户进程,其pcb中的pgdir会在分配页表时创建
351 pf=PF_USER;
352 pool_size=user_pool.pool_size;
353 mem_pool=&user_pool;
354 descs=cur_thread->u_block_desc;
355 }
356
357 /* 若申请的内存不在内存池容量范围内则直接返回NULL */
358 if (!(size>0 && size<pool_size)){
359 return NULL;
360 }
361 struct arena* a;
362 struct mem_block* b;
363 lock_acquire(&mem_pool->lock);
364
365 /* 超过最大内存块1024KB,就分配页框 */
366 if (size>1024){
367 uint32_t page_cnt=DIV_ROUND_UP(size+sizeof(struct arena),PG_SIZE); // 向上取整需要的页框数
368 a=malloc_page(pf,page_cnt);
369
370 if (a!=NULL){
371 memset(a,0,page_cnt*PG_SIZE); // 将分配的内存清0
372
373 /* 对于分配的大块页框,将desc置为NULL,cnt置为页框数,large置为true */
374 a->desc=NULL;
375 a->cnt=page_cnt;
376 a->large=true;
377 lock_release(&mem_pool->lock);
378 return (void*)(a+1); // 跨过arena大小,把剩下的内存返回
379 }else {
380 lock_release(&mem_pool->lock);
381 return NULL;
382 }
383 }else { // 若申请的内存小于等于1024,可在各种规格的mem_block_desc中适配
384 uint8_t desc_idx;
385
386 /* 从内存块描述符中匹配合适的内存块规格 */
387 for (desc_idx=0;desc_idx<DESC_CNT;++desc_idx){
388 if (size<=descs[desc_idx].block_size){ // 从小往大依次寻找,找到第一个比所需大的即可退出
389 break;
390 }
391 }
392
393 /* 若mem_block_desc的free_list中已经没有可用的mem_block,
394 * 就创建新的arena提供mem_block */
395 if (list_empty(&descs[desc_idx].free_list)){
396 a=malloc_page(pf,1); // 分配1页页框作arena
397 if (a==NULL){
398 lock_release(&mem_pool->lock);
399 return NULL;
400 }
401 memset(a,0,PG_SIZE);
402
403 /* 对于分配小块内存,将desc置为相应内存块描述符,
404 * cnt置为此arena可用的内存块数,large置为false */
405 a->desc=&descs[desc_idx];
406 a->large=false;
407 a->cnt=descs[desc_idx].blocks_per_arena;
408 uint32_t block_idx;
409
410 enum intr_status old_status=intr_disable();
411
412 /* 开始将arena拆开成内存块,并添加到内存块描述符的free_list中 */
413 for (block_idx=0;block_idx<descs[desc_idx].blocks_per_arena;++block_idx){
414 b=arena2block(a,block_idx);
415 ASSERT(!elem_find(&a->desc->free_list,&b->free_elem));
416 list_append(&a->desc->free_list,&b->free_elem);
417 }
418 intr_set_status(old_status);
419 }
420
421 /* 开始分配内存块 */
422 b=elem2entry(struct mem_block,free_elem,list_pop(&(descs[desc_idx].free_list)));
423 memset(b,0,descs[desc_idx].block_size);
424
425 a=block2arena(b); // 获取内存块b最在的arena
426 --a->cnt; // 将此arena中的空闲内存块-1
427 lock_release(&mem_pool->lock);
428 return (void*)b;
429 }
430 }
③在thread/thread.c中对pcb做些小改动:
④使用上面的数组前要在userprog/process.c中完成初始化:
⑤修改kernel/main.c:

1 #include "print.h"
2 #include "init.h"
3 #include "thread.h"
4 #include "interrupt.h"
5 #include "console.h"
6 #include "ioqueue.h"
7 #include "process.h"
8 #include "syscall.h"
9 #include "syscall-init.h"
10
11 void k_thread_a(void*);
12 void k_thread_b(void*);
13 void u_prog_a(void);
14 void u_prog_b(void);
15 int prog_a_pid=0,prog_b_pid=0;
16
17 int main(void){
18 put_str("Welcome,\nI am kernel!\n");
19 init_all();
20
21 // process_execute(u_prog_a,"user_prog_a");
22 // process_execute(u_prog_b,"user_prog_b");
23 //
24 // console_put_str(" I'm main, my pid:0x");
25 // console_put_int(sys_getpid());
26 // console_put_char('\n');
27
28 intr_enable();
29 thread_start("k_thread_a",31,k_thread_a,"argA ");
30 thread_start("k_thread_b",31,k_thread_b,"argB ");
31
32 while(1);
33 return 0;
34 }
35
36 void k_thread_a(void* arg){
37 char* para=arg;
38 void* addr=sys_malloc(33);
39 console_put_str(" I'm thread_a, sys_malloc(33), addr is 0x");
40 console_put_int((int)addr);
41 console_put_char('\n');
42 while(1);
43 }
44
45 void k_thread_b(void* arg){
46 char* para=arg;
47 void* addr=sys_malloc(33);
48 console_put_str(" I'm thread_b, sys_malloc(63), addr is 0x");
49 console_put_int((int)addr);
50 console_put_char('\n');
51 while(1);
52 }
53
54 void u_prog_a(void){
55 char* name="prog_a";
56 printf(" I'm %s, my pid:%d%c",name,getpid(),'\n');
57 while(1);
58 }
59
60 void u_prog_b(void){
61 char* name="prog_b";
62 printf(" I'm %s, my pid:%d%c",name,getpid(),'\n');
63 while(1);
64 }
⑥别忘记修改makefile,然后看看运行结果:
线程获得的内存地址分别为0xc010200c和0xc010204c,第一个线程申请33字节——sys_malloc(33)——实际上获得了64字节的arena,所以获得的内存地址为0xc01020000(arena首地址)+0xc(arena元信息大小)=0xc010200c;而第二个线程申请63字节——sys_malloc(63)——实际上也是获得了64字节的arena,0xc010204c-0xc010200c=0x40刚好为64字节,验证通过。
为实现基本的内存管理机制,光分配内存还不行,还要有回收内存的功能。
主要就是在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 #include "sync.h"
9 #include "interrupt.h"
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 struct lock lock; // 申请内存时互斥锁
26 };
27
28 /* 内存仓库 */
29 struct arena{
30 struct mem_block_desc* desc; // 此arena关联的mem_block_desc
31 /* large为true,cnt表示页框数
32 * 否则false,表示空闲的mem_block数 */
33 uint32_t cnt;
34 bool large;
35 };
36
37 struct mem_block_desc k_block_descs[DESC_CNT]; // 内核内存块描述符数组
38
39 struct pool kernel_pool,user_pool; // 生成内核内存池和用户内存池
40 struct virtual_addr kernel_vaddr; // 此结构用来给内核分配虚拟地址
41
42 /* 在pf表示的虚拟内存池中申请pg_cnt个虚拟页
43 * 成功则返回虚拟页的起始地址,失败则返回NULL */
44 static void* vaddr_get(enum pool_flags pf,uint32_t pg_cnt){
45 int vaddr_start=0,bit_idx_start=-1;
46 uint32_t cnt=0;
47 if (pf==PF_KERNEL){ // 内核内存池
48 bit_idx_start=bitmap_scan(&kernel_vaddr.vaddr_bitmap,pg_cnt);
49 if (bit_idx_start==-1){
50 return NULL;
51 }
52 while (cnt<pg_cnt){
53 bitmap_set(&kernel_vaddr.vaddr_bitmap,bit_idx_start+cnt++,1);
54 }
55 vaddr_start=kernel_vaddr.vaddr_start+bit_idx_start*PG_SIZE;
56 }else{ // 用户内存池
57 struct task_struct* cur=running_thread();
58 bit_idx_start=bitmap_scan(&cur->userprog_vaddr.vaddr_bitmap,pg_cnt);
59 if (bit_idx_start==-1){
60 return NULL;
61 }
62 while (cnt<pg_cnt){
63 bitmap_set(&cur->userprog_vaddr.vaddr_bitmap,bit_idx_start+cnt++,1);
64 }
65 vaddr_start=cur->userprog_vaddr.vaddr_start+bit_idx_start*PG_SIZE;
66
67 ASSERT((uint32_t)vaddr_start<(0xc0000000-PG_SIZE));
68 }
69 return (void*)vaddr_start;
70 }
71
72 /* 得到虚拟地址vaddr对应的pte指针 */
73 uint32_t* pte_ptr(uint32_t vaddr){
74 /* 先访问到页表自己 +
75 * 再用页目录项pde作为pte的索引访问到页表 +
76 * 再用页表项pte作为页内偏移 */
77 uint32_t* pte=(uint32_t*)(0xffc00000+\
78 ((vaddr & 0xffc00000)>>10)+\
79 PTE_IDX(vaddr)*4);
80 return pte;
81 }
82
83 /* 得到虚拟地址vaddr对应的pde指针 */
84 uint32_t* pde_ptr(uint32_t vaddr){
85 /* 0xfffff用来访问到页表本身所在的地址 */
86 uint32_t* pde=(uint32_t*)((0xfffff000)+PDE_IDX(vaddr)*4);
87 return pde;
88 }
89
90 /* 在m_pool指向的物理内存池中分配1个物理页,
91 * 成功则返回页框的物理地址,失败则返回NULL */
92 static void* palloc(struct pool* m_pool){
93 /* 扫描或设置位图要保证原子操作 */
94 int bit_idx=bitmap_scan(&m_pool->pool_bitmap,1); // 找一个物理页面
95 if (bit_idx==-1){
96 return NULL;
97 }
98 bitmap_set(&m_pool->pool_bitmap,bit_idx,1); // 将此位bit_idx置为1
99 uint32_t page_phyaddr=((bit_idx*PG_SIZE)+m_pool->phy_addr_start);
100 return (void*)page_phyaddr;
101 }
102
103 /* 页表中添加虚拟地址_vaddr与物理地址_page_phyaddr的映射 */
104 static void page_table_add(void* _vaddr,void* _page_phyaddr){
105 uint32_t vaddr=(uint32_t)_vaddr,page_phyaddr=(uint32_t)_page_phyaddr;
106 uint32_t* pde=pde_ptr(vaddr);
107 uint32_t* pte=pte_ptr(vaddr);
108
109 /***************************** 注意 ****************************
110 * 执行*pte,会访问到空的pde。所以确保pde创建完成后才嫩执行*pte,
111 * 否则会引发page_fault。因此在*pde为0时,pte只能在下面else语句块的*pde后面。
112 **************************************************************/
113 /* 先在页目录内判断目录项的P位,若为1,则表示该表已存在 */
114 if (*pde & 0x00000001){ //P位,此处判断目录项是否存在。若存在
115 ASSERT(!(*pte & 0x00000001));
116
117 if(!(*pte & 0x00000001)){
118 *pte=(page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
119 }else{ // 理论上不会执行到这里
120 PANIC("pte repeat");
121 *pte=(page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
122 }
123 }else{ // 页目录项不存在,所以要先创建页目录项再创建页表项
124 /* 页表中用到的页框一律从内核空间分配 */
125 uint32_t pde_phyaddr=(uint32_t)palloc(&kernel_pool);
126
127 *pde=(pde_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
128
129 /* 分配到的物理页地址pde_phyaddr对应的物理内存清0,
130 * 避免里面的陈旧数据变成了页表项,从而让页表混乱。
131 * 访问到pde对应的物理地址,用pte取高20位即可。
132 * 因为pte基于该pde对应的物理地址内再寻址,
133 * 把低12位置0便是该pde对应的物理页的起始 */
134 memset((void*)((int)pte & 0xfffff000),0,PG_SIZE);
135
136 ASSERT(!(*pte & 0x00000001));
137 *pte=(page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
138 }
139 }
140
141 /* 分配pg_cnt个页空间,成功则返回起始虚拟地址,失败则返回NULL */
142 void* malloc_page(enum pool_flags pf,uint32_t pg_cnt){
143 ASSERT(pg_cnt>0 && pg_cnt<3840);
144 /************ malloc_page 的原理是三个动作的合成:**********
145 * 1.通过vaddr_get在虚拟地址内存池中的申请虚拟地址
146 * 2.通过palloc在物理内存池中申请物理页
147 * 3.通过page_table_add将以上得到的虚拟地址和物理地址在页表中完成映射
148 **********************************************************/
149 void* vaddr_start=vaddr_get(pf,pg_cnt);
150 if (vaddr_start==NULL){
151 return NULL;
152 }
153
154 uint32_t vaddr=(uint32_t)vaddr_start,cnt=pg_cnt;
155 struct pool* mem_pool=(pf & PF_KERNEL)?&kernel_pool:&user_pool;
156
157 /* 因为虚拟地址是连续的,但物理地址可以是不连续的,所以逐个做映射 */
158 while (cnt-->0){
159 void* page_phyaddr=palloc(mem_pool);
160 if (page_phyaddr==NULL){ // 申请物理内存失败,将已申请的虚拟地址和物理页全部回滚,在将来完成内存回收时再补充
161 return NULL;
162 }
163 page_table_add((void*)vaddr,page_phyaddr); // 在页表中做映射
164 vaddr+=PG_SIZE; // 下一个虚拟页
165 }
166 return vaddr_start;
167 }
168
169 /* 在用户空间中申请4k内存,并返回其虚拟地址 */
170 void* get_user_pages(uint32_t pg_cnt){
171 lock_acquire(&user_pool.lock);
172 void* vaddr=malloc_page(PF_USER,pg_cnt);
173 memset(vaddr,0,pg_cnt*PG_SIZE);
174 lock_release(&user_pool.lock);
175 return vaddr;
176 }
177
178 /* 从内核物理内存池中申请1页内存,成功则返回其虚拟地址,失败则返回NULL */
179 void* get_kernel_pages(uint32_t pg_cnt){
180 void* vaddr=malloc_page(PF_KERNEL,pg_cnt);
181 if (vaddr!=NULL){ // 若分配的地址不为空,将页框清0
182 memset(vaddr,0,pg_cnt*PG_SIZE);
183 }
184 return vaddr;
185 }
186
187 /* 将地址vaddr与pf池中的物理地址关联,仅支持一页空间分配 */
188 void* get_a_page(enum pool_flags pf,uint32_t vaddr){
189 struct pool* mem_pool=pf & PF_KERNEL?&kernel_pool:&user_pool;
190 lock_acquire(&mem_pool->lock);
191
192 /* 先将虚拟地址对应的位图置1 */
193 struct task_struct* cur=running_thread();
194 int32_t bit_idx=-1;
195
196 /* 若当前是用户进程申请用户内存,就修改用户进程自己的虚拟地址位图 */
197 if (cur->pgdir!=NULL && pf==PF_USER){
198 bit_idx=(vaddr-cur->userprog_vaddr.vaddr_start)/PG_SIZE;
199 ASSERT(bit_idx>0);
200 bitmap_set(&cur->userprog_vaddr.vaddr_bitmap,bit_idx,1);
201
202 }else if (cur->pgdir==NULL && pf==PF_KERNEL){
203 /* 如果是内核线程申请内核内存,就修改kernel_vaddr. */
204 bit_idx=(vaddr-kernel_vaddr.vaddr_start)/PG_SIZE;
205 ASSERT(bit_idx>0);
206 bitmap_set(&kernel_vaddr.vaddr_bitmap,bit_idx,1);
207 }else {
208 PANIC("get_a_page:not allow kernel alloc userspace or user alloc kernelspace by get_a_page");
209 }
210
211 void* page_phyaddr=palloc(mem_pool);
212 if (page_phyaddr==NULL){
213 return NULL;
214 }
215 page_table_add((void*)vaddr,page_phyaddr);
216 lock_release(&mem_pool->lock);
217 return (void*)vaddr;
218 }
219
220 uint32_t addr_v2p(uint32_t vaddr){
221 uint32_t* pte=pte_ptr(vaddr);
222 /* (*pte)的值是页表所在的物理页框地址
223 * 去掉其低12位的页表项属性+虚拟地址vaddr的低12位 */
224 return ((*pte & 0xfffff000)+(vaddr & 0x00000fff));
225 }
226
227 /* 初始化内存池 */
228 static void mem_pool_init(uint32_t all_mem){
229 put_str(" mem_pool_init start\n");
230 uint32_t page_table_size=PG_SIZE*256;
231
232 uint32_t used_mem=page_table_size+0x100000;
233 uint32_t free_mem=all_mem-used_mem;
234 uint16_t all_free_pages=free_mem/PG_SIZE;
235
236 uint16_t kernel_free_pages=all_free_pages/2;
237 uint16_t user_free_pages=all_free_pages-kernel_free_pages;
238
239 /* 为简化位图操作,余数不处理,不用考虑越界检查,但会丢失部分内存 */
240 uint32_t kbm_length=kernel_free_pages/8; // kernel bitmap的长度。1位管理1页,单位为字节
241 uint32_t ubm_length=user_free_pages/8; // user bitmap的长度
242
243 uint32_t kp_start=used_mem; // KernelLPool,内核内存池的起始地址
244 uint32_t up_start=kp_start+kernel_free_pages*PG_SIZE; // UserPool,用户内存池的起始地址
245
246 kernel_pool.phy_addr_start=kp_start;
247 user_pool.phy_addr_start=up_start;
248
249 kernel_pool.pool_size=kernel_free_pages*PG_SIZE;
250 user_pool.pool_size=user_free_pages*PG_SIZE;
251
252 kernel_pool.pool_bitmap.btmp_bytes_len=kbm_length;
253 user_pool.pool_bitmap.btmp_bytes_len=ubm_length;
254
255
256 /************** 内核内存池和用户内存池位图 ***************
257 * 位图是全局的数据,长度不固定。
258 * 全局或静态的数组需要在编译时知道其长度,
259 * 而我们需要根据总内存大小算出需要多少字节,
260 * 所以改为指定一块内存来生成位图。
261 * ******************************************************/
262 // 内核使用的最高地址是0xc009f000,这是主线程的站地址
263 // 32MB内存占用的位图是2KB,内核内存池位图先定在MEM_BITMAP_BASE(0xc009a000)处
264 kernel_pool.pool_bitmap.bits=(void*)MEM_BITMAP_BASE;
265
266 /* 用户内存池的位图紧跟在内核内存池位图之后 */
267 user_pool.pool_bitmap.bits=(void*)(MEM_BITMAP_BASE+kbm_length);
268
269 /***************** 输出内存池信息 ***********************/
270 put_str(" kernel_pool_bitmap_start:");
271 put_int((int)kernel_pool.pool_bitmap.bits);
272 put_str(" kernel_pool_phy_addr_start:");
273 put_int((int)kernel_pool.phy_addr_start);
274 put_str("\n");
275 put_str(" user_pool_bitmap_start:");
276 put_int((int)user_pool.pool_bitmap.bits);
277 put_str(" user_pool_phy_addr_strar:");
278 put_int((int)user_pool.phy_addr_start);
279 put_str("\n");
280
281 /*将位图置0*/
282 bitmap_init(&kernel_pool.pool_bitmap);
283 bitmap_init(&user_pool.pool_bitmap);
284
285 lock_init(&kernel_pool.lock);
286 lock_init(&user_pool.lock);
287
288 /* 下面初始化内核虚拟地址位图,按实际物理内存大小生成数组 */
289 kernel_vaddr.vaddr_bitmap.btmp_bytes_len=kbm_length;
290 // 用于维护内核堆的虚拟地址,所以要和内核内存池大小一致
291
292 /* 位图的数组指向一块未使用的内存,目前定位在内核内存池和用户内存池之外 */
293 kernel_vaddr.vaddr_bitmap.bits=(void*)(MEM_BITMAP_BASE+kbm_length+ubm_length);
294
295 kernel_vaddr.vaddr_start=K_HEAP_START;
296 bitmap_init(&kernel_vaddr.vaddr_bitmap);
297 put_str(" mem_pool_init done\n");
298 }
299
300 /* 内存管理部分初始化入口 */
301 void mem_init(){
302 put_str("mem_init start\n");
303 uint32_t mem_bytes_total=(*(uint32_t*)(0xb00));
304 mem_pool_init(mem_bytes_total); // 初始化内存池
305 /* 初始化mem_block_desc数组descs,为malloc做准备 */
306 block_desc_init(k_block_descs);
307 put_str("mem_init done\n");
308 }
309
310 /* 为malloc做准备 */
311 void block_desc_init(struct mem_block_desc* desc_array){
312 uint16_t desc_idx,block_size=16;
313
314 /* 初始化每个mem_block_desc描述符 */
315 for (desc_idx=0;desc_idx<DESC_CNT;++desc_idx){
316 desc_array[desc_idx].block_size=block_size;
317
318 /* 初始化arena中的内存块数量 */
319 desc_array[desc_idx].blocks_per_arena=(PG_SIZE-sizeof(struct arena))/block_size;
320 list_init(&desc_array[desc_idx].free_list);
321
322 block_size*=2; // 更新为下一个内存块
323 }
324 }
325
326 /* 返回arena中第idx个内存块的地址 */
327 static struct mem_block* arena2block(struct arena* a,uint32_t idx){
328 return (struct mem_block*)((uint32_t)a+sizeof(struct arena)+idx*a->desc->block_size);
329 }
330
331 /* 返回内存块b所在的arena地址 */
332 static struct arena* block2arena(struct mem_block* b){
333 return (struct arena*)((uint32_t)b & 0xfffff000);
334 }
335
336 /* 在堆中申请size个字节内存 */
337 void* sys_malloc(uint32_t size){
338 enum pool_flags pf;
339 struct pool* mem_pool;
340 uint32_t pool_size;
341 struct mem_block_desc* descs;
342 struct task_struct* cur_thread=running_thread();
343
344 /* 判断用哪个内存池 */
345 if (cur_thread->pgdir==NULL){ // 若为内核线程
346 pf=PF_KERNEL;
347 pool_size=kernel_pool.pool_size;
348 mem_pool=&kernel_pool;
349 descs=k_block_descs;
350 }else { // 若为用户进程,其pcb中的pgdir会在分配页表时创建
351 pf=PF_USER;
352 pool_size=user_pool.pool_size;
353 mem_pool=&user_pool;
354 descs=cur_thread->u_block_desc;
355 }
356
357 /* 若申请的内存不在内存池容量范围内则直接返回NULL */
358 if (!(size>0 && size<pool_size)){
359 return NULL;
360 }
361 struct arena* a;
362 struct mem_block* b;
363 lock_acquire(&mem_pool->lock);
364
365 /* 超过最大内存块1024KB,就分配页框 */
366 if (size>1024){
367 uint32_t page_cnt=DIV_ROUND_UP(size+sizeof(struct arena),PG_SIZE); // 向上取整需要的页框数
368 a=malloc_page(pf,page_cnt);
369
370 if (a!=NULL){
371 memset(a,0,page_cnt*PG_SIZE); // 将分配的内存清0
372
373 /* 对于分配的大块页框,将desc置为NULL,cnt置为页框数,large置为true */
374 a->desc=NULL;
375 a->cnt=page_cnt;
376 a->large=true;
377 lock_release(&mem_pool->lock);
378 return (void*)(a+1); // 跨过arena大小,把剩下的内存返回
379 }else {
380 lock_release(&mem_pool->lock);
381 return NULL;
382 }
383 }else { // 若申请的内存小于等于1024,可在各种规格的mem_block_desc中适配
384 uint8_t desc_idx;
385
386 /* 从内存块描述符中匹配合适的内存块规格 */
387 for (desc_idx=0;desc_idx<DESC_CNT;++desc_idx){
388 if (size<=descs[desc_idx].block_size){ // 从小往大依次寻找,找到第一个比所需大的即可退出
389 break;
390 }
391 }
392
393 /* 若mem_block_desc的free_list中已经没有可用的mem_block,
394 * 就创建新的arena提供mem_block */
395 if (list_empty(&descs[desc_idx].free_list)){
396 a=malloc_page(pf,1); // 分配1页页框作arena
397 if (a==NULL){
398 lock_release(&mem_pool->lock);
399 return NULL;
400 }
401 memset(a,0,PG_SIZE);
402
403 /* 对于分配小块内存,将desc置为相应内存块描述符,
404 * cnt置为此arena可用的内存块数,large置为false */
405 a->desc=&descs[desc_idx];
406 a->large=false;
407 a->cnt=descs[desc_idx].blocks_per_arena;
408 uint32_t block_idx;
409
410 enum intr_status old_status=intr_disable();
411
412 /* 开始将arena拆开成内存块,并添加到内存块描述符的free_list中 */
413 for (block_idx=0;block_idx<descs[desc_idx].blocks_per_arena;++block_idx){
414 b=arena2block(a,block_idx);
415 ASSERT(!elem_find(&a->desc->free_list,&b->free_elem));
416 list_append(&a->desc->free_list,&b->free_elem);
417 }
418 intr_set_status(old_status);
419 }
420
421 /* 开始分配内存块 */
422 b=elem2entry(struct mem_block,free_elem,list_pop(&(descs[desc_idx].free_list)));
423 memset(b,0,descs[desc_idx].block_size);
424
425 a=block2arena(b); // 获取内存块b最在的arena
426 --a->cnt; // 将此arena中的空闲内存块-1
427 lock_release(&mem_pool->lock);
428 return (void*)b;
429 }
430 }
431
432 void pfree(uint32_t pg_phy_addr){
433 struct pool* mem_pool;
434 uint32_t bit_idx=0;
435 if (pg_phy_addr>=user_pool.phy_addr_start){ // 用户物理内存池
436 mem_pool=&user_pool;
437 bit_idx=(pg_phy_addr-user_pool.phy_addr_start)/PG_SIZE;
438 }else {
439 mem_pool=&kernel_pool;
440 bit_idx=(pg_phy_addr-kernel_pool.phy_addr_start)/PG_SIZE;
441 }
442 bitmap_set(&mem_pool->pool_bitmap,bit_idx,0); // 将位图中的该位清0
443 }
444
445 /* 去掉页表中虚拟地址vaddr的映射,只去掉vaddr对应的pte */
446 static void page_table_pte_remove(uint32_t vaddr){
447 uint32_t* pte=pte_ptr(vaddr);
448 *pte&=~PG_P_1; // 将页表项pte位置0
449 asm volatile("invlpg %0"::"m"(vaddr):"memory");// 更新tlb
450 }
451
452
453 /* 在虚拟地址池中释放_vaddr起始的连续pg_cnt个虚拟地址 */
454 static void vaddr_remove(enum pool_flags pf,void* _vaddr,uint32_t pg_cnt){
455 uint32_t bit_idx_start=0,vaddr=(uint32_t)_vaddr,cnt=0;
456 if (pf==PF_KERNEL){ // 内核虚拟地址池
457 bit_idx_start=(vaddr-kernel_vaddr.vaddr_start)/PG_SIZE;
458 while (cnt<pg_cnt){
459 bitmap_set(&kernel_vaddr.vaddr_bitmap,bit_idx_start+cnt++,0);
460 }
461 }else { // 用户虚拟地址池
462 struct task_struct* cur_thread=running_thread();
463 bit_idx_start=(vaddr-cur_thread->userprog_vaddr.vaddr_start)/PG_SIZE;
464 while (cnt<pg_cnt){
465 bitmap_set(&cur_thread->userprog_vaddr.vaddr_bitmap,bit_idx_start+cnt++,0);
466 }
467 }
468 }
469
470 /* 释放以虚拟地址vaddr为起始的cnt个物理页框 */
471 void mfree_page(enum pool_flags pf,void* _vaddr,uint32_t pg_cnt){
472 uint32_t pg_phy_addr;
473 uint32_t vaddr=(int32_t)_vaddr,page_cnt=0;
474 ASSERT(pg_cnt>=1 && vaddr%PG_SIZE==0);
475 pg_phy_addr=addr_v2p(vaddr); // 获取虚拟地址vaddr对应的物理地址
476
477 /* 确保待释放的物理内存在低端1M+1K大小的页目录+1K大小的页表地址范围外 */
478 ASSERT((pg_phy_addr%PG_SIZE)==0 && pg_phy_addr>=0x102000);
479
480 /* 判断pg_phy_addr属于用户物理内存池还是内核五i了内存池 */
481 if (pg_phy_addr>=user_pool.phy_addr_start){ // 位于user_pool内存池
482 vaddr-=PG_SIZE;
483 while (page_cnt<pg_cnt){
484 vaddr+=PG_SIZE;
485 pg_phy_addr=addr_v2p(vaddr);
486
487 /* 确保物理地址属于用户物理内存池 */
488 ASSERT((pg_phy_addr%PG_SIZE)==0 && pg_phy_addr>=user_pool.phy_addr_start);
489 /* 先将对应的物理页框归还到内存池 */
490 pfree(pg_phy_addr);
491
492 /* 再从页表中清除此虚拟地址所在的页表项pte */
493 page_table_pte_remove(vaddr);
494
495 ++page_cnt;
496 }
497 /* 清空虚拟地址的为途中的相应位 */
498 vaddr_remove(pf,_vaddr,pg_cnt);
499
500 }else { // 位于kernel_pool内存池
501 vaddr-=PG_SIZE;
502 while (page_cnt<pg_cnt){
503 vaddr+=PG_SIZE;
504 pg_phy_addr=addr_v2p(vaddr);
505 /* 确保待释放的物理内存只属于内核物理内存池 */
506 ASSERT((pg_phy_addr%PG_SIZE)==0 && \
507 pg_phy_addr>=kernel_pool.phy_addr_start && \
508 pg_phy_addr<user_pool.phy_addr_start);
509
510 /* 先将对应的物理页框归还到内存池 */
511 pfree(pg_phy_addr);
512
513 /* 再从页表中清除此虚拟地址所在的页表项pte */
514 page_table_pte_remove(vaddr);
515
516 ++page_cnt;
517 }
518 /* 清空虚拟地址的位图中相应位 */
519 vaddr_remove(pf,_vaddr,pg_cnt);
520 }
521 }
522
523 /* 回收内存ptr */
524 void sys_free(void* ptr){
525 ASSERT(ptr!=NULL);
526 if (ptr!=NULL){
527 enum pool_flags pf;
528 struct pool* mem_pool;
529
530 /* 判断是线程还是进程 */
531 if (running_thread()->pgdir==NULL){
532 ASSERT((uint32_t)ptr>=K_HEAP_START);
533 pf=PF_KERNEL;
534 mem_pool=&kernel_pool;
535 }else {
536 pf=PF_USER;
537 mem_pool=&user_pool;
538 }
539
540 lock_acquire(&mem_pool->lock);
541 struct mem_block* b=ptr;
542 struct arena* a=block2arena(b); // 把mem_block转换成arena,获取元信息
543 ASSERT(a->large==0 || a->large==1);
544 if (a->desc==NULL && a->large==true){ // 大于1024的内存
545 mfree_page(pf,a,a->cnt);
546 }else {
547 list_append(&a->desc->free_list,&b->free_elem);
548
549 /* 再判断此arena中的内存块是否都是空闲,如果是就释放arena */
550 if (++a->cnt==a->desc->blocks_per_arena){
551 uint32_t block_idx;
552 for (block_idx=0;block_idx<a->desc->blocks_per_arena;++block_idx){
553 struct mem_block* b=arena2block(a,block_idx);
554 ASSERT(elem_find(&a->desc->free_list,&b->free_elem));
555 list_remove(&b->free_elem);
556 }
557 mfree_page(pf,a,1);
558 }
559 }
560 lock_release(&mem_pool->lock);
561 }
562 }
再修改下kernel/main.c:

1 #include "print.h"
2 #include "init.h"
3 #include "thread.h"
4 #include "interrupt.h"
5 #include "console.h"
6 #include "ioqueue.h"
7 #include "process.h"
8 #include "syscall.h"
9 #include "syscall-init.h"
10 #include "stdio.h"
11 #include "memory.h"
12
13 void k_thread_a(void*);
14 void k_thread_b(void*);
15 void u_prog_a(void);
16 void u_prog_b(void);
17
18 int main(void){
19 put_str("Welcome,\nI am kernel!\n");
20 init_all();
21
22 intr_enable();
23 thread_start("k_thread_a",31,k_thread_a,"argA ");
24 thread_start("k_thread_b",31,k_thread_b,"argB ");
25
26 while(1);
27 return 0;
28 }
29
30 /* 在线程中运行的函数 */
31 void k_thread_a(void* arg) {
32 char* para=arg;
33 void* addr1;
34 void* addr2;
35 void* addr3;
36 void* addr4;
37 void* addr5;
38 void* addr6;
39 void* addr7;
40 console_put_str(" thread_a start\n");
41 int max=1000;
42 while (max-->0) {
43 int size=128;
44 addr1=sys_malloc(size);
45 size*=2;
46 addr2=sys_malloc(size);
47 size*=2;
48 addr3=sys_malloc(size);
49 sys_free(addr1);
50 addr4=sys_malloc(size);
51 size*=2;size*=2;size*=2;size*=2;
52 size*=2;size*=2;size*=2;
53 addr5=sys_malloc(size);
54 addr6=sys_malloc(size);
55 sys_free(addr5);
56 size*=2;
57 addr7=sys_malloc(size);
58 sys_free(addr6);
59 sys_free(addr7);
60 sys_free(addr2);
61 sys_free(addr3);
62 sys_free(addr4);
63 }
64 console_put_str(" thread_a end\n");
65 while(1);
66 }
67
68 /* 在线程中运行的函数 */
69 void k_thread_b(void* arg) {
70 char* para=arg;
71 void* addr1;
72 void* addr2;
73 void* addr3;
74 void* addr4;
75 void* addr5;
76 void* addr6;
77 void* addr7;
78 void* addr8;
79 void* addr9;
80 int max=1000;
81 console_put_str(" thread_b start\n");
82 while (max-->0) {
83 int size=9;
84 addr1=sys_malloc(size);
85 size*=2;
86 addr2=sys_malloc(size);
87 size*=2;
88 sys_free(addr2);
89 addr3=sys_malloc(size);
90 sys_free(addr1);
91 addr4=sys_malloc(size);
92 addr5=sys_malloc(size);
93 addr6=sys_malloc(size);
94 sys_free(addr5);
95 size*=2;
96 addr7=sys_malloc(size);
97 sys_free(addr6);
98 sys_free(addr7);
99 sys_free(addr3);
100 sys_free(addr4);
101
102 size*=2;size*=2;size*=2;
103 addr1=sys_malloc(size);
104 addr2=sys_malloc(size);
105 addr3=sys_malloc(size);
106 addr4=sys_malloc(size);
107 addr5=sys_malloc(size);
108 addr6=sys_malloc(size);
109 addr7=sys_malloc(size);
110 addr8=sys_malloc(size);
111 addr9=sys_malloc(size);
112 sys_free(addr1);
113 sys_free(addr2);
114 sys_free(addr3);
115 sys_free(addr4);
116 sys_free(addr5);
117 sys_free(addr6);
118 sys_free(addr7);
119 sys_free(addr8);
120 sys_free(addr9);
121 }
122 console_put_str(" thread_b end\n");
123 while(1);
124 }
125
126 /* 测试用户进程 */
127 void u_prog_a(void) {
128 char* name="prog_a";
129 printf(" I'm %s, my pid:%d%c",name,getpid(),'\n');
130 while(1);
131 }
132
133 /* 测试用户进程 */
134 void u_prog_b(void) {
135 char* name="prog_b";
136 printf(" I'm %s, my pid:%d%c",name,getpid(),'\n');
137 while(1);
138 }
就有了以下结果:
输出“thread_a end”和“thread_b end”要稍微等一会儿,你要是想看到实时进度的话可以在函数中把max的值输出出来。
最后就是要实现free()函数了——复习一下增加系统调用的过程吧。
①首先是lib/user/syscall.h:

1 #ifndef __LIB_USER_SYSCALL_H
2 #define __LIB_USER_SYSCALL_H
3 #include "stdint.h"
4 enum SYSCALL_NR{
5 SYS_GETPID,
6 SYS_WRITE,
7 SYS_MALLOC,
8 SYS_FREE
9 };
10 uint32_t getpid(void);
11 uint32_t write(char* str);
12 void* malloc(uint32_t size);
13 void free(void* ptr);
14 #endif
②然后是lib/user/syscall.c:

1 #include "syscall.h"
2
3 /* 无参数的系统调用 */
4 #define _syscall0(NUMBER) ({ \
5 int retval; \
6 asm volatile ( \
7 "int $0x80" \
8 : "=a"(retval) \
9 : "a"(NUMBER) \
10 : "memory"); \
11 retval; \
12 })
13
14 /* 一个参数的系统调用 */
15 #define _syscall1(NUMBER,ARG1) ({ \
16 int retval; \
17 asm volatile ( \
18 "int $0x80" \
19 : "=a"(retval) \
20 : "a"(NUMBER),"b"(ARG1) \
21 : "memory"); \
22 retval; \
23 })
24
25 #define _syscall2(NUMBER,ARG1,ARG2) ({ \
26 int retval; \
27 asm volatile ( \
28 "int $0x80" \
29 : "=a"(retval) \
30 : "a"(NUMBER),"b"(ARG1),"c"(ARG2) \
31 : "memory"); \
32 retval; \
33 })
34
35 #define _syscall3(NUMBER,ARG1,ARG2,ARG3)({ \
36 int retval; \
37 asm volatile ( \
38 "int $0x80" \
39 : "=a"(retval) \
40 : "a"(NUMBER),"b"(ARG1),"c"(ARG2),"d"(ARG3) \
41 : "memory"); \
42 retval; \
43 })
44
45 /* 获取任务pid */
46 uint32_t getpid(void){
47 return _syscall0(SYS_GETPID);
48 }
49
50 /* 打印字符串str */
51 uint32_t write(char* str){
52 return _syscall1(SYS_WRITE,str);
53 }
54
55 /* 申请size字节大小的内存,并返回结果 */
56 void* malloc(uint32_t size){
57 return (void*)_syscall1(SYS_MALLOC,size);
58 }
59
60 /* 释放ptr指向的内存 */
61 void free(void* ptr){
62 _syscall1(SYS_FREE,ptr);
63 }
③还有userprog/syscall-init.c:

1 #include "syscall-init.h"
2 #include "syscall.h"
3 #include "stdint.h"
4 #include "print.h"
5 #include "thread.h"
6 #include "console.h"
7 #include "string.h"
8
9 #define syscall_nr 32
10 typedef void* syscall;
11 syscall syscall_table[syscall_nr];
12
13 /* 返回当前任务的pid */
14 uint32_t sys_getpid(void){
15 return running_thread()->pid;
16 }
17
18 /* 打印字符串str(未实现文件系统的版本)*/
19 uint32_t sys_write(char* str){
20 console_put_str(str);
21 return strlen(str);
22 }
23
24 /* 初始化系统调用 */
25 void syscall_init(void){
26 put_str("syscall_init start\n");
27 syscall_table[SYS_GETPID]=sys_getpid;
28 syscall_table[SYS_WRITE]=sys_write;
29 syscall_table[SYS_MALLOC]=sys_malloc;
30 syscall_table[SYS_FREE]=sys_free;
31 put_str("syscall_init done\n");
32 }
④还要再修改kernel/memory.c,因为我发现在get_user_pages()中如果不对user_pool.lock上锁,就会产生竞争:

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 #include "sync.h"
9 #include "interrupt.h"
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 struct lock lock; // 申请内存时互斥锁
26 };
27
28 /* 内存仓库 */
29 struct arena{
30 struct mem_block_desc* desc; // 此arena关联的mem_block_desc
31 /* large为true,cnt表示页框数
32 * 否则false,表示空闲的mem_block数 */
33 uint32_t cnt;
34 bool large;
35 };
36
37 struct mem_block_desc k_block_descs[DESC_CNT]; // 内核内存块描述符数组
38
39 struct pool kernel_pool,user_pool; // 生成内核内存池和用户内存池
40 struct virtual_addr kernel_vaddr; // 此结构用来给内核分配虚拟地址
41
42 /* 在pf表示的虚拟内存池中申请pg_cnt个虚拟页
43 * 成功则返回虚拟页的起始地址,失败则返回NULL */
44 static void* vaddr_get(enum pool_flags pf,uint32_t pg_cnt){
45 int vaddr_start=0,bit_idx_start=-1;
46 uint32_t cnt=0;
47 if (pf==PF_KERNEL){ // 内核内存池
48 bit_idx_start=bitmap_scan(&kernel_vaddr.vaddr_bitmap,pg_cnt);
49 if (bit_idx_start==-1){
50 return NULL;
51 }
52 while (cnt<pg_cnt){
53 bitmap_set(&kernel_vaddr.vaddr_bitmap,bit_idx_start+cnt++,1);
54 }
55 vaddr_start=kernel_vaddr.vaddr_start+bit_idx_start*PG_SIZE;
56 }else{ // 用户内存池
57 struct task_struct* cur=running_thread();
58 bit_idx_start=bitmap_scan(&cur->userprog_vaddr.vaddr_bitmap,pg_cnt);
59 if (bit_idx_start==-1){
60 return NULL;
61 }
62 while (cnt<pg_cnt){
63 bitmap_set(&cur->userprog_vaddr.vaddr_bitmap,bit_idx_start+cnt++,1);
64 }
65 vaddr_start=cur->userprog_vaddr.vaddr_start+bit_idx_start*PG_SIZE;
66
67 ASSERT((uint32_t)vaddr_start<(0xc0000000-PG_SIZE));
68 }
69 return (void*)vaddr_start;
70 }
71
72 /* 得到虚拟地址vaddr对应的pte指针 */
73 uint32_t* pte_ptr(uint32_t vaddr){
74 /* 先访问到页表自己 +
75 * 再用页目录项pde作为pte的索引访问到页表 +
76 * 再用页表项pte作为页内偏移 */
77 uint32_t* pte=(uint32_t*)(0xffc00000+\
78 ((vaddr & 0xffc00000)>>10)+\
79 PTE_IDX(vaddr)*4);
80 return pte;
81 }
82
83 /* 得到虚拟地址vaddr对应的pde指针 */
84 uint32_t* pde_ptr(uint32_t vaddr){
85 /* 0xfffff用来访问到页表本身所在的地址 */
86 uint32_t* pde=(uint32_t*)((0xfffff000)+PDE_IDX(vaddr)*4);
87 return pde;
88 }
89
90 /* 在m_pool指向的物理内存池中分配1个物理页,
91 * 成功则返回页框的物理地址,失败则返回NULL */
92 static void* palloc(struct pool* m_pool){
93 /* 扫描或设置位图要保证原子操作 */
94 int bit_idx=bitmap_scan(&m_pool->pool_bitmap,1); // 找一个物理页面
95 if (bit_idx==-1){
96 return NULL;
97 }
98 bitmap_set(&m_pool->pool_bitmap,bit_idx,1); // 将此位bit_idx置为1
99 uint32_t page_phyaddr=((bit_idx*PG_SIZE)+m_pool->phy_addr_start);
100 return (void*)page_phyaddr;
101 }
102
103 /* 页表中添加虚拟地址_vaddr与物理地址_page_phyaddr的映射 */
104 static void page_table_add(void* _vaddr,void* _page_phyaddr){
105 uint32_t vaddr=(uint32_t)_vaddr,page_phyaddr=(uint32_t)_page_phyaddr;
106 uint32_t* pde=pde_ptr(vaddr);
107 uint32_t* pte=pte_ptr(vaddr);
108
109 /***************************** 注意 ****************************
110 * 执行*pte,会访问到空的pde。所以确保pde创建完成后才嫩执行*pte,
111 * 否则会引发page_fault。因此在*pde为0时,pte只能在下面else语句块的*pde后面。
112 **************************************************************/
113 /* 先在页目录内判断目录项的P位,若为1,则表示该表已存在 */
114 if (*pde & 0x00000001){ //P位,此处判断目录项是否存在。若存在
115 ASSERT(!(*pte & 0x00000001));
116
117 if(!(*pte & 0x00000001)){
118 *pte=(page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
119 }else{ // 理论上不会执行到这里
120 PANIC("pte repeat");
121 *pte=(page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
122 }
123 }else{ // 页目录项不存在,所以要先创建页目录项再创建页表项
124 /* 页表中用到的页框一律从内核空间分配 */
125 uint32_t pde_phyaddr=(uint32_t)palloc(&kernel_pool);
126
127 *pde=(pde_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
128
129 /* 分配到的物理页地址pde_phyaddr对应的物理内存清0,
130 * 避免里面的陈旧数据变成了页表项,从而让页表混乱。
131 * 访问到pde对应的物理地址,用pte取高20位即可。
132 * 因为pte基于该pde对应的物理地址内再寻址,
133 * 把低12位置0便是该pde对应的物理页的起始 */
134 memset((void*)((int)pte & 0xfffff000),0,PG_SIZE);
135
136 ASSERT(!(*pte & 0x00000001));
137 *pte=(page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
138 }
139 }
140
141 /* 分配pg_cnt个页空间,成功则返回起始虚拟地址,失败则返回NULL */
142 void* malloc_page(enum pool_flags pf,uint32_t pg_cnt){
143 ASSERT(pg_cnt>0 && pg_cnt<3840);
144 /************ malloc_page 的原理是三个动作的合成:**********
145 * 1.通过vaddr_get在虚拟地址内存池中的申请虚拟地址
146 * 2.通过palloc在物理内存池中申请物理页
147 * 3.通过page_table_add将以上得到的虚拟地址和物理地址在页表中完成映射
148 **********************************************************/
149 void* vaddr_start=vaddr_get(pf,pg_cnt);
150 if (vaddr_start==NULL){
151 return NULL;
152 }
153
154 uint32_t vaddr=(uint32_t)vaddr_start,cnt=pg_cnt;
155 struct pool* mem_pool=(pf & PF_KERNEL)?&kernel_pool:&user_pool;
156
157 /* 因为虚拟地址是连续的,但物理地址可以是不连续的,所以逐个做映射 */
158 while (cnt-->0){
159 void* page_phyaddr=palloc(mem_pool);
160 if (page_phyaddr==NULL){ // 申请物理内存失败,将已申请的虚拟地址和物理页全部回滚,在将来完成内存回收时再补充
161 return NULL;
162 }
163 page_table_add((void*)vaddr,page_phyaddr); // 在页表中做映射
164 vaddr+=PG_SIZE; // 下一个虚拟页
165 }
166 return vaddr_start;
167 }
168
169 /* 在用户空间中申请4k内存,并返回其虚拟地址 */
170 void* get_user_pages(uint32_t pg_cnt){
171 lock_acquire(&user_pool.lock);
172 void* vaddr=malloc_page(PF_USER,pg_cnt);
173 if (vaddr!=NULL){
174 memset(vaddr,0,pg_cnt*PG_SIZE);
175 }
176 lock_release(&user_pool.lock);
177 return vaddr;
178 }
179
180 /* 从内核物理内存池中申请1页内存,成功则返回其虚拟地址,失败则返回NULL */
181 void* get_kernel_pages(uint32_t pg_cnt){
182 lock_acquire(&kernel_pool.lock);
183 void* vaddr=malloc_page(PF_KERNEL,pg_cnt);
184 if (vaddr!=NULL){ // 若分配的地址不为空,将页框清0
185 memset(vaddr,0,pg_cnt*PG_SIZE);
186 }
187 lock_release(&kernel_pool.lock);
188 return vaddr;
189 }
190
191 /* 将地址vaddr与pf池中的物理地址关联,仅支持一页空间分配 */
192 void* get_a_page(enum pool_flags pf,uint32_t vaddr){
193 struct pool* mem_pool=pf & PF_KERNEL?&kernel_pool:&user_pool;
194 lock_acquire(&mem_pool->lock);
195
196 /* 先将虚拟地址对应的位图置1 */
197 struct task_struct* cur=running_thread();
198 int32_t bit_idx=-1;
199
200 /* 若当前是用户进程申请用户内存,就修改用户进程自己的虚拟地址位图 */
201 if (cur->pgdir!=NULL && pf==PF_USER){
202 bit_idx=(vaddr-cur->userprog_vaddr.vaddr_start)/PG_SIZE;
203 ASSERT(bit_idx>0);
204 bitmap_set(&cur->userprog_vaddr.vaddr_bitmap,bit_idx,1);
205
206 }else if (cur->pgdir==NULL && pf==PF_KERNEL){
207 /* 如果是内核线程申请内核内存,就修改kernel_vaddr. */
208 bit_idx=(vaddr-kernel_vaddr.vaddr_start)/PG_SIZE;
209 ASSERT(bit_idx>0);
210 bitmap_set(&kernel_vaddr.vaddr_bitmap,bit_idx,1);
211 }else {
212 PANIC("get_a_page:not allow kernel alloc userspace or user alloc kernelspace by get_a_page");
213 }
214
215 void* page_phyaddr=palloc(mem_pool);
216 if (page_phyaddr==NULL){
217 return NULL;
218 }
219 page_table_add((void*)vaddr,page_phyaddr);
220 lock_release(&mem_pool->lock);
221 return (void*)vaddr;
222 }
223
224 uint32_t addr_v2p(uint32_t vaddr){
225 uint32_t* pte=pte_ptr(vaddr);
226 /* (*pte)的值是页表所在的物理页框地址
227 * 去掉其低12位的页表项属性+虚拟地址vaddr的低12位 */
228 return ((*pte & 0xfffff000)+(vaddr & 0x00000fff));
229 }
230
231 /* 初始化内存池 */
232 static void mem_pool_init(uint32_t all_mem){
233 put_str(" mem_pool_init start\n");
234 uint32_t page_table_size=PG_SIZE*256;
235
236 uint32_t used_mem=page_table_size+0x100000;
237 uint32_t free_mem=all_mem-used_mem;
238 uint16_t all_free_pages=free_mem/PG_SIZE;
239
240 uint16_t kernel_free_pages=all_free_pages/2;
241 uint16_t user_free_pages=all_free_pages-kernel_free_pages;
242
243 /* 为简化位图操作,余数不处理,不用考虑越界检查,但会丢失部分内存 */
244 uint32_t kbm_length=kernel_free_pages/8; // kernel bitmap的长度。1位管理1页,单位为字节
245 uint32_t ubm_length=user_free_pages/8; // user bitmap的长度
246
247 uint32_t kp_start=used_mem; // KernelLPool,内核内存池的起始地址
248 uint32_t up_start=kp_start+kernel_free_pages*PG_SIZE; // UserPool,用户内存池的起始地址
249
250 kernel_pool.phy_addr_start=kp_start;
251 user_pool.phy_addr_start=up_start;
252
253 kernel_pool.pool_size=kernel_free_pages*PG_SIZE;
254 user_pool.pool_size=user_free_pages*PG_SIZE;
255
256 kernel_pool.pool_bitmap.btmp_bytes_len=kbm_length;
257 user_pool.pool_bitmap.btmp_bytes_len=ubm_length;
258
259
260 /************** 内核内存池和用户内存池位图 ***************
261 * 位图是全局的数据,长度不固定。
262 * 全局或静态的数组需要在编译时知道其长度,
263 * 而我们需要根据总内存大小算出需要多少字节,
264 * 所以改为指定一块内存来生成位图。
265 * ******************************************************/
266 // 内核使用的最高地址是0xc009f000,这是主线程的站地址
267 // 32MB内存占用的位图是2KB,内核内存池位图先定在MEM_BITMAP_BASE(0xc009a000)处
268 kernel_pool.pool_bitmap.bits=(void*)MEM_BITMAP_BASE;
269
270 /* 用户内存池的位图紧跟在内核内存池位图之后 */
271 user_pool.pool_bitmap.bits=(void*)(MEM_BITMAP_BASE+kbm_length);
272
273 /***************** 输出内存池信息 ***********************/
274 put_str(" kernel_pool_bitmap_start:");
275 put_int((int)kernel_pool.pool_bitmap.bits);
276 put_str(" kernel_pool_phy_addr_start:");
277 put_int((int)kernel_pool.phy_addr_start);
278 put_str("\n");
279 put_str(" user_pool_bitmap_start:");
280 put_int((int)user_pool.pool_bitmap.bits);
281 put_str(" user_pool_phy_addr_strar:");
282 put_int((int)user_pool.phy_addr_start);
283 put_str("\n");
284
285 /*将位图置0*/
286 bitmap_init(&kernel_pool.pool_bitmap);
287 bitmap_init(&user_pool.pool_bitmap);
288
289 lock_init(&kernel_pool.lock);
290 lock_init(&user_pool.lock);
291
292 /* 下面初始化内核虚拟地址位图,按实际物理内存大小生成数组 */
293 kernel_vaddr.vaddr_bitmap.btmp_bytes_len=kbm_length;
294 // 用于维护内核堆的虚拟地址,所以要和内核内存池大小一致
295
296 /* 位图的数组指向一块未使用的内存,目前定位在内核内存池和用户内存池之外 */
297 kernel_vaddr.vaddr_bitmap.bits=(void*)(MEM_BITMAP_BASE+kbm_length+ubm_length);
298
299 kernel_vaddr.vaddr_start=K_HEAP_START;
300 bitmap_init(&kernel_vaddr.vaddr_bitmap);
301 put_str(" mem_pool_init done\n");
302 }
303
304 /* 内存管理部分初始化入口 */
305 void mem_init(){
306 put_str("mem_init start\n");
307 uint32_t mem_bytes_total=(*(uint32_t*)(0xb00));
308 mem_pool_init(mem_bytes_total); // 初始化内存池
309 /* 初始化mem_block_desc数组descs,为malloc做准备 */
310 block_desc_init(k_block_descs);
311 put_str("mem_init done\n");
312 }
313
314 /* 为malloc做准备 */
315 void block_desc_init(struct mem_block_desc* desc_array){
316 uint16_t desc_idx,block_size=16;
317
318 /* 初始化每个mem_block_desc描述符 */
319 for (desc_idx=0;desc_idx<DESC_CNT;++desc_idx){
320 desc_array[desc_idx].block_size=block_size;
321
322 /* 初始化arena中的内存块数量 */
323 desc_array[desc_idx].blocks_per_arena=(PG_SIZE-sizeof(struct arena))/block_size;
324 list_init(&desc_array[desc_idx].free_list);
325
326 block_size*=2; // 更新为下一个内存块
327 }
328 }
329
330 /* 返回arena中第idx个内存块的地址 */
331 static struct mem_block* arena2block(struct arena* a,uint32_t idx){
332 return (struct mem_block*)((uint32_t)a+sizeof(struct arena)+idx*a->desc->block_size);
333 }
334
335 /* 返回内存块b所在的arena地址 */
336 static struct arena* block2arena(struct mem_block* b){
337 return (struct arena*)((uint32_t)b & 0xfffff000);
338 }
339
340 /* 在堆中申请size个字节内存 */
341 void* sys_malloc(uint32_t size){
342 enum pool_flags pf;
343 struct pool* mem_pool;
344 uint32_t pool_size;
345 struct mem_block_desc* descs;
346 struct task_struct* cur_thread=running_thread();
347
348 /* 判断用哪个内存池 */
349 if (cur_thread->pgdir==NULL){ // 若为内核线程
350 pf=PF_KERNEL;
351 pool_size=kernel_pool.pool_size;
352 mem_pool=&kernel_pool;
353 descs=k_block_descs;
354 }else { // 若为用户进程,其pcb中的pgdir会在分配页表时创建
355 pf=PF_USER;
356 pool_size=user_pool.pool_size;
357 mem_pool=&user_pool;
358 descs=cur_thread->u_block_desc;
359 }
360
361 /* 若申请的内存不在内存池容量范围内则直接返回NULL */
362 if (!(size>0 && size<pool_size)){
363 return NULL;
364 }
365 struct arena* a;
366 struct mem_block* b;
367 lock_acquire(&mem_pool->lock);
368
369 /* 超过最大内存块1024KB,就分配页框 */
370 if (size>1024){
371 uint32_t page_cnt=DIV_ROUND_UP(size+sizeof(struct arena),PG_SIZE); // 向上取整需要的页框数
372 a=malloc_page(pf,page_cnt);
373
374 if (a!=NULL){
375 memset(a,0,page_cnt*PG_SIZE); // 将分配的内存清0
376
377 /* 对于分配的大块页框,将desc置为NULL,cnt置为页框数,large置为true */
378 a->desc=NULL;
379 a->cnt=page_cnt;
380 a->large=true;
381 lock_release(&mem_pool->lock);
382 return (void*)(a+1); // 跨过arena大小,把剩下的内存返回
383 }else {
384 lock_release(&mem_pool->lock);
385 return NULL;
386 }
387 }else { // 若申请的内存小于等于1024,可在各种规格的mem_block_desc中适配
388 uint8_t desc_idx;
389
390 /* 从内存块描述符中匹配合适的内存块规格 */
391 for (desc_idx=0;desc_idx<DESC_CNT;++desc_idx){
392 if (size<=descs[desc_idx].block_size){ // 从小往大依次寻找,找到第一个比所需大的即可退出
393 break;
394 }
395 }
396
397 /* 若mem_block_desc的free_list中已经没有可用的mem_block,
398 * 就创建新的arena提供mem_block */
399 if (list_empty(&descs[desc_idx].free_list)){
400 a=malloc_page(pf,1); // 分配1页页框作arena
401 if (a==NULL){
402 lock_release(&mem_pool->lock);
403 return NULL;
404 }
405 memset(a,0,PG_SIZE);
406
407 /* 对于分配小块内存,将desc置为相应内存块描述符,
408 * cnt置为此arena可用的内存块数,large置为false */
409 a->desc=&descs[desc_idx];
410 a->large=false;
411 a->cnt=descs[desc_idx].blocks_per_arena;
412 uint32_t block_idx;
413
414 enum intr_status old_status=intr_disable();
415
416 /* 开始将arena拆开成内存块,并添加到内存块描述符的free_list中 */
417 for (block_idx=0;block_idx<descs[desc_idx].blocks_per_arena;++block_idx){
418 b=arena2block(a,block_idx);
419 ASSERT(!elem_find(&a->desc->free_list,&b->free_elem));
420 list_append(&a->desc->free_list,&b->free_elem);
421 }
422 intr_set_status(old_status);
423 }
424
425 /* 开始分配内存块 */
426 b=elem2entry(struct mem_block,free_elem,list_pop(&(descs[desc_idx].free_list)));
427 memset(b,0,descs[desc_idx].block_size);
428
429 a=block2arena(b); // 获取内存块b最在的arena
430 --a->cnt; // 将此arena中的空闲内存块-1
431 lock_release(&mem_pool->lock);
432 return (void*)b;
433 }
434 }
435
436 void pfree(uint32_t pg_phy_addr){
437 struct pool* mem_pool;
438 uint32_t bit_idx=0;
439 if (pg_phy_addr>=user_pool.phy_addr_start){ // 用户物理内存池
440 mem_pool=&user_pool;
441 bit_idx=(pg_phy_addr-user_pool.phy_addr_start)/PG_SIZE;
442 }else {
443 mem_pool=&kernel_pool;
444 bit_idx=(pg_phy_addr-kernel_pool.phy_addr_start)/PG_SIZE;
445 }
446 bitmap_set(&mem_pool->pool_bitmap,bit_idx,0); // 将位图中的该位清0
447 }
448
449 /* 去掉页表中虚拟地址vaddr的映射,只去掉vaddr对应的pte */
450 static void page_table_pte_remove(uint32_t vaddr){
451 uint32_t* pte=pte_ptr(vaddr);
452 *pte&=~PG_P_1; // 将页表项pte位置0
453 asm volatile("invlpg %0"::"m"(vaddr):"memory");// 更新tlb
454 }
455
456 /* 在虚拟地址池中释放_vaddr起始的连续pg_cnt个虚拟地址 */
457 static void vaddr_remove(enum pool_flags pf,void* _vaddr,uint32_t pg_cnt){
458 uint32_t bit_idx_start=0,vaddr=(uint32_t)_vaddr,cnt=0;
459 if (pf==PF_KERNEL){ // 内核虚拟地址池
460 bit_idx_start=(vaddr-kernel_vaddr.vaddr_start)/PG_SIZE;
461 while (cnt<pg_cnt){
462 bitmap_set(&kernel_vaddr.vaddr_bitmap,bit_idx_start+cnt++,0);
463 }
464 }else { // 用户虚拟地址池
465 struct task_struct* cur_thread=running_thread();
466 bit_idx_start=(vaddr-cur_thread->userprog_vaddr.vaddr_start)/PG_SIZE;
467 while (cnt<pg_cnt){
468 bitmap_set(&cur_thread->userprog_vaddr.vaddr_bitmap,bit_idx_start+cnt++,0);
469 }
470 }
471 }
472
473 /* 释放以虚拟地址vaddr为起始的cnt个物理页框 */
474 void mfree_page(enum pool_flags pf,void* _vaddr,uint32_t pg_cnt){
475 uint32_t pg_phy_addr;
476 uint32_t vaddr=(int32_t)_vaddr,page_cnt=0;
477 ASSERT(pg_cnt>=1 && vaddr%PG_SIZE==0);
478 pg_phy_addr=addr_v2p(vaddr); // 获取虚拟地址vaddr对应的物理地址
479
480 /* 确保待释放的物理内存在低端1M+1K大小的页目录+1K大小的页表地址范围外 */
481 ASSERT((pg_phy_addr%PG_SIZE)==0 && pg_phy_addr>=0x102000);
482
483 /* 判断pg_phy_addr属于用户物理内存池还是内核五i了内存池 */
484 if (pg_phy_addr>=user_pool.phy_addr_start){ // 位于user_pool内存池
485 vaddr-=PG_SIZE;
486 while (page_cnt<pg_cnt){
487 vaddr+=PG_SIZE;
488 pg_phy_addr=addr_v2p(vaddr);
489
490 /* 确保物理地址属于用户物理内存池 */
491 ASSERT((pg_phy_addr%PG_SIZE)==0 && pg_phy_addr>=user_pool.phy_addr_start);
492 /* 先将对应的物理页框归还到内存池 */
493 pfree(pg_phy_addr);
494
495 /* 再从页表中清除此虚拟地址所在的页表项pte */
496 page_table_pte_remove(vaddr);
497
498 ++page_cnt;
499 }
500 /* 清空虚拟地址的为途中的相应位 */
501 vaddr_remove(pf,_vaddr,pg_cnt);
502
503 }else { // 位于kernel_pool内存池
504 vaddr-=PG_SIZE;
505 while (page_cnt<pg_cnt){
506 vaddr+=PG_SIZE;
507 pg_phy_addr=addr_v2p(vaddr);
508 /* 确保待释放的物理内存只属于内核物理内存池 */
509 ASSERT((pg_phy_addr%PG_SIZE)==0 && \
510 pg_phy_addr>=kernel_pool.phy_addr_start && \
511 pg_phy_addr<user_pool.phy_addr_start);
512
513 /* 先将对应的物理页框归还到内存池 */
514 pfree(pg_phy_addr);
515
516 /* 再从页表中清除此虚拟地址所在的页表项pte */
517 page_table_pte_remove(vaddr);
518
519 ++page_cnt;
520 }
521 /* 清空虚拟地址的位图中相应位 */
522 vaddr_remove(pf,_vaddr,pg_cnt);
523 }
524 }
525
526 /* 回收内存ptr */
527 void sys_free(void* ptr){
528 ASSERT(ptr!=NULL);
529 if (ptr!=NULL){
530 enum pool_flags pf;
531 struct pool* mem_pool;
532
533 /* 判断是线程还是进程 */
534 if (running_thread()->pgdir==NULL){
535 ASSERT((uint32_t)ptr>=K_HEAP_START);
536 pf=PF_KERNEL;
537 mem_pool=&kernel_pool;
538 }else {
539 pf=PF_USER;
540 mem_pool=&user_pool;
541 }
542
543 lock_acquire(&mem_pool->lock);
544 struct mem_block* b=ptr;
545 struct arena* a=block2arena(b); // 把mem_block转换成arena,获取元信息
546 ASSERT(a->large==0 || a->large==1);
547 if (a->desc==NULL && a->large==true){ // 大于1024的内存
548 mfree_page(pf,a,a->cnt);
549 }else {
550 list_append(&a->desc->free_list,&b->free_elem);
551
552 /* 再判断此arena中的内存块是否都是空闲,如果是就释放arena */
553 if (++a->cnt==a->desc->blocks_per_arena){
554 uint32_t block_idx;
555 for (block_idx=0;block_idx<a->desc->blocks_per_arena;++block_idx){
556 struct mem_block* b=arena2block(a,block_idx);
557 ASSERT(elem_find(&a->desc->free_list,&b->free_elem));
558 list_remove(&b->free_elem);
559 }
560 mfree_page(pf,a,1);
561 }
562 }
563 lock_release(&mem_pool->lock);
564 }
565 }
最后运行结果如下:
dengdengdengdeng,其中对于用户进程a和b的分配地址是相同的,因为它们使用的是互相独立的虚拟地址;而两个内核线程的地址则是相同的,应为内核线程共享内核地址。ok,内存分配结束!
不早了,要赶紧溜了,11点还要熄灯呢QAQ
参考博客:
【推荐】编程新体验,更懂你的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 打造的强大开源交互式图表库