Преглед на файлове

实现线程及根据时钟中断做线程切换

simon преди 10 месеца
родител
ревизия
93219e2c87
променени са 14 файла, в които са добавени 300 реда и са изтрити 53 реда
  1. 1 0
      .gitignore
  2. 1 1
      boot/loader.S
  3. 24 0
      device/timer.c
  4. 5 3
      kernel/init.c
  5. 36 8
      kernel/interrupt.c
  6. 7 0
      kernel/interrupt.h
  7. 17 1
      kernel/main.c
  8. 4 3
      lib/kernel/list.h
  9. 24 0
      lib/kernel/print.S
  10. 7 0
      lib/kernel/print.h
  11. 24 21
      makefile
  12. 27 0
      thread/switch.S
  13. 102 12
      thread/thread.c
  14. 21 4
      thread/thread.h

+ 1 - 0
.gitignore

@@ -74,3 +74,4 @@ bochs_log.txt
 .vscode/
 .dist/
 build/
+.idea/

+ 1 - 1
boot/loader.S

@@ -228,7 +228,7 @@ p_mode_start:
     enter_kernel:
         call kernel_init
         mov esp, 0xc009f000
-        jmp KERNEL_ENTRY_POINT
+        jmp KERNEL_ENTRY_POINT ; 跳转到内核入口 0xc0001500
 
     jmp $
 

+ 24 - 0
device/timer.c

@@ -1,6 +1,9 @@
 #include "timer.h"
 #include "../lib/kernel/io.h"
 #include "../lib/kernel/print.h"
+#include "../thread/thread.h"
+#include "../kernel/debug.h"
+#include "../kernel/interrupt.h"
 
 /**
  * 8253定时器初始化 计数器的工作频率均是 1.19318MHz
@@ -18,6 +21,26 @@
 #define READ_WRITE_LATCH 3
 #define PIT_CONTROL_PORT 0x43
 
+uint32_t ticks; // ticks 是内核自中断开启以来总共的嘀嗒数
+
+/// @brief 时钟中断处理函数
+static void intr_timer_handler(void)
+{
+    struct task_struct *cur_thread = running_thread();
+    ASSERT(cur_thread->stack_magic == 0x19940625); // 检查栈是否溢出
+    cur_thread->elapsed_ticks++;                   // 记录此线程占用的 CPU 时间嘀嗒数
+    ticks++;                                       // 内核态的嘀嗒数
+
+    if (cur_thread->ticks == 0)
+    { // 若进程时间片用完就开始调度新的进程上 CPU
+        schedule();
+    }
+    else
+    {
+        cur_thread->ticks--; // 将当前进程的时间片-1
+    }
+}
+
 /// @brief 设置计数器
 /// @param counter_port 操作的计数器端口
 /// @param counter_no  计数器编号
@@ -40,5 +63,6 @@ void timer_init(void)
     put_str("timer_init start\n");
     // 设置计数器 0,工作方式 2,初始值 1193180/100=11931,即每 10ms 发送一次时钟中断
     frequency_set(COUNTER0_PORT, COUNTER0_NO, READ_WRITE_LATCH, COUNTER_MODE, COUNTER0_VALUE);
+    register_handler(0x20, intr_timer_handler);
     put_str("timer_init done\n");
 }

+ 5 - 3
kernel/init.c

@@ -3,11 +3,13 @@
 #include "interrupt.h"
 #include "../device/timer.h"
 #include "memory.h"
+#include "../thread/thread.h"
 
 void init_all(void)
 {
     put_str("init_all\n");
-    itd_init();   // 初始化中断
-    timer_init(); // 初始化定时器
-    mem_init();   // 初始化内存管理系统
+    itd_init();    // 初始化中断
+    timer_init();  // 初始化定时器
+    mem_init();    // 初始化内存管理系统
+    thread_init(); // 初始化线程管理系统
 }

+ 36 - 8
kernel/interrupt.c

@@ -15,11 +15,11 @@ static gate_desc idt[IDT_DESC_CNT]; // 中断描述符表,本质上就是个中
 /*创建中断门描述符*/
 static void make_idt_desc(gate_desc *p_gdesc, uint8_t attr, intr_handler function)
 {
-    p_gdesc->offset_low = (uint32_t)function & 0x0000FFFF;
+    p_gdesc->offset_low = (uint32_t)((uintptr_t)function & 0x0000FFFF);
     p_gdesc->selector = SELECTOR_K_CODE;
     p_gdesc->dcount = 0;
     p_gdesc->attribute = attr;
-    p_gdesc->offset_high = ((uint32_t)function & 0xFFFF0000) >> 16;
+    p_gdesc->offset_high = (uint32_t)(((uintptr_t)function & 0xFFFF0000) >> 16);
 }
 /* 初始化中断描述符表*/
 static void itd_desc_init(void)
