Jelajahi Sumber

needfix: page fault laddr=0x00000000c011d000

simon 10 bulan lalu
induk
melakukan
47fec926d3
16 mengubah file dengan 348 tambahan dan 50 penghapusan
  1. TEMPAT SAMPAH
      doc/8080_eflags.jpg
  2. 17 0
      kernel/debug.h
  3. 14 1
      kernel/global.h
  4. 1 1
      kernel/interrupt.c
  5. 34 25
      kernel/main.c
  6. 81 5
      kernel/memory.c
  7. 3 0
      kernel/memory.h
  8. 3 0
      lib/stdint.h
  9. 10 8
      makefile
  10. 1 1
      test/makefile
  11. 8 4
      thread/thread.c
  12. 8 4
      thread/thread.h
  13. 134 0
      userprog/process.c
  14. 32 0
      userprog/process.h
  15. 1 0
      userprog/readme.md
  16. 1 1
      userprog/tss.c

TEMPAT SAMPAH
doc/8080_eflags.jpg


+ 17 - 0
kernel/debug.h

@@ -22,4 +22,21 @@ void panic_spin(char *filename, int line, const char *func, const char *conditio
     }
 #endif // NDEBUG
 
+#ifdef UNUSED
+#elif defined(__GNUC__)
+#define UNUSED(x) UNUSED_##x __attribute__((unused))
+#elif defined(__clang__)
+#define UNUSED(x) _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wunused-parameter\"") \
+    x _Pragma("clang diagnostic pop")
+#elif defined(__LCLINT__)
+#define UNUSED(x) /*@unused@*/ x
+#elif defined(_MSC_VER)
+#define UNUSED(x) __pragma(warning(push)) __pragma(warning(disable : 4101)) x __pragma(warning(pop)) // warning C4101: unreferenced local variable
+#elif defined(__ICC)
+#define UNUSED(x) x __attribute__((unused))
+#else
+#warning "Platform doesn't support attribute 'unused'"
+#define UNUSED(x) (void)x
+#endif // UNUSED
+
 #endif // __KERNEL_DEBUG_H

+ 14 - 1
kernel/global.h

@@ -66,7 +66,20 @@
 #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)
 
-/* 定义 GDT 中描述符的结构 */
+/*---------------------- EFLAGES 属性 -----------------------------------------*/
+#define EFLAGES_MBS (1 << 1)     // 些项必须设置
+#define EFLAGES_IF_1 (1 << 9)    // if 为 1, 开中断
+#define EFLAGES_IF_0 0           // if 为 0, 关中断
+#define EFLAGES_IOPL_0 (0 << 12) // IOPL0
+#define EFLAGES_IOPL_3 (3 << 12) // IOPL3 用于测试用户程序在非系统调用来进行 IO
+
+#define EFLAGES_NT (0 << 14)  // NTP
+#define EFLAGES_RF (1 << 16)  // 工作状态位,0 表示在内核模式下运行
+#define EFLAGES_VM (1 << 17)  // 虚拟 8086 模式位,0 表示在 32 位模式下运行
+#define EFLAGES_AC (1 << 18)  // 累加器状态位,0 表示在 32 位模式下运行
+#define EFLAGES_VIF (1 << 19) // 中断标志位
+
+/*---------------------- 定义 GDT 中描述符的结构 -----------------------------------------*/
 // ! 段描述符格式
 // !  31~24  23  22  21  20  19~16   15  14~13 12  11~8   7~0
 // !  ----------------------------------------------------------

+ 1 - 1
kernel/interrupt.c

@@ -106,7 +106,7 @@ static void general_intr_handler(uint8_t vec_nr)
     {
         int page_fault_vaddr = 0;
         asm("movl %%cr2, %0" : "=r"(page_fault_vaddr)); // cr2是存放page_fault的线性地址
-        put_str("\npage fault addr is: 0X");            // 输出缺失的地址
+        put_str("\nPage fault addr is: 0X");            // 输出缺失的地址
         put_int(page_fault_vaddr);
     }
     put_str("\n!!!!!!! exception message end !!!!!!!!!!!!!\n");

+ 34 - 25
kernel/main.c

@@ -7,9 +7,14 @@
 #include "../device/console.h"
 #include "../device/ioqueue.h"
 #include "../device/keyboard.h"
