已完成实验
已完成实验链接
简介
实验 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