@@ -54,6 +54,7 @@ static void pic_init(void)
     put_str("   pic_init done\n");
 }
 
+// 通用中断处理函数, 一般用于处理异常
 static void general_intr_handler(uint8_t vec_nr)
 {
     if (vec_nr == 0x27 || vec_nr == 0x2f)
@@ -62,9 +63,30 @@ static void general_intr_handler(uint8_t vec_nr)
         // 0x2f 是从片8259A上的最后一个 IRQ 引脚,保留项
         return;
     }
-    put_str("int vector: 0x");
-    put_int(vec_nr);
-    put_char('\n');
+    set_cursor(0); // 将光标置为屏幕左上角,从此处打印异常信息,方便阅读
+    int cursor_pos = 0;
+    while (cursor_pos < 320)
+    { // 清空 4 行位置的字符
+        put_char(' ');
+        cursor_pos++;
+    }
+    set_cursor(0); // 重置光标位置
+    put_str("!!!!!!! exception message begin !!!!!!!!!!!!!\n");
+    set_cursor(88); // 从第 2 行第 8 个字符开始打印, 80 字符宽
+    put_str("intr name: ");
+    put_str(intr_name[vec_nr]);
+    if (vec_nr == 14) // 若为 Pagefault, 将缺失的地址打印出来并悬停
+    {
+        int page_fault_vaddr = 0;
+        asm("movl %%cr2, %0" : "=r"(page_fault_vaddr)); // cr2是存放page_fault的线性地址
+        put_str("\npage fault addr is: 0X");            // 输出缺失的地址
+        put_int(page_fault_vaddr);
+    }
+    put_str("\n!!!!!!! exception message end !!!!!!!!!!!!!\n");
+    // 能进入中断处理程序就表示已经处在关中断情况下, 不会出现调度进程的情况。
+    // 所以此处的 while 不会再被中断打断,此处的死循环是为了保证当前任务继续运行
+    while (1)
+        ;
 }
 
 static void exception_init(void)
@@ -78,14 +100,14 @@ static void exception_init(void)
     }
     intr_name[0] = "#DE Divide Error";
     intr_name[1] = "#DB Debug Exception";
-    intr_name[2] = "NMI Interrupt";
+    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[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";
@@ -108,7 +130,7 @@ void itd_init(void)
     pic_init();       // 初始化可编程中断控制器8259A
 
     /* 加载 idt */
-    uint64_t idt_operand = ((sizeof(idt) - 1) | ((uint64_t)((uint32_t)idt << 16)));
+    uint64_t idt_operand = ((sizeof(idt) - 1) | ((uint64_t)((uintptr_t)idt << 16)));
     asm volatile("lidt %0" ::"m"(idt_operand));
     put_str("itd_init done\n");
 }
@@ -152,4 +174,10 @@ enum intr_status intr_disable(void)
         old_status = INTR_OFF;
         return old_status;
     }
+}
+
+void register_handler(uint8_t vector_no, intr_handler function)
+{ // idt_table 数组中的函数是进入中断后根据中断向量号调用的,
+    // 见 kernel/kernel.S 的 call [itd_table + %1*4]
+    idt_table[vector_no] = function;
 }

+ 7 - 0
kernel/interrupt.h

@@ -43,4 +43,11 @@ typedef struct
 // 定义中断处理程序数组.在 kernel.S 中定义的intrXXentry只是中断处理程序的入口,最终调用的是ide_table中的处理程序
 extern intr_handler intr_entry_table[IDT_DESC_CNT];
 
