真象还原操作系统_第十二章_进一步完善内核

一、Linux系统调用浅析

  • CPU执行指令int 0x80来触发系统调用。在系统调用之前,linux向寄存器eax中写入子功能号,当用户程序通过int 0x80进行系统调用时,对应的中断处理程序会根据eax的值来判断用户进程申请那种系统调用。
  • 系统调用的使用:
    1. 间接:库函数syscall
    2. 直接:Linux提供的一系列宏_syscall[X],本质上是" mov eax,子功能号; int 0x80"命令的封装。

二、系统调用的实现

1. 系统调用实现框架

  • 系统功能分为两部分:
    1. 一部分是暴露给用户进程的接口函数,它属于用户空间。
    2. 一部分是内核的具体实现,它属于内核空间,此部分完成的是功能需求。
    3. 一般内核空间的函数名要在用户空间函数名前加 “sys_”
  • 系统调用的实现思路:
    1. 用中断门实现系统调用,效仿0x80号中断作为系统调用的入口。
    2. 在IDT中安装0x80号中断对应的描述符,在该描述符中注册系统调用对应的中断处理程序。
    3. 建立系统调用子功能表syscall_table,利用eax寄存器中的子功能号在该表中索引相应的处理函数。
    4. 用宏实现用户空间系统调用接口_syscall,最大支持3个参数的系统调用,故只需要完成_syscall[0~3]。寄存器传递参数,eax为子功能号,ebx保存第一个参数,ecx保存第二个参数,edx保存第三个参数。
      在这里插入图片描述

2. 实现系统调用

  • 中断处理函数的dpl为3,若为0级,则用户程序执行int指令会产生GP异常。
  • 使syscall_table中的元素为子功能处理函数指针

---------------------------代码-------------------------------

  • “/home/lily/OS/boot/kernel/interrupt.c”
#include "interrupt.h"
#include "stdint.h"
#include "global.h"
#include "io.h"
#include "print.h"

#define IDT_DESC_CNT 0x81           //目前共支持的中断数,33
#define PIC_M_CTRL 0x20             //主片的控制端口是0x20
#define PIC_M_DATA 0x21             //主片的数据端口是0x21
#define PIC_S_CTRL 0xa0             //从片的控制端口是0xa0
#define PIC_S_DATA 0xa1             //从片的数据端口是0xa1
#define EFLAGS_IF 0x00000200	    //eflags寄存器中的if=1
#define GET_FLAGS(EFLAG_VAR) asm volatile("pushfl;popl %0" : "=g"(EFLAG_VAR))
//"=g" 指示编译器将结果放在任意通用寄存器中,并将其赋值给 EFLAG_VAR。
//pushfl 指令将标志寄存器 EFLAGS 的值入栈,然后 popl %0 指令将栈顶的值弹出到指定的操作数 %0 中。
//当调用 GET_FLAGS(EFLAG_VAR) 宏时,它将 EFLAGS 寄存器的值存储到 EFLAG_VAR 变量中。

/*中断门描述符结构体*/
struct gate_desc{
    uint16_t func_offset_low_word;  //低32位——0~15位:中断处理程序在目标代码段内的偏移量的第15~0位
    uint16_t selector;              //低32位——16~31位:目标CS段选择子
    uint8_t dcount;                 //高32位——0~7位:此项为双字计数字段,是门描述符中的第4字节,为固定值
    uint8_t attribute;              //高32位——8~15位:P+DPL+S+TYPE
    uint16_t func_offset_high_word; //高32位——16~31位:中断处理程序在目标代码段内的偏移量的第16~31位
};

//静态函数声明,非必须
static void make_idt_desc(struct gate_desc* p_gdesc,uint8_t attr,intr_handler function);
static struct gate_desc idt[IDT_DESC_CNT];  //IDT是中描述符表,实际上是中断门描述符数组

extern uint32_t syscall_handler(void);
extern intr_handler intr_entry_table[IDT_DESC_CNT]; //指针格式应该与数组类型一致,这里intr_entry_table中的元素类型就是function,就是.text的地址。所以用intr_handle来引用。声明引用定义在kernel.S中的中断处理函数入口数组
char* intr_name[IDT_DESC_CNT];      //用于保存异常的名字
/*定义中断处理函数:在kernel.S中定义的intrXXentry只是中断处理程序的入口,最终调用的是ide_table中的处理程序*/
intr_handler idt_table[IDT_DESC_CNT];   //idt_table为函数数组,里面保持了中断处理函数的指针

/*创建中断门描述符*/
static void make_idt_desc(struct gate_desc* p_gdesc,uint8_t attr,intr_handler function){ //intr_handler是个空指针类型,仅用来表示地址 
    //中断门描述符的指针、中断描述符内的属性、中断描述符内对应的中断处理函数
    p_gdesc->func_offset_low_word = (uint32_t)function & 0x0000ffff;
    p_gdesc->selector = SELECTOR_K_CODE;
    p_gdesc->dcount = 0;
    p_gdesc->attribute = attr;
    p_gdesc->func_offset_high_word = ((uint32_t)function & 0xffff0000) >> 16;
}

/*初始化中断描述符表*/
static void idt_desc_init(void){
    int i,lastindex = IDT_DESC_CNT - 1;
    for(i = 0;i < IDT_DESC_CNT;i++){
        make_idt_desc(&idt[i],IDT_DESC_ATTR_DPL0,intr_entry_table[i]);
    }
    /*单独处理系统调用,系统调用对应的中断门dpl为3,中断处理程序为单独的syscall_handler*/
    make_idt_desc(&idt[lastindex],IDT_DESC_ATTR_DPL3,syscall_handler);
    put_str("   idt_desc_init done\n");
}

/*初始化可编程中断控制器8259A*/
static void pic_init(void){
    /*初始化主片*/
    outb(PIC_M_CTRL,0x11);          //ICW1:边沿触发,级联8259,需要ICW4
    outb(PIC_M_DATA,0x20);          //ICW2:起始中断向量号为0x20,也就是IR[0-7]为0x20~0x27
    outb(PIC_M_DATA,0x04);          //ICW3:IR2接从片
    outb(PIC_M_DATA,0x01);          //ICW4:8086模式,正常EOI
    /*初始化从片*/
    outb(PIC_S_CTRL,0x11);          //ICW1:边沿触发,级联8259,需要ICW4
    outb(PIC_S_DATA,0x28);          //ICW2:起始中断向量号为0x28,也就是IR[8-15]为0x28~0x2F
    outb(PIC_S_DATA,0x02);          //ICW3:设置从片连接到主片的IR2引脚
    outb(PIC_S_DATA,0x01);          //ICW4:8086模式,正常EOI
    /*打开主片上IR0,打开时钟产生的中断*/
    outb(PIC_M_DATA,0xfe);
    outb(PIC_S_DATA,0xff);
    // /*测试键盘,打开键盘中断*/
    // outb(PIC_M_DATA,0xfd);
    // outb(PIC_S_DATA,0xff);
    /*打开时钟中断和键盘中断*/
    // outb(PIC_M_DATA,0xfc);
    // outb(PIC_S_DATA,0xff);
    put_str("   pic_init done\n");
}

/*通用的中断处理函数,一般用在异常出现时的处理*/
static void general_intr_handler(uint8_t vec_nr){
    if(vec_nr == 0x27 || vec_nr == 0x2f){   //IRQ7和IRQ5会产生伪中断,无需处理;0x2f是从片8259A的最后一个IRQ引脚,保留项
        return;
    }
    //将光标置零,从屏幕左上角清出一片打印异常信息的区域,方便阅读
    set_cursor(0);
    int cursor_pos = 0;
    while (cursor_pos < 320)
    {
        put_char(' ');
        cursor_pos++;
    }
    set_cursor(0);      //重置光标为屏幕左上角
    put_str("!!!!!!! excetion message begin !!!!!!!\n");
    set_cursor(88);     //从第2行第8个字符开始打印
    put_str(intr_name[vec_nr]);
    if(vec_nr == 14){   //若为PageFault,将缺失的地址打印出来并悬停
        int page_fault_vaddr = 0;
        asm("movl %%cr2,%0" : "=r"(page_fault_vaddr));     //将cr2的值转存到page_fault中。cr2是存放造成page_fault的地址
        put_str("\npage fault addr is ");
        put_int(page_fault_vaddr);
    }
    put_str("!!!!!!! excetion message end !!!!!!!\n");
    //能进入中断处理程序就表示已经处在关中断情况下
    //不会出现调度进程的情况,故下面的死循环不会再被打断
    while (1);
}

/*初始化idt_table*/
static void exception_init(void){
    //将idt_table的元素都指向通用的中断处理函数,名称为unknown
    int i;
    for(i = 0;i < IDT_DESC_CNT;i++){
        idt_table[i] = general_intr_handler;    //默认为general_intr_handler,以后会由register_handler来注册具体函数
        intr_name[i] = "unknown";
    }
    intr_name[0] = "#DE Divide Error";
    intr_name[1] = "#DB Debug Exception";
    intr_name[2] = "NMI Interrupt";
    intr_name[3] = "BP Breakpoint Exception";
    intr_name[4] = "#OF Overflow Exception";
    intr_name[5] = "#BR BOUND Range Exceeded Exception";
    intr_name[6] = "#UD Invalid Opcode Exception";
    intr_name[7] = "#NM Device Not Available Exception";
    intr_name[8] = "#DF Double Fault Exception";
    intr_name[9] = "Coprocessor Segment Overrun";
    intr_name[10] = "#TS Invalid TSS Exception";
    intr_name[11] = "#NP Segment Not Present";
    intr_name[12] = "#SS Stack Fault Exception";
    intr_name[13] = "#GP General Protection Exception";
    intr_name[14] = "#PF Page-Fault Exception";
    //intr_name[15]是intel保留项,未使用
    intr_name[16] = "#MF x87 FPU Floating-Point Error";
    intr_name[17] = "#AC Alignment Check Exception";
    intr_name[18] = "#MC Machine-Check Exception";
    intr_name[19] = "#XF SIMD Floating-Point Exception";
}

/*完成所有有关中断的初始化工作*/
void idt_init(){
    put_str("idt_init start\n");
    idt_desc_init();                //初始化中断描述符表
    exception_init();
    pic_init();                     //初始化8259A
    /*加载idt*/
    // uint64_t idt_operand = ((sizeof(idt)-1) | (uint64_t)((uint32_t)idt << 16)); //书上是错的
    uint64_t idt_operand = ((sizeof(idt)-1) | ((uint64_t)(uint32_t)idt << 16)); //低16位是idt的大小,高48位是IDT的基址。因为idt是32位,左移16位后会丢失高16位,所以先转换为64位再左移
    asm volatile("lidt %0" : : "m" (idt_operand));   //加载IDT,IDT的0~15位是表界限,16~47位是表基址
    put_str("idt_init done\n");
}

/*开中断,并返回开中断前的状态*/
enum intr_status intr_enable(){
	enum intr_status old_status;
	if(INTR_ON == intr_get_status()){
		old_status = INTR_ON;
		return old_status;
	}else{
		old_status = INTR_OFF;
		asm volatile("sti");	//开中断
		return old_status;
	}
}

/*关中断,并返回关中断前的状态*/
enum intr_status intr_disable(){
	enum intr_status old_status;
	if(INTR_ON == intr_get_status()){
		old_status = INTR_ON;
		asm volatile("cli" : : : "memory");	//关中断,cli指令将IF位置0
		return old_status;
	}else{
		old_status = INTR_OFF;
		return old_status;
	}
}

/*将中断状态设置为status*/
enum intr_status intr_set_status(enum intr_status status){
	return status & INTR_ON ? intr_enable() : intr_disable();
}

/*获取当前中断状态*/
enum intr_status intr_get_status(){
	uint32_t eflags = 0;
	GET_FLAGS(eflags);
	return (EFLAGS_IF & eflags) ? INTR_ON : INTR_OFF;
}

/*在中断处理程序数组第vector_no个元素中注册安装中断处理程序function*/
void register_handler(uint8_t vector_no,intr_handler function){
    idt_table[vector_no] = function;    //idt_table数组中的函数是在进入中断后根据中断向量号调用的
}
  • “/home/lily/OS/boot/lib/user/syscall.c”
#include "syscall.h"

/*无参数的系统调用*/
#define _syscall0(NUMBER)({ \
    int retval; \
    asm volatile("int $0x80":"=a"(retval):"a"(NUMBER):"memory");    \
    retval;    \ 
})  //大括号中最后一个值作为返回值

/*一个参数的系统调用*/
#define _syscall1(NUMBER,ARG1)({  \
    int retval; \
    asm volatile("int $0x80":"=a"(retval):"a"(NUMBER),"b"(ARG1):"memory");  \
    retval; \
})

/*二个参数的系统调用*/
#define _syscall2(NUMBER,ARG1,ARG2)({  \
    int retval; \
    asm volatile("int $0x80":"=a"(retval):"a"(NUMBER),"b"(ARG1),"c"(ARG2):"memory");  \
    retval; \
})

/*三个参数的系统调用*/
#define _syscall3(NUMBER,ARG1,ARG2,ARG3)({  \
    int retval; \
    asm volatile("int $0x80":"=a"(retval):"a"(NUMBER),"b"(ARG1),"c"(ARG2),"d"(ARG3):"memory");  \
    retval; \
})

/*返回当前任务pid*/
uint32_t getpid(){
    return _syscall0(SYS_GETPID);
}
  • “/home/lily/OS/boot/lib/user/syscall.h”
#ifndef __LIB_USER_SYSCALL_H
#define __LIB_USER_SYSCALL_H
#include "stdint.h"

enum SYSCALL_NR{    //用来存放系统调用子功能号
    SYS_GETPID
};

//函数声明
uint32_t getpid(void);

#endif
  • “/home/lily/OS/boot/kernel/kernel.S”
[bits 32]
%define ERROR_CODE nop          ;若在相关的异常中CPU已经自动压入了错误码,为保持栈中格式同一,这里不做操作
%define ZERO push 0             ;若在相关异常中CPU没有压入错误码,为了格式统一,就手工压入一个0

extern put_str                  ;声明外部函数
extern idt_table

section .data                   ;代码段开始
global intr_entry_table         ;intr_entry_table位于data段,链接时会和宏中的data段放在一起
intr_entry_table:               ;构造intr_entry_table数组,只是构造数组,并不调用中断,中断由硬件触发,目前由时钟触发
;宏开始
%macro VECTOR 2                 ;定义了一个叫VECTOR的宏,接收两个参数。一个参数是中断向量号,第二个参数也是个宏
section .text 
intr%1entry:                    ;%1表示第一个参数,不是英文L。中断处理程序的起始地址;每个中队处理程序都要压入中断向量号,所以一个中断类型一个向量号。自己知道自己的向量号是多少
    %2                          ;压入错误码,中断若有错误码会压入到eip后
    
    ;保存上下文环境,保存ds,es,fs,gs和832位通用寄存器
    push ds
    push es 
    push fs 
    push gs 
    pushad 

    ;发送EOI信号的目的是告知硬件中断控制器8259A,当前中断已经处理完成,可以继续处理其他中断请求。
    ;如果是从片中进入的中断,除了往从片上发送EOI外,还要往主片上发送EOI
    mov al,0x20                 ;中断结束命令EOI。OCW2的第5位是EOI位,其余都是0,所以是0x20。我们要向8259A发送结束标志,不然手动结束的8259A不知道中断结束了。
    out 0xa0,al                 ;向主片发送
    out 0x20,al                 ;向从片发送

    ;处理中断,即调用中断处理函数
    push %1                     ;不管idt_table中的目标程序是否需要参数,都一律压入中断向量号
    call [idt_table + %1*4]

    ;中断结束,返回原进程
    jmp intr_exit
 
section .data                   ;表示上一个代码段结束
    dd intr%1entry              ;dd用来定义数组元素的宽度,元素值为intr%lentry;存储各个中断入口程序的地址,形成intr_entry_table数组。这样每个宏调用都会在数组中产生新的地址元素
%endmacro
;宏结束

section .text 
global intr_exit
intr_exit:
    ;跳过中断号
    add esp,4                   
    ;恢复上下文环境
    popad 
    pop gs 
    pop fs
    pop es 
    pop ds 
    ;跳过error_code
    add esp,4                   
    ;返回主程序,iretd用于从中断服务例程(ISR)返回到被中断的程序。
    iretd                        

;;;;;;;;;;;;;; 0x80号中断  ;;;;;;;;;;;;;;;;;;
[bits 32]
extern syscall_table
section .text
global syscall_handler
syscall_handler:    
    ;1.保护上下文环境
    push 0      ;压入0,使栈中格式统一
    push ds
    push es
    push fs
    push gs
    pushad      ;pushad指令压入32位寄存器,其入栈顺序为eax,ecx,edx,ebx,esp,ebp,esi,edi
    push 0x80   ;此位置压入0x80也是为了保持统一的栈格式

    ;2.为系统调用子功能传入参数
    push edx    ;系统调用中第3个参数
    push ecx    ;系统调用中第2个参数
    push ebx    ;系统调用中第1个参数

    ;3.调用子功能处理函数
    call [syscall_table + eax*4]
    add esp,12  ;跨过上面三个参数

    ;4.将call调用后的返回值存入当前内核栈中eax的位置
    mov [esp + 8*4],eax     ;根据ABI约定,寄存器eax用来存储返回值。于是我们将eax中的返回值写入到pushad中的eax中,防止被篡改。8*4 = (0x80 + 7个寄存器)*4
    jmp intr_exit   ;返回,恢复上下文

