《操作系统真象还原》第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 }
View Code
复制代码

我们将进程和线程的创建顺序换了,尽量使得在进程执行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
View Code
复制代码

②修改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 }
View Code
复制代码

③修改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
View Code
复制代码

④修改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 }
View Code
复制代码

⑤增加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 }
View Code
复制代码

⑧修改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
View Code
复制代码

最后结果如图所示:


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 }
View Code
复制代码

③在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 }
View Code
复制代码

⑥别忘记修改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 }
View Code
复制代码

再修改下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 }
View Code
复制代码

就有了以下结果:

输出“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
View Code
复制代码

②然后是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 }
View Code
复制代码

③还有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 }
View Code
复制代码

④还要再修改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 }
View Code
复制代码

最后运行结果如下:

dengdengdengdeng,其中对于用户进程a和b的分配地址是相同的,因为它们使用的是互相独立的虚拟地址;而两个内核线程的地址则是相同的,应为内核线程共享内核地址。ok,内存分配结束!

不早了,要赶紧溜了,11点还要熄灯呢QAQ


参考博客:

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