+/// @brief 注册中断处理函数
+/// @param vector_no  中断号
+/// @param function  中断处理函数
+void register_handler(uint8_t vector_no, intr_handler function);
+enum intr_status intr_enable(void);
+enum intr_status intr_disable(void);
+
 #endif // __KERNEL_INTERRUPT_H

+ 17 - 1
kernel/main.c

@@ -3,8 +3,10 @@
 #include "init.h"
 #include "memory.h"
 #include "../thread/thread.h"
+#include "interrupt.h"
 
 void thread_a_func(void *);
+void thread_b_func(void *);
 
 int main(void)
 {
@@ -19,9 +21,14 @@ int main(void)
     // put_str("\n");
 
     thread_start("thread_a", 31, thread_a_func, "argA ");
+    thread_start("thread_b", 8, thread_b_func, "argB ");
 
+    intr_enable(); // 打开中断,使时钟中断起作用
+    // 主线程会一直执行, 直到被中断或被其他线程强制结束
     while (1)
-        ;
+    {
+        put_str("Main ");
+    }
     return 0;
 }
 
@@ -33,4 +40,13 @@ void thread_a_func(void *arg)
     {
         put_str(para);
     }
+}
+void thread_b_func(void *arg)
+{
+    // 用 void 来表示通用类型,这样就可以传递任意类型的参数
+    char *para = arg;
+    while (1)
+    {
+        put_str(para);
+    }
 }

+ 4 - 3
lib/kernel/list.h

@@ -3,10 +3,11 @@
 #define __LIB_KERNEL_LIST_H
 #include "../stdint.h"
 
-#define offset(struct_type, member) (int)(&((struct_type *)0)->member)
+// 获取结构体中成员的偏移量, 并将其转换为 void* 类型
+#define offset(struct_type, member) (uintptr_t)(&((struct_type *)0)->member)
 
-#define node2entry(struct_type, struct_member_name, node_ptr) \
-    (struct_type *)((int)node_ptr - offset(struct_type, struct_member_name))
+#define elem2entry(struct_type, struct_member_name, elem_ptr) \
+    (struct_type *)((uintptr_t)elem_ptr - offset(struct_type, struct_member_name))
 
 // 链表节点, 节点中不需要数据成员, 只要求前驱和后继指针
 struct list_elem

+ 24 - 0
lib/kernel/print.S

@@ -25,6 +25,7 @@ section .text
 global put_char ; 导出为全局符号
 global put_str  
 global put_int
+global set_cursor
 ;------------------------ put_char -----------------------------------------------
 ; 功能描述: 把栈中的 1 个字符写入光标所在处
 ;---------------------------------------------------------------------------------
@@ -223,4 +224,27 @@ put_int:
     cmp edi, 8
     jl .put_each_num
     popad
+    ret
+
+set_cursor:
+    pushad            ; set_cursor 会改变寄存器的值,所以需要备份
+
+    mov ebp, esp
+    mov ebx, [ebp + 36] ; pushad 压入 4 X 8 = 32 字节 加上主调函数 4 字节
+
+    ;; 1. 先设置高 8 位
+    mov dx, 0x3d4     ; 索引寄存器
+    mov al, 0x0e      ; 用于提供光标位置的高 8 位
+    out dx, al
+    mov dx, 0x3d5     ; 通过读写数据端口 0x3d5 来获取或设置光标位置
+    mov al, bh 
+    out dx, al
+    ;; 2. 再设置低 8 位
+    mov dx, 0x3d4 
+    mov al, 0x0f
+    out dx, al 
+    mov dx, 0x3d5
+    mov al, bl 
+    out dx, al
+    popad
     ret

+ 7 - 0
lib/kernel/print.h

@@ -25,5 +25,12 @@ void put_str(char *message);
  * @retval None
  */
 void put_int(uint32_t num);
+/**
+ * @brief  设置光标位置
+ * @note
+ * @param  pos: 0~1999
+ * @retval None
+ */
+void set_cursor(unsigned pos);
 
 #endif // __LIB_KERNEL_PRINT_H

+ 24 - 21
makefile

@@ -1,5 +1,5 @@
-BUID_O_DIR = ./build
-BUILD_BIN_DIR = $(BUID_O_DIR)/bin
+BUILD_O_DIR = ./build
+BUILD_BIN_DIR = $(BUILD_O_DIR)/bin
 ENTRY_POINT = 0xc0001500
 DIS_IMG = ./hd30M.img
 AS = nasm
