实验 19. 用户进程

news2024/12/28 21:03:16

已完成实验

已完成实验链接

简介

实验 19. 用户进程

总结

    main函数创造了1个主线程,两个内核线程,两个用户进程。
    共5个PCB,进程PCB->pgdir为进程自己的页表首虚拟地址,线程为null。
    因此本次实验是五个进/线程在来回切换。他们切换的方式无疑都是利用自己PCB->kstack保存切换前esp,然后不断pop完成cs:eip的跳转。

主要代码

引导

省略

内核

main.c

 // 文件: main.c
 // 时间: 2024-07-19
 // 来自: ccj
 // 描述: 内核从此处开始

 #include "print.h"
 #include "init.h"
 #include "thread.h"
 #include "interrupt.h"
 #include "console.h"
+#include "process.h"
+
+// 两个内核线程
+void k_thread_a(void*);
+void k_thread_b(void*);
+// 两个用户进程
+void u_prog_a(void);
+void u_prog_b(void);
+int test_var_a = 0, test_var_b = 0;

-void k_thread(void*);

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

     init_all();

+    thread_start("k_thread_a", 31, k_thread_a, "argA ");
+    thread_start("k_thread_b", 31, k_thread_b, "argB ");
+    process_execute(u_prog_a, "user_prog_a");
+    process_execute(u_prog_b, "user_prog_b");
+
     intr_enable();  // 打开中断,使时钟中断起作用
     while (1) {};
     return 0;
 }

-void k_thread(void* arg) {
-    char* para = arg;
-    while (1) { console_put_str(para); }
+
+// 内核线程函数
+void k_thread_a(void* arg) {
+    while (1) {
+        console_put_str("v_a:0x");
+        console_put_int(test_var_a);
+        console_put_str("\n");
+    }
+}
+void k_thread_b(void* arg) {
+    while (1) {
+        console_put_str("v_b:0x");
+        console_put_int(test_var_b);
+        console_put_str("\n");
+    }
+}
+
+// 测试用户进程
+void u_prog_a(void) {
+    while (1) { test_var_a++; }
+}
+void u_prog_b(void) {
+    while (1) { test_var_b++; }
 }

init.c

 // 文件: init.c
 // 时间: 2024-07-22
 // 来自: ccj
 // 描述: 内核所有初始化操作

 #include "init.h"
 #include "print.h"
 #include "interrupt.h"
 #include "timer.h"
 #include "memory.h"
 #include "thread.h"
 #include "keyboard.h"
+#include "console.h"
+#include "tss.h"

 /// @brief 内核所有初始化
 void init_all() {
     put_str("init all\n");

     idt_init();       // 初始化中断
     timer_init();     // 调快时钟、注册时钟中断来调度线程
     mem_init();       // 初始化内存管理系统
     thread_init();    // 初始化线程
     console_init();   // 控制台初始化最好放在开中断之前
     keyboard_init();  // 键盘初始化
+    tss_init();       // tss初始化
 }

tss.c

// 文件: tss.c
// 时间: 2024-07-31
// 来自: ccj
// 描述: 注册tss、用户代码段、用户数据段描述符,加载tss

#include "tss.h"
#include "stdint.h"
#include "global.h"
#include "string.h"
#include "print.h"

/// @brief 任务状态段tss结构
struct tss {
    uint32_t backlink;
    uint32_t* esp0;
    uint32_t ss0;
    uint32_t* esp1;
    uint32_t ss1;
    uint32_t* esp2;
    uint32_t ss2;
    uint32_t cr3;
    uint32_t (*eip)(void);
    uint32_t eflags;
    uint32_t eax;
    uint32_t ecx;
    uint32_t edx;
    uint32_t ebx;
    uint32_t esp;
    uint32_t ebp;
    uint32_t esi;
    uint32_t edi;
    uint32_t es;
    uint32_t cs;
    uint32_t ss;
    uint32_t ds;
    uint32_t fs;
    uint32_t gs;
    uint32_t ldt;
    uint32_t trace;
    uint32_t io_base;
};

// tss
static struct tss tss;

/// @brief 更新tss中esp0字段的值为pthread的0级线
/// @param pthread
void update_tss_esp(struct task_struct* pthread) {
    tss.esp0 = (uint32_t*)((uint32_t)pthread + PG_SIZE);
}

/// @brief 创建gdt描述符
/// @param
static struct gdt_desc make_gdt_desc(uint32_t* desc_addr,
                                     uint32_t limit,
                                     uint8_t attr_low,
                                     uint8_t attr_high) {
    uint32_t desc_base = (uint32_t)desc_addr;
    struct gdt_desc desc;
    desc.limit_low_word = limit & 0x0000ffff;
    desc.base_low_word = desc_base & 0x0000ffff;
    desc.base_mid_byte = ((desc_base & 0x00ff0000) >> 16);
    desc.attr_low_byte = (uint8_t)(attr_low);
    desc.limit_high_attr_high = (((limit & 0x000f0000) >> 16) + (uint8_t)(attr_high));
    desc.base_high_byte = desc_base >> 24;
    return desc;
}

/// @brief 在gdt中创建tss并重新加载gdt
void tss_init() {
    put_str("[tss] tss_init start\n");

    uint32_t tss_size = sizeof(tss);
    memset(&tss, 0, tss_size);
    tss.ss0 = SELECTOR_K_STACK;
    tss.io_base = tss_size;

    // gdt段基址为0x900,把tss放到第4个位置,也就是0x900+0x20的位置

    // 在gdt中添加dpl为0的TSS描述符
    *((struct gdt_desc*)0xc0000920) =
        make_gdt_desc((uint32_t*)&tss, tss_size - 1, TSS_ATTR_LOW, TSS_ATTR_HIGH);

    // 在gdt中添加dpl为3的数据段和代码段描述符
    *((struct gdt_desc*)0xc0000928) =
        make_gdt_desc((uint32_t*)0, 0xfffff, GDT_CODE_ATTR_LOW_DPL3, GDT_ATTR_HIGH);
    *((struct gdt_desc*)0xc0000930) =
        make_gdt_desc((uint32_t*)0, 0xfffff, GDT_DATA_ATTR_LOW_DPL3, GDT_ATTR_HIGH);

    /// 加载gdt 16位的limit 32位的段基址
    uint64_t gdt_operand = ((8 * 7 - 1) | ((uint64_t)(uint32_t)0xc0000900 << 16));  // 7个描述符大小
    asm volatile("lgdt %0" : : "m"(gdt_operand));

    // 加载 tss
    asm volatile("ltr %w0" : : "r"(SELECTOR_TSS));

    put_str("[tss] tss_init and ltr done\n");
}

process.h

#ifndef __USERPROG_PROCESS_H
#define __USERPROG_PROCESS_H

#include "thread.h"
#include "stdint.h"

#define default_prio      31
#define USER_STACK3_VADDR (0xc0000000 - 0x1000)
#define USER_VADDR_START  0x8048000  // 用户虚拟地址起始地址

void process_execute(void* filename, char* name);
void start_process(void* filename_);
void process_activate(struct task_struct* p_thread);
void page_dir_activate(struct task_struct* p_thread);
uint32_t* create_page_dir(void);
void create_user_vaddr_bitmap(struct task_struct* user_prog);

#endif

process.c

// 文件: process.c
// 时间: 2024-07-30
// 来自: ccj
// 描述: 用户进程初始化相关
//          1.
//          进程的创立不仅预留了中断栈,填写了线程栈,而且还申请了两页用作进程虚拟地址位图和页表,虚拟首地址均保存在PCB。
//          2.当主线程时间片耗尽中断,scheduel,switch上半部将自己esp保存于PCB->kstack后,
//              switch下半部esp就指向了用户进程早已设置好的线程栈栈顶,依次弹栈,ret执行start_process(u_prog_b)。
//          3.start_process仍然位于0特权级,该函数设置了用户进程中断栈,其中PCB->cs设置成了指向DPL等于3的代码段描述符,
//              PCB->ds、PCB->ss设置成指向了DPL等于3的数据段,PCB->esp设置成了Linux默认的3特权级栈地址,PCB->eip指向了u_prog_b。
//              最后将esp置为中断栈栈顶,jmp intr_exit。
//          4.执行intr_exit,程序一路pop中断栈内容,最后iretd指令,用户进程切换到了特权级3,成功执行了u_prog_b,也成功访问了全局变量,相当于在3特权级用自己的页表访问了DPL为3的代码段、数据段、栈段。

#include "process.h"
#include "global.h"
#include "debug.h"
#include "memory.h"
#include "thread.h"
#include "list.h"
#include "tss.h"
#include "interrupt.h"
#include "string.h"
#include "console.h"

extern void intr_exit(void);

/// @brief 构建用户pcb
/// @param filename_ 用户要执行的函数
void start_process(void* filename_) {
    void* function = filename_;

    struct task_struct* cur = running_thread();  // 获取当前线程

    cur->self_kstack += sizeof(struct thread_stack);  // 现在self_kstack指向中断栈
    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;  // 用户态用不上,直接初始为0
    proc_stack->ds = proc_stack->es = proc_stack->fs = SELECTOR_U_DATA;  // 3特权数据段
    proc_stack->eip = function;        // 待执行的用户程序地址
    proc_stack->cs = SELECTOR_U_CODE;  // 3特权代码段
    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);
    proc_stack->ss = SELECTOR_U_DATA;

    // esp置为中断栈栈顶,jmp intr_exit
    asm volatile("movl %0, %%esp; jmp intr_exit" : : "g"(proc_stack) : "memory");
}