+#include "../userprog/process.h"
 
-void thread_a_func(void *);
-void thread_b_func(void *);
+void kernel_thread_a_func(void *);
+void kernel_thread_b_func(void *);
+void user_prog_a(void *);
+void user_prog_b(void *);
+
+int test_var_a = 0, test_var_b = 0;
 
 int main(void)
 {
@@ -23,45 +28,49 @@ int main(void)
     // put_int((uintptr_t)addr);
     // put_str("\n");
 
-    thread_start("consumer_a", 31, thread_a_func, " A_");
-    thread_start("consumer_b", 31, thread_b_func, " B_");
+    thread_start("kernel_thread_a_func", 31, kernel_thread_a_func, "argA ");
+    thread_start("kernel_thread_a_func", 31, kernel_thread_b_func, "argB ");
+    process_execute(user_prog_a, "user_prog_a");
+    // process_execute(user_prog_b, "user_prog_b");
 
     intr_enable(); // 打开中断,使中断起作用
     // 主线程会一直执行, 直到被中断或被其他线程强制结束
     while (1)
         ;
-    // {
-    //     console_put_str("Main ");
-    // }
     return 0;
 }
 
-void thread_a_func(void *arg)
+/*在线程中运行的函数*/
+void kernel_thread_a_func(void *UNUSED(arg))
 {
     // 用 void 来表示通用类型,这样就可以传递任意类型的参数
     while (1)
     {
-        enum intr_status old_status = intr_disable();
-        if (!ioq_empty(&keyboard_buf))
-        {
-            console_put_str(arg);
-            char byte = ioq_getchar(&keyboard_buf);
-            console_put_char(byte);
-        }
-        intr_set_status(old_status);
+        console_put_str("v_a:0x");
+        console_put_int(test_var_a);
+    }
+}
+void kernel_thread_b_func(void *UNUSED(arg))
+{
+    while (1)
+    {
+        console_put_str(" v_b:0x");
+        console_put_int(test_var_b);
+    }
+}
+
+/*测试用户进程*/
+void user_prog_a(void *UNUSED(arg))
+{
+    while (1)
+    {
+        test_var_a++;
     }
 }
-void thread_b_func(void *arg)
+void user_prog_b(void *UNUSED(arg))
 {
     while (1)
     {
-        enum intr_status old_status = intr_disable();
-        if (!ioq_empty(&keyboard_buf))
-        {
-            console_put_str(arg);
-            char byte = ioq_getchar(&keyboard_buf);
-            console_put_char(byte);
-        }
-        intr_set_status(old_status);
+        test_var_b++;
     }
 }

+ 81 - 5
kernel/memory.c

@@ -2,8 +2,10 @@
 #include "debug.h"
 #include "../lib/kernel/print.h"
 #include "../lib/string.h"
+#include "../lib/stdint.h"
+#include "../thread/sync.h"
+#include "../thread/thread.h"
 