@@ -10,47 +10,50 @@ BOOTLIB = -I boot/include/
 ASFLAGS = -f elf
 CFLAGS = -Wall $(LIB) -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes
 LDFLAGS = -Ttext $(ENTRY_POINT) -e main -Map $(BUILD_BIN_DIR)/kernel.map
-OBJS = $(BUID_O_DIR)/main.o $(BUID_O_DIR)/init.o $(BUID_O_DIR)/interrupt.o  \
-	$(BUID_O_DIR)/timer.o $(BUID_O_DIR)/kernel.o $(BUID_O_DIR)/print.o   \
-	$(BUID_O_DIR)/debug.o $(BUID_O_DIR)/string.o $(BUID_O_DIR)/bitmap.o  \
-	$(BUID_O_DIR)/memory.o $(BUID_O_DIR)/thread.o $(BUID_O_DIR)/list.o
+OBJS = $(BUILD_O_DIR)/main.o $(BUILD_O_DIR)/init.o $(BUILD_O_DIR)/interrupt.o  \
+	$(BUILD_O_DIR)/timer.o $(BUILD_O_DIR)/kernel.o $(BUILD_O_DIR)/print.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
 
 ################################ C 代码编译 ################################
-$(BUID_O_DIR)/main.o: kernel/main.c lib/kernel/print.h lib/stdint.h kernel/init.h
+$(BUILD_O_DIR)/main.o: kernel/main.c lib/kernel/print.h lib/stdint.h kernel/init.h
 	$(CC) $(CFLAGS) $< -o $@
 
-$(BUID_O_DIR)/init.o: kernel/init.c kernel/init.h lib/kernel/print.h lib/stdint.h kernel/interrupt.h device/timer.h
+$(BUILD_O_DIR)/init.o: kernel/init.c kernel/init.h lib/kernel/print.h lib/stdint.h kernel/interrupt.h device/timer.h
 	$(CC) $(CFLAGS) $< -o $@
 
-$(BUID_O_DIR)/interrupt.o: kernel/interrupt.c lib/stdint.h kernel/interrupt.h kernel/global.h lib/kernel/print.h
+$(BUILD_O_DIR)/interrupt.o: kernel/interrupt.c lib/stdint.h kernel/interrupt.h kernel/global.h lib/kernel/print.h
 	$(CC) $(CFLAGS) $< -o $@
 
-$(BUID_O_DIR)/timer.o: device/timer.c lib/stdint.h device/timer.h kernel/interrupt.h lib/kernel/io.h lib/kernel/print.h
+$(BUILD_O_DIR)/timer.o: device/timer.c lib/stdint.h device/timer.h kernel/interrupt.h lib/kernel/io.h lib/kernel/print.h
 	$(CC) $(CFLAGS) $< -o $@
 
-$(BUID_O_DIR)/debug.o: kernel/debug.c kernel/debug.h lib/kernel/print.h lib/stdint.h kernel/interrupt.h
+$(BUILD_O_DIR)/debug.o: kernel/debug.c kernel/debug.h lib/kernel/print.h lib/stdint.h kernel/interrupt.h
 	$(CC) $(CFLAGS) $< -o $@
 
-$(BUID_O_DIR)/string.o: lib/string.c lib/string.h lib/stdint.h
+$(BUILD_O_DIR)/string.o: lib/string.c lib/string.h lib/stdint.h
 	$(CC) $(CFLAGS) $< -o $@
 
-$(BUID_O_DIR)/bitmap.o: lib/kernel/bitmap.c lib/kernel/bitmap.h lib/stdint.h lib/string.h
+$(BUILD_O_DIR)/bitmap.o: lib/kernel/bitmap.c lib/kernel/bitmap.h lib/stdint.h lib/string.h
 	$(CC) $(CFLAGS) $< -o $@
 
-$(BUID_O_DIR)/memory.o: kernel/memory.c kernel/memory.h lib/stdint.h lib/kernel/bitmap.h lib/string.h lib/kernel/print.h
+$(BUILD_O_DIR)/memory.o: kernel/memory.c kernel/memory.h lib/stdint.h lib/kernel/bitmap.h lib/string.h lib/kernel/print.h
 	$(CC) $(CFLAGS) $< -o $@
 