/// @brief 击活页表,把页目录物理地址给到cr3
/// 页目录物理地址:内核线程固定0x100000s,用户进程则是pcb的pgdir
/// @param p_thread pcb
void page_dir_activate(struct task_struct* p_thread) {
    // 若为内核线程,需要重新填充页表为0x100000
    uint32_t pagedir_phy_addr = 0x100000;
    if (p_thread->pgdir != NULL) {  // 用户态进程有自己的页目录表
        pagedir_phy_addr = addr_v2p((uint32_t)p_thread->pgdir);
    }

    /* 更新页目录寄存器cr3,使新页表生效 */
    asm volatile("movl %0, %%cr3" : : "r"(pagedir_phy_addr) : "memory");
}

/// @brief 击活页表,更新进程的esp0
/// @param p_thread 线程或进程pcb
void process_activate(struct task_struct* p_thread) {
    ASSERT(p_thread != NULL);
    // 击活该进程或线程的页表
    page_dir_activate(p_thread);

    // 内核线程特权级本身就是0,处理器进入中断时并不会从tss中获取0特权级栈地址,故不需要更新esp0
    if (p_thread->pgdir) {
        // 更新该进程的esp0,用于此进程被中断时保留上下文
        update_tss_esp(p_thread);
    }
}


/// @brief 创建页目录表
/// @return 成功则返回页目录的虚拟地址,否则返回-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;
    }

    // 把内核的[768-1023]这256个页目录项复制到用户的[768-1023]页目录项,也就是1M空间的内核
    memcpy((uint32_t*)((uint32_t)page_dir_vaddr + 0x300 * 4), (uint32_t*)(0xfffff000 + 0x300 * 4),
           1024);

    // 用户的第1023个页目录项内容为自己
    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;  // 写入第1023个页目录
    return page_dir_vaddr;
}

/// @brief 创建用户进程虚拟地址位图
/// @param user_prog 用户pcb
void create_user_vaddr_bitmap(struct task_struct* user_prog) {
    // 虚拟内存池起始地址
    user_prog->userprog_vaddr.vaddr_start = USER_VADDR_START;

    // 计算位图的起始地址和要申请多少页存放位图
    // 0xc0000000 - USER_VADDR_START = 用户可以使用的全部字节
    // 0xc0000000 - USER_VADDR_START / PG_SIZE = (位图1bit代表1个页)
    // 0xc0000000 - USER_VADDR_START / PG_SIZE / 8 = 位图的字节长度
    uint32_t user_bit_map_bytes_len = (0xc0000000 - USER_VADDR_START) / PG_SIZE / 8;
    // 要申请多少个页来存放位图
    uint32_t bitmap_pg_cnt = DIV_ROUND_UP(user_bit_map_bytes_len, PG_SIZE);

    // 虚拟内存池位图初始化,起始虚拟地址,位图的字节长度
    user_prog->userprog_vaddr.vaddr_bitmap.bits = get_kernel_pages(bitmap_pg_cnt);
    user_prog->userprog_vaddr.vaddr_bitmap.btmp_bytes_len = user_bit_map_bytes_len;
    bitmap_init(&user_prog->userprog_vaddr.vaddr_bitmap);
}

/// @brief 创建用户进程
/// @param filename 进程函数指针
/// @param name 进程名
void process_execute(void* filename, char* name) {
    // 在内核池中申请一页存放pcb
    struct task_struct* thread = get_kernel_pages(1);
    init_thread(thread, name, default_prio);  // 初始化pcb

    create_user_vaddr_bitmap(thread);  // 申请并初始化pcb的位图
    // 初始化线程栈,start_process是待执行的函数, 函数参数是进程要执行的函数
    thread_create(thread, start_process, filename);
    thread->pgdir = create_page_dir();  // 创建用户页目录

    // 把当前进程加入就绪队列,所有队列
    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);
}

interrupt.c

 // 文件: interrupt.c
 // 时间: 2024-07-22
 // 来自: ccj
 // 描述: 中断
 //          硬件初始化
 //          初始化中断门描述符表,绑定中断门描述符到kernel.s的中断服务函数
 //          创建一个中断函数处理数组,让kernel.s的中断服务函数调用
 //          开启或关闭中断机制

 #include "interrupt.h"

 #include "stdint.h"
 #include "global.h"
 #include "io.h"
 #include "print.h"

 // 可编程中断控制器是8259A
 #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 IDT_DESC_CNT 0x30  // 目前总共支持的中断数
+#define IDT_DESC_CNT 0x81  // 目前总共支持的中断数

 #define EFLAGS_IF             0x00000200  // eflags寄存器中的if位为1
 #define GET_EFLAGS(EFLAG_VAR) asm volatile("pushfl; popl %0" : "=g"(EFLAG_VAR))

 /// @brief 中断门描述符结构体
 struct gate_desc {
     uint16_t func_offset_low_word;   // offset15-0
     uint16_t selector;               // 段选择子
     uint8_t dcount;                  // 忽略
     uint8_t attribute;               // P+EDL+S+TYPE
     uint16_t func_offset_high_word;  // pffset31-16
 };

 // 中断描述符表,每个表项叫做一个门描述符(gate descriptor) 门的含义是当中断发生时必须先通过这些门
 static struct gate_desc idt[IDT_DESC_CNT];
 // 声明引用定义在kernel.S中的中断处理函数入口数组
 extern intr_handler intr_entry_table[IDT_DESC_CNT];
 // 用于保存中断的名字
 char* intr_name[IDT_DESC_CNT];
 // 中断处理函数 kernel.S中的中断处理函数入口里调用的这里的函数
 intr_handler idt_table[IDT_DESC_CNT];

 /// @brief 可编程中断控制器8259A
 /// @param
 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, 0xfe);