VECTOR 0x0 ,ZERO
VECTOR 0X1 ,ZERO
VECTOR 0X2 ,ZERO
VECTOR 0x3 ,ZERO
VECTOR 0X4 ,ZERO
VECTOR 0X5 ,ZERO
VECTOR 0x6 ,ZERO
VECTOR 0X7 ,ZERO
VECTOR 0X8 ,ERROR_CODE
VECTOR 0x9 ,ZERO
VECTOR 0XA ,ERROR_CODE
VECTOR 0XB ,ERROR_CODE
VECTOR 0XC ,ERROR_CODE
VECTOR 0XD ,ERROR_CODE
VECTOR 0XE ,ERROR_CODE
VECTOR 0XF ,ZERO
VECTOR 0X10 ,ZERO
VECTOR 0X11 ,ERROR_CODE
VECTOR 0x12 ,ZERO
VECTOR 0X13 ,ZERO
VECTOR 0X14 ,ZERO
VECTOR 0x15 ,ZERO
VECTOR 0X16 ,ZERO
VECTOR 0X17 ,ZERO
VECTOR 0X18 ,ZERO
VECTOR 0X19 ,ZERO
VECTOR 0X1A ,ZERO
VECTOR 0X1B ,ZERO
VECTOR 0X1C ,ZERO
VECTOR 0X1D ,ZERO
VECTOR 0X1E ,ERROR_CODE                               ;处理器自动推错误码
VECTOR 0X1F ,ZERO					
VECTOR 0X20 ,ZERO					;时钟中断
VECTOR 0X21 ,ZERO					;键盘中断
VECTOR 0X22 ,ZERO					;级联
VECTOR 0X23 ,ZERO					;串口2
VECTOR 0X24 ,ZERO					;串口1
VECTOR 0X25 ,ZERO					;并口2
VECTOR 0X26 ,ZERO					;软盘
VECTOR 0X27 ,ZERO					;并口1
VECTOR 0X28 ,ZERO					;实时时钟
VECTOR 0X29 ,ZERO					;重定向
VECTOR 0X2A ,ZERO					;保留
VECTOR 0x2B ,ZERO					;保留
VECTOR 0x2C ,ZERO					;ps/2 鼠标
VECTOR 0x2D ,ZERO					;fpu 浮点单元异常
VECTOR 0x2E ,ZERO					;硬盘
VECTOR 0x2F ,ZERO					;保留
VECTOR 0x30 ,ZERO
VECTOR 0x31 ,ZERO
VECTOR 0x32 ,ZERO
VECTOR 0x33 ,ZERO
VECTOR 0x34 ,ZERO
VECTOR 0x35 ,ZERO
VECTOR 0x36 ,ZERO
VECTOR 0x37 ,ZERO
VECTOR 0x38 ,ZERO
VECTOR 0x39 ,ZERO
VECTOR 0x3A ,ZERO
VECTOR 0x3B ,ZERO
VECTOR 0x3C ,ZERO
VECTOR 0x3D ,ZERO
VECTOR 0x3E ,ZERO
VECTOR 0x3F ,ZERO
VECTOR 0x40 ,ZERO
VECTOR 0x41 ,ZERO
VECTOR 0x42 ,ZERO
VECTOR 0x43 ,ZERO
VECTOR 0x44 ,ZERO
VECTOR 0x45 ,ZERO
VECTOR 0x46 ,ZERO
VECTOR 0x47 ,ZERO
VECTOR 0x48 ,ZERO
VECTOR 0x49 ,ZERO
VECTOR 0x4A ,ZERO
VECTOR 0x4B ,ZERO
VECTOR 0x4C ,ZERO
VECTOR 0x4D ,ZERO
VECTOR 0x4E ,ZERO
VECTOR 0x4F ,ZERO
VECTOR 0x50 ,ZERO
VECTOR 0x51 ,ZERO
VECTOR 0x52 ,ZERO
VECTOR 0x53 ,ZERO
VECTOR 0x54 ,ZERO
VECTOR 0x55 ,ZERO
VECTOR 0x56 ,ZERO
VECTOR 0x57 ,ZERO
VECTOR 0x58 ,ZERO
VECTOR 0x59 ,ZERO
VECTOR 0x5A ,ZERO
VECTOR 0x5B ,ZERO
VECTOR 0x5C ,ZERO
VECTOR 0x5D ,ZERO
VECTOR 0x5E ,ZERO
VECTOR 0x5F ,ZERO
VECTOR 0x61 ,ZERO
VECTOR 0x62 ,ZERO
VECTOR 0x63 ,ZERO
VECTOR 0x64 ,ZERO
VECTOR 0x65 ,ZERO
VECTOR 0x66 ,ZERO
VECTOR 0x67 ,ZERO
VECTOR 0x68 ,ZERO
VECTOR 0x69 ,ZERO
VECTOR 0x6A ,ZERO
VECTOR 0x6B ,ZERO
VECTOR 0x6C ,ZERO
VECTOR 0x6D ,ZERO
VECTOR 0x6E ,ZERO
VECTOR 0x6F ,ZERO
VECTOR 0x70 ,ZERO
VECTOR 0x71 ,ZERO
VECTOR 0x72 ,ZERO
VECTOR 0x73 ,ZERO
VECTOR 0x74 ,ZERO
VECTOR 0x75 ,ZERO
VECTOR 0x76 ,ZERO
VECTOR 0x77 ,ZERO
VECTOR 0x78 ,ZERO
VECTOR 0x79 ,ZERO
VECTOR 0x7A ,ZERO
VECTOR 0x7B ,ZERO
VECTOR 0x7C ,ZERO
VECTOR 0x7D ,ZERO
VECTOR 0x7E ,ZERO
VECTOR 0x7F ,ZERO
VECTOR 0x80 ,ZERO
  • “/home/lily/OS/boot/userprog/syscall_init.c”
#include "syscall_init.h"
#include "thread.h"
#include "../lib/user/syscall.h"
#include "../lib/string.h"
#include "print.h"

#define syscall_nr 32
typedef void* syscall;
syscall syscall_table[syscall_nr];

//返回当前任务的pid
uint32_t sys_getpid(void){
    return running_thread()->pid;
}

//初始化系统调用
void syscall_init(void){
    put_str("syscall_init start\n");
    syscall_table[SYS_GETPID] = sys_getpid;
    put_str("syscall_init done\n");
}
  • “/home/lily/OS/boot/userprog/syscall_init.h”
#ifndef __USERPROG_SYSCALL_INIT_H
#define __USERPROG_SYSCALL_INIT_H
#include "stdint.h"

//函数声明
//返回当前任务的pid
uint32_t sys_getpid(void);

//初始化系统调用
void syscall_init(void);
#endif
  • “/home/lily/OS/boot/thread/thread.h”
#ifndef __THREAD_THREAD_H
#define __THREAD_THREAD_H
#include "stdint.h"
#include "list.h"
#include "../kernel/memory.h"

/*自定义通用函数类型,它将在很多线程函数中作为形参类型*/
typedef void thread_func(void*);
typedef int16_t pid_t;

/*进程或线程的状态*/
enum task_status{
    TASK_RUNNING,
    TASK_READY,
    TASK_BLOCKED,
    TASK_WAITING,
    TASK_HANGING,
    TASK_DIED
};

/*中断栈intr_stack
用于中断发生时保护程序上下文环境:
进程/线程被外部中断/软中断打断时,会按照此结构压入上下文
寄存器,intr_exit中的出栈操作是此结构的逆操作
此栈在线程自己的内核栈中位置固定,所在页的最顶端
*/
struct intr_stack{
    uint32_t vec_no;    //kernel.S宏VECTOR中push %1压入的中断号
    uint32_t edi;
    uint32_t esi;
    uint32_t ebp;
    uint32_t esp_dummy; //虽然pushad把esp也压入了,但esp是不断变化的,所以会被popad忽略
    uint32_t ebx;
    uint32_t edx;
    uint32_t ecx;
    uint32_t eax;
    uint32_t gs;
    uint32_t fs;
    uint32_t es;
    uint32_t ds;

    /*下面由CPU从低特权级进入高特权级时压入*/
    uint32_t err_code;  //err_code会被压入在eip之后
    void (*eip) (void);
    uint32_t cs;
    uint32_t eflags;
    void* esp;
    uint32_t ss;
};

/*线程栈thread_stack
线程自己的栈,用于存储线程中待执行的函数
此结构在线程自己的内核栈中位置不固定
仅用在switch_to时保存线程环境
实际位置取决于实际情况。
*/
struct thread_stack{
    uint32_t ebp;
    uint32_t ebx;
    uint32_t edi;
    uint32_t esi;

    /*线程第一次执行时,eip指向待调用函数kernel_thread,
    其他时候,eip指向switch_to的返回地址*/
    void (*eip) (thread_func* func,void* func_arg);

    /*以下仅供第一次被调度上CPU时使用*/

    /*参数unused_ret只为占位置充数为返回地址*/
    void (*unused_retaddr);     //占位符,基线,表示返回地址
    thread_func* function;      //由kernel_thread所调用的函数名
    void* func_arg;             //由kernel_thread所调用的函数所需的参数
};

/*进程或线程的pcb,程序控制块*/
struct task_struct{
    uint32_t* self_kstack;      //各内核线程都用自己当前的内核栈的栈顶
    pid_t pid;
    enum task_status status;
    uint8_t priority;           //线程优先级
    char name[16];
    uint8_t ticks;              //每次在处理器上的嘀咕数
    uint32_t elapsed_ticks;     //此任务自上CPU运行至今用了多少CPU嘀咕数,也就是任务运行了多久
    struct list_elem general_tag;       //用于线程在一般队列中的节点
    struct list_elem all_list_tag;      //用于线程队列thread_all_list中的结点
    uint32_t* pgdir;            //进程自己页表的虚拟地址,如果是线程则为NULL
    struct virtual_addr userprog_vaddr;     //用户进程的虚拟地址
    uint32_t stack_magic;       //栈的边界标记,用于检测栈的溢出
};

struct task_struct* main_thread;        //主线程PCB
struct list thread_ready_list;          //就绪队列
struct list thread_all_list;            //所有任务队列
// static struct list_elem* thread_tag;    //用于保存队列中的线程结点

/*获取当前线程的pcb指针*/
struct task_struct* running_thread(void);
/*初始化线程栈thread_stack,将待执行的函数和参数放到thread_stack中相应的位置*/
void thread_create(struct task_struct* pthread,thread_func function,void* func_arg);

/*线程初始化*/
void init_thread(struct task_struct* pthread,char* name,int prio);

/*创建一优先级为prio,线程名为name,线程所执行函数为function(func_arg)的线程*/
struct task_struct* thread_start(char* name,int prio,thread_func function,void* func_arg);

/*将kernel中的main函数完善为主线程*/
// static void make_main_thread(void);

/*实现任务调度*/
void schedule(void);

/*初始化线程环境*/
void thread_init(void);

/*当前线程将自己阻塞,标志其状态为stat*/
void thread_block(enum task_status stat);

/*将线程pthread接触阻塞*/
void thread_unblock(struct task_struct* pthread);
#endif
  • “/home/lily/OS/boot/thread/thread.c”
#include "thread.h"
#include "stdint.h"
#include "string.h"
#include "global.h"
#include "memory.h"
#include "interrupt.h"
#include "list.h"
#include "debug.h"
#include "print.h"
#include "process.h"
#include "sync.h"

#define PG_SIZE 4096

// struct task_struct* main_thread;        //主线程PCB
// struct list thread_ready_list;          //就绪队列
// struct list thread_all_list;            //所有任务队列
static struct list_elem* thread_tag;    //用于保存队列中的线程结点
struct lock pid_lock;

extern void switch_to(struct task_struct* cur,struct task_struct* next);

/*获取当前线程的pcb指针*/
struct task_struct* running_thread(void){
    uint32_t esp;
    asm("mov %%esp,%0" : "=g"(esp));      //将当前的栈指针(ESP)的值存储到变量esp中。
    return (struct task_struct*)(esp & 0xfffff000);     //取esp整数部分,即pcb起始地址
}

/*由kernel_thread去执行function(func_arg)*/
static void kernel_thread(thread_func* funcion,void* func_arg){
    intr_enable();      //开中断,避免后面的时钟中断被屏蔽,而无法调度其他线程
    funcion(func_arg);
}

/*分配pid*/
static pid_t allocate_pid(void){
    static pid_t next_pid = 0;
    lock_acquire(&pid_lock);
    next_pid++;
    lock_release(&pid_lock);
    return next_pid;
}

/*初始化线程栈thread_stack,将待执行的函数和参数放到thread_stack中相应的位置*/
void thread_create(struct task_struct* pthread,thread_func function,void* func_arg){
    /*先预留中断使用栈的空间*/
    pthread->self_kstack -= sizeof(struct intr_stack);

    /*再预留出线程栈空间*/
    pthread->self_kstack -= sizeof(struct thread_stack);
    
    struct thread_stack* kthread_stack = (struct thread_stack*)pthread->self_kstack;    //线程栈指针
    kthread_stack->eip = kernel_thread;
    kthread_stack->function = function;
    kthread_stack->func_arg = func_arg;
    kthread_stack->ebp = kthread_stack->ebx = kthread_stack->edi = kthread_stack->esi = 0;  //寄存器初始化为0
}

/*线程初始化*/
void init_thread(struct task_struct* pthread,char* name,int prio){
    memset(pthread,0,sizeof(*pthread));
    pthread->pid = allocate_pid();
    strcpy(pthread->name,name);
    if(pthread == main_thread)
        pthread->status = TASK_RUNNING;
    else
        pthread->status = TASK_READY;

    /*self_kstack是线程自己在内核态下使用的栈顶地址*/
    pthread->self_kstack = (uint32_t*)((uint32_t)pthread + PG_SIZE);
    pthread->priority = prio;
    pthread->ticks = prio;
    pthread->elapsed_ticks = 0;
    pthread->pgdir = NULL;
    pthread->stack_magic = 0x19870916;
}

/*创建一优先级为prio,线程名为name,线程所执行函数为function(func_arg)的线程*/
struct task_struct* thread_start(char* name,int prio,thread_func function,void* func_arg){
    /*pcb都位于内核空间,包括用户进程的pcb也在内核空间*/
    struct task_struct* thread = get_kernel_pages(1);
    init_thread(thread,name,prio);
    thread_create(thread,function,func_arg);
    //确保之前不在就绪队列中
    ASSERT(!elem_find(&thread_ready_list,&thread->general_tag));
    //加入就绪队列
    list_append(&thread_ready_list,&thread->general_tag);
    //确保之前不在队列中
    ASSERT(!elem_find(&thread_all_list,&thread->all_list_tag));
    //加入全部线程队列
    list_append(&thread_all_list,&thread->all_list_tag);

    // asm volatile("movl %0,%%esp; \
    // pop %%ebp;pop %%ebx;pop %%edi;pop %%esi; \
    // ret": : "g"(thread->self_kstack) : "memory");
    // //esp = thread->self_kstack为栈顶;4个出栈,将初始化的0弹出到相应寄存器中;执行ret,将栈顶数据作为返回地址送入CPU的eip中;thread->self_kstack为输入
    return thread;
}

/*将kernel中的main函数完善为主线程*/
static void make_main_thread(void){
    /*因为main线程早已在运行,咱们在loader.S中进入内核时的mov esp,0xc009f000,就是为其预留pcb的。
    因为pcb的地址为0xc009e000,不需要通过get_kernel_page另外分配一页*/
    main_thread = running_thread();
    init_thread(main_thread,"main",31);

    /*main函数是当前线程,当前线程不在thread_ready_list中,所以只将其加在thread_all_list中*/
    ASSERT(!elem_find(&thread_all_list,&main_thread->all_list_tag));
    list_append(&thread_all_list,&main_thread->all_list_tag);
}

/*实现任务调度*/
void schedule(){
    ASSERT(intr_get_status() == INTR_OFF);
    struct task_struct* cur = running_thread();
    if(cur->status == TASK_RUNNING){
        //若此线程只是cpu时间片到了,将其加入就绪队列队尾
        ASSERT(!elem_find(&thread_ready_list,&cur->general_tag));
        list_append(&thread_ready_list,&cur->general_tag);
        cur->ticks = cur->priority;
        cur->status = TASK_READY;
    }else{
        //若此线程需要某事件发生后才能继续上cpu运行,不需要将其放入队列中,因为当前线程不在就绪队列中
    }

    ASSERT(!list_empty(&thread_ready_list));
    thread_tag = NULL;
    thread_tag = list_pop(&thread_ready_list);
    struct task_struct* next = elem2entry(struct task_struct,general_tag,thread_tag);   //将thread_tag转化为线程(链表)
    next->status = TASK_RUNNING;
    /*激活任务页表等*/
    process_activate(next);
    switch_to(cur,next);
}

/*初始化线程环境*/
void thread_init(void){
    put_str("thread_init start\n");
    list_init(&thread_ready_list);
    list_init(&thread_all_list);
    lock_init(&pid_lock);
    /*将当前main函数创建为线程*/
    make_main_thread();
    put_str("thread_init done\n");
}

/*当前线程将自己阻塞,标志其状态为stat*/
void thread_block(enum task_status stat){
    /*stat取值为TASK_BLOCKED,TASK_WAITING,TASK_HANGING,只有这三种状态才不会被调度*/
    ASSERT(((stat == TASK_BLOCKED) || (stat == TASK_WAITING) || (stat == TASK_HANGING)));
    enum intr_status old_status = intr_disable();
    struct task_struct* cur_thread = running_thread();
    cur_thread->status = stat;
    schedule();     //将当前线程换下CPU
    intr_set_status(old_status);    //待当前线程被解除阻塞后才能继续运行intr_set_status
}

/*将线程pthread接触阻塞*/
void thread_unblock(struct task_struct* pthread){
    enum intr_status old_status = intr_disable();
    ASSERT(((pthread->status == TASK_BLOCKED) || (pthread->status == TASK_WAITING) || (pthread->status == TASK_HANGING)));
    if(pthread->status != TASK_READY){
        ASSERT(!elem_find(&thread_ready_list,&pthread->general_tag));
        if(elem_find(&thread_ready_list,&pthread->general_tag)){
            PANIC("thread_unblock:blocked thread in ready_list\n");
        }
        list_push(&thread_ready_list,&pthread->general_tag);
        pthread->status = TASK_READY;
    }
    intr_set_status(old_status);
}
  • “/home/lily/OS/boot/kernel/init.c”
#include "init.h"
#include "print.h"
#include "interrupt.h"
#include "../device/timer.h"
#include "memory.h"
#include "../thread/thread.h"
#include "../device/console.h"
#include "../device/keyboard.h"
#include "../userprog/tss.h"
#include "syscall_init.h"

/*负责初始化所有模块*/
void init_all(){
    put_str("init_all\n");
    idt_init();     //初始化中断
    mem_init();
    thread_init();
    timer_init();   //初始化PIT   
    console_init(); //控制台初始化最好放在开中断之前
    keyboard_init();
    tss_init();
    syscall_init();
}

  • “/home/lily/OS/boot/kernel/main.c”
#include "print.h"
#include "init.h"
#include "thread.h"
#include "interrupt.h"
#include "console.h"
#include "keyboard.h"
#include "ioqueue.h"
#include "process.h"
#include "syscall.h"
#include "syscall_init.h"

void k_thread_a(void*);
void k_thread_b(void*);
void u_prog_a(void);
void u_prog_b(void);

int prog_a_pid = 0,prog_b_pid = 0;

int main(void){
	put_str("I am kernel\n");
	init_all();

	process_execute(u_prog_a,"user_prog_a");
	process_execute(u_prog_b,"user_prog_b");

	intr_enable();	//打开中断,使时钟中断起作用
	console_put_str(" main_pid:0x");
	console_put_int(sys_getpid());
	console_put_char('\n');
	
	thread_start("k_thread_a",31,k_thread_a,"argA ");
	thread_start("k_thread_b",31,k_thread_b,"argB ");

	while(1);
	return 0;
}

/*在线程中运行函数*/
void k_thread_a(void* arg){
	char* para = arg;
	console_put_str(" thread_a_pid:0x");
	console_put_int(sys_getpid());	//线程取得pid的方式
	console_put_char('\n');
	console_put_str(" prog_a_pid:0x");
	console_put_int(prog_a_pid);
	console_put_char('\n');
	while(1);
}

/*在线程中运行函数*/
void k_thread_b(void* arg){
	char* para = arg;
	console_put_str(" thread_a_pid:0x");
	console_put_int(sys_getpid());
	console_put_char('\n');
	console_put_str(" prog_b_pid:0x");
	console_put_int(prog_b_pid);
	console_put_char('\n');
	while(1);
}

/*测试用户进程*/
void u_prog_a(void){
	prog_a_pid = getpid();	//进程取得pid只能通过系统调用
	while(1);
}

/*测试用户进程*/
void u_prog_b(void){
	prog_b_pid = getpid();
	while(1);
}
  • “/home/lily/OS/boot/makefile”
BUILD_DIR = ./build
##用来存储生成的所有目标文件
ENTRY_POINT = 0xc0001500
AS = nasm
CC = gcc
LD = ld
LIB = -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -I device/ -I thread/ -I userprog/ 
ASFLAGS = -f elf
CFLAGS = -Wall -m32 -fno-stack-protector $(LIB) -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes
##-fno-builtin是告诉编译器不要采用内部函数 -Wstrict-prototypes是要求函数声明中必须有参数类型 
## -Wmissing-prototypes要求函数必须有声明
LDFLAGS = -m elf_i386 -Ttext $(ENTRY_POINT) -e main -Map $(BUILD_DIR)/kernel.map
OBJS = $(BUILD_DIR)/main.o $(BUILD_DIR)/init.o $(BUILD_DIR)/interrupt.o \
	$(BUILD_DIR)/timer.o $(BUILD_DIR)/kernel.o $(BUILD_DIR)/print.o $(BUILD_DIR)/debug.o \
	$(BUILD_DIR)/string.o $(BUILD_DIR)/memory.o $(BUILD_DIR)/bitmap.o $(BUILD_DIR)/thread.o \
	$(BUILD_DIR)/switch.o $(BUILD_DIR)/list.o $(BUILD_DIR)/sync.o $(BUILD_DIR)/console.o  \
	$(BUILD_DIR)/keyboard.o $(BUILD_DIR)/ioqueue.o $(BUILD_DIR)/tss.o $(BUILD_DIR)/process.o \
	$(BUILD_DIR)/syscall.o $(BUILD_DIR)/syscall_init.o
