爲了讓你們清楚目前的程序進度,畫了到目前爲止的程序流程圖,以下。紅色部分是咱們今天要實現的html
相信看這篇文章的人,確定不是對基本概念感興趣,這也不是個人主要目的。因此這裏真的是簡述一下node
進程和線程都是 獨立的程序執行流,只不過進程有本身獨立的內存空間,同一個進程裏的線程共享內存空間,具體體如今 pcb 表中一個字段上,指向頁表的地址值。git
線程分 用戶線程 和 內核線程,用戶線程能夠理解爲就是沒有線程,只是用戶程序中寫了一個線程調度器程序在僞裝切換,操做系統根本無感知。編程
咱們分三步實現最終的多線程機制,其實就對應着下面三節的內容數組
那麼本節先實現第一步,先看代碼微信
1 #include "print.h" 2 #include "init.h" 3 #include "thread.h" 4 5 void k_thread_a(void*); 6 7 int main(void){ 8 put_str("I am kernel\n"); 9 init_all(); 10 thread_start("k_thread_a", 31, k_thread_a, "argA "); 11 while(1); 12 return 0; 13 } 14 15 void k_thread_a(void* arg) { 16 char* para = arg; 17 while(1) { 18 put_str(para); 19 } 20 }
1 #include "thread.h" 2 #include "stdint.h" 3 #include "string.h" 4 #include "global.h" 5 #include "memory.h" 6 7 #define PG_SIZE 4096 8 9 // 由 kernel_thread 去執行 function(func_arg) 10 static void kernel_thread(thread_func* function, void* func_arg) { 11 function(func_arg); 12 } 13 14 // 初始化線程棧 thread_stack 15 void thread_create(struct task_struct* pthread, thread_func function, void* func_arg) { 16 // 先預留中斷使用棧的空間 17 pthread->self_kstack -= sizeof(struct intr_stack); 18 19 // 再留出線程棧空間 20 pthread->self_kstack -= sizeof(struct thread_stack); 21 struct thread_stack* kthread_stack = (struct thread_stack*)pthread->self_kstack; 22 kthread_stack->eip = kernel_thread; 23 kthread_stack->function = function; 24 kthread_stack->func_arg = func_arg; 25 kthread_stack->ebp = kthread_stack->ebx = kthread_stack->esi = kthread_stack->edi = 0; 26 } 27 28 // 初始化線程基本信息 29 void init_thread(struct task_struct* pthread, char* name, int prio) { 30 memset(pthread, 0, sizeof(*pthread)); 31 strcpy(pthread->name, name); 32 pthread->status = TASK_RUNNING; 33 pthread->priority = prio; 34 // 線程本身在內核態下使用的棧頂地址 35 pthread->self_kstack = (uint32_t*)((uint32_t)pthread + PG_SIZE); 36 pthread->stack_magic = 0x19870916; // 自定義魔數 37 } 38 39 // 建立一優先級爲 prio 的線程,線程名爲 name,線程所執行的函數爲 function 40 struct task_struct* thread_start(char* name, int prio, thread_func function, void* func_arg) { 41 // pcb 都位於內核空間,包括用戶進程的 pcb 也是在內核空間 42 struct task_struct* thread = get_kernel_pages(1); 43 44 init_thread(thread, name, prio); 45 thread_create(thread, function, func_arg); 46 47 asm volatile("mov %0, %%esp; pop %%ebp; pop %%ebx; pop %%edi; pop %%esi; ret ": : "g" (thread->self_kstack) : "memory"); 48 return thread; 49 }
1 #ifndef __THREAD_THREAD_H 2 #define __THREAD_THREAD_H 3 #include "stdint.h" 4 5 // 自定義通用函數類型,它將在不少線程函數中做爲形式參數類型 6 typedef void thread_func(void*); 7 8 // 進程或線程的狀態 9 enum task_status { 10 TASK_RUNNING, 11 TASK_READY, 12 TASK_BLOCKED, 13 TASK_WAITING, 14 TASK_HANGING, 15 TASK_DIED 16 }; 17 18 /*********** 中斷棧intr_stack *********** 19 * 此結構用於中斷髮生時保護程序(線程或進程)的上下文環境: 20 * 進程或線程被外部中斷或軟中斷打斷時,會按照此結構壓入上下文 21 * 寄存器, intr_exit中的出棧操做是此結構的逆操做 22 * 此棧在線程本身的內核棧中位置固定,所在頁的最頂端 23 ********************************************/ 24 struct intr_stack { 25 uint32_t vec_no; // 壓入的中斷號 26 uint32_t edi; 27 uint32_t esi; 28 uint32_t ebp; 29 uint32_t esp_dummy; 30 uint32_t ebx; 31 uint32_t edx; 32 uint32_t ecx; 33 uint32_t eax; 34 uint32_t gs; 35 uint32_t fs; 36 uint32_t es; 37 uint32_t ds; 38 39 // 如下由 cpu 從低特權級進入高特權級時壓入 40 uint32_t err_code; 41 void (*eip) (void); 42 uint32_t cs; 43 uint32_t eflags; 44 void* esp; 45 uint32_t ss; 46 }; 47 48 /*********** 線程棧thread_stack *********** 49 * 線程本身的棧,用於存儲線程中待執行的函數 50 * 此結構在線程本身的內核棧中位置不固定, 51 * 用在switch_to時保存線程環境。 52 * 實際位置取決於實際運行狀況。 53 ******************************************/ 54 struct thread_stack { 55 uint32_t ebp; 56 uint32_t ebx; 57 uint32_t edi; 58 uint32_t esi; 59 60 61 // 線程第一次執行時,eip指向待調用的函數kernel_thread 其它時候,eip是指向switch_to的返回地址 62 void (*eip) (thread_func* func, void* func_arg); 63 64 /***** 如下僅供第一次被調度上cpu時使用 ****/ 65 66 // 參數unused_ret只爲佔位置充數爲返回地址 67 void (*unused_retaddr); 68 thread_func* function; // 由kernel_thread所調用的函數名 69 void* func_arg; // 由kernel_thread所調用的函數所需的參數 70 }; 71 72 // 進程或線程的 pcb 程序控制塊 73 struct task_struct { 74 uint32_t* self_kstack; // 各內核線程都用本身的內核棧 75 enum task_status status; 76 uint8_t priority; // 線程優先級 77 char name[16]; 78 uint32_t stack_magic; // 棧的邊界標記,用於檢測棧溢出 79 }; 80 81 #endif
寫代碼的順序是先寫定義,再寫實現,最後再調用它。但看代碼我仍是喜歡正着看,這樣知道正向的調用邏輯數據結構
1 int main(void){ 2 put_str("I am kernel\n"); 3 init_all(); 4 thread_start("k_thread_a", 31, k_thread_a, "argA "); 5 while(1); 6 return 0; 7 } 8 9 void k_thread_a(void* arg) { 10 char* para = arg; 11 while(1) { 12 put_str(para); 13 } 14 }
1 struct task_struct* thread_start(char* name, int prio, thread_func function, void* func_arg) { 2 // 申請內核空間的一片內存 3 struct task_struct* thread = get_kernel_pages(1); 4 // pcb結構賦值 5 init_thread(thread, name, prio); 6 thread_create(thread, function, func_arg); 7 // 暫時用一句彙編把函數跑起來 8 asm volatile("mov %0, %%esp; pop %%ebp; pop %%ebx; pop %%edi; pop %%esi; ret ": : "g" (thread->self_kstack) : "memory"); 9 return thread; 10 }
1 struct task_struct { 2 uint32_t* self_kstack; // 各內核線程都用本身的內核棧 3 enum task_status status; // 線程狀態 4 uint8_t priority; // 線程優先級 5 char name[16]; // 線程名字 6 uint32_t stack_magic; // 棧的邊界標記,用於檢測棧溢出 7 };
1 void init_thread(struct task_struct* pthread, char* name, int prio) { 2 memset(pthread, 0, sizeof(*pthread)); 3 strcpy(pthread->name, name); 4 pthread->status = TASK_RUNNING; 5 pthread->priority = prio; 6 // 線程本身在內核態下使用的棧頂地址 7 pthread->self_kstack = (uint32_t*)((uint32_t)pthread + PG_SIZE); 8 pthread->stack_magic = 0x19870916; // 自定義魔數 9 }
1 void thread_create(struct task_struct* pthread, thread_func function, void* func_arg) { 2 // 先預留中斷使用棧的空間 3 pthread->self_kstack -= sizeof(struct intr_stack); 4 // 再留出線程棧空間 5 pthread->self_kstack -= sizeof(struct thread_stack); 6 struct thread_stack* kthread_stack = (struct thread_stack*)pthread->self_kstack; 7 kthread_stack->eip = kernel_thread; 8 kthread_stack->function = function; 9 kthread_stack->func_arg = func_arg; 10 kthread_stack->ebp = kthread_stack->ebx = kthread_stack->esi = kthread_stack->edi = 0; 11 } 12 13 static void kernel_thread(thread_func* function, void* func_arg) { 14 function(func_arg); 15 }
asm volatile("mov %0, %%esp; pop %%ebp; pop %%ebx; pop %%edi; pop %%esi; ret ": : "g" (thread->self_kstack) : "memory");
總結起來一句話:這麼多代碼實現的,就僅僅給申請的一頁內核的內存空間附上值(按照task_struct結構來賦值),而已,爲後續工做作準備。多線程
執行 make brun 後,運行效果以下,天然是 main 方法中的函數所寫的那樣,不斷打印 argA 字符串app
1 #include "timer.h" 2 #include "io.h" 3 #include "print.h" 4 #include "thread.h" 5 6 #define IRQ0_FREQUENCY 100 7 #define INPUT_FREQUENCY 1193180 8 #define COUNTER0_VALUE INPUT_FREQUENCY / IRQ0_FREQUENCY 9 #define CONTRER0_PORT 0x40 10 #define COUNTER0_NO 0 11 #define COUNTER_MODE 2 12 #define READ_WRITE_LATCH 3 13 #define PIT_CONTROL_PORT 0x43 14 15 uint32_t ticks; // ticks是內核自中斷開啓以來總共的嘀嗒數 16 17 /* 把操做的計數器 counter_no? 讀寫鎖屬性 rwl? 計數器模式 counter_mode 寫入模式控制寄存器並賦予初始值 counter_value */ 18 static void frequency_set(uint8_t counter_port, uint8_t counter_no, uint8_t rwl, uint8_t counter_mode, uint16_t counter_value) { 19 /* 往控制字寄存器端口 0x43 中寫入控制字 */ 20 outb(PIT_CONTROL_PORT, (uint8_t)(counter_no << 6 | rwl << 4 | counter_mode << 1)); 21 /* 先寫入 counter_value 的低 8 位 */ 22 outb(counter_port, (uint8_t)counter_value); 23 /* 再寫入 counter_value 的高 8 位 */ 24 outb(counter_port, (uint8_t)counter_value >> 8); 25 } 26 27 // 時鐘的中斷處理函數 28 static void intr_timer_handler(void) { 29 struct task_struct* cur_thread = running_thread(); 30 cur_thread->elapsed_ticks++; 31 ticks++; 32 33 if (cur_thread->ticks == 0) { 34 //schedule(); 35 } else { 36 cur_thread->ticks--; 37 } 38 39 } 40 41 /* 初始化 PIT8253 */ 42 void timer_init() { 43 put_str("timer_init start\n"); 44 /* 設置 8253 的定時週期,也就是發中斷的週期 */ 45 frequency_set(CONTRER0_PORT, COUNTER0_NO, READ_WRITE_LATCH, COUNTER_MODE, COUNTER0_VALUE); 46 register_handler(0x20, intr_timer_handler); 47 put_str("timer_init done\n"); 48 }
1 #include "interrupt.h" 2 #include "stdint.h" 3 #include "global.h" 4 #include "io.h" 5 #include "print.h" 6 7 #define PIC_M_CTRL 0x20 // 這裏用的可編程中斷控制器是8259A,主片的控制端口是0x20 8 #define PIC_M_DATA 0x21 // 主片的數據端口是0x21 9 #define PIC_S_CTRL 0xa0 // 從片的控制端口是0xa0 10 #define PIC_S_DATA 0xa1 // 從片的數據端口是0xa1 11 12 #define IDT_DESC_CNT 0x81 // 目前總共支持的中斷數 13 14 #define EFLAGS_IF 0x00000200 // eflags寄存器中的if位爲1 15 #define GET_EFLAGS(EFLAG_VAR) asm volatile("pushfl; popl %0" : "=g" (EFLAG_VAR)) 16 17 // 中斷門描述符結構體 18 struct gate_desc{ 19 uint16_t func_offset_low_word; 20 uint16_t selector; 21 uint8_t dcount; 22 uint8_t attribute; 23 uint16_t func_offset_high_word; 24 }; 25 26 // 靜態函數聲明,非必須 27 static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, intr_handler function); 28 // 中斷門描述符表的數組 29 static struct gate_desc idt[IDT_DESC_CNT]; 30 // 用於保存異常名 31 char* intr_name[IDT_DESC_CNT]; 32 // 定義中斷處理程序數組,在kernel.asm中定義的intrXXentry。只是中斷處理程序的入口,最終調用idt_table中的處理程序 33 intr_handler idt_table[IDT_DESC_CNT]; 34 // 聲明引用定義在kernel.asm中的中斷處理函數入口數組 35 extern intr_handler intr_entry_table[IDT_DESC_CNT]; 36 // 初始化可編程中斷控制器 8259A 37 static void pic_init(void) { 38 39 /*初始化主片 */ 40 outb (PIC_M_CTRL, 0x11); // ICW1: 邊沿觸發,級聯8259, 須要ICW4 41 outb (PIC_M_DATA, 0x20); // ICW2: 起始中斷向量號爲0x20, 也就是IR[0-7] 爲 0x20 ~ 0x27 42 outb (PIC_M_DATA, 0x04); // ICW3: IR2 接從片 43 outb (PIC_M_DATA, 0x01); // ICW4: 8086 模式, 正常EOI 44 45 /*初始化從片 */ 46 outb (PIC_S_CTRL, 0x11); // ICW1: 邊沿觸發,級聯8259, 須要ICW4 47 outb (PIC_S_DATA, 0x28); // ICW2: 起始中斷向量號爲0x28, 也就是IR[8-15]爲0x28 ~ 0x2F 48 outb (PIC_S_DATA, 0x02); // ICW3: 設置從片鏈接到主片的IR2 引腳 49 outb (PIC_S_DATA, 0x01); // ICW4: 8086 模式, 正常EOI 50 51 /*打開主片上IR0,也就是目前只接受時鐘產生的中斷 */ 52 outb (PIC_M_DATA, 0xfe); 53 outb (PIC_S_DATA, 0xff); 54 55 put_str(" pic_init done\n"); 56 } 57 58 //建立中斷門描述符 59 static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, intr_handler function) { 60 p_gdesc->func_offset_low_word = (uint32_t)function & 0x0000FFFF; 61 p_gdesc->selector = SELECTOR_K_CODE; 62 p_gdesc->dcount = 0; 63 p_gdesc->attribute = attr; 64 p_gdesc->func_offset_high_word = ((uint32_t)function & 0xFFFF0000) >> 16; 65 } 66 67 // 初始化中斷描述符表 68 static void idt_desc_init(void) { 69 int i; 70 for(i = 0; i < IDT_DESC_CNT; i++) { 71 make_idt_desc(&idt[i], IDT_DESC_ATTR_DPL0, intr_entry_table[i]); 72 } 73 put_str(" idt_desc_init done\n"); 74 } 75 76 // 通用的中斷處理函數,通常用在異常出現時的處理 77 static void general_intr_handler(uint8_t vec_nr) { 78 if(vec_nr == 0x27 || vec_nr == 0x2f) { 79 return; 80 } 81 set_cursor(0); 82 int cursor_pos = 0; 83 while(cursor_pos < 320) { 84 put_char(' '); 85 cursor_pos++; 86 } 87 88 set_cursor(0); 89 put_str("!!!!!! exception message begin !!!!!!n"); 90 set_cursor(88); 91 put_str(intr_name[vec_nr]); 92 if (vec_nr == 14) { // PageFault 93 int page_fault_vaddr = 0; 94 asm ("movl %%cr2, %0" : "=r" (page_fault_vaddr)); 95 put_str("\npage fault addr is "); 96 put_int(page_fault_vaddr); 97 } 98 put_str("\n!!!!!!! exception message end !!!!!!\n"); 99 while(1); 100 } 101 102 // 完成通常中斷處理函數註冊及異常名稱註冊 103 static void exception_init(void) { 104 int i; 105 for(i = 0; i < IDT_DESC_CNT; i++) { 106 // 默認爲這個,之後會由 register_handler 來註冊具體處理函數 107 idt_table[i] = general_intr_handler; 108 intr_name[i] = "unknown"; 109 } 110 intr_name[0] = "#DE Divide Error"; 111 intr_name[1] = "#DB Debug Exception"; 112 intr_name[2] = "NMI Interrupt"; 113 intr_name[3] = "#BP Breakpoint Exception"; 114 intr_name[4] = "#OF Overflow Exception"; 115 intr_name[5] = "#BR BOUND Range Exceeded Exception"; 116 intr_name[6] = "#UD Invalid Opcode Exception"; 117 intr_name[7] = "#NM Device Not Available Exception"; 118 intr_name[8] = "#DF Double Fault Exception"; 119 intr_name[9] = "Coprocessor Segment Overrun"; 120 intr_name[10] = "#TS Invalid TSS Exception"; 121 intr_name[11] = "#NP Segment Not Present"; 122 intr_name[12] = "#SS Stack Fault Exception"; 123 intr_name[13] = "#GP General Protection Exception"; 124 intr_name[14] = "#PF Page-Fault Exception"; 125 // intr_name[15] 第 15 項是 intel 保留項,未使用 126 intr_name[16] = "#MF x87 FPU Floating-Point Error"; 127 intr_name[17] = "#AC Alignment Check Exception"; 128 intr_name[18] = "#MC Machine-Check Exception"; 129 intr_name[19] = "#XF SIMD Floating-Point Exception"; 130 } 131 132 /* 開中斷並返回開中斷前的狀態*/ 133 enum intr_status intr_enable() { 134 enum intr_status old_status; 135 if (INTR_ON == intr_get_status()) { 136 old_status = INTR_ON; 137 return old_status; 138 } else { 139 old_status = INTR_OFF; 140 asm volatile("sti"); // 開中斷,sti指令將IF位置1 141 return old_status; 142 } 143 } 144 145 /* 關中斷,而且返回關中斷前的狀態 */ 146 enum intr_status intr_disable() { 147 enum intr_status old_status; 148 if (INTR_ON == intr_get_status()) { 149 old_status = INTR_ON; 150 asm volatile("cli" : : : "memory"); // 關中斷,cli指令將IF位置0 151 return old_status; 152 } else { 153 old_status = INTR_OFF; 154 return old_status; 155 } 156 } 157 158 /* 將中斷狀態設置爲status */ 159 enum intr_status intr_set_status(enum intr_status status) { 160 return status & INTR_ON ? intr_enable() : intr_disable(); 161 } 162 163 /* 獲取當前中斷狀態 */ 164 enum intr_status intr_get_status() { 165 uint32_t eflags = 0; 166 GET_EFLAGS(eflags); 167 return (EFLAGS_IF & eflags) ? INTR_ON : INTR_OFF; 168 } 169 170 // 完成有關中斷到全部初始化工做 171 void idt_init() { 172 put_str("idt_init start\n"); 173 idt_desc_init(); // 初始化中斷描述符表 174 exception_init(); // 初始化通用中斷處理函數 175 pic_init(); // 初始化8259A 176 177 // 加載idt 178 uint64_t idt_operand = ((sizeof(idt) - 1) | ((uint64_t)((uint32_t)idt << 16))); 179 asm volatile("lidt %0" : : "m" (idt_operand)); 180 put_str("idt_init done\n"); 181 } 182 183 // 註冊中斷處理函數 184 void register_handler(uint8_t vector_no, intr_handler function) { 185 idt_table[vector_no] = function; 186 }
1 #include "thread.h" 2 #include "stdint.h" 3 #include "string.h" 4 #include "global.h" 5 #include "memory.h" 6 #include "list.h" 7 8 #define PG_SIZE 4096 9 10 struct task_struct* main_thread; // 主線程 PCB 11 struct list thread_ready_list; // 就緒隊列 12 struct list thread_all_list; // 全部任務隊列 13 static struct list_elem* thread_tag; // 用於保存隊列中的線程結點 14 15 extern void switch_to(struct task_struct* cur, struct task_struct* next); 16 17 struct task_struct* running_thread() { 18 uint32_t esp; 19 asm ("mov %%esp, %0" : "=g" (esp)); 20 // 返回esp整數部分,即pcb起始地址 21 return (struct task_struct*)(esp & 0xfffff000); 22 } 23 24 // 由 kernel_thread 去執行 function(func_arg) 25 static void kernel_thread(thread_func* function, void* func_arg) { 26 intr_enable(); 27 function(func_arg); 28 } 29 30 // 初始化線程棧 thread_stack 31 void thread_create(struct task_struct* pthread, thread_func function, void* func_arg) { 32 // 先預留中斷使用棧的空間 33 pthread->self_kstack -= sizeof(struct intr_stack); 34 35 // 再留出線程棧空間 36 pthread->self_kstack -= sizeof(struct thread_stack); 37 struct thread_stack* kthread_stack = (struct thread_stack*)pthread->self_kstack; 38 kthread_stack->eip = kernel_thread; 39 kthread_stack->function = function; 40 kthread_stack->func_arg = func_arg; 41 kthread_stack->ebp = kthread_stack->ebx = kthread_stack->esi = kthread_stack->edi = 0; 42 } 43 44 // 初始化線程基本信息 45 void init_thread(struct task_struct* pthread, char* name, int prio) { 46 memset(pthread, 0, sizeof(*pthread)); 47 strcpy(pthread->name, name); 48 49 if (pthread == main_thread) { 50 pthread->status = TASK_RUNNING; 51 } else { 52 pthread->status = TASK_READY; 53 } 54 pthread->priority = prio; 55 // 線程本身在內核態下使用的棧頂地址 56 pthread->self_kstack = (uint32_t*)((uint32_t)pthread + PG_SIZE); 57 pthread->ticks = prio; 58 pthread->elapsed_ticks = 0; 59 pthread->pgdir = NULL; 60 pthread->stack_magic = 0x19870916; // 自定義魔數 61 } 62 63 // 建立一優先級爲 prio 的線程,線程名爲 name,線程所執行的函數爲 function_start 64 struct task_struct* thread_start(char* name, int prio, thread_func function, void* func_arg) { 65 // pcb 都位於內核空間,包括用戶進程的 pcb 也是在內核空間 66 struct task_struct* thread = get_kernel_pages(1); 67 68 init_thread(thread, name, prio); 69 thread_create(thread, function, func_arg); 70 71 list_append(&thread_ready_list, &thread->general_tag); 72 list_append(&thread_all_list, &thread->all_list_tag); 73 74 return thread; 75 } 76 77 static void make_main_thread(void) { 78 main_thread = running_thread(); 79 init_thread(main_thread, "main", 31); 80 list_append(&thread_all_list, &main_thread->all_list_tag); 81 }
上節咱們經過 main 函數調用ide
thread_start("k_thread_a", 31, k_thread_a, "argA ")
僅僅使得一個線程的結構,也就是 PCB 被附上了值。而且僞裝讓它跑了起來,但跑起來就停不下來了。本節目的就是經過加入中斷,在中斷代碼處用一些手段來改變這個現狀。
1 // 時鐘的中斷處理函數 2 static void intr_timer_handler(void) { 3 struct task_struct* cur_thread = running_thread(); 4 cur_thread->elapsed_ticks++; 5 ticks++; 6 if (cur_thread->ticks == 0) { 7 //schedule(); 8 } else { 9 cur_thread->ticks--; 10 } 11 } 12 13 /* 初始化 PIT8253 */ 14 void timer_init() { 15 frequency_set(CONTRER0_PORT, COUNTER0_NO, READ_WRITE_LATCH, COUNTER_MODE, COUNTER0_VALUE); 16 register_handler(0x20, intr_timer_handler); 17 }
首先從最頂層的 timer.c 看,時鐘中斷處理函數被註冊到了中斷向量表裏,這樣當中斷來臨時就會執行。每次時鐘中斷一來,就 獲取一下當前的線程,並判斷當前線程的 ticks 是否到 0 了,若是到了則執行函數 schedule(),也就是咱們下一節要實現的 任務切換,若是沒到 0,就遞減。這段代碼瓜熟蒂落,很好理解。下面咱們深刻細節,也就是 ticks 是什麼意思呢?
首先咱們看 task_struct 這個結構的變化,增長了一些參數
1 struct task_struct { 2 uint32_t* self_kstack; 3 pid_t pid; 4 enum task_status status; 5 char name[TASK_NAME_LEN]; 6 uint8_t priority; 7 uint8_t ticks; // 每次在處理器上執行的時間嘀嗒數 8 uint32_t elapsed_ticks; // 此任務自上cpu運行後至今佔用了多少cpu嘀嗒數 9 struct list_elem general_tag; // 線程在通常的隊列中的結點 10 struct list_elem all_list_tag; // 線程隊列thread_all_list中的結點 11 uint32_t* pgdir; // 進程本身頁表的虛擬地址 12 struct virtual_addr userprog_vaddr; // 用戶進程的虛擬地址 13 struct mem_block_desc u_block_desc[DESC_CNT]; // 用戶進程內存塊描述符 14 int32_t fd_table[MAX_FILES_OPEN_PER_PROC]; // 已打開文件數組 15 uint32_t cwd_inode_nr; // 進程所在的工做目錄的inode編號 16 pid_t parent_pid // 父進程pid 17 int8_t exit_status; // 進程結束時本身調用exit傳入的參數 18 uint32_t stack_magic; // 用這串數字作棧的邊界標記,用於檢測棧的溢出 19 };
有些多,由於我把好久以後須要的也加上了,只看黃色部分便可。
前兩個就是時間,一個是 剩餘時間,一個是 流逝時間,很顯然是留給後面時鐘中斷去 遞減 和 遞增 的,毫無神祕感。
後面兩個 list 結構裏面的節點的變量,分別是指向兩個重要隊列的節點,隊列後面再說
下面看這些新增的結構,是怎麼被 thread.c 賦值而且利用的
1 .... 2 3 struct task_struct* main_thread; // 主線程 PCB 4 struct list thread_ready_list; // 就緒隊列 5 struct list thread_all_list; // 全部任務隊列 6 static struct list_elem* thread_tag; // 用於保存隊列中的線程結點 7 8 ... 9 10 struct task_struct* running_thread() { 11 uint32_t esp; 12 asm ("mov %%esp, %0" : "=g" (esp)); 13 // 返回esp整數部分,即pcb起始地址 14 return (struct task_struct*)(esp & 0xfffff000); 15 } 16 17 ... 18 19 // 初始化線程基本信息 20 void init_thread(struct task_struct* pthread, char* name, int prio) { 21 memset(pthread, 0, sizeof(*pthread)); 22 strcpy(pthread->name, name); 23 if (pthread == main_thread) { 24 pthread->status = TASK_RUNNING; 25 } else { 26 pthread->status = TASK_READY; 27 } 28 pthread->priority = prio; 29 // 線程本身在內核態下使用的棧頂地址 30 pthread->self_kstack = (uint32_t*)((uint32_t)pthread + PG_SIZE); 31 pthread->ticks = prio; 32 pthread->elapsed_ticks = 0; 33 pthread->pgdir = NULL; 34 pthread->stack_magic = 0x19870916; // 自定義魔數 35 } 36 37 struct task_struct* thread_start(char* name, int prio, thread_func function, void* func_arg) { 38 struct task_struct* thread = get_kernel_pages(1); 39 init_thread(thread, name, prio); 40 thread_create(thread, function, func_arg); 41 list_append(&thread_ready_list, &thread->general_tag); 42 list_append(&thread_all_list, &thread->all_list_tag); 43 return thread; 44 } 45 46 static void make_main_thread(void) { 47 main_thread = running_thread(); 48 init_thread(main_thread, "main", 31); 49 list_append(&thread_all_list, &main_thread->all_list_tag); 50 }
代碼只須要看咱們重要的變化部分,也就是黃色部分便可。
首先咱們增長了兩個隊列(這個是個新數據結構,也是咱們定義的,這個細節就再也不講解了,相信隊列你們都知道)
接下來咱們提供了一個能夠獲取到當前線程的 task_struct 結構體的 running_thread 方法,其實就是取 esp 的整數頁的開頭部分
接下來咱們把 init_thread 的方法,爲 ticks 和 elapsed_ticks 賦值,ticks 簡單地等於 prio,說明優先級與分的時間片呈簡單的線性關係(相等)
最後 thread_start 再也不僞裝地直接運行了,而是把線程加入到隊列中,由另外一段代碼不斷從隊列中取出而後運行
如今咱們的線程,終於開始有點模樣了。
線程的結構,以及經過時鐘改變關鍵的變量,都已經萬事俱備了,這部分主要就是實現還未實現的 schedule 函數,也就是線程切換
shedule 函數很簡單,就是把當前線程放到隊列中,再從隊列中取出一個線程開始運行,經過 c 和彙編的組合來實現
1 // 實現任務調度 2 void schedule() { 3 struct task_struct* cur = running_thread(); 4 if (cur->status == TASK_RUNNING) { 5 // 只是時間片到了,加入就緒隊列隊尾 6 list_append(&thread_ready_list, &cur->general_tag); 7 cur->ticks = cur->priority; 8 cur->status = TASK_READY; 9 } else { 10 // 須要等某事件發生後才能繼續上 cpu,不加入就緒隊列 11 } 12 13 thread_tag = NULL; 14 // 就緒隊列取第一個,準備上cpu 15 thread_tag = list_pop(&thread_ready_list); 16 struct task_struct* next = elem2entry(struct task_struct, general_tag, thread_tag); 17 next->status = TASK_RUNNING; 18 switch_to(cur, next); 19 }
1 [bits 32] 2 section .text 3 global switch_to 4 switch_to: 5 ;棧中此處時返回地址 6 push esi 7 push edi 8 push ebx 9 push ebp 10 mov eax,[esp+20] ;獲得棧中的參數cur 11 mov [eax],esp ;保存棧頂指針esp,task_struct的self_kstack字段 12 13 mov eax,[esp+24] ;獲得棧中的參數next 14 mov esp,[eax] 15 pop ebp 16 pop ebx 17 pop edi 18 pop esi 19 ret
該函數是任務切換的關鍵,但代碼十分清晰,你們本身品味一下
還有一個問題沒有解決,就是咱們每次開一個線程,都是將他加到隊列裏,那必然就得有第一個默認被運行的且加到了隊列裏的線程,否則一切沒法開始呀
1 // 初始化線程環境 2 void thread_init(void) { 3 put_str("thread_init_start\n"); 4 list_init(&thread_ready_list); 5 list_init(&thread_all_list); 6 make_main_thread(); 7 put_str("thread_init done\n"); 8 }
就是這段代碼,把咱們 main 方法首先建立成了一個線程,這就是 一切的開始,以後的操做系統,便開啓了 中斷驅動的死循環 生涯。
最後 main 方法建立兩個線程看看效果
1 #include "print.h" 2 #include "init.h" 3 #include "thread.h" 4 5 void k_thread_a(void*); 6 void k_thread_b(void*); 7 8 int main(void){ 9 put_str("I am kernel\n"); 10 init_all(); 11 thread_start("k_thread_a", 31, k_thread_a, "argA "); 12 thread_start("k_thread_b", 8, k_thread_b, "argB "); 13 intr_enable(); 14 while(1) { 15 put_str("Main "); 16 } 17 return 0; 18 } 19 20 void k_thread_a(void* arg) { 21 char* para = arg; 22 while(1) { 23 put_str(para); 24 } 25 } 26 27 void k_thread_b(void* arg) { 28 char* para = arg; 29 while(1) { 30 put_str(para); 31 } 32 }
還算符合預期,不過留了兩個坑,你發現了麼?哈哈咱們得下講才能解決
若是你對自制一個操做系統感興趣,不妨跟隨這個系列課程看下去,甚至加入咱們(下方有公衆號和小助手微信),一塊兒來開發。
《操做系統真相還原》這本書真的贊!強烈推薦
當你看到該文章時,代碼可能已經比文章中的又多寫了一些部分了。你能夠經過提交記錄歷史來查看歷史的代碼,我會慢慢梳理提交歷史以及項目說明文檔,爭取給每一課都準備一個可執行的代碼。固然文章中的代碼也是全的,採用複製粘貼的方式也是徹底能夠的。
若是你有興趣加入這個自制操做系統的大軍,也能夠在留言區留下您的聯繫方式,或者在 gitee 私信我您的聯繫方式。
本課程打算出系列課程,我寫到哪以爲能夠寫成一篇文章了就寫出來分享給你們,最終會完成一個功能全面的操做系統,我以爲這是最好的學習操做系統的方式了。因此中間遇到的各類坎也會寫進去,若是你能持續跟進,跟着我一塊寫,必然會有很好的收貨。即便沒有,交個朋友也是好的哈哈。
目前的系列包括
微信公衆號
我要去阿里(woyaoquali)
小助手微信號
Angel(angel19980323)