+    outb(PIC_S_DATA, 0xff);

     // 测试键盘,只打开键盘中断,其它全部关闭
-    outb(PIC_M_DATA, 0xfd);
-    outb(PIC_S_DATA, 0xff);
+    // outb(PIC_M_DATA, 0xfd);
+    // outb(PIC_S_DATA, 0xff);

     put_str("[int] init 8259A done\n");
 }

 /// @brief 把中断服务程序地址绑定到中断门描述符
 /// @param p_gdesc
 /// @param attr 属性
 /// @param function 中断服务程序
 static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, intr_handler function) {
     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;
 }

 /// @brief 把kernel.s的中断服务程序地址写入到中断描述符中
 /// @param
 static void idt_desc_init(void) {
     int i;
     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 */

     put_str("[int] init int_desc_table done\n");
 }

 /// @brief 默认中断处理函数
 /// @param vec_nr
 static void general_intr_handler(uint8_t vec_nr) {
     // 0x2f是从片8259A上的最后一个irq引脚,保留
     // IRQ7和IRQ15会产生伪中断(spurious interrupt),无须处理。
     if (vec_nr == 0x27 || vec_nr == 0x2f) return;

     // 清空4行
     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(84);
     put_str(intr_name[vec_nr]);
     if (vec_nr == 14) {
         int page_fault_vaddr = 0;
         asm("movl %%cr2, %0" : "=r"(page_fault_vaddr));
         put_str("\npage fault addr is ");
         put_int(page_fault_vaddr);
     }
     put_str("\n---excetion message end---\n");

     // 阻塞
     while (1) {}
 }

 /// @brief 给每一个中断一个中断名、默认的处理函数
 /// @param
 static void exception_init(void) {
     // 先初始化每一个中断
     int i;
     for (i = 0; i < IDT_DESC_CNT; i++) {
         idt_table[i] = general_intr_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] 第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";
 }

 /// @brief 开启中断机制
 /// @return 调用之前的状态
 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");  // 开中断,sti指令将IF位置1
         return old_status;
     }
 }

 /// @brief 关闭中断机制
 /// @return 调用之前的状态
 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;
     }
 }

 /// @brief 获取中断机制是否开启
 enum intr_status intr_get_status() {
     uint32_t eflags = 0;
     GET_EFLAGS(eflags);
     return (EFLAGS_IF & eflags) ? INTR_ON : INTR_OFF;
 }

 /// @brief 开始或者关闭中断机制
 enum intr_status intr_set_status(enum intr_status status) {
     return status & INTR_ON ? intr_enable() : intr_disable();
 }

 /// @brief 在中断处理程序数组第vector_no个元素中注册安装中断处理程序function
 /// @param vector_no 中断号
 /// @param function 中断处理程序
 void register_handler(uint8_t vector_no, intr_handler function) { idt_table[vector_no] = function; }

 /// @brief 完成有关中断的所有初始化工作
 void idt_init() {
     put_str("[int] init start\n");

     idt_desc_init();   // 初始化中断描述符表
     exception_init();  // 定义每一个中断的中断名和中断处理函数
     pic_init();        // 初始化8259A

     // 加载中断描述符表 Load Interrupt Descriptor Table
     // lidt 2字节中断描述符偏移限制 4字节中断描述表的基地址
     // (sizeof(idt) - 1) 为 263
     uint64_t idt_operand = ((sizeof(idt) - 1) | ((uint64_t)(uint32_t)idt << 16));
     asm volatile("lidt %0" : : "m"(idt_operand));

     put_str("[int] init done\n");
 }

thread.h

 // 文件: thread.h
 // 时间: 2024-07-29
 // 来自: ccj
 // 描述: 定义线程pcb、线程栈、线程中断栈数据结构
 
 #ifndef __THREAD_THREAD_H
 #define __THREAD_THREAD_H
 
 #include "stdint.h"
 #include "list.h"
+#include "memory.h"
 
 typedef void thread_func(void*);
 
 /// @brief 进程或线程的状态
 enum task_status {
     TASK_RUNNING,  //
     TASK_READY,
     TASK_BLOCKED,
     TASK_WAITING,
     TASK_HANGING,
     TASK_DIED
 };
 
 /// @brief 中断栈,中断发生时,保护程序(线程或进程)的上下文环境
 /// 进程或线程被外部中断或软中断打断时,会按照此结构压入上下文寄存器
 /// kernel.s中intr_exit的出栈操作是此结构的逆操作
 /// 中断栈在pcb的最顶端
 struct intr_stack {
     // kernel.s 宏VECTOR中push %1压入的中断号
     uint32_t vec_no;
 
     // kernel.s 中pushad压入的寄存器
     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;
 
     // kernel.s 中保存上下文环境压入的寄存器
     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;
 };
 
 /// @brief 线程栈 保存线程的上下文
 /// ABI规则:主调函数调用被调函数,被调函数一定要保存ebp、ebx、edi、esi、esp
 /// eip:线程下一步
 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时使用
     void(*unused_retaddr);  // 占位置充数为返回地址
     thread_func* function;  // 由Kernel_thread所调用的函数名
     void* func_arg;         // 由Kernel_thread所调用的函数所需的参数
 };
 
 /// @brief 进程或线程的pcb process control block 4096字节
 /// 一个pcb包含1个中断栈,1个线程栈,
 struct task_struct {
     uint32_t* self_kstack;    // pcb中线程栈的地址
     enum task_status status;  // 状态
     char name[16];            //
     uint8_t priority;         // 线程优先级
 
     uint8_t ticks;           // 每次在处理器上执行的时间嘀嗒数
     uint32_t elapsed_ticks;  // 处理器上执行的时间嘀嗒总数
 
     struct list_elem general_tag;   // 放入就绪队列
     struct list_elem all_list_tag;  // 放入全部队列
 
-    uint32_t* pgdir;       // pcb的虚拟地址
+    // 用户进程使用 内核线程不使用
+    uint32_t* pgdir;                     // 指向用户的页目录项
+    struct virtual_addr userprog_vaddr;  // 用户进程的虚拟地址池
+
     uint32_t stack_magic;  // pcb魔数,用于检测栈的溢出
 };
 
+
+extern struct list thread_ready_list;  // 引用pcb就绪队列
+extern struct list thread_all_list;    // 引用pcb所有队列
+
+
 void thread_create(struct task_struct* pthread, thread_func function, void* func_arg);
 void init_thread(struct task_struct* pthread, char* name, int prio);
 struct task_struct* thread_start(char* name, int prio, thread_func function, void* func_arg);
 
 struct task_struct* running_thread(void);
 void schedule(void);
 void thread_init(void);
 void thread_block(enum task_status stat);
 void thread_unblock(struct task_struct* pthread);
 
 #endif