## OBJS用来存储所有目标文件名,不要用%.o,因为不能保证链接顺序

########## c代码编译 ##########
$(BUILD_DIR)/main.o:kernel/main.c lib/kernel/print.h lib/kernel/stdint.h kernel/init.h kernel/memory.h \
	thread/thread.h kernel/interrupt.h device/console.h device/keyboard.h device/ioqueue.h userprog/process.h \
	userprog/syscall_init.h lib/user/syscall.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/init.o:kernel/init.c kernel/init.h lib/kernel/print.h lib/kernel/stdint.h \
	kernel/interrupt.h device/timer.h kernel/memory.h thread/thread.h device/console.h device/keyboard.h \
	userprog/tss.h userprog/syscall_init.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/interrupt.o:kernel/interrupt.c kernel/interrupt.h lib/kernel/stdint.h \
	kernel/global.h lib/kernel/io.h lib/kernel/print.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/timer.o:device/timer.c device/timer.h lib/kernel/stdint.h lib/kernel/io.h lib/kernel/print.h thread/thread.c \
	kernel/debug.h kernel/interrupt.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/thread.o:thread/thread.c thread/thread.h lib/kernel/stdint.h lib/string.h kernel/global.h kernel/memory.h \
	kernel/interrupt.h lib/kernel/list.h kernel/debug.h lib/kernel/print.h userprog/process.h thread/sync.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/debug.o:kernel/debug.c kernel/debug.h lib/kernel/print.h lib/kernel/stdint.h kernel/interrupt.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/string.o:lib/string.c lib/string.h kernel/debug.h kernel/global.h lib/kernel/stdint.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/memory.o:kernel/memory.c kernel/memory.h lib/kernel/stdint.h lib/kernel/bitmap.h kernel/debug.h lib/string.h \
	lib/kernel/print.h kernel/global.h thread/sync.h thread/thread.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/bitmap.o:lib/kernel/bitmap.c lib/kernel/bitmap.h lib/string.h kernel/interrupt.h lib/kernel/print.h \
	kernel/debug.h lib/kernel/stdint.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/list.o: lib/kernel/list.c lib/kernel/list.h kernel/interrupt.h lib/kernel/stdint.h kernel/debug.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/sync.o: thread/sync.c thread/sync.h lib/kernel/list.h lib/kernel/stdint.h thread/thread.h lib/string.h \
	kernel/interrupt.h kernel/debug.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/console.o: device/console.c device/console.h lib/kernel/print.h lib/kernel/stdint.h thread/sync.h thread/thread.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/keyboard.o: device/keyboard.c device/keyboard.h lib/kernel/print.h lib/kernel/stdint.h kernel/interrupt.h lib/kernel/io.h \
	kernel/global.h device/ioqueue.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/ioqueue.o: device/ioqueue.c device/ioqueue.h lib/kernel/stdint.h kernel/interrupt.h  kernel/debug.h thread/thread.h \
	kernel/global.h thread/sync.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/tss.o: userprog/tss.c userprog/tss.h lib/kernel/stdint.h thread/thread.h lib/kernel/print.h lib/string.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/process.o: userprog/process.c userprog/process.h thread/thread.h userprog/tss.h device/console.h \
	kernel/memory.h kernel/global.h lib/kernel/bitmap.h kernel/interrupt.h lib/kernel/stdint.h kernel/debug.h \
	lib/string.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/syscall.o: lib/user/syscall.c lib/user/syscall.h lib/kernel/stdint.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/syscall_init.o: userprog/syscall_init.c userprog/syscall_init.h thread/thread.h lib/user/syscall.h lib/kernel/stdint.h \
	lib/string.h lib/kernel/print.h
	$(CC) $(CFLAGS) $< -o $@

###########汇编代码编译############
$(BUILD_DIR)/kernel.o:kernel/kernel.S
	$(AS) $(ASFLAGS) $< -o $@

$(BUILD_DIR)/print.o:lib/kernel/print.S
	$(AS) $(ASFLAGS) $< -o $@

$(BUILD_DIR)/switch.o:thread/switch.S
	$(AS) $(ASFLAGS) $< -o $@

##########链接所有目标文件#############
$(BUILD_DIR)/kernel.bin:$(OBJS)
	$(LD) $(LDFLAGS) $^ -o $@

.PHONY: mk_dir hd clean all

mk_dir:
	if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fi	
###fi为终止符

hd:	
	dd if=$(BUILD_DIR)/kernel.bin of=/home/lily/bochs/hd60M.img bs=512 count=200 seek=9 conv=notrunc

clean:	##将build目录下文件清空
	cd $(BUILD_DIR) && rm -f ./*

build:$(BUILD_DIR)/kernel.bin	##编译kernel.bin,只要执行make build就是编译文件

all:mk_dir build hd	
##依次执行伪目标mk_dir build hd,只要执行make all就是完成了编译到写入硬盘的全过程

在这里插入图片描述
在这里插入图片描述

3. 栈传递参数

  • 我们目前的系统调用都是通过寄存器来传递参数,若用栈传递参数的话,调用者(用户进程)首先要把参数压入3特权级的栈中,然后内核将其读出并压入0特权级的栈,这涉及到两种栈的读写,所以通过寄存器传参效率更高。
//举例:
/*无参数的系统调用*/
#define _syscall0(NuMBER)({\
	int retval;\
	asm volatile("push %[number];int $0x80;addl $4,%%esp"\
	:"=a"(retval):[number]"i"(NUMBER):"memory");\
	retval;\
})
/*三个参数的系统调用*/
#define _syscall0(NuMBER,ARG0,ARG1,ARG2)({\
	int retval;\
	asm volatile("pushl %[arg2];pushl %[arg1];pushl %[arg0]";
	"pushl %[number];int $0x80;addl $16,%%esp"\
	:"=a"(retval):[number] "i"(NUMBER),\
	[arg0] "g"(ARG0),[arg1] "g"(ARG1),[arg2] "g"(ARG2)\
	:"memory");\
	retval;\
})
  • 系统调用需要参数和子功能号,因此用户程序要在执行int 0x80前将参数和子功能号压入用户栈。参数先入栈,子功能号后入栈,只有提前确定好它们在栈中的次序,相应的0x80号中断处理程序才能正确获得参数及子功能号。
syscall_handler:
	;系统调用传入的参数在用户栈中,此时是内核栈
	;1. 保存上下文环境
	push 0
	push ds
	push es
	push fs
	push gs
	pushad
	push 0x80
	;2. 从内核栈中获取cpu自动压入的用户栈指针esp的值
	mov ebx,[esp + 4 + 48 + 4 + 12]
	;3. 再把参数重新压在内核栈中,此时ebx是用户栈指针。由于此处只压入了3个参数,所以目前系统调用最多支持3个参数
	push dword [ebx + 12]
	push dword [ebx + 8]
	push dword [ebx + 4]
	mov edx,[ebx]
	;编译器会在栈中根据C函数声明匹配正确数量的参数
	call [syscall_table + edx*4]
	add esp,12
	;4. 将call调用后的返回值存入待当前内核中eax的位置
	mov [esp + 8*4],eax
	jmp intr_exit
  • “mov ebx,[esp + 4 + 48 + 4 + 12]”:
    1. 这里esp是当前内核栈顶,4是push 0x80所占的位置。
    2. 48是上面4个push段寄存器操作和1个pushd压入的8个通用寄存器,共48字节。
    3. 第二个4是错误码0。
    4. 12是中断发生后,CPU由低特权级->高特权级,它会将ss3,esp3,eflag,cs,eip依次压入栈中,共20字节。为了访问到栈中的esp3,需要跨过eip,cs,eflags,所以需要跨过12字节。
    5. "mov ebx,[esp + 4 + 48 + 4 + 12]"是在内核栈中esp3位置取值,将值写入寄存器而不像,故ebx此时是用户栈顶指针。

三、让用户进程“说话”

1. 可变参数的原理

  • printf(“hello %s!”,“matrin”);中,"hello %s"就是format。
  • 格式化字符串中的字符’%'便是在栈中寻找可变参数的依据,跟在%后的是类型字符。类型字符表示数据类型和进制相关的内容。在format中,每找到一个%,就到栈中去找一个参数。

2. 实现系统调用write

---------------------------代码-------------------------------

  • “/home/lily/OS/boot/lib/user/syscall.h”
#ifndef __LIB_USER_SYSCALL_H
#define __LIB_USER_SYSCALL_H
#include "stdint.h"

enum SYSCALL_NR{    //用来存放系统调用子功能号
    SYS_GETPID,
    SYS_WRITE
};

//函数声明
uint32_t getpid(void);
uint32_t write(char* str);
#endif
  • “/home/lily/OS/boot/lib/user/syscall.c”
#include "syscall.h"

/*无参数的系统调用*/    //大体意思:将NUMBER的内容输入到eax寄存器中
#define _syscall0(NUMBER)({ \
    int retval; \
    asm volatile("int $0x80":"=a"(retval):"a"(NUMBER):"memory");    \
    retval;    \ 
})  //大括号中最后一个值作为返回值

/*一个参数的系统调用*/
#define _syscall1(NUMBER,ARG1)({  \
    int retval; \
    asm volatile("int $0x80":"=a"(retval):"a"(NUMBER),"b"(ARG1):"memory");  \
    retval; \
})

/*二个参数的系统调用*/
#define _syscall2(NUMBER,ARG1,ARG2)({  \
    int retval; \
    asm volatile("int $0x80":"=a"(retval):"a"(NUMBER),"b"(ARG1),"c"(ARG2):"memory");  \
    retval; \
})

/*三个参数的系统调用*/
#define _syscall3(NUMBER,ARG1,ARG2,ARG3)({  \
    int retval; \
    asm volatile("int $0x80":"=a"(retval):"a"(NUMBER),"b"(ARG1),"c"(ARG2),"d"(ARG3):"memory");  \
    retval; \
})

/*返回当前任务pid*/
uint32_t getpid(){
    return _syscall0(SYS_GETPID);
}

/*打印字符串str*/
uint32_t write(char* str){
    return _syscall1(SYS_WRITE,str);
}
  • “/home/lily/OS/boot/userprog/syscall_init.c”
#include "syscall_init.h"
#include "thread.h"
#include "../lib/user/syscall.h"
#include "../lib/string.h"
#include "print.h"
#include "console.h"

#define syscall_nr 32
typedef void* syscall;
syscall syscall_table[syscall_nr];

//返回当前任务的pid
uint32_t sys_getpid(void){
    return running_thread()->pid;
}

/*打印字符串str(未实现文件系统前的版本)*/
uint32_t sys_write(char* str){
    console_put_str(str);
    return strlen(str);
}
//初始化系统调用
void syscall_init(void){
    put_str("syscall_init start\n");
    syscall_table[SYS_GETPID] = sys_getpid;
    syscall_table[SYS_WRITE] = sys_write;
    put_str("syscall_init done\n");
}

3. 实现printf

---------------------------代码-------------------------------

  • 之前的string.h的strlen()函数写错了,导致死循环,现在已经改正。
  • pritnf的原型:int printf(const char* format,…);
  • 此函数的功能是根据格式化字符串format向标准输出打印字符串。
  • “/home/lily/OS/boot/lib/stdio.c”
#include "stdio.h"
#include "../kernel/interrupt.h"
#include "string.h"
#include "user/syscall.h"
#include "kernel/print.h"

#define va_start(ap,v) ap = (va_list)&v     //把ap指向第一个固定参数v
#define va_arg(ap,t) *((t*)(ap += 4))       //ap指向下一个参数并返回其值
#define va_end(ap) ap = NULL                //清除ap

/*将整形转换为字符(integer to ascii)*/
static void itoa(uint32_t value,char** buf_ptr_addr,uint8_t base){
    uint32_t m = value % base;      //取模
    uint32_t i = value / base;      //取整
    if(i){
        itoa(i,buf_ptr_addr,base);
    }
    if(m < 10){
        *((*buf_ptr_addr)++) = m + '0';
    }else{
        *((*buf_ptr_addr)++) = m - 10 + 'A';
    }
}

/*将参数ap按照格式format输出到字符串str,并返回替换后str长度*/
uint32_t vsprintf(char* str,const char* format,va_list ap){
    char* buf_ptr = str;    //buf_ptr指向返回字符串
    const char* index_ptr = format;
    char index_char = *index_ptr;
    int32_t arg_int;
    char* arg_str;

    while(index_char){
        if(index_char != '%'){
            *(buf_ptr++) = index_char;
            index_char = *(++index_ptr);
            continue;
        }
        index_char = *(++index_ptr);    //跳过'%'
        switch(index_char){
            case 'x':
                arg_int = va_arg(ap,int);
                itoa(arg_int,&buf_ptr,16);
                index_char = *(++index_ptr);
                break;
            case 's':
                arg_str = va_arg(ap,char*);     //arg_str = format
                strcpy(buf_ptr,arg_str);        //buf_ptr = format
                buf_ptr += strlen(arg_str);     //指针移到最后一位
                index_char = *(++index_ptr);
                break;
            case 'c':
                *(buf_ptr++) = va_arg(ap,char);
                index_char = *(++index_ptr);
                break;
            case 'd':
                arg_int = va_arg(ap,int);
                /*若是负数,将其转正后在面前加负号*/
                if(arg_int < 0){
                    arg_int = 0 - arg_int;
                    *buf_ptr++ = '-';
                }
                itoa(arg_int,&buf_ptr,10);
                index_char = *(++index_ptr);
                break;
        }
    }
    return strlen(str);
}

/* 同 printf 不同的地方就是字符串不是写到终端, 而是写到 buf 中 */
uint32_t sprintf(char* buf, const char* format, ...) {
    va_list args;
    uint32_t retval;        // buf 中字符串的长度
    va_start(args, format);     
    retval = vsprintf(buf, format, args);
    put_str(buf);
    va_end(args);
    return retval;
}

/*格式化输出字符串format*/
uint32_t printf(const char* format,...){
    va_list args;
    va_start(args,format);  //args = format
    char buf[1024] = {0};   //用于存储拼接后的字符串
    vsprintf(buf,format,args);
    va_end(args);           //args = NULL
    return write(buf);
}
  • “/home/lily/OS/boot/lib/stdio.h”
#ifndef __LIB_STDIO_H
#define __LIB_STDIO_H
#include "stdint.h"

#define NULL 0
typedef char* va_list;
//函数声明

/*将参数ap按照格式format输出到字符串str,并返回替换后str长度*/
uint32_t vsprintf(char* str,const char* format,va_list ap);
/*格式化输出字符串format*/
uint32_t printf(const char* format,...);
/*同printf,不同的地方是字符串不是写到终端,而是写到buf中*/
uint32_t sprintf(char* buf,const char* format,...);

#endif
  • “/home/lily/OS/boot/kernel/main.c”
#include "print.h"
#include "init.h"
#include "thread.h"
#include "interrupt.h"
#include "console.h"
#include "keyboard.h"
#include "ioqueue.h"
#include "process.h"
#include "syscall.h"
#include "syscall_init.h"
#include "../lib/stdio.h"

void k_thread_a(void*);
void k_thread_b(void*);
void u_prog_a(void);
void u_prog_b(void);

int prog_a_pid = 0,prog_b_pid = 0;

int main(void){
	put_str("I am kernel\n");
	init_all();

	process_execute(u_prog_a,"user_prog_a");
	process_execute(u_prog_b,"user_prog_b");
	intr_enable();	//打开中断,使时钟中断起作用
	console_put_str(" main_pid:0x");
	console_put_int(sys_getpid());
	console_put_char('\n');
	thread_start("k_thread_a",31,k_thread_a,"argA ");
	thread_start("k_thread_b",31,k_thread_b,"argB ");

	while(1);
	return 0;
}

/*在线程中运行函数*/
void k_thread_a(void* arg){
	char* para = arg;
	console_put_str(" I am thread_a, my pid:0x");
	console_put_int(sys_getpid());
	console_put_char('\n');
	while(1);
}

/*在线程中运行函数*/
void k_thread_b(void* arg){
	char* para = arg;
	console_put_str(" I am thread_b, my pid:0x");
	console_put_int(sys_getpid());
	console_put_char('\n');
	while(1);
}

/*测试用户进程*/
void u_prog_a(void){
	char* name = "prog_a";
	// printf("u_prog_a\n");
	printf(" I am %s,my pid:%d%c",name,getpid(),'\n');
	while(1);
}

/*测试用户进程*/
void u_prog_b(void){
	char* name = "prog_b";
	printf(" I am %s,my pid:%d%c",name,getpid(),'\n');
	while(1);
}
  • “/home/lily/OS/boot/makefile”
BUILD_DIR = ./build
##用来存储生成的所有目标文件
ENTRY_POINT = 0xc0001500
AS = nasm
CC = gcc
LD = ld
LIB = -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -I device/ -I thread/ -I userprog/ 
ASFLAGS = -f elf
CFLAGS = -Wall -m32 -fno-stack-protector $(LIB) -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes
##-fno-builtin是告诉编译器不要采用内部函数 -Wstrict-prototypes是要求函数声明中必须有参数类型 
## -Wmissing-prototypes要求函数必须有声明
LDFLAGS = -m elf_i386 -Ttext $(ENTRY_POINT) -e main -Map $(BUILD_DIR)/kernel.map
OBJS = $(BUILD_DIR)/main.o $(BUILD_DIR)/init.o $(BUILD_DIR)/interrupt.o \
	$(BUILD_DIR)/timer.o $(BUILD_DIR)/kernel.o $(BUILD_DIR)/print.o $(BUILD_DIR)/debug.o \
	$(BUILD_DIR)/string.o $(BUILD_DIR)/memory.o $(BUILD_DIR)/bitmap.o $(BUILD_DIR)/thread.o \
	$(BUILD_DIR)/switch.o $(BUILD_DIR)/list.o $(BUILD_DIR)/sync.o $(BUILD_DIR)/console.o  \
	$(BUILD_DIR)/keyboard.o $(BUILD_DIR)/ioqueue.o $(BUILD_DIR)/tss.o $(BUILD_DIR)/process.o \
	$(BUILD_DIR)/syscall.o $(BUILD_DIR)/syscall_init.o $(BUILD_DIR)/stdio.o
## OBJS用来存储所有目标文件名,不要用%.o,因为不能保证链接顺序