-#define PG_SIZE 4096 // 1 页 = 4KB = 4096 字节
 /************************ 位图地址 ****************************************
  * 0xc009f000 是内核主线程栈顶,0xc009e000 是内核主线程的pcb
  * 一个页框大小的位图可表示128MB内存,位图位置安排在地址0xc009a000
@@ -29,6 +31,7 @@ struct pool
     struct bitmap pool_bitmap; // 本内存池用到的位图结构,用于管理物理内存
     uint32_t phy_addr_start;   // 本内存池所管理物理内存的起始地址
     uint32_t pool_size;        // 本内存池字节容量
+    struct lock lock;          // 申请内存时互斥
 };
 
 struct pool kernel_pool, user_pool; // 生成内核内存池和用户内存池
@@ -38,6 +41,8 @@ struct virtual_addr kernel_vaddr;   // 此结构是用来给内核分配虚拟
 static void mem_pool_init(uint32_t all_mem)
 {
     put_str("    mem_pool_init start\n");
+    lock_init(&kernel_pool.lock);
+    lock_init(&user_pool.lock);
     // 页表大小 = 页目录项数 * 每个页目录项占用的字节数
     // 页目录项数 = 1 页的页目录表 + 第 0 和第 768(内核页目录项)个页目录项指向同一个页表 +
     // 第 769 ~ 1022 个页目录项指向 254 个页表 = 256 个页目录项
@@ -123,7 +128,7 @@ static void *vaddr_get(enum pool_flags pf, uint32_t pg_cnt)
     int bit_idx_start = -1;
     uint32_t cnt = 0;
     if (PF_KERNEL == pf)
-    {
+    { // 内核内存池
         bit_idx_start = bitmap_scan(&kernel_vaddr.vaddr_bitmap, pg_cnt);
         if (-1 == bit_idx_start)
         {
@@ -136,9 +141,21 @@ static void *vaddr_get(enum pool_flags pf, uint32_t pg_cnt)
         vaddr_start = kernel_vaddr.vaddr_start + bit_idx_start * PG_SIZE;
     }
     else
-    {
-        // 用户内存池
-        // TODO
+    { // 用户内存池 -- 需要使用当前线程来分配
+        struct task_struct *cur = running_thread();
+        bit_idx_start = bitmap_scan(&cur->userprog_addr.vaddr_bitmap, pg_cnt);
+        if (bit_idx_start == -1)
+        {
+            return NULL;
+        }
+        while (cnt < pg_cnt)
+        {
+            bitmap_set(&cur->userprog_addr.vaddr_bitmap, bit_idx_start + cnt++, 1);
+        }
+        vaddr_start = cur->userprog_addr.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;
 }
@@ -277,4 +294,63 @@ void *get_kernel_pages(uint32_t pg_cnt)
         _memset(vaddr, 0, pg_cnt * PG_SIZE);
     }
     return vaddr;
+}
+
+// 申请用户内存,成功则返回其虚拟地址,失败则返回 NULL
+void *get_user_pages(uint32_t pg_cnt)
+{
+    lock_acquire(&user_pool.lock);
+    void *vaddr = malloc_page(PF_USER, pg_cnt);
+    if (NULL != vaddr)
+    {
+        // 若分配的地址不为空,则将页内存清 0 后返回
+        _memset(vaddr, 0, pg_cnt * PG_SIZE);
+    }
+    lock_release(&user_pool.lock);
+    return vaddr;
+}
+
+// 将地址 vaddr 与 pf 池中的物理地址关联,仅支持一页空间分配
+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_addr.vaddr_start) / PG_SIZE;
+        ASSERT(bit_idx > 0);
+        bitmap_set(&cur->userprog_addr.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 kernelspace by get_a_page");
+    }
+
+    /* 然后申请物理页 */
+    void *page_phyaddr = palloc(mem_pool);
+    if (page_phyaddr == NULL)
+        return NULL;
+    page_table_add((void *)(uintptr_t)vaddr, page_phyaddr);
+
+    lock_release(&mem_pool->lock);
+    return (void *)(uintptr_t)vaddr;
+}
+
+// 得到虚拟地址映射到的物理地址
+uint32_t addr_vaddr2phy(uint32_t vaddr)
+{
+    uint32_t *pte = pte_ptr(vaddr);
+    /* (*pte)是值是页表所在的物理页框地址,去掉其低 12 位的页表项属性+虚拟地址 vaddr 的低 12 位 */
+    return ((*pte & 0xfffff000) + (vaddr & 0xfff));
 }

+ 3 - 0
kernel/memory.h

@@ -42,5 +42,8 @@ uint32_t *pte_ptr(uint32_t vaddr);
 uint32_t *pde_ptr(uint32_t vaddr);
 void *malloc_page(enum pool_flags pf, uint32_t pg_cnt); // 申请 pg_cnt 个虚拟页
 void *get_kernel_pages(uint32_t pg_cnt);                // 申请内核内存
+void *get_user_pages(uint32_t pg_cnt);                  // 在用户空间申请内存
+void *get_a_page(enum pool_flags pf, uint32_t vaddr);   // 将地址 vaddr 与 pf 池中的物理地址关联,仅支持一页空间分配
+uint32_t addr_vaddr2phy(uint32_t vaddr);                // 得到虚拟地址映射到的物理地址
 
 #endif // __KERNEL_MEMORY_H

+ 3 - 0
lib/stdint.h

@@ -2,9 +2,12 @@
 #define __LIB_STDINT_H
 
 #define NULL ((void *)0)