thread.c

 // 文件: thread.c
 // 时间: 2024-07-23
 // 来自: ccj
 // 描述: 线程相关定义
 //          1.添加线程,申请pcb,加入就绪队列
 //          2.线程调度,切换pcb
 //          3.线程阻塞,设置状态非就绪,不加入就绪队列,启动调度
 //          4.线程唤醒,设置状态就绪,加入就绪队列
 
 #include "thread.h"
 
 #include "stdint.h"
 #include "string.h"
 #include "global.h"
 #include "memory.h"
 #include "interrupt.h"
 #include "debug.h"
 #include "print.h"
-
-#define PG_SIZE 4096
+#include "process.h"
 
 struct task_struct* main_thread;      // 主线程PCB
 struct list thread_ready_list;        // 就绪队列
 struct list thread_all_list;          // 所有任务队列
 static struct list_elem* thread_tag;  // 用于保存队列中的线程结点
 
 // 引用声明.s的switch_to
 extern void switch_to(struct task_struct* cur, struct task_struct* next);
 
 /// @brief 获取当前线程pcb指针
 /// @return
 struct task_struct* running_thread() {
     // 获取esp指针
     uint32_t esp;
     asm("mov %%esp, %0" : "=g"(esp));
 
     // esp % 4096就是pcb起始地址
     return (struct task_struct*)(esp & 0xfffff000);
 }
 
 /// @brief 由kernel_thread去执行function(func_arg)
 /// @param function 函数指针
 /// @param func_arg 函数参数
 static void kernel_thread(thread_func* function, void* func_arg) {
     // 执行function前要开中断,避免后面的时钟中断被屏蔽,而无法调度其它线程
     intr_enable();
     function(func_arg);
 }
 
 /// @brief 初始化线程栈,将待执行的函数和参数放到pcb中相应的位置
 /// @param pthread pcb
 /// @param function 待执行的函数
 /// @param func_arg 函数参数
 void thread_create(struct task_struct* pthread, thread_func function, void* func_arg) {
     // 先预留中断使用栈的空间,可见thread.h中定义的结构
     pthread->self_kstack -= sizeof(struct intr_stack);
 
     // 再留出线程栈空间
     pthread->self_kstack -= sizeof(struct thread_stack);
     // 此时的self_kstack看作线程栈的首地址
     struct thread_stack* kthread_stack = (struct thread_stack*)pthread->self_kstack;
     kthread_stack->eip = kernel_thread;  // 指向kernel_thread
     kthread_stack->function = function;  // 设置函数
     kthread_stack->func_arg = func_arg;  // 设置参数
     kthread_stack->ebp = kthread_stack->ebx = kthread_stack->esi = kthread_stack->edi = 0;
 }
 
 /// @brief 初始化pcb
 /// @param pthread pcb
 /// @param name 线程名
 /// @param prio 优先级
 void init_thread(struct task_struct* pthread, char* name, int prio) {
     // 清0
     memset(pthread, 0, sizeof(*pthread));
 
     // 设置状态
     if (pthread == main_thread) {  // 如果是主线程pcb
         pthread->status = TASK_RUNNING;
     } else {
         pthread->status = TASK_READY;
     }
 
     // 初始化名字、优先级、时钟、总时钟、魔数
     strcpy(pthread->name, name);
     pthread->priority = prio;
     pthread->ticks = prio;
     pthread->elapsed_ticks = 0;
     pthread->pgdir = NULL;
     pthread->stack_magic = 0x19870916;  // 自定义的魔数
 
     // self_kstack是线程自己在内核态下使用的栈顶地址
     pthread->self_kstack = (uint32_t*)((uint32_t)pthread + PG_SIZE);
 }
 
 /// @brief 创建一优先级为prio的线程,线程名为name,线程所执行的函数是function(func_arg)
 /// @param name 线程名
 /// @param prio 优先级
 /// @param function 线程要执行的函数
 /// @param void* 函数参数
 struct task_struct* thread_start(char* name, int prio, thread_func function, void* func_arg) {
     // 申请1个物理页来存放pcb
     struct task_struct* thread = get_kernel_pages(1);
 
     // 初始化pcb
     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);
 
     return thread;
 }
 
 /// @brief 将kernel中的main函数完善为主线程
 /// @param
 static void make_main_thread(void) {
     // 咱们在loader.S中进入内核时的mov
     // esp,0xc009f000,在压入四个4个栈指针,所以主线程的pcb地址为0xc009e000
     // 不需要通过get_kernel_page另分配一页
     main_thread = running_thread();
     init_thread(main_thread, "main", 31);
 
     // 主线程不加入就绪队列,只用加入全部队列
     ASSERT(!elem_find(&thread_all_list, &main_thread->all_list_tag));
     list_append(&thread_all_list, &main_thread->all_list_tag);
 }
 
 /// @brief 实现任务调度
 void schedule() {
     ASSERT(intr_get_status() == INTR_OFF);
 
     struct task_struct* cur = running_thread();
 
     // 若此线程只是cpu时间片到了,将其加入到就绪队列尾
     if (cur->status == TASK_RUNNING) {
         ASSERT(!elem_find(&thread_ready_list, &cur->general_tag));
         list_append(&thread_ready_list, &cur->general_tag);
 
         cur->ticks = cur->priority;  // 重新将当前线程的ticks再重置为其priority;
         cur->status = TASK_READY;
     } else {
         /* 若此线程需要某事件发生后才能继续上cpu运行,
         不需要将其加入队列,因为当前线程不在就绪队列中。*/
     }
 
     // 就绪队列队首弹出,准别切换到这个线程
     ASSERT(!list_empty(&thread_ready_list));
     thread_tag = NULL;
     thread_tag = list_pop(&thread_ready_list);
 
     // 拿到队首的pcb
     struct task_struct* next = elem2entry(struct task_struct, general_tag, thread_tag);
     next->status = TASK_RUNNING;
 
+    // 击活任务页表等
+    process_activate(next);
     // 切换
     switch_to(cur, next);
 }
 
 /// @brief 当前线程将自己阻塞,标志其状态为stat
 /// @param 状态
 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;  // 置其状态为stat
     schedule();                 // 将当前线程换下处理器
 
     /* 待当前线程被解除阻塞后才继续运行下面的intr_set_status */
     intr_set_status(old_status);
 }
 
 /// @brief 将线程pthread解除阻塞
 /// @param pthread pcb
 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);
 }
 
 /// @brief 初始化线程环境
 /// @param
 void thread_init(void) {
     put_str("[thread] thread_init start\n");
 
     list_init(&thread_ready_list);
     list_init(&thread_all_list);
 
     // 将当前main函数创建为线程
     make_main_thread();
     put_str("[thread] thread_init done\n");
 }
\ No newline at end of file

memory.h

 #ifndef __KERNEL_MEMORY_H
 #define __KERNEL_MEMORY_H
 
 #include "stdint.h"
 #include "bitmap.h"
 
 /// @brief 内存池标记,用于判断用哪个内存池
 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 属性位值, 系统级
 #define PG_US_U 4  // U/S 属性位值, 用户级
 
 /// @brief 虚拟地址管理
 struct virtual_addr {
     struct bitmap
         vaddr_bitmap;  // 位图,每一个位代表4096个字节,每一个位表示虚拟起始地址的4096字节的使用或空闲
     uint32_t vaddr_start;  // 虚拟起始地址
 };
 
 extern struct pool kernel_pool, user_pool;
 
 void mem_init(void);
 void* get_kernel_pages(uint32_t pg_cnt);
 void* malloc_page(enum pool_flags pf, uint32_t pg_cnt);
 void malloc_init(void);
 uint32_t* pte_ptr(uint32_t vaddr);
 uint32_t* pde_ptr(uint32_t vaddr);