########## c代码编译 ##########
$(BUILD_DIR)/main.o:kernel/main.c lib/kernel/print.h lib/kernel/stdint.h kernel/init.h kernel/memory.h \
	thread/thread.h kernel/interrupt.h device/console.h device/keyboard.h device/ioqueue.h userprog/process.h \
	userprog/syscall_init.h lib/user/syscall.h lib/stdio.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/init.o:kernel/init.c kernel/init.h lib/kernel/print.h lib/kernel/stdint.h \
	kernel/interrupt.h device/timer.h kernel/memory.h thread/thread.h device/console.h device/keyboard.h \
	userprog/tss.h userprog/syscall_init.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/interrupt.o:kernel/interrupt.c kernel/interrupt.h lib/kernel/stdint.h \
	kernel/global.h lib/kernel/io.h lib/kernel/print.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/timer.o:device/timer.c device/timer.h lib/kernel/stdint.h lib/kernel/io.h lib/kernel/print.h thread/thread.c \
	kernel/debug.h kernel/interrupt.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/thread.o:thread/thread.c thread/thread.h lib/kernel/stdint.h lib/string.h kernel/global.h kernel/memory.h \
	kernel/interrupt.h lib/kernel/list.h kernel/debug.h lib/kernel/print.h userprog/process.h thread/sync.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/debug.o:kernel/debug.c kernel/debug.h lib/kernel/print.h lib/kernel/stdint.h kernel/interrupt.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/string.o:lib/string.c lib/string.h kernel/debug.h kernel/global.h lib/kernel/stdint.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/memory.o:kernel/memory.c kernel/memory.h lib/kernel/stdint.h lib/kernel/bitmap.h kernel/debug.h lib/string.h \
	lib/kernel/print.h kernel/global.h thread/sync.h thread/thread.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/bitmap.o:lib/kernel/bitmap.c lib/kernel/bitmap.h lib/string.h kernel/interrupt.h lib/kernel/print.h \
	kernel/debug.h lib/kernel/stdint.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/list.o: lib/kernel/list.c lib/kernel/list.h kernel/interrupt.h lib/kernel/stdint.h kernel/debug.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/sync.o: thread/sync.c thread/sync.h lib/kernel/list.h lib/kernel/stdint.h thread/thread.h lib/string.h \
	kernel/interrupt.h kernel/debug.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/console.o: device/console.c device/console.h lib/kernel/print.h lib/kernel/stdint.h thread/sync.h thread/thread.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/keyboard.o: device/keyboard.c device/keyboard.h lib/kernel/print.h lib/kernel/stdint.h kernel/interrupt.h lib/kernel/io.h \
	kernel/global.h device/ioqueue.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/ioqueue.o: device/ioqueue.c device/ioqueue.h lib/kernel/stdint.h kernel/interrupt.h  kernel/debug.h thread/thread.h \
	kernel/global.h thread/sync.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/tss.o: userprog/tss.c userprog/tss.h lib/kernel/stdint.h thread/thread.h lib/kernel/print.h lib/string.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/process.o: userprog/process.c userprog/process.h thread/thread.h userprog/tss.h device/console.h \
	kernel/memory.h kernel/global.h lib/kernel/bitmap.h kernel/interrupt.h lib/kernel/stdint.h kernel/debug.h \
	lib/string.h lib/kernel/print.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/syscall.o: lib/user/syscall.c lib/user/syscall.h lib/kernel/stdint.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/syscall_init.o: userprog/syscall_init.c userprog/syscall_init.h thread/thread.h lib/user/syscall.h lib/kernel/stdint.h \
	lib/string.h lib/kernel/print.h device/console.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/stdio.o: lib/stdio.c lib/stdio.h lib/kernel/stdint.h lib/string.h lib/user/syscall.h kernel/interrupt.h  \
	lib/kernel/print.h
	$(CC) $(CFLAGS) $< -o $@

###########汇编代码编译############
$(BUILD_DIR)/kernel.o:kernel/kernel.S
	$(AS) $(ASFLAGS) $< -o $@

$(BUILD_DIR)/print.o:lib/kernel/print.S
	$(AS) $(ASFLAGS) $< -o $@

$(BUILD_DIR)/switch.o:thread/switch.S
	$(AS) $(ASFLAGS) $< -o $@

##########链接所有目标文件#############
$(BUILD_DIR)/kernel.bin:$(OBJS)
	$(LD) $(LDFLAGS) $^ -o $@

.PHONY: mk_dir hd clean all

mk_dir:
	if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fi	
###fi为终止符

hd:	
	dd if=$(BUILD_DIR)/kernel.bin of=/home/lily/bochs/hd60M.img bs=512 count=200 seek=9 conv=notrunc

clean:	##将build目录下文件清空
	cd $(BUILD_DIR) && rm -f ./*

build:$(BUILD_DIR)/kernel.bin	##编译kernel.bin,只要执行make build就是编译文件

all:mk_dir build hd	
##依次执行伪目标mk_dir build hd,只要执行make all就是完成了编译到写入硬盘的全过程

在这里插入图片描述
在这里插入图片描述

四、完善堆内存管理

1. malloc底层原理

  • arena:将一个大内存划分为多个小内存,每个小内存之间互不干涉,可以分别管理。
  • arena的构成:
    1. 元信息:用来描述自己内存池中空闲的内存块数量,包括内存块描述符指针,通过它可以间接获得本arena所包含的内存块规格大小。
    2. 内存池区域:无数内存块,名为mem_block。
      在这里插入图片描述
  • 为每一种规格的内存块建立一个内存块描述符,即mem_block_desc,在其中记录内存规格大小,以及位于所有同类arena中的空闲内存链表。
  • 最大内存块容量不超过1024字节。
    在这里插入图片描述
  • 内存块描述符将所有同类arena中空闲块汇总,因此相当于内存块超级大仓库,分配小块内存时必须先经过此入口,系统从它的空闲内存块链表free list中挑选一块内存。
  • 例:假设arena元信息大小为12字节,对于内存块规格16字节的arena,其包含的内存块数量为(4096 - 12)/16。
    在这里插入图片描述
  • 在内存管理系统中,arena为任意大小内存的分配提供了统一的接口,它既支持1024字节以下的小块内存的分配,又支持1024字节以上的大块内存。
  • malloc实际上是通过arena申请这些内存块。
    在这里插入图片描述
  • arena管理的是物理页空间(页框),arena元信息放在页框(一页物理页)的最低地址处。
    在这里插入图片描述

2. 底层初始化

---------------------------代码-------------------------------

  • “/home/lily/OS/boot/kernel/memory.h”
#ifndef __KERNEL_MEMORY_H
#define __KERNEL_MEMORY_H
#include "stdint.h"
#include "bitmap.h"
//虚拟地址池,用于虚拟地址管理
struct virtual_addr
{
    struct bitmap vaddr_bitmap; //虚拟地址用到的位图结构
    uint32_t vaddr_start;       //虚拟地址起始地址
};

enum pool_flags{
    PF_KERNEL = 1,  //内核内存池
    PF_USER = 2     //用户内存池
};

#define PG_P_1 1    //页表项或页目录项存在属性位:此页在内存中存在
#define PG_P_0 0    //页表项或页目录项存在属性位:此页在内存中不存在
#define PG_RW_R 0   //R/W属性位值,读/写操作:此页运行读、写、执行
#define PG_RW_W 2   //R/W属性位值,读/写操作:此页允许读、执行
#define PG_US_S 0   //U/S属性位值,系统级:只允许特权级0、1、2的程序访问
#define PG_US_U 4   //U/S属性位值,用户级:允许所有特权级的进程访问

extern struct pool kernel_pool, user_pool;

/*内存块*/
struct mem_block{
    struct list_elem free_elem;
};

/*内存块描述符*/
struct mem_block_desc{
    uint32_t block_size;        //内存块大小
    uint32_t blocks_per_arena;  //本arena中可容纳此mem_block的数量
    struct list free_list;      //目前可用的mem_block链表,长度无限长,可以由多个arena提供内存块
};

#define DESC_CNT 7              //内存块描述符个数:16,32,64,128,256,512,1024字节,总共7种规格的内存块大小

/*得到虚拟地址vaddr对应的pte指针*/
uint32_t* pte_ptr(uint32_t vaddr);

/*得到虚拟地址vaddr对应的pde指针*/
uint32_t* pde_ptr(uint32_t vaddr);

/*分配pg_cnt个页空间,成功则返回起始虚拟地址,失败则返回NULL*/
void* malloc_page(enum pool_flags pf,uint32_t pg_cnt);

/*将地址vaddr与pf池中的物理地址关联,仅支持一页空间分配;申请一页内存,并用vaddr映射到该页(可以指定虚拟地址)*/
void* get_a_page(enum pool_flags pf,uint32_t vaddr);

/*在内核物理池中申请一页内存,成功则返回其虚拟地址,失败返回NULL*/
void* get_kernel_pages(uint32_t pg_cnt);

/*在用户空间中申请4K内存,并返回虚拟地址*/
void* get_user_pages(uint32_t pg_cnt);

/*得到虚拟地址映射到的物理地址*/
uint32_t addr_v2p(uint32_t vaddr);

/*内存管理部分初始化入口*/
void mem_init(void);

#endif
  • “/home/lily/OS/boot/kernel/memory.c”
#include "memory.h"
#include "bitmap.h"
#include "stdint.h"
#include "global.h"
#include "debug.h"
#include "print.h"
#include "../lib/string.h"
#include "sync.h"
#include "thread.h"

#define PG_SIZE 4096
//4KB
#define PDE_IDX(addr) ((addr & 0xffc00000) >> 22)
#define PTE_IDX(addr) ((addr & 0x003ff000) >> 12)

/*位图地址
因为0xc009_f000是内核主线程栈顶,0xc009_e000是内核主线程的pcb。
一个页框大小的位图可表示128MB内存,位图位置安排在地址0xc009_a000,
这样本系统最大支持4个页框的位图,即512MB
*/
#define MEM_BITMAP_BASE 0xc009a000
/****************/

/*0xc000_0000是内核从虚拟地址3G起
0x10_0000是指跨过低端1MB内存,使虚拟地址在逻辑上连续*/
#define K_HEAP_START 0xc0100000

//内存池结构,生成两个实例用于管理内核内存池和用户内存池
struct pool{
    struct bitmap pool_bitmap;  //本内存池用到的位图结构,用于管理物理内存
    uint32_t phy_addr_start;    //本内存池所管理物理内存的起始地址
    uint32_t pool_size;         //本内存池的字节容量
    struct lock lock;           //申请内存时互斥
};

/*内存仓库*/
struct arena{
    struct mem_block_desc* desc;    //此arena关联的mem_block_desc
    uint32_t cnt;       //large为true时,cnt表示的是页框数。否则cnt表示空闲mem_block数量
    bool large;
};

struct mem_block_desc k_block_descs[DESC_CNT];

struct pool kernel_pool,user_pool;  //生成内核内存池和用于内存池
struct virtual_addr kernel_vaddr;   //用来给内核分配虚拟地址

/*在pf表示的虚拟内存池中申请pg_cnt个虚拟页,成功则返回虚拟页起始地址,失败则返回NULL*/
static void* vaddr_get(enum pool_flags pf,uint32_t pg_cnt){
    int vaddr_start = 0,bit_idx_start = -1;
    uint32_t cnt = 0;
    if(pf == PF_KERNEL){        //内核内存池
        //在位图中申请pg_cnt位
        bit_idx_start = bitmap_scan(&kernel_vaddr.vaddr_bitmap,pg_cnt);
        if(bit_idx_start == -1)
            return NULL;
        //申请成功则分配页
        while (cnt < pg_cnt)
            bitmap_set(&kernel_vaddr.vaddr_bitmap,bit_idx_start + cnt++,1);
        vaddr_start = kernel_vaddr.vaddr_start + bit_idx_start*PG_SIZE;
    }else{          //用户内存池
        struct task_struct* cur = running_thread();
        bit_idx_start = bitmap_scan(&cur->userprog_vaddr.vaddr_bitmap,pg_cnt);
        if(bit_idx_start == -1)
            return NULL;
        //申请成功则分配页
        while(cnt < pg_cnt)
            bitmap_set(&cur->userprog_vaddr.vaddr_bitmap,bit_idx_start + cnt++,1);
        vaddr_start = cur->userprog_vaddr.vaddr_start + bit_idx_start*PG_SIZE;
        /*(0xc0000000 - PG_SIZE)作为用户3级栈已经在start_process被分配*/
        ASSERT((uint32_t)vaddr_start < (0xc0000000 - PG_SIZE));
    }
    return (void*)vaddr_start;
}


/*得到虚拟地址vaddr对应的pte指针*/
uint32_t* pte_ptr(uint32_t vaddr){
    uint32_t* pte = (uint32_t*)(0xffc00000 + ((vaddr & 0xffc00000) >> 10) + PTE_IDX(vaddr)*4);
    return pte;
}

/*得到虚拟地址vaddr对应的pde指针*/
uint32_t* pde_ptr(uint32_t vaddr){
    uint32_t* pde = (uint32_t*)((0xfffff000) + PDE_IDX(vaddr)*4);
    return pde;
}

/*在m_pool指向的物理内存池中分配 一个物理页,成功则返回页框物理地址,失败则返回NULL*/
static void* palloc(struct pool* m_pool){
    //检测有无空间
    int bit_idx = bitmap_scan(&m_pool->pool_bitmap,1);
    //分配空间
    if(bit_idx == -1)
        return NULL;
    bitmap_set(&m_pool->pool_bitmap,bit_idx,1);
    uint32_t page_phyaddr = m_pool->phy_addr_start + (bit_idx*PG_SIZE);
    return (void*)page_phyaddr;
}

// /*页表中添加虚拟地址_vaddr与物理地址_page_phyadddr的映射*/
static void page_table_add(void* _vaddr,void* _page_phyaddr){
    uint32_t vaddr = (uint32_t)_vaddr;
    uint32_t page_phyaddr = (uint32_t)_page_phyaddr;
    uint32_t* pte = pte_ptr(vaddr);
    uint32_t* pde = pde_ptr(vaddr);

    /*要确保pde创建完成后才能执行pte*/
    if(*pde & 0x00000001){
        //如果pde存在,则将pte指向物理地址
        ASSERT(!(*pte & 0x00000001));   //pte理应不存在
        if(!(*pte & 0x00000001)){
            *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
        }else{
            PANIC("pte repeat!");
            *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
        }
    }else{
        //页目录项不存在,创建页目录项
        uint32_t pde_phyaddr = (uint32_t)palloc(&kernel_pool);  //页表中用到的页框一律从内核空间分配
        *pde = (pde_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
        //pte清零
        memset((void*)((int)pte & 0xfffff000),0,PG_SIZE);   //pte & 0xfffff000表示新页表的起始地址,将整页清零
        ASSERT(!(*pte & 0x00000001));
        *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
    }
}

// /*分配pg_cnt个页空间,成功则返回起始虚拟地址,失败则返回NULL*/
void* malloc_page(enum pool_flags pf,uint32_t pg_cnt){
    ASSERT(pg_cnt > 0 && pg_cnt < 3840);
    /*malloc_page原理的三个动作:
    1. 通过vaddr_get在虚拟内存池中申请虚拟地址
    2. 通过palloc在物理内存池中申请物理地址
    3. 通过page_table_add将两个地址进行映射*/

    //1.在虚拟内存池中申请
    void* vaddr_start = vaddr_get(pf,pg_cnt);   
    if(vaddr_start == NULL)
        return NULL;
    uint32_t vaddr = (uint32_t)vaddr_start;
    uint32_t cnt = (uint32_t)pg_cnt;
    struct pool* mem_pool = pf & PF_KERNEL ? &kernel_pool : &user_pool;

    //2.虚拟内存是连续的,物理内存不是,需要逐个映射
    while (cnt--){
        void* page_phyaddr = palloc(mem_pool);  //申请一个物理地址
        if(page_phyaddr == NULL)
            return NULL;
        //3.在页表中做映射
        page_table_add((void*)vaddr,page_phyaddr);
        vaddr += PG_SIZE;   //下一个虚拟页
    }
    return vaddr_start;
}

// /*在内核物理池中申请一页内存,成功则返回其虚拟地址,失败返回NULL*/
void* get_kernel_pages(uint32_t pg_cnt){
    void* vaddr = malloc_page(PF_KERNEL,pg_cnt);
    if(vaddr == NULL)
        return NULL;
    memset(vaddr,0,pg_cnt*PG_SIZE);     //把虚拟地址对应的物理地址清零
    return vaddr;
}

/*在用户空间中申请4K内存,并返回虚拟地址*/
void* get_user_pages(uint32_t pg_cnt){
    lock_acquire(&user_pool.lock);
    void* vaddr = malloc_page(PF_USER,pg_cnt);      //在用户内存池中以整页为单位分配内存,并返回分配的虚拟地址
    memset(vaddr,0,pg_cnt*PG_SIZE);
    lock_release(&user_pool.lock);
    return vaddr;
}


/*将地址vaddr与pf池中的物理地址关联,仅支持一页空间分配;申请一页内存,并用vaddr映射到该页(可以指定虚拟地址)*/
void* get_a_page(enum pool_flags pf,uint32_t vaddr){
    struct pool* mem_pool = pf & PF_KERNEL ? &kernel_pool : &user_pool;
    lock_acquire(&mem_pool->lock);
    /*先将虚拟地址对应的位图置1*/
    struct task_struct* cur = running_thread();
    int32_t bit_idx = -1;

    /*若当前是用户进程申请用户内存,就修改用户进程自己的虚拟地址位图*/
    if(cur->pgdir != NULL && pf ==PF_USER){
        bit_idx = (vaddr - cur->userprog_vaddr.vaddr_start) / PG_SIZE;
        ASSERT(bit_idx > 0);
        bitmap_set(&cur->userprog_vaddr.vaddr_bitmap,bit_idx,1);
    }else if(cur->pgdir == NULL && pf == PF_KERNEL){
        /*如果是内核线程申请内核内存,就修改kernel_vaddr*/
        bit_idx = (vaddr - kernel_vaddr.vaddr_start) / PG_SIZE;
        ASSERT(bit_idx > 0);
        bitmap_set(&kernel_vaddr.vaddr_bitmap,bit_idx,1);
    }else{
        PANIC("get_a_page:not allow kernel alloc userspace or user alloc kernel space by get_a_page");
    }

    void* page_phyaddr = palloc(mem_pool);
    if(page_phyaddr == NULL)
        return NULL;
    page_table_add((void*)vaddr,page_phyaddr);
    lock_release(&mem_pool->lock);
    return (void*)vaddr;
}

/*得到虚拟地址映射到的物理地址*/
uint32_t addr_v2p(uint32_t vaddr){
    uint32_t* pte = pte_ptr(vaddr);
    /*(pte)的值是页表所在的物理页框地址,去掉其低12位的页表项属性 + 虚拟地址vaddr的低12位*/
    return ((*pte & 0xfffff000) + (vaddr & 0x00000fff));    //原理:PDE + PTE + 12位偏移地址 = 物理地址;这里直接用pte得到页表项,然后页表项 + 12位偏移地址 = 物理地址
}

// //初始化内存池
static void mem_pool_init(uint32_t all_mem){
    put_str("   mem_pool_init start\n");
    uint32_t page_table_size = PG_SIZE * 256;
    //页表大小=1页的页目录表 + 第0和第768个页目录项指向同一个页表 + (769~1022)个页目录项共指向254个页表 = 256
    uint32_t used_mem = page_table_size + 0x100000;     //0x10_0000是指跨过低端1MB内存 + 页表,使虚拟地址在逻辑上连续
    uint32_t free_mem = all_mem - used_mem;
    uint16_t all_free_pages = free_mem/PG_SIZE;
    uint16_t kernel_free_pages = all_free_pages / 2;
    uint16_t user_free_pages = all_free_pages - kernel_free_pages;

    //为简化位图操作,余数不处理
    uint32_t kbm_length = kernel_free_pages / 8;
    uint32_t ubm_length = user_free_pages / 8;
    uint32_t kp_start = used_mem;   //内核内存池起始地址
    uint32_t up_start = kp_start + kernel_free_pages*PG_SIZE;   //用户内存池起始地址

    kernel_pool.phy_addr_start = kp_start;
    user_pool.phy_addr_start = up_start;

    kernel_pool.pool_size = kernel_free_pages*PG_SIZE;
    user_pool.pool_size = user_free_pages*PG_SIZE;

    kernel_pool.pool_bitmap.btmp_bytes_len = kbm_length;
    user_pool.pool_bitmap.btmp_bytes_len = ubm_length;

    /*内核内存池和用户内存池位图
    位图是全局数据,长度不固定
    全局/静态数组需要在编译时知道长度
    我们需要根据总内存大小算出需要多少字节,所以改为指定一块内存来生成位图
    */
    kernel_pool.pool_bitmap.bits = (void*)MEM_BITMAP_BASE;
    user_pool.pool_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length);
    
    /*输出内存池信息*/
    put_str("   kernel_pool_bitmap_start:");
    put_int((int)kernel_pool.pool_bitmap.bits);
    put_str("   kernel_pool_phy_addr_start:");
    put_int(kernel_pool.phy_addr_start);
        put_str("\n");
        put_str("user_pool_bitmap_start:");
        put_int((int)user_pool.pool_bitmap.bits);
        put_str("   user_pool_phy_addr_start:");
        put_int((int)user_pool.phy_addr_start);
        put_str("\n");

        /*将位图置0*/
        bitmap_init(&kernel_pool.pool_bitmap);
        bitmap_init(&user_pool.pool_bitmap);
        /*初始化锁*/
        lock_init(&kernel_pool.lock);
        lock_init(&user_pool.lock);

        /*初始化内核虚拟地址的位图,按照实际物理内存大小生成数组*/
        kernel_vaddr.vaddr_bitmap.btmp_bytes_len = kbm_length;
        //用于维护内核堆的虚拟地址,所以要和内核内存池大小一致
        /*位图的数组指向一块未使用的内存,目前定位在内核内存池和用户内存池之外*/
        kernel_vaddr.vaddr_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length + ubm_length);
        kernel_vaddr.vaddr_start = K_HEAP_START;
        bitmap_init(&kernel_vaddr.vaddr_bitmap);
        put_str("   mem_pool_init done\n");
}