-$(BUID_O_DIR)/thread.o: thread/thread.c thread/thread.h
+$(BUILD_O_DIR)/thread.o: thread/thread.c thread/thread.h
 	$(CC) $(CFLAGS) $< -o $@
 
-$(BUID_O_DIR)/list.o: lib/kernel/list.c lib/kernel/list.h lib/stdint.h kernel/interrupt.h
+$(BUILD_O_DIR)/list.o: lib/kernel/list.c lib/kernel/list.h lib/stdint.h kernel/interrupt.h
 	$(CC) $(CFLAGS) $< -o $@
 
 ################################ 汇编代码编译 ################################
-$(BUID_O_DIR)/kernel.o: kernel/kernel.S
+$(BUILD_O_DIR)/kernel.o: kernel/kernel.S
 	$(AS) $(ASFLAGS) $< -o $@
 
-$(BUID_O_DIR)/print.o: lib/kernel/print.S
+$(BUILD_O_DIR)/switch.o: thread/switch.S
+	$(AS) $(ASFLAGS) $< -o $@
+
+$(BUILD_O_DIR)/print.o: lib/kernel/print.S
 	$(AS) $(ASFLAGS) $< -o $@
 
 $(BUILD_BIN_DIR)/mbr.bin: boot/mbr.S
@@ -67,7 +70,7 @@ $(BUILD_BIN_DIR)/kernel.bin: $(OBJS)
 .PHONY:clean mk_dir mbr loader K create_img hd run build all
 
 mk_dir:
-	@if [ ! -d $(BUID_O_DIR) ]; then mkdir $(BUID_O_DIR); fi
+	@if [ ! -d $(BUILD_O_DIR) ]; then mkdir $(BUILD_O_DIR); fi
 	@if [ ! -d $(BUILD_BIN_DIR) ]; then mkdir $(BUILD_BIN_DIR); fi
 
 ################################ 创建 DIS_IMG ################################
@@ -81,8 +84,8 @@ hd: mbr loader K create_img
 	dd if=$(BUILD_BIN_DIR)/kernel.bin of=$(DIS_IMG) bs=512 count=200 seek=9 conv=notrunc
 
 clean: mk_dir