+uint32_t addr_v2p(uint32_t vaddr);
+void* get_a_page(enum pool_flags pf, uint32_t vaddr);
+void* get_user_pages(uint32_t pg_cnt);
 #endif

memory.c

 // 文件: memory.c
 // 时间: 2024-07-23
 // 来自: ccj
-// 描述: 
+// 描述:
 //      存放了3个内存池,内核池、用户池、内核虚拟地址池
 //      内核池和用户池来管理真实的物理内存
 //      虚拟内存池的虚拟地址用来绑定内核池的地址
 
 #include "memory.h"
 
 #include "bitmap.h"
 #include "stdint.h"
 #include "global.h"
 #include "debug.h"
 #include "print.h"
 #include "string.h"
+#include "sync.h"
 
-#define PG_SIZE 4096
 
 // 0xc009f000 内核主线程栈顶
 // 0xc009e000 内核主线程的pcb
 // 0xc009a000 位图起始地址
 // 0xc009e000-0xc009a000=16384=4*4096 = 4个物理页
 // 4*4096个字节= 4*4096*8个比特,1个比特代表1个页,最大表示内存 4*4096*8*4096(512M)的空间
 #define MEM_BITMAP_BASE 0xc009a000
 
 #define PDE_IDX(addr) ((addr & 0xffc00000) >> 22)  // 页目录索引
 #define PTE_IDX(addr) ((addr & 0x003ff000) >> 12)  // 页表索引
 
 // 0xc0000000是内核从虚拟地址3G起. 0x100000意指跨过低端1M内存,使虚拟地址在逻辑上连续
 #define K_HEAP_START 0xc0100000
 
 /// @brief 物理内存池结构
 struct pool {
     struct bitmap pool_bitmap;  // 内存池用到的位图结构,用于管理物理内存
     uint32_t phy_addr_start;    // 内存池所管理物理内存的起始地址
     uint32_t pool_size;         // 内存池字节容量
+    struct lock lock;           // 申请内存时互斥
 };
 struct pool kernel_pool, user_pool;  // 生成内核内存池和用户内存池
 
+
 // 内核虚拟内存池
 struct virtual_addr kernel_vaddr;
 
-/// @brief 在虚拟池中申请pg_cnt个虚拟页
+
+/// @brief 在pool_flags所指定的空间的虚拟池中申请pg_cnt个虚拟页
 /// @param  pool_flags 哪1个池
 /// @param pg_cnt 虚拟页数量
 /// @return 返回虚拟页的起始地址, 失败则返回NULL
 static void* vaddr_get(enum pool_flags pf, uint32_t pg_cnt) {
     int vaddr_start = 0, bit_idx_start = -1;
     if (pf == PF_KERNEL) {
         bit_idx_start = bitmap_scan(&kernel_vaddr.vaddr_bitmap, pg_cnt);  // 在虚拟内核位图中申请
         if (bit_idx_start == -1) return NULL;
 
         // 申请成功,把申请到的位设置1
         uint32_t cnt = 0;
         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 {
-        // 用户内存池,将来实现用户进程再补充
+    } 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; }
+
+        // 设置这些比特位为1
+        uint32_t cnt = 0;
+        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;
 }
 
 /// @brief 得到虚拟地址vaddr对应的pde的指针 *pde=vaddr的页目录项内容(包含页表地址)
 /// @param vaddr 虚拟地址
 /// @return 页目录项地址
 uint32_t* pde_ptr(uint32_t vaddr) {
     uint32_t* pde = (uint32_t*)((0xfffff000) + PDE_IDX(vaddr) * 4);
     // 现在的pte
     // 页目录索引=高10位=全1
     // 页表索引=中10位=全1
     // 物理页偏移=低12位=vaddr页目录索引*4
 
     // 分页转换之后 pde指向vaddr的页目录项
     // 1.页目录索引=全1, 是第1023个页目录项,该页目录项的内容是页目录起始位置
     // 2.页表索引=全1,是第1023个页目录项,该页目录项的内容是页目录起始位置
     // 3.物理页偏移=低12位=vaddr页目录索引*4,该页目录项的内容包含是vaddr的页表地址
     return pde;
 }
 
 /// @brief 得到虚拟地址vaddr对应的pte指针 *pte=vaddr的页表项(包含物理页地址)
 /// @param vaddr 虚拟地址
 /// @return 页表项地址
 uint32_t* pte_ptr(uint32_t vaddr) {
     uint32_t* pte = (uint32_t*)(0xffc00000 + ((vaddr & 0xffc00000) >> 10) + PTE_IDX(vaddr) * 4);
     // 现在的pte
     // 页目录索引=高10位=全1
     // 页表索引=中10位=vaddr的高10位=vaddr的页目录索引
     // 物理页偏移=低12位=vaddr的页表索引*4
 
     // 转换
     // 1.页目录索引=全1, 是第1023个页目录项,其内容是页目录起始位置
     // 2.页表索引=vaddr的页目录索引,该页目录项内容是vaddr的页表地址
     // 3.物理页偏移=低12位=vaddr的页表索引*4,该页表项的内容是vaddr所绑定的物理地址
     return pte;
 }
 
 /// @brief 在m_pool指向的物理内存池中分配1个物理页
 /// @param m_pool
 /// @return  成功则返回页框的物理地址,失败则返回NULL
 static void* palloc(struct pool* m_pool) {
     // 找一个物理页面
     int bit_idx = bitmap_scan(&m_pool->pool_bitmap, 1);
     if (bit_idx == -1) { return NULL; }
 
     // 将这个物理页的位图的比特设置为1
     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;
 }
 
 /// @brief 绑定虚拟地址_vaddr与物理地址_page_phyaddr
 /// @param _vaddr 虚拟地址
 /// @param _page_phyaddr 物理地址
 static void page_table_add(void* _vaddr, void* _page_phyaddr) {
     uint32_t vaddr = (uint32_t)_vaddr, page_phyaddr = (uint32_t)_page_phyaddr;
     uint32_t* pde = pde_ptr(vaddr);
     uint32_t* pte = pte_ptr(vaddr);
 
     if (*pde & 0x00000001) {  // 页目录项P=1表示已存在
         ASSERT(!(*pte & 0x00000001));
         if (!(*pte & 0x00000001)) {                              // 页表项P=1表示已存在
             *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);  // US=1,RW=1,P=1
         } else {  // 应该不会执行到这,因为上面的ASSERT会先执行。
             PANIC("pte repeat");
             *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);  // US=1,RW=1,P=1
         }
     } else {  // 页目录项P=1表示不存在
         // 在内核空间申请一个物理页来放页表
         uint32_t pde_phyaddr = (uint32_t)palloc(&kernel_pool);
         *pde = (pde_phyaddr | PG_US_U | PG_RW_W | PG_P_1);  // 绑定页目录到页表
 
         // 把刚才申请的页表清0 (int)pte & 0xfffff000)为页表的虚拟地址
         memset((void*)((int)pte & 0xfffff000), 0, PG_SIZE);
 
         // 绑定页表项到物理地址
         ASSERT(!(*pte & 0x00000001));
         *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);  // US=1,RW=1,P=1
     }
 }
 