/*为malloc做准备*/
void block_desc_init(struct mem_block_desc* desc_array){
    uint16_t desc_idx , block_size = 16;
    /*初始化每个mem_block_desc描述符*/
    for(desc_idx = 0;desc_idx < DESC_CNT;desc_idx++){
        desc_array[desc_idx].block_size = block_size;
        /*初始化arena中的内存块数量*/
        desc_array[desc_idx].blocks_per_arena = (PG_SIZE - sizeof(struct arena)) / block_size;
        list_init(&desc_array[desc_idx].free_list);
        block_size *= 2;    //更新为下一个规格的内存块
    }
}

// /*内存管理部分初始化入口*/
void mem_init(){
    put_str("mem_init start\n");
    uint32_t mem_bytes_total = (*(uint32_t*)(0xb00));
    mem_pool_init(mem_bytes_total);
    /*初始化mem_block_desc数组descs,为malloc做准备*/
    block_desc_init(k_block_descs);
    put_str("mem_init done\n");
}

3. 实现sys_malloc

  • 内存块不是盲目准备好的,它在需要时由程序动态创建,创建它的函数为sys_malloc。
  • sys_malloc的功能是分配并维护内存块资源,动态创建arena以满足内存块的分配。

---------------------------代码-------------------------------

  • “/home/lily/OS/boot/thread/thread.h”
#ifndef __THREAD_THREAD_H
#define __THREAD_THREAD_H
#include "stdint.h"
#include "list.h"
#include "../kernel/memory.h"

/*自定义通用函数类型,它将在很多线程函数中作为形参类型*/
typedef void thread_func(void*);
typedef int16_t pid_t;

/*进程或线程的状态*/
enum task_status{
    TASK_RUNNING,
    TASK_READY,
    TASK_BLOCKED,
    TASK_WAITING,
    TASK_HANGING,
    TASK_DIED
};

/*中断栈intr_stack
用于中断发生时保护程序上下文环境:
进程/线程被外部中断/软中断打断时,会按照此结构压入上下文
寄存器,intr_exit中的出栈操作是此结构的逆操作
此栈在线程自己的内核栈中位置固定,所在页的最顶端
*/
struct intr_stack{
    uint32_t vec_no;    //kernel.S宏VECTOR中push %1压入的中断号
    uint32_t edi;
    uint32_t esi;
    uint32_t ebp;
    uint32_t esp_dummy; //虽然pushad把esp也压入了,但esp是不断变化的,所以会被popad忽略
    uint32_t ebx;
    uint32_t edx;
    uint32_t ecx;
    uint32_t eax;
    uint32_t gs;
    uint32_t fs;
    uint32_t es;
    uint32_t ds;

    /*下面由CPU从低特权级进入高特权级时压入*/
    uint32_t err_code;  //err_code会被压入在eip之后
    void (*eip) (void);
    uint32_t cs;
    uint32_t eflags;
    void* esp;
    uint32_t ss;
};

/*线程栈thread_stack
线程自己的栈,用于存储线程中待执行的函数
此结构在线程自己的内核栈中位置不固定
仅用在switch_to时保存线程环境
实际位置取决于实际情况。
*/
struct thread_stack{
    uint32_t ebp;
    uint32_t ebx;
    uint32_t edi;
    uint32_t esi;

    /*线程第一次执行时,eip指向待调用函数kernel_thread,
    其他时候,eip指向switch_to的返回地址*/
    void (*eip) (thread_func* func,void* func_arg);

    /*以下仅供第一次被调度上CPU时使用*/
    /*参数unused_ret只为占位置充数为返回地址*/
    void (*unused_retaddr);     //占位符,基线,表示返回地址
    thread_func* function;      //由kernel_thread所调用的函数名
    void* func_arg;             //由kernel_thread所调用的函数所需的参数
};

/*进程或线程的pcb,程序控制块*/
struct task_struct{
    uint32_t* self_kstack;      //各内核线程都用自己当前的内核栈的栈顶
    pid_t pid;
    enum task_status status;
    uint8_t priority;           //线程优先级
    char name[16];
    uint8_t ticks;              //每次在处理器上的嘀咕数
    uint32_t elapsed_ticks;     //此任务自上CPU运行至今用了多少CPU嘀咕数,也就是任务运行了多久
    struct list_elem general_tag;                   //用于线程在一般队列中的节点
    struct list_elem all_list_tag;                  //用于线程队列thread_all_list中的结点
    uint32_t* pgdir;            //进程自己页表的虚拟地址,如果是线程则为NULL
    struct virtual_addr userprog_vaddr;             //用户进程的虚拟地址
    struct mem_block_desc u_block_desc[DESC_CNT];   //用户进程的内存块描述符
    uint32_t stack_magic;       //栈的边界标记,用于检测栈的溢出
};

struct task_struct* main_thread;        //主线程PCB
struct list thread_ready_list;          //就绪队列
struct list thread_all_list;            //所有任务队列
// static struct list_elem* thread_tag;    //用于保存队列中的线程结点

/*获取当前线程的pcb指针*/
struct task_struct* running_thread(void);
/*初始化线程栈thread_stack,将待执行的函数和参数放到thread_stack中相应的位置*/
void thread_create(struct task_struct* pthread,thread_func function,void* func_arg);

/*线程初始化*/
void init_thread(struct task_struct* pthread,char* name,int prio);

/*创建一优先级为prio,线程名为name,线程所执行函数为function(func_arg)的线程*/
struct task_struct* thread_start(char* name,int prio,thread_func function,void* func_arg);

/*将kernel中的main函数完善为主线程*/
// static void make_main_thread(void);

/*实现任务调度*/
void schedule(void);

/*初始化线程环境*/
void thread_init(void);

/*当前线程将自己阻塞,标志其状态为stat*/
void thread_block(enum task_status stat);

/*将线程pthread接触阻塞*/
void thread_unblock(struct task_struct* pthread);
#endif
  • “/home/lily/OS/boot/userprog/process.c”
#include "thread.h"
#include "process.h"
#include "tss.h"
#include "console.h"
#include "../kernel/memory.h"
#include "global.h"
#include "bitmap.h"
#include "interrupt.h"
#include "debug.h"
#include "../string.h"
#include "print.h"

extern void intr_exit(void);

/*构建用户进程filename_的初始上下文信息,即struct intr_stack*/
void start_process(void* filename_){    //filename_表示用户进程的名称,因为用户进程是从文件系统中加载到内存的
    void* function = filename_;
    struct task_struct* cur = running_thread();
    cur->self_kstack += sizeof(struct thread_stack);
    struct intr_stack* proc_stack = (struct intr_stack*)cur->self_kstack;
    proc_stack->edi = proc_stack->esi = proc_stack->ebp = proc_stack->esp_dummy = 0;
    proc_stack->ebx = proc_stack->edx = proc_stack->ecx = proc_stack->eax = 0;
    proc_stack->gs = 0;
    proc_stack->ds = proc_stack->es = proc_stack->fs = SELECTOR_U_DATA;
    proc_stack->eip = function;         //cs:eip是程序入口地址
    proc_stack->cs = SELECTOR_U_CODE;   //cs寄存器赋值为用户级代码段
    proc_stack->eflags = (EFLAGS_IOPL_0 | EFLAGS_MBS | EFLAGS_IF_1);
    proc_stack->esp = (void*)((uint32_t)get_a_page(PF_USER,USER_STACK3_VADDR) + PG_SIZE);   //3特权级下的栈
    proc_stack->ss = SELECTOR_U_DATA;
    asm volatile("movl %0,%%esp;jmp intr_exit" : : "g"(proc_stack) : "memory");     //通过假装从中断返回的方式,使filename_运行
}

/*激活页表*/
void page_dir_activate(struct task_struct* p_thread){
    /*执行此函数时,当前任务可能是线程。
    *因为线程是内核级,所以线程用的是内核的页表。每个用户进程用的是独立的页表。
    *之所以对线程也要重新安装页表,原因是上一次被调度的可能是进程,否则不恢复页表的话,线程就会使用进程的页表了
    */
    uint32_t pagedir_phy_addr = 0x100000;   /*若为内核线程,需要重新填充页表为内核页表:00x10_0000*/
    //默认为内核的页目录物理地址,也就是内核线程所用的页目录表
    if(p_thread->pgdir != NULL){
        pagedir_phy_addr = addr_v2p((uint32_t)p_thread->pgdir);     //判断是线程/进程的方法:进程的pcb中pgdir指向页表的虚拟地址,而线程中无页表所以pgdir = NULL
    }
    /*更新页目录表寄存器cr3,使新页表生效*/
    asm volatile("movl %0,%%cr3" : : "r"(pagedir_phy_addr):"memory");   //将进程/线程的页表/内核页表加载到cr3寄存器中
    //每个进程都有独立的虚拟地址空间,本质上是各个进程都有自己单独的页表。
    //页表是存储在页表寄存器cr3中的,cr3只有一个。在不同进程执行前,我们要在cr3寄存器中为其换上配套的页表,从而实现虚拟地址空间的隔离。
}

/*激活线程/进程的页表,更新tss中的esp0为进程的特权级0的栈*/
void process_activate(struct task_struct* p_thread){
    ASSERT(p_thread != NULL);
    /*激活该进程/线程的页表*/
    page_dir_activate(p_thread);
    /*内核线程特权级本身是0,CPU进入中断时并不会从tss中获取0特权级栈地址,故不需要更新esp0*/
    if(p_thread->pgdir){
        /*更新该进程的esp0,用于此进程被中断时保留上下文*/
        update_tss_esp(p_thread);   //更新tss中的esp0为进程的特权级0栈
        //时钟中断进行进程调度,中断需要用到0特权级的栈
    }
}

/*创建页目录表,将当前页表的表示内核空间的pde复制,成功则返回页目录表的虚拟地址,否则返回-1*/
uint32_t* create_page_dir(void){
    /*用户进程的页表不能让用户直接访问到,所以在内核空间中申请*/
    uint32_t* page_dir_vaddr = get_kernel_pages(1);
    if(page_dir_vaddr == NULL){
        console_put_str("create_page_dir:get_kernel_page failed!");
        return NULL;
    }

    /* 1 先复制页表,将内核的页目录项复制到用户进程使用的页目录表中*/
    /*page_dir_vaddr + 0x300*4是内核页目录的第768项 [0x300是十六进制的768,4是页目录项的大小]*/
    memcpy((uint32_t*)((uint32_t)page_dir_vaddr + 0x300*4),(uint32_t*)(0xfffff000 + 0x300*4),1024);     //dst src size
    //0xfffff000 + 0x300*4表示内核页目录表中第768个页目录项的地址,1024/4=256个页目录项的大小,相当于低端1GB的内容
    /*********************************************/

    /* 2 更新页目录地址*/
    uint32_t new_page_dir_phy_addr = addr_v2p((uint32_t)page_dir_vaddr);
    /*页目录地址是存入在页目录的最后一项,更新页目录地址为新页目录的物理地址*/
    page_dir_vaddr[1023] = new_page_dir_phy_addr | PG_US_U | PG_RW_W | PG_P_1;
    /*****************************************************************/

    return page_dir_vaddr;
}

/*创建用户进程虚拟地址位图*/
void create_user_vaddr_bitmap(struct task_struct* user_prog){
    user_prog->userprog_vaddr.vaddr_start = USER_VADDR_START;
    uint32_t bitmap_pg_cnt = DIV_ROUND_UP((0xc0000000 - USER_VADDR_START) / PG_SIZE / 8 ,PG_SIZE);
    user_prog->userprog_vaddr.vaddr_bitmap.bits = get_kernel_pages(bitmap_pg_cnt);
    user_prog->userprog_vaddr.vaddr_bitmap.btmp_bytes_len = (0xc0000000 - USER_VADDR_START) / PG_SIZE / 8;
    bitmap_init(&user_prog->userprog_vaddr.vaddr_bitmap);
}

/*创建用户进程并加入到就绪队列中*/
void process_execute(void* filename,char* name){
    /*PCB内核的数据结构,由内核来维护进程信息,因此要在内核内存池中申请*/
    struct task_struct* thread = get_kernel_pages(1);
    init_thread(thread,name,default_prio);
    create_user_vaddr_bitmap(thread);
    thread_create(thread,start_process,filename);
    thread->pgdir = create_page_dir();
    block_desc_init(thread->u_block_desc);

    enum intr_status old_status = intr_disable();
    ASSERT(!elem_find(&thread_ready_list,&thread->general_tag));
    list_append(&thread_ready_list,&thread->general_tag);

    ASSERT(!elem_find(&thread_all_list,&thread->all_list_tag));
    list_append(&thread_all_list,&thread->all_list_tag);

    intr_set_status(old_status);
}
  • “/home/lily/OS/boot/kernel/memory.c”
#include "memory.h"
#include "bitmap.h"
#include "stdint.h"
#include "global.h"
#include "debug.h"
#include "print.h"
#include "../lib/string.h"
#include "sync.h"
#include "thread.h"
#include "interrupt.h"

#define PG_SIZE 4096
//4KB
#define PDE_IDX(addr) ((addr & 0xffc00000) >> 22)
#define PTE_IDX(addr) ((addr & 0x003ff000) >> 12)

/*位图地址
因为0xc009_f000是内核主线程栈顶,0xc009_e000是内核主线程的pcb。
一个页框大小的位图可表示128MB内存,位图位置安排在地址0xc009_a000,
这样本系统最大支持4个页框的位图,即512MB
*/
#define MEM_BITMAP_BASE 0xc009a000
/****************/

/*0xc000_0000是内核从虚拟地址3G起
0x10_0000是指跨过低端1MB内存,使虚拟地址在逻辑上连续*/
#define K_HEAP_START 0xc0100000

//内存池结构,生成两个实例用于管理内核内存池和用户内存池
struct pool{
    struct bitmap pool_bitmap;  //本内存池用到的位图结构,用于管理物理内存
    uint32_t phy_addr_start;    //本内存池所管理物理内存的起始地址
    uint32_t pool_size;         //本内存池的字节容量
    struct lock lock;           //申请内存时互斥
};

/*内存仓库*/
struct arena{
    struct mem_block_desc* desc;    //此arena关联的mem_block_desc
    uint32_t cnt;       //large为true时,cnt表示的是页框数。否则cnt表示空闲mem_block数量
    bool large;
};

struct mem_block_desc k_block_descs[DESC_CNT];

struct pool kernel_pool,user_pool;  //生成内核内存池和用于内存池
struct virtual_addr kernel_vaddr;   //用来给内核分配虚拟地址

/*在pf表示的虚拟内存池中申请pg_cnt个虚拟页,成功则返回虚拟页起始地址,失败则返回NULL*/
static void* vaddr_get(enum pool_flags pf,uint32_t pg_cnt){
    int vaddr_start = 0,bit_idx_start = -1;
    uint32_t cnt = 0;
    if(pf == PF_KERNEL){        //内核内存池
        //在位图中申请pg_cnt位
        bit_idx_start = bitmap_scan(&kernel_vaddr.vaddr_bitmap,pg_cnt);
        if(bit_idx_start == -1)
            return NULL;
        //申请成功则分配页
        while (cnt < pg_cnt)
            bitmap_set(&kernel_vaddr.vaddr_bitmap,bit_idx_start + cnt++,1);
        vaddr_start = kernel_vaddr.vaddr_start + bit_idx_start*PG_SIZE;
    }else{          //用户内存池
        struct task_struct* cur = running_thread();
        bit_idx_start = bitmap_scan(&cur->userprog_vaddr.vaddr_bitmap,pg_cnt);
        if(bit_idx_start == -1)
            return NULL;
        //申请成功则分配页
        while(cnt < pg_cnt)
            bitmap_set(&cur->userprog_vaddr.vaddr_bitmap,bit_idx_start + cnt++,1);
        vaddr_start = cur->userprog_vaddr.vaddr_start + bit_idx_start*PG_SIZE;
        /*(0xc0000000 - PG_SIZE)作为用户3级栈已经在start_process被分配*/
        ASSERT((uint32_t)vaddr_start < (0xc0000000 - PG_SIZE));
    }
    return (void*)vaddr_start;
}


/*得到虚拟地址vaddr对应的pte指针*/
uint32_t* pte_ptr(uint32_t vaddr){
    uint32_t* pte = (uint32_t*)(0xffc00000 + ((vaddr & 0xffc00000) >> 10) + PTE_IDX(vaddr)*4);
    return pte;
}

/*得到虚拟地址vaddr对应的pde指针*/
uint32_t* pde_ptr(uint32_t vaddr){
    uint32_t* pde = (uint32_t*)((0xfffff000) + PDE_IDX(vaddr)*4);
    return pde;
}

/*在m_pool指向的物理内存池中分配 一个物理页,成功则返回页框物理地址,失败则返回NULL*/
static void* palloc(struct pool* m_pool){
    //检测有无空间
    int bit_idx = bitmap_scan(&m_pool->pool_bitmap,1);
    //分配空间
    if(bit_idx == -1)
        return NULL;
    bitmap_set(&m_pool->pool_bitmap,bit_idx,1);
    uint32_t page_phyaddr = m_pool->phy_addr_start + (bit_idx*PG_SIZE);
    return (void*)page_phyaddr;
}