+#define DIV_ROUND_UP(X, SETUP) ((X + SETUP - 1) / (SETUP))
 #define true 1
 #define false 0
 
+#define PG_SIZE 4096 // 1 页 = 4KB = 4096 字节
+
 typedef signed char int8_t;
 typedef signed short int16_t;
 typedef signed int int32_t;

+ 10 - 8
makefile

@@ -3,8 +3,8 @@ BUILD_BIN_DIR = $(BUILD_O_DIR)/bin
 ENTRY_POINT = 0xc0001500
 DIS_IMG = ./hd30M.img
 AS = nasm
-CC = i386-elf-gcc
-LD = i386-elf-ld
+CC = /usr/local/i386elfgcc/bin/i386-elf-gcc
+LD = /usr/local/i386elfgcc/bin/i386-elf-ld
 LIB = -I lib/ -I lib/kernel -I lib/user/ -I kernel/ -I device/ -I thread/
 BOOTLIB = -I boot/include/
 ASFLAGS = -f elf
@@ -15,7 +15,8 @@ OBJS = $(BUILD_O_DIR)/main.o $(BUILD_O_DIR)/init.o $(BUILD_O_DIR)/interrupt.o  \
 	$(BUILD_O_DIR)/debug.o $(BUILD_O_DIR)/string.o $(BUILD_O_DIR)/bitmap.o  \
 	$(BUILD_O_DIR)/memory.o $(BUILD_O_DIR)/thread.o $(BUILD_O_DIR)/list.o  \
 	$(BUILD_O_DIR)/switch.o $(BUILD_O_DIR)/sync.o $(BUILD_O_DIR)/console.o \
-	$(BUILD_O_DIR)/keyboard.o $(BUILD_O_DIR)/ioqueue.o $(BUILD_O_DIR)/tss.o
+	$(BUILD_O_DIR)/keyboard.o $(BUILD_O_DIR)/ioqueue.o $(BUILD_O_DIR)/tss.o \
+	$(BUILD_O_DIR)/process.o
 
 ################################ C 代码编译 ################################
 $(BUILD_O_DIR)/main.o: kernel/main.c lib/kernel/print.h lib/stdint.h kernel/init.h
@@ -63,6 +64,9 @@ $(BUILD_O_DIR)/ioqueue.o: device/ioqueue.c device/ioqueue.h
 $(BUILD_O_DIR)/tss.o: userprog/tss.c userprog/tss.h
 	$(CC) $(CFLAGS) $< -o $@
 
+$(BUILD_O_DIR)/process.o: userprog/process.c userprog/process.h
+	$(CC) $(CFLAGS) $< -o $@
+
 ################################ 汇编代码编译 ################################
 $(BUILD_O_DIR)/kernel.o: kernel/kernel.S
 	$(AS) $(ASFLAGS) $< -o $@
@@ -98,7 +102,7 @@ create_img:
 hd: mbr loader K create_img
 	dd if=$(BUILD_BIN_DIR)/mbr.bin of=$(DIS_IMG) bs=512 count=1 conv=notrunc
 	dd if=$(BUILD_BIN_DIR)/loader.bin of=$(DIS_IMG) bs=512 count=4 seek=2 conv=notrunc