-/// @brief 分配pg_cnt个页空间,
+/// @brief 在pool_flags指定的空间,在虚拟池中创建连续的pg_cnt个页,创建pg_cnt个页物理空间并绑定
 /// @param  pool_flags 区别内核空间还是用户空间
 /// @param pg_cnt 连续的页数量
 /// @return 成功则返回起始虚拟地址,失败时返回NULL
 void* malloc_page(enum pool_flags pf, uint32_t pg_cnt) {
     ASSERT(pg_cnt > 0 && pg_cnt < 3840);
 
     // 申请连续的虚机空间
     void* vaddr_start = vaddr_get(pf, pg_cnt);
     if (vaddr_start == NULL) { return NULL; }
 
+    // 把连续的页绑定到不一定连续的物理页
     uint32_t vaddr = (uint32_t)vaddr_start, cnt = pg_cnt;
     struct pool* mem_pool = pf & PF_KERNEL ? &kernel_pool : &user_pool;  // 判断是哪个空间
     // 申请的物理地址不用连续,所以逐个做映射就可以
     while (cnt-- > 0) {
         void* page_phyaddr = palloc(mem_pool);  // 申请一个物理页
         if (page_phyaddr == NULL) {
             // 重要!!!
             // 失败时要将曾经已申请的虚拟地址和物理页全部回滚,在将来完成内存回收时再补充
             return NULL;
         }
 
         // 绑定虚拟地址和物理地址
         page_table_add((void*)vaddr, page_phyaddr);
         vaddr += PG_SIZE;  // 虚拟地址+4096
     }
     return vaddr_start;
 }
 
 /// @brief 从内核物理内存池中申请pg_cnt页内存,
 /// @param pg_cnt 页数量
 /// @return 成功则返回其虚拟地址,失败则返回NULL
 void* get_kernel_pages(uint32_t pg_cnt) {
-    void* vaddr = malloc_page(PF_KERNEL, pg_cnt);
+    lock_acquire(&kernel_pool.lock);
 
-    // 申请成功,将页框清0后返回
+    // 分配空间,清0
+    void* vaddr = malloc_page(PF_KERNEL, pg_cnt);
     if (vaddr != NULL) { memset(vaddr, 0, pg_cnt * PG_SIZE); }
+
+    lock_release(&kernel_pool.lock);
+    return vaddr;
+}
+/// @brief 在用户空间中申请pg_cnt页内存
+/// @param pg_cnt
+/// @return 成功则返回其虚拟地址,失败则返回NULL
+void* get_user_pages(uint32_t pg_cnt) {
+    lock_acquire(&user_pool.lock);
+
+    // 分配空间,清0
+    void* vaddr = malloc_page(PF_USER, pg_cnt);
+    memset(vaddr, 0, pg_cnt * PG_SIZE);
+
+    lock_release(&user_pool.lock);
     return vaddr;
 }
 
+/// @brief 在pf指定的池中,申请一个物理页,并把虚拟地址vaddr所在页绑定到物理页
+/// @param pool_flags 区别内核空间还是用户空间
+/// @param 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;
+    // 用户进程的虚拟起始地址是pcb的userprog_vaddr里的地址
+    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.vaddr_start
+        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 kernelspace 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;
+}
+
+/// @brief 得到虚拟地址映射到的物理地址
+/// @param vaddr 虚拟地址
+/// @return
+uint32_t addr_v2p(uint32_t vaddr) {
+    uint32_t* pte = pte_ptr(vaddr);
+    return ((*pte & 0xfffff000) + (vaddr & 0x00000fff));
+}
+
 /// @brief 初始化内存池
 /// @param all_mem 系统的内存大小
 static void mem_pool_init(uint32_t all_mem) {
     put_str("[mem] mem_pool_init start\n");
 
     //---分配物理内核池和用户池的内存 begin---
     // 统计已使用的内存大小
     // 1个页目录表(4096字节) + 第0个页表((4096字节)) + 第[1-254]个页表(254*4096字节)
     uint32_t page_table_size = PG_SIZE * 256;
     uint32_t used_mem = page_table_size + 0x100000;  // 0x100000为低端1M内存
 
     // 把可用内存各分一半给内核空间和用户空间 舍弃小于1个页的空间
     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;
 
     // kernel bitmap length 和 user bitmap length
     uint32_t kbm_length = kernel_free_pages / 8;  // 内核空间位图长度
     uint32_t ubm_length = user_free_pages / 8;    // 用户空间位图长度
 
     // kernel pool start 和 user pool start
     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;
     //---分配物理内核池和用户池的内存 end---
 
     // 内核池位图首地址和用户池位图首地址
     kernel_pool.pool_bitmap.bits = (void*)MEM_BITMAP_BASE;
     user_pool.pool_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length);
 
     //---输出相关信息 begin---
     put_str("[mem] kernel_pool_bitmap_start:0x");
     put_int((int)kernel_pool.pool_bitmap.bits);
     put_str("\n");
 
     put_str("[mem] kernel_pool_phy_addr_start:0x");
     put_int(kernel_pool.phy_addr_start);
     put_str("\n");
 
     put_str("[mem] user_pool_bitmap_start:0x");
     put_int((int)user_pool.pool_bitmap.bits);
     put_str("\n");
 
     put_str("[mem] user_pool_phy_addr_start:0x");
     put_int(user_pool.phy_addr_start);
     put_str("\n");
     //---输出相关信息 begin---
 
     // 将位图清0
     bitmap_init(&kernel_pool.pool_bitmap);
     bitmap_init(&user_pool.pool_bitmap);
 
+    lock_init(&kernel_pool.lock);
+    lock_init(&user_pool.lock);
+
     // 虚拟内存池位图长度=真实物理内存池位图长度
     // 虚拟内存池的位图起始地址是物理用户内存池之后
     // 虚拟内存池的起始地址是K_HEAP_START
     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] mem_pool_init done\n");
 }
 
 /// @brief 内存管理部分初始化入口
 void mem_init() {
     put_str("[mem] mem_init start\n");
 
     uint32_t mem_bytes_total = (*(uint32_t*)(0xb00));
     mem_pool_init(mem_bytes_total);
 
     put_str("[mem] mem_init done\n");
 }
\ No newline at end of file

global.h

// 文件: global.h
// 时间: 2024-07-22
// 来自: ccj
// 描述:

#ifndef __KERNEL_GLOBAL_H
#define __KERNEL_GLOBAL_H

#include "stdint.h"

// ---描述符 begin---
#define DESC_G_4K 1  // 第23位G 段界限颗粒度4K
#define DESC_D_32 1  // 第22位D/B位 指令中的有效地址及操作数是32位
#define DESC_L    0  // 第21位 64位代码标记,此处标记为0便可。
#define DESC_AVL  0  // 第20位 AVL: cpu不用此位,暂置为0
#define DESC_P    1  // 第15位 present 段是否存在于内存 0:不在 / 1:在

// 第14-13位 DPL: Privilege Level 0为操作系统 权力最高 3为用户段
#define DESC_DPL_0 0
#define DESC_DPL_1 1
#define DESC_DPL_2 2
#define DESC_DPL_3 3

// 第12位 S/O: 系统段 / 1 : 数据段
#define DESC_S_CODE 1
#define DESC_S_DATA DESC_S_CODE
#define DESC_S_SYS  0