// /*页表中添加虚拟地址_vaddr与物理地址_page_phyadddr的映射*/
static void page_table_add(void* _vaddr,void* _page_phyaddr){
    uint32_t vaddr = (uint32_t)_vaddr;
    uint32_t page_phyaddr = (uint32_t)_page_phyaddr;
    uint32_t* pte = pte_ptr(vaddr);
    uint32_t* pde = pde_ptr(vaddr);

    /*要确保pde创建完成后才能执行pte*/
    if(*pde & 0x00000001){
        //如果pde存在,则将pte指向物理地址
        ASSERT(!(*pte & 0x00000001));   //pte理应不存在
        if(!(*pte & 0x00000001)){
            *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
        }else{
            PANIC("pte repeat!");
            *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
        }
    }else{
        //页目录项不存在,创建页目录项
        uint32_t pde_phyaddr = (uint32_t)palloc(&kernel_pool);  //页表中用到的页框一律从内核空间分配
        *pde = (pde_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
        //pte清零
        memset((void*)((int)pte & 0xfffff000),0,PG_SIZE);   //pte & 0xfffff000表示新页表的起始地址,将整页清零
        ASSERT(!(*pte & 0x00000001));
        *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
    }
}

// /*分配pg_cnt个页空间,成功则返回起始虚拟地址,失败则返回NULL*/
void* malloc_page(enum pool_flags pf,uint32_t pg_cnt){
    ASSERT(pg_cnt > 0 && pg_cnt < 3840);
    /*malloc_page原理的三个动作:
    1. 通过vaddr_get在虚拟内存池中申请虚拟地址
    2. 通过palloc在物理内存池中申请物理地址
    3. 通过page_table_add将两个地址进行映射*/

    //1.在虚拟内存池中申请
    void* vaddr_start = vaddr_get(pf,pg_cnt);   
    if(vaddr_start == NULL)
        return NULL;
    uint32_t vaddr = (uint32_t)vaddr_start;
    uint32_t cnt = (uint32_t)pg_cnt;
    struct pool* mem_pool = pf & PF_KERNEL ? &kernel_pool : &user_pool;

    //2.虚拟内存是连续的,物理内存不是,需要逐个映射
    while (cnt--){
        void* page_phyaddr = palloc(mem_pool);  //申请一个物理地址
        if(page_phyaddr == NULL)
            return NULL;
        //3.在页表中做映射
        page_table_add((void*)vaddr,page_phyaddr);
        vaddr += PG_SIZE;   //下一个虚拟页
    }
    return vaddr_start;
}

// /*在内核物理池中申请一页内存,成功则返回其虚拟地址,失败返回NULL*/
void* get_kernel_pages(uint32_t pg_cnt){
    void* vaddr = malloc_page(PF_KERNEL,pg_cnt);
    if(vaddr == NULL)
        return NULL;
    memset(vaddr,0,pg_cnt*PG_SIZE);     //把虚拟地址对应的物理地址清零
    return vaddr;
}

/*在用户空间中申请4K内存,并返回虚拟地址*/
void* get_user_pages(uint32_t pg_cnt){
    lock_acquire(&user_pool.lock);
    void* vaddr = malloc_page(PF_USER,pg_cnt);      //在用户内存池中以整页为单位分配内存,并返回分配的虚拟地址
    memset(vaddr,0,pg_cnt*PG_SIZE);
    lock_release(&user_pool.lock);
    return vaddr;
}


/*将地址vaddr与pf池中的物理地址关联,仅支持一页空间分配;申请一页内存,并用vaddr映射到该页(可以指定虚拟地址)*/
void* get_a_page(enum pool_flags pf,uint32_t vaddr){
    struct pool* mem_pool = pf & PF_KERNEL ? &kernel_pool : &user_pool;
    lock_acquire(&mem_pool->lock);
    /*先将虚拟地址对应的位图置1*/
    struct task_struct* cur = running_thread();
    int32_t bit_idx = -1;

    /*若当前是用户进程申请用户内存,就修改用户进程自己的虚拟地址位图*/
    if(cur->pgdir != NULL && pf ==PF_USER){
        bit_idx = (vaddr - cur->userprog_vaddr.vaddr_start) / PG_SIZE;
        ASSERT(bit_idx > 0);
        bitmap_set(&cur->userprog_vaddr.vaddr_bitmap,bit_idx,1);
    }else if(cur->pgdir == NULL && pf == PF_KERNEL){
        /*如果是内核线程申请内核内存,就修改kernel_vaddr*/
        bit_idx = (vaddr - kernel_vaddr.vaddr_start) / PG_SIZE;
        ASSERT(bit_idx > 0);
        bitmap_set(&kernel_vaddr.vaddr_bitmap,bit_idx,1);
    }else{
        PANIC("get_a_page:not allow kernel alloc userspace or user alloc kernel space by get_a_page");
    }

    void* page_phyaddr = palloc(mem_pool);
    if(page_phyaddr == NULL)
        return NULL;
    page_table_add((void*)vaddr,page_phyaddr);
    lock_release(&mem_pool->lock);
    return (void*)vaddr;
}

/*得到虚拟地址映射到的物理地址*/
uint32_t addr_v2p(uint32_t vaddr){
    uint32_t* pte = pte_ptr(vaddr);
    /*(pte)的值是页表所在的物理页框地址,去掉其低12位的页表项属性 + 虚拟地址vaddr的低12位*/
    return ((*pte & 0xfffff000) + (vaddr & 0x00000fff));    //原理:PDE + PTE + 12位偏移地址 = 物理地址;这里直接用pte得到页表项,然后页表项 + 12位偏移地址 = 物理地址
}

// //初始化内存池
static void mem_pool_init(uint32_t all_mem){
    put_str("   mem_pool_init start\n");
    uint32_t page_table_size = PG_SIZE * 256;
    //页表大小=1页的页目录表 + 第0和第768个页目录项指向同一个页表 + (769~1022)个页目录项共指向254个页表 = 256
    uint32_t used_mem = page_table_size + 0x100000;     //0x10_0000是指跨过低端1MB内存 + 页表,使虚拟地址在逻辑上连续
    uint32_t free_mem = all_mem - used_mem;
    uint16_t all_free_pages = free_mem/PG_SIZE;
    uint16_t kernel_free_pages = all_free_pages / 2;
    uint16_t user_free_pages = all_free_pages - kernel_free_pages;

    //为简化位图操作,余数不处理
    uint32_t kbm_length = kernel_free_pages / 8;
    uint32_t ubm_length = user_free_pages / 8;
    uint32_t kp_start = used_mem;   //内核内存池起始地址
    uint32_t up_start = kp_start + kernel_free_pages*PG_SIZE;   //用户内存池起始地址

    kernel_pool.phy_addr_start = kp_start;
    user_pool.phy_addr_start = up_start;

    kernel_pool.pool_size = kernel_free_pages*PG_SIZE;
    user_pool.pool_size = user_free_pages*PG_SIZE;

    kernel_pool.pool_bitmap.btmp_bytes_len = kbm_length;
    user_pool.pool_bitmap.btmp_bytes_len = ubm_length;

    /*内核内存池和用户内存池位图
    位图是全局数据,长度不固定
    全局/静态数组需要在编译时知道长度
    我们需要根据总内存大小算出需要多少字节,所以改为指定一块内存来生成位图
    */
    kernel_pool.pool_bitmap.bits = (void*)MEM_BITMAP_BASE;
    user_pool.pool_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length);
    
    /*输出内存池信息*/
    put_str("   kernel_pool_bitmap_start:");
    put_int((int)kernel_pool.pool_bitmap.bits);
    put_str("   kernel_pool_phy_addr_start:");
    put_int(kernel_pool.phy_addr_start);
        put_str("\n");
        put_str("user_pool_bitmap_start:");
        put_int((int)user_pool.pool_bitmap.bits);
        put_str("   user_pool_phy_addr_start:");
        put_int((int)user_pool.phy_addr_start);
        put_str("\n");

        /*将位图置0*/
        bitmap_init(&kernel_pool.pool_bitmap);
        bitmap_init(&user_pool.pool_bitmap);
        /*初始化锁*/
        lock_init(&kernel_pool.lock);
        lock_init(&user_pool.lock);

        /*初始化内核虚拟地址的位图,按照实际物理内存大小生成数组*/
        kernel_vaddr.vaddr_bitmap.btmp_bytes_len = kbm_length;
        //用于维护内核堆的虚拟地址,所以要和内核内存池大小一致
        /*位图的数组指向一块未使用的内存,目前定位在内核内存池和用户内存池之外*/
        kernel_vaddr.vaddr_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length + ubm_length);
        kernel_vaddr.vaddr_start = K_HEAP_START;
        bitmap_init(&kernel_vaddr.vaddr_bitmap);
        put_str("   mem_pool_init done\n");
}

/*为malloc做准备*/
void block_desc_init(struct mem_block_desc* desc_array){
    uint16_t desc_idx , block_size = 16;
    /*初始化每个mem_block_desc描述符*/
    for(desc_idx = 0;desc_idx < DESC_CNT;desc_idx++){
        desc_array[desc_idx].block_size = block_size;
        /*初始化arena中的内存块数量*/
        desc_array[desc_idx].blocks_per_arena = (PG_SIZE - sizeof(struct arena)) / block_size;
        list_init(&desc_array[desc_idx].free_list);
        block_size *= 2;    //更新为下一个规格的内存块
    }
}

// /*内存管理部分初始化入口*/
void mem_init(){
    put_str("mem_init start\n");
    uint32_t mem_bytes_total = (*(uint32_t*)(0xb00));
    mem_pool_init(mem_bytes_total);
    /*初始化mem_block_desc数组descs,为malloc做准备*/
    block_desc_init(k_block_descs);
    put_str("mem_init done\n");
}

/*返回arena中第idx个内存块的地址*/
static struct mem_block* arena2block(struct arena* a,uint32_t idx){
    return (struct mem_block*)((uint32_t)a + sizeof(struct arena) + idx*(a->desc->block_size)); 
    //sizeof(struct arena)只是元信息大小
}

/*返回内存块b所在的arena地址*/
static struct arena* block2arena(struct mem_block* b){
    return (struct arena*)((uint32_t)b & 0xfffff000);
}

/*在堆中申请size字节内存*/
void* sys_malloc(uint32_t size){
    enum pool_flags PF;
    struct pool* mem_pool;
    uint32_t pool_size;
    struct mem_block_desc* descs;   //内存块描述符
    struct task_struct* cur_thread = running_thread();

    /*判断用哪个内存池*/
    if(cur_thread->pgdir == NULL){
        PF = PF_KERNEL;
        pool_size = kernel_pool.pool_size;
        mem_pool = &kernel_pool;
        descs = k_block_descs;
    }else{
        PF = PF_USER;
        pool_size = user_pool.pool_size;
        mem_pool = &user_pool;
        descs = cur_thread->u_block_desc;
    }

    /*若申请的内存不在内存池容量范围内,则直接返回NULL*/
    if(!(size > 0 && size < pool_size)){
        return NULL;
    }
    struct arena* a;
    struct mem_block* b;
    lock_acquire(&mem_pool->lock);

    /*超过最大内存块1024,就分配页框*/
    if(size > 1024){
        uint32_t page_cnt = DIV_ROUND_UP(size + sizeof(struct arena),PG_SIZE);  //需要几个物理页
        a = malloc_page(PF,page_cnt);
        if(a != NULL){
            memset(a,0,page_cnt*PG_SIZE);   //将内存清零
            /*对于分配的大块页框,将desc置为NULL,cnt置为页框数,large置为true*/
            a->desc = NULL;     //因为不需要分成小内存,所以没有对应的内存块描述符
            a->cnt = page_cnt;
            a->large = true;
            lock_release(&mem_pool->lock);
            return (void*)(a + 1);  //*(a+1)是指针a跨过一个arena大小的空间,即:a指向了mem_block
        }else{
            lock_release(&mem_pool->lock);
            return NULL;
        }
    }else{
        uint8_t desc_idx;
        /*从内存块描述符中匹配合适的内存块规格*/
        for(desc_idx = 0;desc_idx < DESC_CNT;desc_idx++){
            if(size <= descs[desc_idx].block_size){
                break;
            }
        }
        /*若mem_block_desc的free_list中已经没有可用的mem_block,就创建新的arena提供mem_block*/
        if(list_empty(&descs[desc_idx].free_list)){
            a = malloc_page(PF,1);
            if(a == NULL){
                lock_release(&mem_pool->lock);
                return NULL;
            }
            memset(a,0,PG_SIZE);

            /*对于分配的小块内存,将desc置为相应内存块描述符,cnt置为此arena可用的内存块数,large置为false*/
            a->desc = &descs[desc_idx];
            a->large = false;
            a->cnt = &descs[desc_idx].blocks_per_arena;
            uint32_t block_idx;
            enum intr_status old_status = intr_disable();

            /*开始将arena拆分成内存块,并添加到内存块描述符的free_list中*/
            for(block_idx = 0;block_idx < descs[desc_idx].blocks_per_arena;block_idx++){
                b = arena2block(a,block_idx);
                ASSERT(!elem_find(&a->desc->free_list,&b->free_elem));
                list_append(&a->desc->free_list,&b->free_elem);
            }
            intr_set_status(old_status);
        }
        /*开始分配内存块*/
        b = elem2entry(struct mem_block,free_elem,list_pop(&(descs[desc_idx].free_list)));  //结构体mem_block的起始地址的指针类型
        memset(b,0,descs[desc_idx].block_size);
        a = block2arena(b);
        a->cnt--;
        lock_release(&mem_pool->lock);
        return (void*)b;
    }
}
  • “/home/lily/OS/boot/kernel/memory.h”
#ifndef __KERNEL_MEMORY_H
#define __KERNEL_MEMORY_H
#include "stdint.h"
#include "bitmap.h"
#include "../lib/kernel/list.h"
//虚拟地址池,用于虚拟地址管理
struct virtual_addr
{
    struct bitmap vaddr_bitmap; //虚拟地址用到的位图结构
    uint32_t vaddr_start;       //虚拟地址起始地址
};

enum pool_flags{
    PF_KERNEL = 1,  //内核内存池
    PF_USER = 2     //用户内存池
};

#define PG_P_1 1    //页表项或页目录项存在属性位:此页在内存中存在
#define PG_P_0 0    //页表项或页目录项存在属性位:此页在内存中不存在
#define PG_RW_R 0   //R/W属性位值,读/写操作:此页运行读、写、执行
#define PG_RW_W 2   //R/W属性位值,读/写操作:此页允许读、执行
#define PG_US_S 0   //U/S属性位值,系统级:只允许特权级0、1、2的程序访问
#define PG_US_U 4   //U/S属性位值,用户级:允许所有特权级的进程访问

extern struct pool kernel_pool, user_pool;

/*内存块*/
struct mem_block{
    struct list_elem free_elem;
};

/*内存块描述符*/
struct mem_block_desc{
    uint32_t block_size;        //内存块大小
    uint32_t blocks_per_arena;  //本arena中可容纳此mem_block的数量
    struct list free_list;      //目前可用的mem_block链表,长度无限长,可以由多个arena提供内存块
};

#define DESC_CNT 7              //内存块描述符个数:16,32,64,128,256,512,1024字节,总共7种规格的内存块大小

/*得到虚拟地址vaddr对应的pte指针*/
uint32_t* pte_ptr(uint32_t vaddr);

/*得到虚拟地址vaddr对应的pde指针*/
uint32_t* pde_ptr(uint32_t vaddr);

/*分配pg_cnt个页空间,成功则返回起始虚拟地址,失败则返回NULL*/
void* malloc_page(enum pool_flags pf,uint32_t pg_cnt);

/*将地址vaddr与pf池中的物理地址关联,仅支持一页空间分配;申请一页内存,并用vaddr映射到该页(可以指定虚拟地址)*/
void* get_a_page(enum pool_flags pf,uint32_t vaddr);

/*在内核物理池中申请一页内存,成功则返回其虚拟地址,失败返回NULL*/
void* get_kernel_pages(uint32_t pg_cnt);

/*在用户空间中申请4K内存,并返回虚拟地址*/
void* get_user_pages(uint32_t pg_cnt);

/*得到虚拟地址映射到的物理地址*/
uint32_t addr_v2p(uint32_t vaddr);

/*内存管理部分初始化入口*/
void mem_init(void);

/*在堆中申请size字节内存*/
void* sys_malloc(uint32_t size);

#endif
  • “/home/lily/OS/boot/kernel/main.c”
#include "print.h"
#include "init.h"
#include "thread.h"
#include "interrupt.h"
#include "console.h"
#include "keyboard.h"
#include "ioqueue.h"
#include "process.h"
#include "syscall.h"
#include "syscall_init.h"
#include "../lib/stdio.h"
#include "memory.h"

void k_thread_a(void*);
void k_thread_b(void*);
void u_prog_a(void);
void u_prog_b(void);

int prog_a_pid = 0,prog_b_pid = 0;

int main(void){
	put_str("I am kernel\n");
	init_all();

	// process_execute(u_prog_a,"user_prog_a");
	// process_execute(u_prog_b,"user_prog_b");
	intr_enable();	//打开中断,使时钟中断起作用
	// console_put_str(" main_pid:0x");
	// console_put_int(sys_getpid());
	// console_put_char('\n');
	thread_start("k_thread_a",31,k_thread_a,"I am thread_a");
	thread_start("k_thread_b",31,k_thread_b,"I am thread_b");
	while(1);
	return 0;
}

/*在线程中运行函数*/
void k_thread_a(void* arg){
	char* para = arg;
	void* addr = sys_malloc(33);
	console_put_str(" I am thread_a, sys_malloc(33),addr is:0x");
	console_put_int((int)addr);
	console_put_char('\n');
	while(1);
}

/*在线程中运行函数*/
void k_thread_b(void* arg){
	char* para = arg;
	void* addr = sys_malloc(63);
	console_put_str(" I am thread_a, sys_malloc(63),addr is:0x");
	console_put_int((int)addr);
	console_put_char('\n');
	while(1);
}

/*测试用户进程*/
void u_prog_a(void){
	char* name = "prog_a";
	// printf("u_prog_a\n");
	printf(" I am %s,my pid:%d%c",name,getpid(),'\n');
	while(1);
}

/*测试用户进程*/
void u_prog_b(void){
	char* name = "prog_b";
	printf(" I am %s,my pid:%d%c",name,getpid(),'\n');
	while(1);
}
  • “/home/lily/OS/boot/makefile”
BUILD_DIR = ./build
##用来存储生成的所有目标文件
ENTRY_POINT = 0xc0001500
AS = nasm
CC = gcc
LD = ld
LIB = -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -I device/ -I thread/ -I userprog/ 
ASFLAGS = -f elf
CFLAGS = -Wall -m32 -fno-stack-protector $(LIB) -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes
##-fno-builtin是告诉编译器不要采用内部函数 -Wstrict-prototypes是要求函数声明中必须有参数类型 
## -Wmissing-prototypes要求函数必须有声明
LDFLAGS = -m elf_i386 -Ttext $(ENTRY_POINT) -e main -Map $(BUILD_DIR)/kernel.map
OBJS = $(BUILD_DIR)/main.o $(BUILD_DIR)/init.o $(BUILD_DIR)/interrupt.o \
	$(BUILD_DIR)/timer.o $(BUILD_DIR)/kernel.o $(BUILD_DIR)/print.o $(BUILD_DIR)/debug.o \
	$(BUILD_DIR)/string.o $(BUILD_DIR)/memory.o $(BUILD_DIR)/bitmap.o $(BUILD_DIR)/thread.o \
	$(BUILD_DIR)/switch.o $(BUILD_DIR)/list.o $(BUILD_DIR)/sync.o $(BUILD_DIR)/console.o  \
	$(BUILD_DIR)/keyboard.o $(BUILD_DIR)/ioqueue.o $(BUILD_DIR)/tss.o $(BUILD_DIR)/process.o \
	$(BUILD_DIR)/syscall.o $(BUILD_DIR)/syscall_init.o $(BUILD_DIR)/stdio.o
## OBJS用来存储所有目标文件名,不要用%.o,因为不能保证链接顺序