-	cd $(BUID_O_DIR) && rm -rf ./*
-	rm -rf $(BUID_O_DIR)
+	cd $(BUILD_O_DIR) && rm -rf ./*
+	rm -rf $(BUILD_O_DIR)
 	rm -rf $(DIS_IMG)
 	rm -rf bochs_log.txt
 

+ 27 - 0
thread/switch.S

@@ -0,0 +1,27 @@
+[bits 32]
+section .text 
+global switch_to  ; void switch_to(struct task_struct *cur, struct task_struct *next);
+switch_to:
+    ; 栈中此处是返回地址
+    push esi
+    push edi
+    push ebx
+    push ebp 
+
+    mov eax, [esp + 20] ; 得到栈中的参数 cur, cur = [esp + 20]
+    mov [eax], esp      ; 保存栈顶指针 esp 到task_struct 的 self_kstack 字段。
+                        ; self_kstack 在 task_struct 中的偏移为 0,所以直接往 thread 开头处存储 4 字节便可
+
+    ;-------------------- 上面是备份当前线程环境,下面是恢复下一个线程环境 -------------------------------------
+
+    mov eax, [esp + 24] ; 得到栈中的参数 next, next = [esp + 24]
+    mov esp, [eax]      ; pcb 的第一个成员是 self_kstack 成员
+                        ; 它用来记录 0 级栈顶指针,被换上 CPU 时用来恢复 0 级栈
+                        ; 因此,恢复 esp 到 pcb 的 self_kstack 位置,就可以恢复 0 级栈
+    pop ebp
+    pop ebx
+    pop edi
+    pop esi
+
+    ret                 ; 返回到上面 switch_to 下面的注释返回地址
+                        ; 未由中断进入,第一次执行时会返回到 kernel_thread  

+ 102 - 12
thread/thread.c

@@ -1,17 +1,39 @@
 #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"
 
 #define PG_SIZE 4096
 
+struct task_struct *main_thread;     // 主线程 PCB
+struct list thread_ready_list;       // 就绪队列
+struct list thread_all_list;         // 所有任务队列
+static struct list_elem *thread_tag; // 用于保存队列中的线程节点
+
+extern void switch_to(struct task_struct *cur, struct task_struct *next);
+
+// 获取当前线程的 PCB 指针
+struct task_struct *running_thread()
+{
+    uint32_t esp;
+    asm("mov %%esp, %0" : "=g"(esp));
+    // 取 esp 整数部分作为 PCB 的指针
+    return (struct task_struct *)((uintptr_t)esp & 0xfffff000);
+}
+
 // 由 kernel_thread 去执行 function(func_arg)
 static void kernel_thread(thread_func *function, void *func_arg)
 {
+    // 执行 function 前开中断,避免后面的时钟中断被屏蔽,而无法调度其它线程
+    intr_enable();
     function(func_arg);
 }
 
 // 初始化线程栈 thread_stack
-void thread_create(struct task_struct *pthread, thread_func function, void *func_arg)
+void thread_stack_create(struct task_struct *pthread, thread_func function, void *func_arg)
 {
     // 先预留中断栈空间
     pthread->self_kstack -= sizeof(struct intr_stack);
@@ -28,13 +50,25 @@ void thread_create(struct task_struct *pthread, thread_func function, void *func
 // 初始化线程基本信息
 void init_thread(struct task_struct *pthread, char *name, int prio)
 {
-    _memset(pthread, 0, sizeof(*pthread));
+    _memset(pthread, 0, sizeof(*pthread)); // 全部置为 0
     _strcpy(pthread->name, name);
-    pthread->status = TASK_RUNNING;
-    pthread->priority = prio;
-    // self_kstack是线程自己在内核态下使用的栈顶地址
-    pthread->self_kstack = (uint32_t *)((uintptr_t)pthread + PG_SIZE);
-    pthread->stack_magic = 0x19940625; // 自定义的魔数
+
+    // 线程状态, 目前只有两种状态: TASK_RUNNING, TASK_READY
+    if (pthread == main_thread)
+    { // 主线程,把 main 也封装成一个线程,并且它一直是运行的,所以是 TASK_RUNNING
+        pthread->status = TASK_RUNNING;
+    }
+    else
+    {
+        pthread->status = TASK_READY;
+    }
+
+    pthread->self_kstack = (uint32_t *)((uintptr_t)pthread + PG_SIZE); // self_kstack是线程自己在内核态下使用的栈顶地址
+    pthread->priority = prio;                                          // 线程优先级
+    pthread->ticks = prio;                                             // 嘀嗒数
+    pthread->elapsed_ticks = 0;                                        // 线程已执行的时间嘀嗒数
+    pthread->pgdir = NULL;                                             // 进程自己页表的虚拟地址, 如果是线程则为 NULL
+    pthread->stack_magic = 0x19940625;                                 // 自定义的魔数
 }
 
 // 创建优先级为 prio 的线程, 线程名为 name, 线程所执行的函数是 function(func_arg)
@@ -43,12 +77,68 @@ 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_create(thread, function, func_arg);        // 初始化线程栈 thread_stack
+    thread_stack_create(thread, function, func_arg);  // 初始化线程栈 thread_stack
+
+    ASSERT(!elem_find(&thread_ready_list, &thread->general_tag)); // 保证加入就绪队列的线程不在队列中
+    list_append(&thread_ready_list, &thread->general_tag);        // 加入就绪队列
 
-    // ret 指令的作用是弹出栈顶的数据到 eip 寄存器,然后跳转到 eip 寄存器的地址执行
-    // 此时栈顶数据是kthread_stack->eip 的值 kernel_thread。
-    // 因此,在执行 ret 后,会跳转到 kernel_thread 函数执行。
-    asm volatile("movl %0, %%esp; pop %%ebp; pop %%ebx; pop %%edi; pop %%esi; ret" : : "g"(thread->self_kstack) : "memory");
+    ASSERT(!elem_find(&thread_all_list, &thread->all_list_tag)); // 保证加入所有线程队列的线程不在队列中
+    list_append(&thread_all_list, &thread->all_list_tag);        // 加入所有线程队列
 
     return thread;
+}
+
+// 将 kernel 中的 main 函数封装成线程
+static void make_main_thread(void)
+{
+    // 因为 main 函数也是一个线程,但它被单独执行,所以要单独处理
+    // 因为 main 函数是操作系统的第一个函数,所以它的 PCB 也是第一个
+    // ! 在 loader.S 中进入内核时 mov esp, 0xc009f000,
+    // ! 所以 main 函数的栈顶是 0xc009f000, PCB 是 0xc009e000
+    main_thread = running_thread();
+    init_thread(main_thread, "main", 31); // 31 是最高优先级
+
+    // main 函数是当前线程,当前线程不在 thread_ready_list 中
+    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();
+    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
+        cur->status = TASK_READY;
+    }
+    else
+    {
+        // 若此线程需要某事件发生后才能继续上 cpu 运行, 不需要加入队列
+    }
+    // todo: 暂未实现 idle 线程,暂用 assertion 来保障
+    ASSERT(!list_empty(&thread_ready_list));
+    thread_tag = NULL;                         // thread_tag 清空
+    thread_tag = list_pop(&thread_ready_list); // 弹出队列中的第一个就绪线程
+    struct task_struct *next = elem2entry(struct task_struct, general_tag, thread_tag);
+    // 方法 2: PCB 在自然页的起始地址, 所以 pcb 地址=0xfffff000&(&(PCB.general_tag))
+
+    next->status = TASK_RUNNING;
+    switch_to(cur, next); // 切换线程
+}
+
+// 初始化线程环境
+void thread_init(void)
+{
+    put_str("thread_init start\n");
+    list_init(&thread_ready_list);
+    list_init(&thread_all_list);
+    make_main_thread(); // 将当前 main 函数创建为线程
+    put_str("thread_init end\n");
 }

+ 21 - 4
thread/thread.h

@@ -1,6 +1,7 @@
 #ifndef __THREAD_THREAD_H
 #define __THREAD_THREAD_H
 #include "../lib/stdint.h"
+#include "../lib/kernel/list.h"
 
 // 通用函数类型,它将在很多线程函数中作为参数类型
 typedef void thread_func(void *);
@@ -77,18 +78,34 @@ struct thread_stack
     void *func_arg;        // 由kernel_thread所调用的函数所需的参数
 };
 
-// 进程或线程的pcb,程序控制块(process control block)
+// 进程或线程的 pcb,程序控制块(process control block)
 struct task_struct
 {
     uint32_t *self_kstack;   // 各内核线程都用自己的内核栈
     enum task_status status; // 线程状态
-    uint8_t priority;        // 线程优先级
     char name[16];           // 进程或线程的名字
-    uint32_t stack_magic;    // 用这串数字做栈的边界标记,用于检测栈的溢出
+    uint8_t priority;        // 线程优先级
+    uint8_t ticks;           // 每次在处理器上执行的时间嘀嗒数
+    uint32_t elapsed_ticks;  //  累计计执行的 ticks 数
+
+    struct list_elem general_tag;  // 用于线程在一般队列中的结点
+    struct list_elem all_list_tag; // 用于线程队列 thread_all_list 中的结点
+
+    uint32_t *pgdir;      // 进程自己页表的虚拟地址, 如果是线程则为 NULL
+    uint32_t stack_magic; // 用这串数字做栈的边界标记,用于检测栈的溢出
 };
 
-void thread_create(struct task_struct *pthread, thread_func function, void *func_arg);
+// 获取当前线程的 PCB 指针
+struct task_struct *running_thread();
+// 初始化线程栈 thread_stack
+void thread_stack_create(struct task_struct *pthread, thread_func function, void *func_arg);
+// 初始化线程基本信息
 void init_thread(struct task_struct *pthread, char *name, int prio);
+// 创建优先级为 prio 的线程, 线程名为 name, 线程所执行的函数是 function(func_arg)
 struct task_struct *thread_start(char *name, int prio, thread_func function, void *func_arg);
+// 任务调度
+void schedule();
+// 初始化线程环境
+void thread_init(void);
 
 #endif // __THREAD_THREAD_H