// 第11-8位 TYPE: 根据 S 位的结果,再次对段类型进行细分
#define DESC_TYPE_CODE 8  // 1000(可执行 不允许可读 已访问位0)
#define DESC_TYPE_DATA 2  // x=0,e=0,w=1,a=0 0010(可写)
#define DESC_TYPE_TSS  9  // 1001 TYPE中的B=0
// ---描述符 end---

//---选择子 begin---
#define SELECTOR_K_CODE  ((1 << 3) + (TI_GDT << 2) + RPL0)  // 内核
#define SELECTOR_K_DATA  ((2 << 3) + (TI_GDT << 2) + RPL0)
#define SELECTOR_K_STACK SELECTOR_K_DATA
#define SELECTOR_K_GS    ((3 << 3) + (TI_GDT << 2) + RPL0)  // 显存
#define SELECTOR_TSS     ((4 << 3) + (TI_GDT << 2) + RPL0)  // tss
#define SELECTOR_U_CODE  ((5 << 3) + (TI_GDT << 2) + RPL3)  // 用户
#define SELECTOR_U_DATA  ((6 << 3) + (TI_GDT << 2) + RPL3)
#define SELECTOR_U_STACK SELECTOR_U_DATA

// ; 第2位TI 0表示查找GDT 1表示查找LDT
#define TI_GDT 0
#define TI_LDT 1

// ; 第0-1位 RPL 特权级,以什么样的权限去访问段
#define RPL0 0
#define RPL1 1
#define RPL2 2
#define RPL3 3
//---选择子 end---


//---GDT描述符 begin---
// G+D/B+L+AVL
#define GDT_ATTR_HIGH ((DESC_G_4K << 7) + (DESC_D_32 << 6) + (DESC_L << 5) + (DESC_AVL << 4))

// P+EDL+S+TYPE
#define GDT_CODE_ATTR_LOW_DPL3 \
    ((DESC_P << 7) + (DESC_DPL_3 << 5) + (DESC_S_CODE << 4) + DESC_TYPE_CODE)
#define GDT_DATA_ATTR_LOW_DPL3 \
    ((DESC_P << 7) + (DESC_DPL_3 << 5) + (DESC_S_DATA << 4) + DESC_TYPE_DATA)
//---GDT描述符 end---

//---TSS描述符 begin---
#define TSS_DESC_D 0

// P+EDL+S+TYPE
#define TSS_ATTR_HIGH ((DESC_G_4K << 7) + (TSS_DESC_D << 6) + (DESC_L << 5) + (DESC_AVL << 4) + 0x0)
#define TSS_ATTR_LOW  ((DESC_P << 7) + (DESC_DPL_0 << 5) + (DESC_S_SYS << 4) + DESC_TYPE_TSS)
//---TSS描述符 end---

//---IDT描述符 begin---
// 第15位 P
#define IDT_DESC_P 1

// 第14-13位 DPL
#define IDT_DESC_DPL0 0
#define IDT_DESC_DPL3 3

// 第11-8位 TYPE
#define IDT_DESC_32_TYPE 0xE  // 32位的门
#define IDT_DESC_16_TYPE 0x6  // 16位的门,不用,定义它只为和32位门区分

// P+EDL+S+TYPE
#define IDT_DESC_ATTR_DPL0 ((IDT_DESC_P << 7) + (IDT_DESC_DPL0 << 5) + IDT_DESC_32_TYPE)
#define IDT_DESC_ATTR_DPL3 ((IDT_DESC_P << 7) + (IDT_DESC_DPL3 << 5) + IDT_DESC_32_TYPE)
//---IDT描述符 end---

/// @brief GDT描述符
struct gdt_desc {
    uint16_t limit_low_word;
    uint16_t base_low_word;
    uint8_t base_mid_byte;
    uint8_t attr_low_byte;
    uint8_t limit_high_attr_high;
    uint8_t base_high_byte;
};

//---eflags属性 begin---
/********************************************************
--------------------------------------------------------------
          Intel 8086 Eflags Register
--------------------------------------------------------------
*
*     15|14|13|12|11|10|F|E|D C|B|A|9|8|7|6|5|4|3|2|1|0|
*      |  |  |  |  |  | | |  |  | | | | | | | | | | | '---  CF……Carry Flag
*      |  |  |  |  |  | | |  |  | | | | | | | | | | '---  1 MBS
*      |  |  |  |  |  | | |  |  | | | | | | | | | '---  PF……Parity Flag
*      |  |  |  |  |  | | |  |  | | | | | | | | '---  0
*      |  |  |  |  |  | | |  |  | | | | | | | '---  AF……Auxiliary Flag
*      |  |  |  |  |  | | |  |  | | | | | | '---  0
*      |  |  |  |  |  | | |  |  | | | | | '---  ZF……Zero Flag
*      |  |  |  |  |  | | |  |  | | | | '---  SF……Sign Flag
*      |  |  |  |  |  | | |  |  | | | '---  TF……Trap Flag
*      |  |  |  |  |  | | |  |  | | '---  IF……Interrupt Flag
*      |  |  |  |  |  | | |  |  | '---  DF……Direction Flag
*      |  |  |  |  |  | | |  |  '---  OF……Overflow flag
*      |  |  |  |  |  | | |  '----  IOPL……I/O Privilege Level
*      |  |  |  |  |  | | '-----  NT……Nested Task Flag
*      |  |  |  |  |  | '-----  0
*      |  |  |  |  |  '-----  RF……Resume Flag
*      |  |  |  |  '------  VM……Virtual Mode Flag
*      |  |  |  '-----  AC……Alignment Check
*      |  |  '-----  VIF……Virtual Interrupt Flag
*      |  '-----  VIP……Virtual Interrupt Pending
*      '-----  ID……ID Flag
*
*
**********************************************************/
#define EFLAGS_MBS    (1 << 1)   // 此项必须要设置
#define EFLAGS_IF_1   (1 << 9)   // if为1,开中断
#define EFLAGS_IF_0   0          // if为0,关中断
#define EFLAGS_IOPL_3 (3 << 12)  // IOPL3,用于测试用户程序在非系统调用下进行IO
#define EFLAGS_IOPL_0 (0 << 12)  // IOPL0
//---eflags属性 end---


#define NULL  ((void*)0)
#define bool  int
#define true  1
#define false 0

// 向上取整
// 当 x / y整除时,返回 x / y;
// 当x / y不整除时,返回(x / y) + 1;
#define DIV_ROUND_UP(X, STEP) ((X + STEP - 1) / (STEP))
#define PG_SIZE               4096

#endif

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1965106.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

抖音素材网站有哪些?这几个高质量的抖音无水印素材网站分享

在抖音这个视觉与创意并重的平台上&#xff0c;选择合适的无水印视频素材是提升内容质量的关键。如果你正在寻找既能使你的视频内容更具吸引力&#xff0c;又能避免版权问题的优质素材&#xff0c;那么本文将为你详细介绍几个高质量的抖音无水印素材网站。从国内的蛙学网到国际…

双指针算法大总结!!看完这篇再也没有难的双指针题!!例题C++解法!

题源acwing、力扣 讲解双指针题目一&#xff1a;盛最多水的容器思路AC代码 题目二&#xff1a;查找总价格为目标值的两个商品思路AC代码 题目三&#xff1a;链表的中间节点思路AC代码 题目四&#xff1a;环形链表思路AC代码 题目五&#xff1a;反转字符串中的元音字符思路AC代码…