########## c代码编译 ##########
$(BUILD_DIR)/main.o:kernel/main.c lib/kernel/print.h lib/kernel/stdint.h kernel/init.h kernel/memory.h \
	thread/thread.h kernel/interrupt.h device/console.h device/keyboard.h device/ioqueue.h userprog/process.h \
	userprog/syscall_init.h lib/user/syscall.h lib/stdio.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/init.o:kernel/init.c kernel/init.h lib/kernel/print.h lib/kernel/stdint.h \
	kernel/interrupt.h device/timer.h kernel/memory.h thread/thread.h device/console.h device/keyboard.h \
	userprog/tss.h userprog/syscall_init.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/interrupt.o:kernel/interrupt.c kernel/interrupt.h lib/kernel/stdint.h \
	kernel/global.h lib/kernel/io.h lib/kernel/print.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/timer.o:device/timer.c device/timer.h lib/kernel/stdint.h lib/kernel/io.h lib/kernel/print.h thread/thread.c \
	kernel/debug.h kernel/interrupt.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/thread.o:thread/thread.c thread/thread.h lib/kernel/stdint.h lib/string.h kernel/global.h kernel/memory.h \
	kernel/interrupt.h lib/kernel/list.h kernel/debug.h lib/kernel/print.h userprog/process.h thread/sync.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/debug.o:kernel/debug.c kernel/debug.h lib/kernel/print.h lib/kernel/stdint.h kernel/interrupt.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/string.o:lib/string.c lib/string.h kernel/debug.h kernel/global.h lib/kernel/stdint.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/memory.o:kernel/memory.c kernel/memory.h lib/kernel/stdint.h lib/kernel/bitmap.h kernel/debug.h lib/string.h \
	lib/kernel/print.h kernel/global.h thread/sync.h thread/thread.h kernel/interrupt.h lib/kernel/list.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/bitmap.o:lib/kernel/bitmap.c lib/kernel/bitmap.h lib/string.h kernel/interrupt.h lib/kernel/print.h \
	kernel/debug.h lib/kernel/stdint.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/list.o: lib/kernel/list.c lib/kernel/list.h kernel/interrupt.h lib/kernel/stdint.h kernel/debug.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/sync.o: thread/sync.c thread/sync.h lib/kernel/list.h lib/kernel/stdint.h thread/thread.h lib/string.h \
	kernel/interrupt.h kernel/debug.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/console.o: device/console.c device/console.h lib/kernel/print.h lib/kernel/stdint.h thread/sync.h thread/thread.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/keyboard.o: device/keyboard.c device/keyboard.h lib/kernel/print.h lib/kernel/stdint.h kernel/interrupt.h lib/kernel/io.h \
	kernel/global.h device/ioqueue.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/ioqueue.o: device/ioqueue.c device/ioqueue.h lib/kernel/stdint.h kernel/interrupt.h  kernel/debug.h thread/thread.h \
	kernel/global.h thread/sync.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/tss.o: userprog/tss.c userprog/tss.h lib/kernel/stdint.h thread/thread.h lib/kernel/print.h lib/string.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/process.o: userprog/process.c userprog/process.h thread/thread.h userprog/tss.h device/console.h \
	kernel/memory.h kernel/global.h lib/kernel/bitmap.h kernel/interrupt.h lib/kernel/stdint.h kernel/debug.h \
	lib/string.h lib/kernel/print.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/syscall.o: lib/user/syscall.c lib/user/syscall.h lib/kernel/stdint.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/syscall_init.o: userprog/syscall_init.c userprog/syscall_init.h thread/thread.h lib/user/syscall.h lib/kernel/stdint.h \
	lib/string.h lib/kernel/print.h device/console.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/stdio.o: lib/stdio.c lib/stdio.h lib/kernel/stdint.h lib/string.h lib/user/syscall.h kernel/interrupt.h  \
	lib/kernel/print.h
	$(CC) $(CFLAGS) $< -o $@

###########汇编代码编译############
$(BUILD_DIR)/kernel.o:kernel/kernel.S
	$(AS) $(ASFLAGS) $< -o $@

$(BUILD_DIR)/print.o:lib/kernel/print.S
	$(AS) $(ASFLAGS) $< -o $@

$(BUILD_DIR)/switch.o:thread/switch.S
	$(AS) $(ASFLAGS) $< -o $@

##########链接所有目标文件#############
$(BUILD_DIR)/kernel.bin:$(OBJS)
	$(LD) $(LDFLAGS) $^ -o $@

.PHONY: mk_dir hd clean all

mk_dir:
	if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fi	
###fi为终止符

hd:	
	dd if=$(BUILD_DIR)/kernel.bin of=/home/lily/bochs/hd60M.img bs=512 count=200 seek=9 conv=notrunc

clean:	##将build目录下文件清空
	cd $(BUILD_DIR) && rm -f ./*

build:$(BUILD_DIR)/kernel.bin	##编译kernel.bin,只要执行make build就是编译文件

all:mk_dir build hd	
##依次执行伪目标mk_dir build hd,只要执行make all就是完成了编译到写入硬盘的全过程

在这里插入图片描述
在这里插入图片描述

4. 内存的释放

  • 内存的使用情况是通过位图来管理的,因此,无论内存的分配/释放,本质上都是在设置相关位图中的相应位,都是在读写位图。
  • 回收物理地址就是将物理内存池位图中的相应位清零,无需将该4KB物理页框清零;回收虚拟地址就是将虚拟内存池位图中的相应位清零。
  • 在函数malloc_page中分配内存:
    1. 在虚拟地址池中分配虚拟地址,相关函数是vaddr_get,此函数操作的是内核虚拟内存池位图kernel_vaddr.vaddr_bitmap或用户虚拟内存池位图pcb->userprog_vaddr.vaddr_bitmap。
    2. 在物理内存池中分配物理地址,相关函数是palloc,此函数操作的是内核物理内存池位图kernel_pool.pool_bitmap或用户物理内存池位图user_pool->pool_bitmap。
    3. 在页表中完成虚拟地址->物理地址的映射,函数为page_table_add。
  • 释放内存的函数mfree_page:
    1. 在物理地址池中释放物理页地址,相关函数为pfree,操作的位图同palloc。
    2. 在页表中去掉虚拟地址的映射,原理是将虚拟地址对应pte的P位置0(表示pte无效),相关函数为page_table_pte_remove。
    3. 在虚拟地址池中释放虚拟地址,相关函数为vaddr_remove,操作的位图同vaddr_get。
  • sys_free:系统调用free对应的内核功能函数,是内存释放的统一接口,无论是页框级别的内存还是小的内存块,都统一用sys_free处理。

---------------------------代码-------------------------------

  • “/home/lily/OS/boot/kernel/memory.c”
#include "memory.h"
#include "bitmap.h"
#include "stdint.h"
#include "global.h"
#include "debug.h"
#include "print.h"
#include "../lib/string.h"
#include "sync.h"
#include "thread.h"
#include "interrupt.h"

#define PG_SIZE 4096
//4KB
#define PDE_IDX(addr) ((addr & 0xffc00000) >> 22)
#define PTE_IDX(addr) ((addr & 0x003ff000) >> 12)

/*位图地址
因为0xc009_f000是内核主线程栈顶,0xc009_e000是内核主线程的pcb。
一个页框大小的位图可表示128MB内存,位图位置安排在地址0xc009_a000,
这样本系统最大支持4个页框的位图,即512MB
*/
#define MEM_BITMAP_BASE 0xc009a000
/****************/

/*0xc000_0000是内核从虚拟地址3G起
0x10_0000是指跨过低端1MB内存,使虚拟地址在逻辑上连续*/
#define K_HEAP_START 0xc0100000

//内存池结构,生成两个实例用于管理内核内存池和用户内存池
struct pool{
    struct bitmap pool_bitmap;  //本内存池用到的位图结构,用于管理物理内存
    uint32_t phy_addr_start;    //本内存池所管理物理内存的起始地址
    uint32_t pool_size;         //本内存池的字节容量
    struct lock lock;           //申请内存时互斥
};

/*内存仓库*/
struct arena{
    struct mem_block_desc* desc;    //此arena关联的mem_block_desc
    uint32_t cnt;       //large为true时,cnt表示的是页框数。否则cnt表示空闲mem_block数量
    bool large;
};

struct mem_block_desc k_block_descs[DESC_CNT];

struct pool kernel_pool,user_pool;  //生成内核内存池和用于内存池
struct virtual_addr kernel_vaddr;   //用来给内核分配虚拟地址

/*在pf表示的虚拟内存池中申请pg_cnt个虚拟页,成功则返回虚拟页起始地址,失败则返回NULL*/
static void* vaddr_get(enum pool_flags pf,uint32_t pg_cnt){
    int vaddr_start = 0,bit_idx_start = -1;
    uint32_t cnt = 0;
    if(pf == PF_KERNEL){        //内核内存池
        //在位图中申请pg_cnt位
        bit_idx_start = bitmap_scan(&kernel_vaddr.vaddr_bitmap,pg_cnt);
        if(bit_idx_start == -1)
            return NULL;
        //申请成功则分配页
        while (cnt < pg_cnt)
            bitmap_set(&kernel_vaddr.vaddr_bitmap,bit_idx_start + cnt++,1);
        vaddr_start = kernel_vaddr.vaddr_start + bit_idx_start*PG_SIZE;
    }else{          //用户内存池
        struct task_struct* cur = running_thread();
        bit_idx_start = bitmap_scan(&cur->userprog_vaddr.vaddr_bitmap,pg_cnt);
        if(bit_idx_start == -1)
            return NULL;
        //申请成功则分配页
        while(cnt < pg_cnt)
            bitmap_set(&cur->userprog_vaddr.vaddr_bitmap,bit_idx_start + cnt++,1);
        vaddr_start = cur->userprog_vaddr.vaddr_start + bit_idx_start*PG_SIZE;
        /*(0xc0000000 - PG_SIZE)作为用户3级栈已经在start_process被分配*/
        ASSERT((uint32_t)vaddr_start < (0xc0000000 - PG_SIZE));
    }
    return (void*)vaddr_start;
}


/*得到虚拟地址vaddr对应的pte指针*/
uint32_t* pte_ptr(uint32_t vaddr){
    uint32_t* pte = (uint32_t*)(0xffc00000 + ((vaddr & 0xffc00000) >> 10) + PTE_IDX(vaddr)*4);
    return pte;
}

/*得到虚拟地址vaddr对应的pde指针*/
uint32_t* pde_ptr(uint32_t vaddr){
    uint32_t* pde = (uint32_t*)((0xfffff000) + PDE_IDX(vaddr)*4);
    return pde;
}

/*在m_pool指向的物理内存池中分配 一个物理页,成功则返回页框物理地址,失败则返回NULL*/
static void* palloc(struct pool* m_pool){
    //检测有无空间
    int bit_idx = bitmap_scan(&m_pool->pool_bitmap,1);
    //分配空间
    if(bit_idx == -1)
        return NULL;
    bitmap_set(&m_pool->pool_bitmap,bit_idx,1);
    uint32_t page_phyaddr = m_pool->phy_addr_start + (bit_idx*PG_SIZE);
    return (void*)page_phyaddr;
}

// /*页表中添加虚拟地址_vaddr与物理地址_page_phyadddr的映射*/
static void page_table_add(void* _vaddr,void* _page_phyaddr){
    uint32_t vaddr = (uint32_t)_vaddr;
    uint32_t page_phyaddr = (uint32_t)_page_phyaddr;
    uint32_t* pte = pte_ptr(vaddr);
    uint32_t* pde = pde_ptr(vaddr);

    /*要确保pde创建完成后才能执行pte*/
    if(*pde & 0x00000001){
        //如果pde存在,则将pte指向物理地址
        ASSERT(!(*pte & 0x00000001));   //pte理应不存在
        if(!(*pte & 0x00000001)){
            *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
        }else{
            PANIC("pte repeat!");
            *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
        }
    }else{
        //页目录项不存在,创建页目录项
        uint32_t pde_phyaddr = (uint32_t)palloc(&kernel_pool);  //页表中用到的页框一律从内核空间分配
        *pde = (pde_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
        //pte清零
        memset((void*)((int)pte & 0xfffff000),0,PG_SIZE);   //pte & 0xfffff000表示新页表的起始地址,将整页清零
        ASSERT(!(*pte & 0x00000001));
        *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
    }
}

// /*分配pg_cnt个页空间,成功则返回起始虚拟地址,失败则返回NULL*/
void* malloc_page(enum pool_flags pf,uint32_t pg_cnt){
    ASSERT(pg_cnt > 0 && pg_cnt < 3840);
    /*malloc_page原理的三个动作:
    1. 通过vaddr_get在虚拟内存池中申请虚拟地址
    2. 通过palloc在物理内存池中申请物理地址
    3. 通过page_table_add将两个地址进行映射*/

    //1.在虚拟内存池中申请
    void* vaddr_start = vaddr_get(pf,pg_cnt);   
    if(vaddr_start == NULL)
        return NULL;
    uint32_t vaddr = (uint32_t)vaddr_start;
    uint32_t cnt = (uint32_t)pg_cnt;
    struct pool* mem_pool = pf & PF_KERNEL ? &kernel_pool : &user_pool;

    //2.虚拟内存是连续的,物理内存不是,需要逐个映射
    while (cnt--){
        void* page_phyaddr = palloc(mem_pool);  //申请一个物理地址
        if(page_phyaddr == NULL)
            return NULL;
        //3.在页表中做映射
        page_table_add((void*)vaddr,page_phyaddr);
        vaddr += PG_SIZE;   //下一个虚拟页
    }
    return vaddr_start;
}

// /*在内核物理池中申请一页内存,成功则返回其虚拟地址,失败返回NULL*/
void* get_kernel_pages(uint32_t pg_cnt){
    void* vaddr = malloc_page(PF_KERNEL,pg_cnt);
    if(vaddr == NULL)
        return NULL;
    memset(vaddr,0,pg_cnt*PG_SIZE);     //把虚拟地址对应的物理地址清零
    return vaddr;
}

/*在用户空间中申请4K内存,并返回虚拟地址*/
void* get_user_pages(uint32_t pg_cnt){
    lock_acquire(&user_pool.lock);
    void* vaddr = malloc_page(PF_USER,pg_cnt);      //在用户内存池中以整页为单位分配内存,并返回分配的虚拟地址
    memset(vaddr,0,pg_cnt*PG_SIZE);
    lock_release(&user_pool.lock);
    return vaddr;
}


/*将地址vaddr与pf池中的物理地址关联,仅支持一页空间分配;申请一页内存,并用vaddr映射到该页(可以指定虚拟地址)*/
void* get_a_page(enum pool_flags pf,uint32_t vaddr){
    struct pool* mem_pool = pf & PF_KERNEL ? &kernel_pool : &user_pool;
    lock_acquire(&mem_pool->lock);
    /*先将虚拟地址对应的位图置1*/
    struct task_struct* cur = running_thread();
    int32_t bit_idx = -1;

    /*若当前是用户进程申请用户内存,就修改用户进程自己的虚拟地址位图*/
    if(cur->pgdir != NULL && pf ==PF_USER){
        bit_idx = (vaddr - cur->userprog_vaddr.vaddr_start) / PG_SIZE;
        ASSERT(bit_idx > 0);
        bitmap_set(&cur->userprog_vaddr.vaddr_bitmap,bit_idx,1);
    }else if(cur->pgdir == NULL && pf == PF_KERNEL){
        /*如果是内核线程申请内核内存,就修改kernel_vaddr*/
        bit_idx = (vaddr - kernel_vaddr.vaddr_start) / PG_SIZE;
        ASSERT(bit_idx > 0);
        bitmap_set(&kernel_vaddr.vaddr_bitmap,bit_idx,1);
    }else{
        PANIC("get_a_page:not allow kernel alloc userspace or user alloc kernel space by get_a_page");
    }

    void* page_phyaddr = palloc(mem_pool);
    if(page_phyaddr == NULL)
        return NULL;
    page_table_add((void*)vaddr,page_phyaddr);
    lock_release(&mem_pool->lock);
    return (void*)vaddr;
}

/*得到虚拟地址映射到的物理地址*/
uint32_t addr_v2p(uint32_t vaddr){
    uint32_t* pte = pte_ptr(vaddr);
    /*(pte)的值是页表所在的物理页框地址,去掉其低12位的页表项属性 + 虚拟地址vaddr的低12位*/
    return ((*pte & 0xfffff000) + (vaddr & 0x00000fff));    //原理:PDE + PTE + 12位偏移地址 = 物理地址;这里直接用pte得到页表项,然后页表项 + 12位偏移地址 = 物理地址
}

// //初始化内存池
static void mem_pool_init(uint32_t all_mem){
    put_str("   mem_pool_init start\n");
    uint32_t page_table_size = PG_SIZE * 256;
    //页表大小=1页的页目录表 + 第0和第768个页目录项指向同一个页表 + (769~1022)个页目录项共指向254个页表 = 256
    uint32_t used_mem = page_table_size + 0x100000;     //0x10_0000是指跨过低端1MB内存 + 页表,使虚拟地址在逻辑上连续
    uint32_t free_mem = all_mem - used_mem;
    uint16_t all_free_pages = free_mem/PG_SIZE;
    uint16_t kernel_free_pages = all_free_pages / 2;
    uint16_t user_free_pages = all_free_pages - kernel_free_pages;

    //为简化位图操作,余数不处理
    uint32_t kbm_length = kernel_free_pages / 8;
    uint32_t ubm_length = user_free_pages / 8;
    uint32_t kp_start = used_mem;   //内核内存池起始地址
    uint32_t up_start = kp_start + kernel_free_pages*PG_SIZE;   //用户内存池起始地址

    kernel_pool.phy_addr_start = kp_start;
    user_pool.phy_addr_start = up_start;

    kernel_pool.pool_size = kernel_free_pages*PG_SIZE;
    user_pool.pool_size = user_free_pages*PG_SIZE;

    kernel_pool.pool_bitmap.btmp_bytes_len = kbm_length;
    user_pool.pool_bitmap.btmp_bytes_len = ubm_length;

    /*内核内存池和用户内存池位图
    位图是全局数据,长度不固定
    全局/静态数组需要在编译时知道长度
    我们需要根据总内存大小算出需要多少字节,所以改为指定一块内存来生成位图
    */
    kernel_pool.pool_bitmap.bits = (void*)MEM_BITMAP_BASE;
    user_pool.pool_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length);
    
    /*输出内存池信息*/
    put_str("   kernel_pool_bitmap_start:");
    put_int((int)kernel_pool.pool_bitmap.bits);
    put_str("   kernel_pool_phy_addr_start:");
    put_int(kernel_pool.phy_addr_start);
        put_str("\n");
        put_str("user_pool_bitmap_start:");
        put_int((int)user_pool.pool_bitmap.bits);
        put_str("   user_pool_phy_addr_start:");
        put_int((int)user_pool.phy_addr_start);
        put_str("\n");

        /*将位图置0*/
        bitmap_init(&kernel_pool.pool_bitmap);
        bitmap_init(&user_pool.pool_bitmap);
        /*初始化锁*/
        lock_init(&kernel_pool.lock);
        lock_init(&user_pool.lock);

        /*初始化内核虚拟地址的位图,按照实际物理内存大小生成数组*/
        kernel_vaddr.vaddr_bitmap.btmp_bytes_len = kbm_length;
        //用于维护内核堆的虚拟地址,所以要和内核内存池大小一致
        /*位图的数组指向一块未使用的内存,目前定位在内核内存池和用户内存池之外*/
        kernel_vaddr.vaddr_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length + ubm_length);
        kernel_vaddr.vaddr_start = K_HEAP_START;
        bitmap_init(&kernel_vaddr.vaddr_bitmap);
        put_str("   mem_pool_init done\n");
}

/*为malloc做准备*/
void block_desc_init(struct mem_block_desc* desc_array){
    uint16_t desc_idx , block_size = 16;
    /*初始化每个mem_block_desc描述符*/
    for(desc_idx = 0;desc_idx < DESC_CNT;desc_idx++){
        desc_array[desc_idx].block_size = block_size;
        /*初始化arena中的内存块数量*/
        desc_array[desc_idx].blocks_per_arena = (PG_SIZE - sizeof(struct arena)) / block_size;
        list_init(&desc_array[desc_idx].free_list);
        block_size *= 2;    //更新为下一个规格的内存块
    }
}