-	dd if=$(BUILD_BIN_DIR)/kernel.bin of=$(DIS_IMG) bs=512 count=200 seek=9 conv=notrunc
+	dd if=$(BUILD_BIN_DIR)/kernel.bin of=$(DIS_IMG) bs=512 count=2000 seek=9 conv=notrunc
 
 clean: mk_dir
 	cd $(BUILD_O_DIR) && rm -rf ./*
@@ -115,7 +119,5 @@ loader: mk_dir $(BUILD_BIN_DIR)/loader.bin
 run: hd
 	bochs -f bochsrc -q
 
-build: mk_dir mbr loader K mk_dir
-	@echo "编译完成"
-
-all: mk_dir create_img mbr loader K hd
+all: mk_dir mbr loader K
+	@echo "编译完成"

+ 1 - 1
test/makefile

@@ -1,5 +1,5 @@
 CC = gcc
-CFLAGS = -Wall -g
+CFLAGS = -g -Wall -Wextra -Werror -std=c17 -lpthread
 
 # test2.o: test2.c
 # 	$(CC) $(CFLAGS) -c test2.c -o test2.o

+ 8 - 4
thread/thread.c

@@ -1,10 +1,10 @@
 #include "thread.h"
 #include "../lib/string.h"
-#include "../kernel/memory.h"
 #include "../kernel/interrupt.h"
 #include "../kernel/debug.h"
 #include "../lib/kernel/list.h"
 #include "../lib/kernel/print.h"
+#include "../userprog/process.h"
 
 struct task_struct *main_thread;     // 主线程 PCB
 struct list thread_ready_list;       // 就绪队列
@@ -31,7 +31,7 @@ static void kernel_thread(thread_func *function, void *func_arg)
 }
 
 // 初始化线程栈 thread_stack
-void thread_stack_create(struct task_struct *pthread, thread_func function, void *func_arg)
+void thread_create(struct task_struct *pthread, thread_func function, void *func_arg)
 {
     // 先预留中断栈空间
     pthread->self_kstack -= sizeof(struct intr_stack);
@@ -66,7 +66,8 @@ void init_thread(struct task_struct *pthread, char *name, int prio)
     pthread->ticks = prio;                                             // 嘀嗒数
     pthread->elapsed_ticks = 0;                                        // 线程已执行的时间嘀嗒数
     pthread->pgdir = NULL;                                             // 进程自己页表的虚拟地址, 如果是线程则为 NULL
-    pthread->stack_magic = 0x19940625;                                 // 自定义的魔数
+    // pthread->userprog_addr = NULL;
+    pthread->stack_magic = 0x19940625; // 自定义的魔数
 }
 
 // 创建优先级为 prio 的线程, 线程名为 name, 线程所执行的函数是 function(func_arg)
@@ -75,7 +76,7 @@ struct task_struct *thread_start(char *name, int prio, thread_func function, voi
     // pcb 线程控制块
     struct task_struct *thread = get_kernel_pages(1); // 申请一页内存(内核空间)做为PCB
     init_thread(thread, name, prio);                  // 初始化线程基本信息
-    thread_stack_create(thread, function, func_arg);  // 初始化线程栈 thread_stack
+    thread_create(thread, function, func_arg);        // 初始化线程栈 thread_stack
 
     ASSERT(!elem_find(&thread_ready_list, &thread->general_tag)); // 保证加入就绪队列的线程不在队列中
     list_append(&thread_ready_list, &thread->general_tag);        // 加入就绪队列
@@ -128,6 +129,9 @@ void schedule(void)
     // 方法 2: PCB 在自然页的起始地址, 所以 pcb 地址=0xfffff000&(&(PCB.general_tag))
 
     next->status = TASK_RUNNING;
+    // 激活任务页表等
+    process_activate(next);
+
     switch_to(cur, next); // 切换线程
 }
 

+ 8 - 4
thread/thread.h

@@ -2,8 +2,8 @@
 #define __THREAD_THREAD_H
 #include "../lib/stdint.h"
 #include "../lib/kernel/list.h"
+#include "../kernel/memory.h"
 
-#define PG_SIZE 4096
 // 通用函数类型,它将在很多线程函数中作为参数类型
 typedef void thread_func(void *);
 
@@ -92,14 +92,18 @@ struct task_struct
     struct list_elem general_tag;  // 用于线程在一般队列中的结点
     struct list_elem all_list_tag; // 用于线程队列 thread_all_list 中的结点
 
-    uint32_t *pgdir;      // 进程自己页表的虚拟地址, 如果是线程则为 NULL
-    uint32_t stack_magic; // 用这串数字做栈的边界标记,用于检测栈的溢出
+    uint32_t *pgdir;                   // 进程自己页表的虚拟地址, 如果是线程则为 NULL
+    struct virtual_addr userprog_addr; // 用户进程的虚拟地址
+    uint32_t stack_magic;              // 用这串数字做栈的边界标记,用于检测栈的溢出
 };
 
+extern struct list thread_ready_list; // 就绪队列
+extern struct list thread_all_list;   // 所有任务队列
+
 // 获取当前线程的 PCB 指针
 struct task_struct *running_thread(void);
 // 初始化线程栈 thread_stack
-void thread_stack_create(struct task_struct *pthread, thread_func function, void *func_arg);
+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)

+ 134 - 0
userprog/process.c

@@ -0,0 +1,134 @@
+#include "process.h"
+#include "tss.h"
+#include "../kernel/debug.h"
+#include "../device/console.h"
+#include "../kernel/memory.h"
+#include "../kernel/interrupt.h"
+
+/// @brief 内核中断函数 intr_exit
+/// 实现见 kernel/kernel.S
+/// @param None
+extern void intr_exit(void);
+
+/// @brief 创建用户进程初始化上下文信息
+/// @param filename_ 进程文件名
+void start_process(void *filename_)
+{
+    void *function = filename_;
+    struct task_struct *cur = running_thread();
+
+    // 指向中断栈(intr_stack的低端,即低地址处),PCB布局回顾(从上到下表示内存地址从高到低)
+    // 1. intr_stack
+    // 2. thread_stack
+    // PCB的属性self_kstack在线程创建完毕后指向thread_stack的最底部
+    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 = proc_stack->gs = 0;
+
+    // 为通过中断返回的方式进入 3 特权级做准备
+    proc_stack->ds = proc_stack->es = proc_stack->fs = SELECTOR_U_DATA;
+    proc_stack->eip = function; // 待执行的用户程序地址
+    proc_stack->cs = SELECTOR_U_CODE;
+    proc_stack->eflags = (EFLAGES_IOPL_0 | EFLAGES_MBS | EFLAGES_IF_1);
+    proc_stack->esp = (void *)(get_a_page(PF_USER, USER_STACK3_VADDR) + PG_SIZE);
+    proc_stack->ss = SELECTOR_U_DATA;
+    asm volatile("movl %0, %%esp; jmp intr_exit" ::"g"(proc_stack) : "memory");
+}
+/// @brief 激活页表, 页表切换
+/// @param p_thread
+void page_dir_activate(struct task_struct *p_thread)
+{
+    /*****************************************************
+     * 执行此函数时,当前任务可能是线程。
+     * 之所以对线程也要重新安装页表,原因是上一次被调度的可能是进程,
+     * 否则不恢复页表的话,线程就会使用进程的页表
+     ******************************************************/
+
+    /* 内核页表的物理地址,定义在boot.inc,进入保护模式时确定 */
+    uint32_t pagedir_pyh_addr = 0x100000;
+    if (p_thread->pgdir != NULL)
+    { // 用户态进程有自己的页目录表
+        pagedir_pyh_addr = addr_vaddr2phy((uintptr_t)p_thread->pgdir);
+    }
+    /* 更新页目录寄存器 cr3,使新页表生效 */
+    asm volatile("movl %0, %%cr3" ::"r"(pagedir_pyh_addr) : "memory");
+}
+/// @brief 激活线程或进程的页表,更新 tss 中的 esp0 为进程的特权级 0 的栈
+/// @param p_thread
+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)
+    {
+        update_tss_esp(p_thread); // 更新 tss 中 esp0 为线程的特权级 0 的栈
+    }
+}
+
+/// @brief 创建页目录表,将当前页表的表示内核空间的 pde 复制
+/// @param
+/// @return 成功返回页目录的虚拟地址,否则返回 -1
+uint32_t *create_page_dir(void)
+{
+    //! 用户进程占据页目录表 0~767 目录项
+    //! 内核占据页目录表 768~1023 目录项, 即 0xc0000000 以上的地址空间
+    /* 用户进程的页表不能让用户直接访问到,所以在内核空间来申请 */
+    uint32_t *page_dir_vaddr = get_kernel_pages(1);
+    if (page_dir_vaddr == NULL)
+    {
+        console_put_str("create_page_dir failed: get_kernel_pages failed!");
+        return NULL;
+    }
+    /* 1. 先复制页表 ,将内核所在的页目录项复制到用户进程页目录表中同等位置 */
+    // 用户进程页目录的第 768~1023 个页目录项 = 内核页目录表的 768~1023 页目录项
+    // (uint32_t *)(page_dir_vaddr + 0x768*4) 是进程页目录表基地址 768 个页目录项的地方
+    // 用户进程的创建是在内核中完成的,因此当前是在内核的页表中,其中 0xfffff000 便是用来访问内核页目录表基地址(也是第 0 个页目录项)
+    _memcpy((uint32_t *)(page_dir_vaddr + 768 * 4), (uint32_t *)(0xfffff000 + 768 * 4), 1024); // 1024/4=256 个页目录项大小
+
+    /* 2. 更新页目录地址,把最后一个页目录项更新为用户进程自己的页目录表的物理地址 */
+    uint32_t new_page_dir_phy_addr = addr_vaddr2phy((uintptr_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;
+}
+
+/// @brief 创建用户进程虚拟虚拟内存池
+/// @param user_prog pcb of user
+void create_user_vaddr_bitmap(struct task_struct *user_process)
+{
+    user_process->userprog_addr.vaddr_start = USER_VADDR_START;
+    // 0xc0000000是内核虚拟地址起始处
+    uint32_t bitmap_page_count = DIV_ROUND_UP((0xc0000000 - USER_VADDR_START) / PG_SIZE / 8, PG_SIZE);
+
+    user_process->userprog_addr.vaddr_bitmap.bits = get_kernel_pages(bitmap_page_count);
+    user_process->userprog_addr.vaddr_bitmap.btmp_bytes_len = (0xc0000000 - USER_VADDR_START) / PG_SIZE / 8;
+
+    bitmap_init(&user_process->userprog_addr.vaddr_bitmap);
+}
+
+/// @brief 创建用户进程
+/// @param filename 用户进程地址
+/// @param name 进程名
+void process_execute(void *filename, char *name)
+{
+    /*pcb 内核的数据结构,由内核来维护进程信息, 因此要在内核内存池中申请*/
+    struct task_struct *pcb = get_kernel_pages(1);
+    init_thread(pcb, name, default_prio);
+    create_user_vaddr_bitmap(pcb);
+    thread_create(pcb, start_process, filename);
+    pcb->pgdir = create_page_dir();
+
+    enum intr_status old_status = intr_disable();
+
+    ASSERT(!elem_find(&thread_ready_list, &pcb->general_tag))
+    list_append(&thread_ready_list, &pcb->general_tag);
+
+    ASSERT(!elem_find(&thread_all_list, &pcb->all_list_tag));
+    list_append(&thread_all_list, &pcb->all_list_tag);
+
+    intr_set_status(old_status);
+}

+ 32 - 0
userprog/process.h

@@ -0,0 +1,32 @@
+#ifndef __USERPROG_PROCESS_H
+#define __USERPROG_PROCESS_H
+#include "../kernel/global.h"
+#include "../thread/thread.h"
+#include "../lib/string.h"
+
+#define default_prio 31
+#define USER_STACK3_VADDR (0xc0000000 - 0x1000)
+#define USER_VADDR_START 0x8048000
+
+/// @brief 创建用户进程初始化上下文信息
+/// @param filename_ 进程文件名
+void start_process(void *filename_);
+/// @brief 激活页表
+/// @param p_thread
+void page_dir_activate(struct task_struct *p_thread);
+/// @brief 激活线程或进程的页表,更新 tss 中的 esp0 为进程的特权级 0 的栈
+/// @param p_thread
+void process_activate(struct task_struct *p_thread);
+/// @brief 创建页目录表,将当前页表的表示内核空间的 pde 复制
+/// @param
+/// @return 成功返回页目录的虚拟地址,否则返回 -1
+uint32_t *create_page_dir(void);
+/// @brief 创建用户进程虚拟虚拟内存池
+/// @param user_prog pcb of user
+void create_user_vaddr_bitmap(struct task_struct *user_prog);
+/// @brief 创建用户进程
+/// @param filename 用户进程地址
+/// @param name 进程名
+void process_execute(void *filename, char *name);
+
+#endif // __USERPROG_PROCESS_H

+ 1 - 0
userprog/readme.md

@@ -0,0 +1 @@
+# 用户进程相关

+ 1 - 1
userprog/tss.c

@@ -38,7 +38,7 @@ static GdtDesc make_gdt_desc(uint32_t *desc_addr, uint32_t limit, uint8_t attr_l
     desc.base_low_word = desc_base & 0xFFFF;
     desc.base_middle_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.limit_high_attr_high = ((uint8_t)(attr_high) + ((limit & 0x000f0000) >> 16));
     desc.base_high_byte = desc_base >> 24;
 
     return desc;