【Vulnhub靶场AI-WEB-1.0打靶教程】

第一步&#xff1a;查看虚拟机的ip 第二步&#xff1a;扫描ip下开放的80端口 第三步&#xff1a;扫描查到的ip地址下的目录 第四步&#xff1a;访问查到的目录 访问robot.txt 第五步:访问robot.txt显示出的目录 第六步&#xff1a;打开kali终端&#xff0c;使用sqlmap功能 sq…

【Vue3】标签的 ref 属性

【Vue3】标签的 ref 属性 背景简介开发环境开发步骤及源码 背景 随着年龄的增长&#xff0c;很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来&#xff0c;技术出身的人总是很难放下一些执念&#xff0c;遂将这些知识整理成文&#xff0c;以纪念曾经努力学习奋斗的日子。…

win10游戏出现错误代码0xc0000142是什么情况?几种方法修复错误代码0xc0000142

错误代码 "0xc0000142" 通常表示应用程序无法正确启动&#xff0c;这经常与系统文件损坏、兼容性问题或环境设置不当有关。下面列出了一些步骤&#xff0c;可能帮助你解决在Windows 10上运行游戏时遇到的0xc0000142错误。 快速修复错误代码0xc0000142问题 1. 以管理…

更新Win11后出现无法检测到相机的情况

使用腾讯会议的时候发现摄像头用不了&#xff0c;显示“未检测到摄像头”&#xff0c;打开设置显示“未找到任何相机” 试了很多方法都解决不了&#xff0c;后用了下面这种方法成功解决&#xff0c;相机就能用了 1.下载360驱动大师&#xff0c;检测下有没有要更新或者添加的驱…

希尔排序【C语言】

希尔排序 前言 在上一篇文章中我们了解了直接插入排序算法&#xff08;建议先阅读&#xff09;&#xff0c;但其实这个算法还是有一定优化空间的。而它优化之后&#xff0c;就变成了另一个大名鼎鼎的排序算法&#xff1a;希尔排序。 希尔排序(Shell’s Sort)是插入排序的一种…

PDF怎么转换成Word?这些工具一键搞定!

在日常生活中&#xff0c;我们经常遇到需要将PDF文件转换成Word文档的情况。PDF怎么转换成Word&#xff1f;一些工具的使用十分重要&#xff01;下文中就为大家推荐几个亲测好用的PDF转换工具。 一、Foxit PDF转换大师&#xff08;365客户端&#xff09; 链接&#xff1a;www…

【Story】编程迷航:从 “ 我怎么才学会 ? ” 到 “ 我怎么这么厉害 ! ”

目录 大学生编程入门指南&#xff1a;选择语言、制定计划与避坑技巧1. 选择适合的编程语言1.1 Python1.2 Java1.3 C/C1.4 JavaScript1.5 SQL 2. 制定有效的学习计划2.1 设定明确的目标2.2 制定学习时间表2.3 选择学习资源2.4 实践和项目 3. 避免常见学习陷阱3.1 避免过度焦虑3.…

7月31日学习笔记 基于域名,Ip,端口多虚拟主机配置以及上线商城系统

一&#xff0c;基于域名的虚拟主机 效果 劫持域名访问指定的文件夹的目录 步骤 1. vim /usr/local/nginx/conf/nginx.conf 2. 创建新的目录 mkdir /html/devopt/ 3. 在 devops 目录下添加新的 index.html 文件 4. 在 http 模块下添加新的 server 模块 1 . 设置监听的端…

数字的位操作——7、9、479、564、231、342

7. 整数反转(中等) 给你一个 32 位的有符号整数 x &#xff0c;返回将 x 中的数字部分反转后的结果。 如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] &#xff0c;就返回 0。 假设环境不允许存储 64 位整数&#xff08;有符号或无符号&#xff09;。 示例 1&…

钢铁无组织排放的超低改造(朗观视觉)

朗观视觉小编观察发现&#xff1a;随着环保政策的日益严格和公众对环境质量要求的不断提高&#xff0c;钢铁行业作为重工业的代表&#xff0c;面临着巨大的环保压力。无组织排放作为钢铁企业环保治理的难点之一&#xff0c;如何实现超低排放成为行业关注的焦点。本文将从技术路…

MAC安装mysql以及配置环境变量

安装mysql 下载mysql,网址&#xff1a;MySQL :: Download MySQL Community Server 我下载的版本是mysql-9.0.1-macos14-arm64.dmg 打开&#xff0c;双击 一路点击继续安装即可&#xff1b; 最后需要给root设置密码后就安装完成了 但是打开终端输入mysql&#xff0c;依然显…

河南萌新联赛2024第(三)场:河南大学

传送门&#xff1a;河南萌新联赛2024第&#xff08;三&#xff09;场&#xff1a;河南大学_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ B 正则表达式 思路&#xff1a;模拟 代码&#xff1a; #include<bits/stdc.h> using namespace std; typedef long lo…

狗狗换毛期怎么办?家里狗毛遍地,狗毛空气净化器帮你解决

对于养狗家庭来说&#xff0c;换毛季节的到来无疑是一场家中的“毛发风暴”。特别是养如萨摩耶、金毛等大型长毛犬的朋友们&#xff0c;更是深有体会。每日即便精心梳理&#xff0c;家中仍难以避免地被层层狗毛所覆。狗狗时不时晃一下身体&#xff0c;抖动身上的毛发&#xff0…

【list的模拟实现】

list的模拟实现 小杨 list相关类要实现的接口 namespace yang {// List的节点类template<class T>struct ListNode{ListNode(const T& val T());ListNode<T>* _prev;ListNode<T>* _next;T _val;};//List的迭代器类template<class T, class Ref, cla…

土壤分析仪:解锁土壤奥秘,赋能现代农业的绿色引擎

在广袤无垠的大地上&#xff0c;土壤是生命之源&#xff0c;滋养着万物生长。然而&#xff0c;随着现代农业的快速发展和环境的不断变化&#xff0c;土壤的健康状况日益受到关注。如何科学、精准地了解土壤的性质与养分状况&#xff0c;成为现代农业可持续发展的关键。这时&…

ST-LINK未能串口keil识别的一个可能解决方案(前提驱动安装无问题)

打开这个软件&#xff0c;在点击清除之前&#xff0c;按住单片机复位按钮不放&#xff0c;点击清除按钮&#xff0c;等待3-5秒放开复位按钮&#xff0c;即可清除重置&#xff0c;若提示没识别到&#xff0c;多重复几次&#xff0c;即可重置&#xff0c;重置完成之后再回到烧写软…

兴业严选|朝阳优质好房合集 低至6.3折起~

7月25日&#xff0c;存款挂牌利率迎来今年首次下调。中国工商银行、中国农业银行、中国银行、中国建设银行四家大型商业银行从7月25日起&#xff0c;均下调了人民币存款挂牌利率。这是今年以来大型商业银行首次下调人民币存款利率&#xff0c;也是自2022年9月以来的第五次下调。…

不是ChatGPT模型,第一个GAI是ELIZA,你听说过吗?

人工智能&#xff08;Artificial Intelligence, AI&#xff09;的概念可以追溯到20世纪50年代&#xff0c;当时数学家和计算机科学家开始探讨如何让机器模拟人类智能。1956年&#xff0c;达特茅斯会议被认为是人工智能研究的正式起点。然而&#xff0c;生成式人工智能&#xff…