// /*内存管理部分初始化入口*/
void mem_init(){
    put_str("mem_init start\n");
    uint32_t mem_bytes_total = (*(uint32_t*)(0xb00));
    mem_pool_init(mem_bytes_total);
    /*初始化mem_block_desc数组descs,为malloc做准备*/
    block_desc_init(k_block_descs);
    put_str("mem_init done\n");
}

/*返回arena中第idx个内存块的地址*/
static struct mem_block* arena2block(struct arena* a,uint32_t idx){
    return (struct mem_block*)((uint32_t)a + sizeof(struct arena) + idx*(a->desc->block_size)); 
    //sizeof(struct arena)只是元信息大小
}

/*返回内存块b所在的arena地址*/
static struct arena* block2arena(struct mem_block* b){
    return (struct arena*)((uint32_t)b & 0xfffff000);
}

/*在堆中申请size字节内存*/
void* sys_malloc(uint32_t size){
    enum pool_flags PF;
    struct pool* mem_pool;
    uint32_t pool_size;
    struct mem_block_desc* descs;   //内存块描述符
    struct task_struct* cur_thread = running_thread();

    /*判断用哪个内存池*/
    if(cur_thread->pgdir == NULL){
        PF = PF_KERNEL;
        pool_size = kernel_pool.pool_size;
        mem_pool = &kernel_pool;
        descs = k_block_descs;
    }else{
        PF = PF_USER;
        pool_size = user_pool.pool_size;
        mem_pool = &user_pool;
        descs = cur_thread->u_block_desc;
    }

    /*若申请的内存不在内存池容量范围内,则直接返回NULL*/
    if(!(size > 0 && size < pool_size)){
        return NULL;
    }
    struct arena* a;
    struct mem_block* b;
    lock_acquire(&mem_pool->lock);

    /*超过最大内存块1024,就分配页框*/
    if(size > 1024){
        uint32_t page_cnt = DIV_ROUND_UP(size + sizeof(struct arena),PG_SIZE);  //需要几个物理页
        a = malloc_page(PF,page_cnt);
        if(a != NULL){
            memset(a,0,page_cnt*PG_SIZE);   //将内存清零
            /*对于分配的大块页框,将desc置为NULL,cnt置为页框数,large置为true*/
            a->desc = NULL;     //因为不需要分成小内存,所以没有对应的内存块描述符
            a->cnt = page_cnt;
            a->large = true;
            lock_release(&mem_pool->lock);
            return (void*)(a + 1);  //*(a+1)是指针a跨过一个arena大小的空间,即:a指向了mem_block
        }else{
            lock_release(&mem_pool->lock);
            return NULL;
        }
    }else{
        uint8_t desc_idx;
        /*从内存块描述符中匹配合适的内存块规格*/
        for(desc_idx = 0;desc_idx < DESC_CNT;desc_idx++){
            if(size <= descs[desc_idx].block_size){
                break;
            }
        }
        /*若mem_block_desc的free_list中已经没有可用的mem_block,就创建新的arena提供mem_block*/
        if(list_empty(&descs[desc_idx].free_list)){
            a = malloc_page(PF,1);
            if(a == NULL){
                lock_release(&mem_pool->lock);
                return NULL;
            }
            memset(a,0,PG_SIZE);

            /*对于分配的小块内存,将desc置为相应内存块描述符,cnt置为此arena可用的内存块数,large置为false*/
            a->desc = &descs[desc_idx];
            a->large = false;
            a->cnt = &descs[desc_idx].blocks_per_arena;
            uint32_t block_idx;
            enum intr_status old_status = intr_disable();

            /*开始将arena拆分成内存块,并添加到内存块描述符的free_list中*/
            for(block_idx = 0;block_idx < descs[desc_idx].blocks_per_arena;block_idx++){
                b = arena2block(a,block_idx);
                ASSERT(!elem_find(&a->desc->free_list,&b->free_elem));
                list_append(&a->desc->free_list,&b->free_elem);
            }
            intr_set_status(old_status);
        }
        /*开始分配内存块*/
        b = elem2entry(struct mem_block,free_elem,list_pop(&(descs[desc_idx].free_list)));  //结构体mem_block的起始地址的指针类型
        memset(b,0,descs[desc_idx].block_size);
        a = block2arena(b);
        a->cnt--;
        lock_release(&mem_pool->lock);
        return (void*)b;
    }
}

/*将物理地址pg_phy_addr回收到物理内存池*/
void pfree(uint32_t pg_phy_addr){
    struct pool* mem_pool;
    uint32_t bit_idx = 0;
    if(pg_phy_addr >= user_pool.phy_addr_start){    //用户物理内存池
        mem_pool = &user_pool;
        bit_idx = (pg_phy_addr - user_pool.phy_addr_start) / PG_SIZE;
    }else{      //内核物理内存池
        mem_pool = &kernel_pool;
        bit_idx = (pg_phy_addr - kernel_pool.phy_addr_start) / PG_SIZE;
    }
    bitmap_set(&mem_pool->pool_bitmap,bit_idx,0);   //将位图中的该位清零
}

/*去掉页表中虚拟地址vaddr的映射,只去掉vaddr对应的pte*/
static void page_table_pte_remove(uint32_t vaddr){
    uint32_t* pte = pte_ptr(vaddr);
    *pte &= ~PG_P_1;
    asm volatile("invlpg %0" : : "m"(vaddr) : "memory");    //更新TLB,快表
}

/*在虚拟地址池中释放以vaddr起始的连续pg_cnt个虚拟页地址*/
static void vaddr_remove(enum pool_flags pf,void* _vaddr,uint32_t pg_cnt){
    uint32_t bit_idx_start = 0,vaddr = (uint32_t)_vaddr,cnt = 0;
    if(pf == PF_KERNEL){
        bit_idx_start = (vaddr - kernel_vaddr.vaddr_start) / PG_SIZE;
        while(cnt < pg_cnt){
            bitmap_set(&kernel_vaddr.vaddr_bitmap,bit_idx_start + cnt++,0);
        }
    }else{
        struct task_struct* cur_thread = running_thread();
        bit_idx_start = (vaddr - cur_thread->userprog_vaddr.vaddr_start) / PG_SIZE;
        while(cnt < pg_cnt){
            bitmap_set(&cur_thread->userprog_vaddr.vaddr_bitmap,bit_idx_start + cnt++,0);
        }
    }
}

/*释放以虚拟地址vaddr为起始的cnt个物理页框*/
void mfree_page(enum pool_flags pf,void* _vaddr,uint32_t pg_cnt){
    uint32_t pg_phy_addr;   //要释放的物理页地址
    uint32_t vaddr = (int32_t)_vaddr,page_cnt = 0;
    ASSERT(pg_cnt >= 1 && vaddr % PG_SIZE == 0);
    pg_phy_addr = addr_v2p(vaddr);  //获取虚拟地址vaddr对应的物理地址
    /*确保待释放的物理内存在低端1MB+1KB的页目录+1KB的页表地址范围外*/
    ASSERT((pg_phy_addr % PG_SIZE) == 0 && pg_phy_addr >= 0x102000);
    /*判断pg_phy_addr属于用户物理内存池还是内核物理内存池*/
    if(pg_phy_addr >= user_pool.phy_addr_start){    //位于用户内存池
        vaddr -= PG_SIZE;
        while(page_cnt < pg_cnt){
            vaddr += PG_SIZE;
            pg_phy_addr = addr_v2p(vaddr);
            /*确保物理地址属于用户物理内存池*/
            ASSERT((pg_phy_addr % PG_SIZE) == 0 && pg_phy_addr >= user_pool.phy_addr_start);
            /*先将对应的物理地址页框归还到内存池*/
            pfree(pg_phy_addr);
            /*再从页表中清除此虚拟地址所在的页表项pte*/
            page_table_pte_remove(vaddr);
            page_cnt++;
        }
        /*清空虚拟地址的位图中的相应位*/
        vaddr_remove(pf,_vaddr,pg_cnt);
    }else{      //位于虚拟内存池
        vaddr -= PG_SIZE;
        while(page_cnt < pg_cnt){
            vaddr += PG_SIZE;
            pg_phy_addr = addr_v2p(vaddr);
            /*确保待释放的物理内存只属于内核物理内存池*/
            ASSERT((pg_phy_addr % PG_SIZE) == 0 && pg_phy_addr >= kernel_pool.phy_addr_start && pg_phy_addr < user_pool.phy_addr_start);
            /*先将对应的物理页框归还到内存池*/
            pfree(pg_phy_addr);
            /*再从页表中清除此虚拟地址所在的页表项pte*/
            page_table_pte_remove(vaddr);
            page_cnt++;
        }
        /*清空虚拟地址的位图中的相应位*/
        vaddr_remove(pf,_vaddr,pg_cnt);
    }
}

/*回收内存ptr*/
void sys_free(void* ptr){
    ASSERT(ptr != NULL);
    if(ptr != NULL){
        enum pool_flags PF;
        struct pool* mem_pool;
        /*判断是进程还是线程*/
        if(running_thread()->pgdir == NULL){    //线程
            ASSERT((uint32_t)ptr >= K_HEAP_START);
            PF = PF_KERNEL;
            mem_pool = &kernel_pool;
        }else{  //进程
            PF = PF_USER;
            mem_pool = &user_pool;
        }

        lock_acquire(&mem_pool->lock);
        struct mem_block* b = ptr;
        struct arena* a = block2arena(b);   //获取内存块b所在的arena指针
        //把mem_block转换成arena,获取元信息

        ASSERT(a->large == 0 || a->large == 1);
        if(a->desc == NULL && a->large == true){    //大于1024的内存
            mfree_page(PF,a,a->cnt);
        }else{
            /*先将内存块回收到free_list*/
            list_append(&a->desc->free_list,&b->free_elem);
            /*再判断此arena中的内存块是否都空闲,如果是就释放arena*/
            if(++a->cnt == a->desc->blocks_per_arena){
                uint32_t block_idx;
                for(block_idx = 0;block_idx < a->desc->blocks_per_arena;block_idx++){
                    struct mem_block* b = arena2block(a,block_idx);
                    ASSERT(elem_find(&a->desc->free_list,&b->free_elem));
                    list_remove(&b->free_elem);
                }
                mfree_page(PF,a,1);     //释放以a起始的一个物理页
            }
        }
        lock_release(&mem_pool->lock);
    }
}
  • “/home/lily/OS/boot/kernel/memory.h”
#ifndef __KERNEL_MEMORY_H
#define __KERNEL_MEMORY_H
#include "stdint.h"
#include "bitmap.h"
#include "../lib/kernel/list.h"
//虚拟地址池,用于虚拟地址管理
struct virtual_addr
{
    struct bitmap vaddr_bitmap; //虚拟地址用到的位图结构
    uint32_t vaddr_start;       //虚拟地址起始地址
};

enum pool_flags{
    PF_KERNEL = 1,  //内核内存池
    PF_USER = 2     //用户内存池
};

#define PG_P_1 1    //页表项或页目录项存在属性位:此页在内存中存在
#define PG_P_0 0    //页表项或页目录项存在属性位:此页在内存中不存在
#define PG_RW_R 0   //R/W属性位值,读/写操作:此页运行读、写、执行
#define PG_RW_W 2   //R/W属性位值,读/写操作:此页允许读、执行
#define PG_US_S 0   //U/S属性位值,系统级:只允许特权级0、1、2的程序访问
#define PG_US_U 4   //U/S属性位值,用户级:允许所有特权级的进程访问

extern struct pool kernel_pool, user_pool;

/*内存块*/
struct mem_block{
    struct list_elem free_elem;
};

/*内存块描述符*/
struct mem_block_desc{
    uint32_t block_size;        //内存块大小
    uint32_t blocks_per_arena;  //本arena中可容纳此mem_block的数量
    struct list free_list;      //目前可用的mem_block链表,长度无限长,可以由多个arena提供内存块
};

#define DESC_CNT 7              //内存块描述符个数:16,32,64,128,256,512,1024字节,总共7种规格的内存块大小

/*得到虚拟地址vaddr对应的pte指针*/
uint32_t* pte_ptr(uint32_t vaddr);

/*得到虚拟地址vaddr对应的pde指针*/
uint32_t* pde_ptr(uint32_t vaddr);

/*分配pg_cnt个页空间,成功则返回起始虚拟地址,失败则返回NULL*/
void* malloc_page(enum pool_flags pf,uint32_t pg_cnt);

/*将地址vaddr与pf池中的物理地址关联,仅支持一页空间分配;申请一页内存,并用vaddr映射到该页(可以指定虚拟地址)*/
void* get_a_page(enum pool_flags pf,uint32_t vaddr);

/*在内核物理池中申请一页内存,成功则返回其虚拟地址,失败返回NULL*/
void* get_kernel_pages(uint32_t pg_cnt);

/*在用户空间中申请4K内存,并返回虚拟地址*/
void* get_user_pages(uint32_t pg_cnt);

/*得到虚拟地址映射到的物理地址*/
uint32_t addr_v2p(uint32_t vaddr);

/*为malloc做准备*/
void block_desc_init(struct mem_block_desc* desc_array);
/*内存管理部分初始化入口*/
void mem_init(void);

/*在堆中申请size字节内存*/
void* sys_malloc(uint32_t size);

/*将物理地址pg_phy_addr回收到物理内存池*/
void pfree(uint32_t pg_phy_addr);

/*释放以虚拟地址vaddr为起始的cnt个物理页框*/
void mfree_page(enum pool_flags pf,void* _vaddr,uint32_t pg_cnt);

/*回收内存ptr*/
void sys_free(void* ptr);
#endif
  • “/home/lily/OS/boot/lib/user/syscall.h”
#ifndef __LIB_USER_SYSCALL_H
#define __LIB_USER_SYSCALL_H
#include "stdint.h"

enum SYSCALL_NR{    //用来存放系统调用子功能号
    SYS_GETPID,
    SYS_WRITE,
    SYS_MALLOC,
    SYS_FREE
};

//函数声明
uint32_t getpid(void);
uint32_t write(char* str);
void* malloc(uint32_t size);
void free(void* ptr);

#endif
  • “/home/lily/OS/boot/lib/user/syscall.c”
#include "syscall.h"

/*无参数的系统调用*/    //大体意思:将NUMBER的内容输入到eax寄存器中
#define _syscall0(NUMBER)({ \
    int retval; \
    asm volatile("int $0x80":"=a"(retval):"a"(NUMBER):"memory");    \
    retval;    \
})  //大括号中最后一个值作为返回值

/*一个参数的系统调用*/
#define _syscall1(NUMBER,ARG1)({  \
    int retval; \
    asm volatile("int $0x80":"=a"(retval):"a"(NUMBER),"b"(ARG1):"memory");  \
    retval; \
})

/*二个参数的系统调用*/
#define _syscall2(NUMBER,ARG1,ARG2)({  \
    int retval; \
    asm volatile("int $0x80":"=a"(retval):"a"(NUMBER),"b"(ARG1),"c"(ARG2):"memory");  \
    retval; \
})

/*三个参数的系统调用*/
#define _syscall3(NUMBER,ARG1,ARG2,ARG3)({  \
    int retval; \
    asm volatile("int $0x80":"=a"(retval):"a"(NUMBER),"b"(ARG1),"c"(ARG2),"d"(ARG3):"memory");  \
    retval; \
})

/*返回当前任务pid*/
uint32_t getpid(){
    return _syscall0(SYS_GETPID);
}

/*打印字符串str*/
uint32_t write(char* str){
    return _syscall1(SYS_WRITE,str);
}

/*申请size字节大小的内存,并返回结果*/
void* malloc(uint32_t size){
    return (void*)_syscall1(SYS_MALLOC,size);
}

/*释放ptr指向的内存*/
void free(void* ptr){
    _syscall1(SYS_FREE,ptr);
}
  • “/home/lily/OS/boot/userprog/syscall_init.c”
#include "syscall_init.h"
#include "thread.h"
#include "../lib/user/syscall.h"
#include "../string.h"
#include "print.h"
#include "console.h"

#define syscall_nr 32
typedef void* syscall;
syscall syscall_table[syscall_nr];

//返回当前任务的pid
uint32_t sys_getpid(void){
    return running_thread()->pid;
}

/*打印字符串str(未实现文件系统前的版本)*/
uint32_t sys_write(char* str){
    console_put_str(str);
    return strlen(str);
}
//初始化系统调用
void syscall_init(void){
    put_str("syscall_init start\n");
    syscall_table[SYS_GETPID] = sys_getpid;
    syscall_table[SYS_WRITE] = sys_write;
    syscall_table[SYS_MALLOC] = sys_malloc;
    syscall_table[SYS_FREE] = sys_free;
    put_str("syscall_init done\n");
}
  • “/home/lily/OS/boot/kernel/main.c”
#include "print.h"
#include "init.h"
#include "thread.h"
#include "interrupt.h"
#include "console.h"
#include "keyboard.h"
#include "ioqueue.h"
#include "process.h"
#include "syscall.h"
#include "syscall_init.h"
#include "../lib/stdio.h"
#include "memory.h"

void k_thread_a(void*);
void k_thread_b(void*);
void u_prog_a(void);
void u_prog_b(void);

int prog_a_pid = 0,prog_b_pid = 0;

int main(void){
	put_str("I am kernel\n");
	init_all();
	intr_enable();	//打开中断,使时钟中断起作用

	process_execute(u_prog_a,"user_prog_a");
	process_execute(u_prog_b,"user_prog_b");
	thread_start("k_thread_a",31,k_thread_a,"I am thread_a");
	thread_start("k_thread_b",31,k_thread_b,"I am thread_b");
	while(1);
	return 0;
}

/*在线程中运行函数*/
void k_thread_a(void* arg){
	void* addr1 = sys_malloc(256);
	void* addr2 = sys_malloc(255);
	void* addr3 = sys_malloc(254);
	console_put_str(" thread_a malloc addr:0x");
	console_put_int((int)addr1);
	console_put_char(',');
	console_put_int((int)addr2);
	console_put_char(',');
	console_put_int((int)addr3);
	console_put_char('\n');

	int cpu_delay = 100000;
	while(cpu_delay--);
	sys_free(addr1);
	sys_free(addr2);
	sys_free(addr3);
	while(1);
}

/*在线程中运行函数*/
void k_thread_b(void* arg){
	void* addr1 = sys_malloc(256);
	void* addr2 = sys_malloc(255);
	void* addr3 = sys_malloc(254);
	console_put_str(" thread_b malloc addr:0x");
	console_put_int((int)addr1);
	console_put_char(',');
	console_put_int((int)addr2);
	console_put_char(',');
	console_put_int((int)addr3);
	console_put_char('\n');

	int cpu_delay = 100000;
	while(cpu_delay--);
	sys_free(addr1);
	sys_free(addr2);
	sys_free(addr3);
	while(1);
}

/*测试用户进程*/
void u_prog_a(void){
	void* addr1 = malloc(256);
	void* addr2 = malloc(255);
	void* addr3 = malloc(254);
	printf(" prog_a malloc addr:0x%x,0x%x,0x%x\n",(int)addr1,(int)addr2,(int)addr3);
	int cpu_delay = 100000;
	while(cpu_delay--);
	free(addr1);
	free(addr2);
	free(addr3);
	while(1);
}

/*测试用户进程*/
void u_prog_b(void){
	void* addr1 = malloc(256);
	void* addr2 = malloc(255);
	void* addr3 = malloc(254);
	printf(" prog_b malloc addr:0x%x,0x%x,0x%x\n",(int)addr1,(int)addr2,(int)addr3);
	int cpu_delay = 100000;
	while(cpu_delay--);
	free(addr1);
	free(addr2);
	free(addr3);
	while(1);
}

在这里插入图片描述
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值