《操作系统真象还原》第九章(2)——线程
文章目录
前言
本篇博客是第九章线程的第二部分,第一部分我们实现了线程,实现了我们自己系统的链表,本章就是要实现多线程轮询机制。
第一部分链接:《操作系统真象还原》第九章(1)——线程-CSDN博客
多线程调度
简单优先级调度的基础
本节任务是把thread.c 和 thread.h 进一步完善,在原有线程的基础上添加新的功能。
thread.h新增部分
#include "../lib/kernel/list.h"
...
/* 线程或进程的pcb程序控制块 */
struct task_struct {
uint32_t *self_kstack; // 线程自己的内核栈栈顶指针
enum thread_status status; // 线程的状态
uint8_t priority; // 线程的优先级
uint8_t ticks; // 线程的时间片,在处理器上运行的时间滴答数
uint32_t elapsed_ticks; // 线程的运行时间,也就是这个线程已经执行了多久
struct list_elem general_tag; // 用于线程在一般队列中的节点
struct list_elem all_list_tag; // 用于线程在thread_all_list队列中的节点
uint32_t *pgdir; // 如果是进程,这是进程的页表的虚拟地址,线程则置为NULL
char name[16]; // 线程的名字
uint32_t stack_magic; // 线程栈的魔数,边界标记,用来检测栈溢出
};
...
thread.c更新后
#include "thread.h"
#include "../lib/kernel/stdint.h"
#include "../lib/string.h"
#include "global.h"
#include "../kernel/memory.h"
#include "interrupt.h"
#define PAGE_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(void) {
uint32_t esp;
asm volatile("movl %%esp, %0" : "=g"(esp)); // 获取当前线程的栈顶地址
/* 取得esp整数部分 */
return (struct task_struct *)(esp & 0xfffff000); // 返回pcb指针
}
/* 由kernel_thread执行function(func_arg) */
static void kernel_thread(thread_func *function, void *func_arg) {
intr_enable(); // 开中断,避免后面的时钟中断被屏蔽,而无法调度其他线程
function(func_arg);
//...
}
/* 初始化线程栈thread_stack */
void thread_create(struct task_struct *pthread, thread_func function, void *func_arg) {
/* 先预留中断栈空间 */
pthread->self_kstack -= sizeof(struct intr_stack);
/* 再预留线程栈空间 */
pthread->self_kstack -= sizeof(struct thread_stack);
/* 填充线程栈 */
struct thread_stack *kthread_stack = (struct thread_stack *)pthread->self_kstack;
kthread_stack->eip = kernel_thread; // eip指向kernel_thread
kthread_stack->function = function; // 函数指针
kthread_stack->func_arg = func_arg; // 函数参数
kthread_stack->ebp = 0; // ebp寄存器值
kthread_stack->ebx = 0; // ebx寄存器值
kthread_stack->edi = 0; // edi寄存器值
kthread_stack->esi = 0; // esi寄存器值
}
/* 初始化线程基本信息 */
void init_thread(struct task_struct *pthread, char *name, int prio) {
memset(pthread, 0, sizeof(*pthread)); // 清空线程pcb
strcpy(pthread->name, name); // 线程名字
if(pthread == main_thread) { // 线程状态
pthread->status = TASK_RUNNING;
}
else {
pthread->status = TASK_READY;
}
/* self_kstack 是线程自己在内核态下使用的栈顶地址 */
pthread->self_kstack = (uint32_t *)((uint32_t)pthread + PAGE_SIZE); // 线程内核栈
pthread->priority = prio; // 线程优先级
pthread->ticks = prio; // 线程时间片
pthread->elapsed_ticks = 0; // 线程运行时间
pthread->pgdir = NULL; // 线程页表
pthread->stack_magic = 0x19870916; // 线程栈的魔数,边界标记,用来检测栈溢出
}
/* 创建一个优先级为prio的线程,线程名是name,执行的函数是function(func_arg) */
struct task_struct *thread_start(char *name, int prio, thread_func function, void *func_arg) {
struct task_struct *thread = get_kernel_pages(1); // 分配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;
}
/* 将kernel中的main函数完善成主线程 */
static void make_main_thread(void) {
/* 因为main线程早已运行,
*咱们在loader.S中进入内核时的mov esp,0xc009f000,
* 就是为其预留pcb的,因此pcb地址为0xc009e000,
* 不需要通过get_kernel_page另分配一页*/
main_thread = running_thread(); // 获取主线程pcb
init_thread(main_thread, "main", 31); // 初始化主线程基本信息
ASSERT(!elem_find(&thread_ready_list, &main_thread->all_list_tag)); // 确保主线程不在就绪队列
list_append(&thread_ready_list, &main_thread->general_tag); // 将主线程加入就绪队列
}
任务调度器和任务切换
本节要实现调度器和任务切换,调度器的工作就是根据任务的状态将其从处理器上换上换下。
我们先梳理思路:调度器读写就绪队列,增删里面的节点,节点是general_tag,代表一个个pcb。
每发生一次时钟中断,时钟中断的处理程序便将当前运行 线程的ticks 减 1。当ticks 为 0 时,时钟的中断处理程序调用调度器schedule,也就是该把当前线程换下 处理器了,让调度器选择另一个线程上处理器。
调度器从就绪队列取出队首元素,将pcb里线程的状态置为running。然后通过switc_to函数将新线程的寄存器环境恢复,开始执行新线程。
其他函数也可以调用schedule,例如后续的thread_block函数,到时候再说。
总结一下,完整的调度过程需要三部分的配合。
- 时钟中断处理函数。
- 调度器schedule。
- 任务切换函数switch_to。
注册时钟中断处理函数
我们之前的时钟中断函数使用的是通用函数general_intr_handler,也就是默认的中断处理函数。我们有两个打算:1.简单修改默认中断处理函数。2.写专门的时钟中断处理函数。
修改interrupt.c的general_intr_handler
/* 通用的中断处理函数,一般用在异常出现时的处理 */
static void general_intr_handler(uint8_t vec_nr) {
if (vec_nr == 0x27 || vec_nr == 0x2f) { // 0x2f是从片8259A上的最后一个irq引脚,保留
return; //IRQ7和IRQ15会产生伪中断(spurious interrupt),无须处理。
}
/* 将光标置为0,从屏幕左上角清出一片打印异常信息的区域,方便阅读 */
set_cursor(0); // 设置光标位置为0
int cursor_pos = 0; // 光标位置
while (cursor_pos < 320) { // 清除屏幕
put_char(' '); // 打印空格
cursor_pos++; // 光标位置加1
}
set_cursor(0); // 设置光标位置为0
put_str("!!! exception message begin !!!\n"); // 打印异常信息
set_cursor(88);
put_str(intr_name[vec_nr]); // 打印异常名称
if (vec_nr == 14) { // 如果是页面错误异常
int page_fault_vaddr = 0; // 页面错误地址
/* cr2寄存器存放造成page_fault的地址 */
asm volatile ("movl %%cr2, %0" : "=r" (page_fault_vaddr)); // 获取页面错误地址
put_str("\npage fault addr is "); // 打印页面错误地址
put_int(page_fault_vaddr); // 打印页面错误地址
}
put_str(" \n!!! exception message end !!!\n"); // 打印异常信息结束
// 目前处在关中断状态,不再调度进程,下面的死循环不再被中断覆盖
while (1); // 进入死循环
}
特别说明一下新增的函数set_cursor,这个函数声明在print.h,实现是print.S中.set_cursor,我们先修改print.S
global set_cursor
.set set_cursor,.set_cursor
然后修改print.h
//这个头文件定义了打印函数的原型
//包括打印单个字符、打印字符串和打印16进制整数的函数
#ifndef __LIB_KERNEL_PRINT_H
#define __LIB_KERNEL_PRINT_H
#include "stdint.h"
extern void put_char (uint8_t char_asci);
extern void put_str (char* message);
extern void put_int (uint32_t number);
extern void set_cursor (uint32_t position);
#endif
修改time.c,完成时钟中断处理函数
//时钟中断相关
#include "timer.h"
#include "io.h"
#include "print.h"
#include "../kernel/interrupt.h"
#include "thread.h"
#include "debug.h"
#define IRQ0_FREQUENCY 100
#define INPUT_FREQUENCY 1193180
#define COUNTER0_VALUE INPUT_FREQUENCY / IRQ0_FREQUENCY
#define COUNTER0_PORT 0X40
#define COUNTER0_NO 0
#define COUNTER_MODE 2
#define READ_WRITE_LATCH 3
#define PIT_COUNTROL_PORT 0x43
uint32_t ticks; // ticks是时钟中断的次数
void frequency_set(uint8_t counter_port ,uint8_t counter_no,uint8_t rwl,uint8_t counter_mode,uint16_t counter_value) {
outb(PIT_COUNTROL_PORT,(uint8_t) (counter_no << 6 | rwl << 4 | counter_mode << 1));
outb(counter_port,(uint8_t)counter_value);
outb(counter_port,(uint8_t)counter_value >> 8);
return;
}
/* 时钟的中断处理函数 */
static void intr_timer_handler(void) {
struct task_struct* cur_thread = running_thread();
ASSERT(cur_thread->stack_magic == 0x19870916); // 检测栈溢出
cur_thread->elapsed_ticks++; // 线程运行的时间加1
// 每次时钟中断,ticks加1
ticks++;
if (cur_thread->ticks == 0) { // 如果当前线程的时间片用完
schedule(); // 调度其他线程
} else {
cur_thread->ticks--; // 否则,当前线程的时间片减1
}
}
/* 初始化PIT8253 */
void timer_init(void) {
put_str("timer_init start!\n");
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");
return;
}
关于新函数register_handler,它的实现在interrupt.c里,在exception_init函数之后,在idt_init函数之前
/* 在中断处理程序数组第vector_no个元素中
注册安装中断处理程序function */
void register_handler(uint8_t vector_no, intr_handler function) {
idt_table[vector_no] = function; // 注册中断处理函数
}
实现调度器schedule
依然是修改thread.c,给出新增部分
/* 实现任务调度 */
void scheduler(void) {
ASSERT(intr_get_status() == INTR_OFF); // 确保中断关闭
struct task_struct *cur = running_thread(); // 获取当前线程pcb
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; // 重置时间片
cur->status = TASK_READY; // 设置当前线程状态为就绪
}
else {
/* 如果当前线程不是运行状态,则不需要重置时间片 */
}
ASSERT(!list_empty(&thread_ready_list)); // 确保就绪队列不为空
thread_tag = NULL; // 清空线程节点
thread_tag = list_pop(&thread_ready_list); // 从就绪队列中取出一个线程节点
struct task_struct *next = elem2entry(struct task_struct, \
general_tag, \
thread_tag); // 获取下一个线程pcb
next->status = TASK_RUNNING; // 设置下一个线程状态为运行
switch_to(cur, next); // 任务切换
}
关于宏elem2entry:这个宏定义在list.h
#define offset(struct_type,member) (int)(&((struct_type*)0)->member)
#define elem2entry(struct_type, struct_member_name, elem_ptr) \
(struct_type*)((int)elem_ptr - offset(struct_type, struct_member_name))
这个elem2entry宏的作用获取某个结构体成员对应的结构体的地址,原理是是用 elem_ptr 的地址减去elem_ptr 在结构体struct_type 中的偏移量。偏移量通过宏offset获取。
实现任务切换函数switch_to
书上先提了一下上下文保护,放一张图片
说一下我的理解:开机->bios->mbr->loader->kernel_main,主线程完成中断初始化、计时器初始化、内存池初始化、多次线程初始化等。目前这一系列过程完成后,main线程被多线程系统管理。
中断初始化后,我们的计时器硬件每过一段时间就给cpu发一个中断信号,也就是进行所谓的时钟中断,执行任意中断处理程序时,先保存进入中断前的上下文环境,这部分是kernel.S完成的,称为上下文保护的第一部分。第一部分保护的寄存器环境放在中断栈里。
中断处理程序有始有终,其中调用了schedule,为了后续能结束,要保存中断处理程序运行过程中的上下文环境,称为上下文保护的第二部分。第二部分保护的寄存器环境放在线程栈里。
通过时钟中断的处理程序,运行schedule,schedule运行switch_to,switch_to中实现上下文保护的第二部分。
;switch_to函数接受两个参数,第1个参数是当前线程cur
;第2个参数是下一个上处理器的线程next
;此函数的功能是保存cur线程的寄存器映像,将下一个线程next的寄存器映像装载到处理器。
[bits 32]
section .text
global switch_to
switch_to:
;-----以下是备份当前线程的上下文环境-----
;第一个位置是返回地址
push ESI
push EDI
push EBX
push EBP
mov EAX,[esp+20]
mov [EAX],esp ;获取cur,把esp保存到cur的结构体的self_kstack里
;-----以下是恢复下一个线程的环境-----
mov EAX,[esp+24]
mov esp,[EAX] ;获取next,把esp指向next的结构体的self_kstack
pop EBP
pop EBX
pop EDI
pop ESI
ret ;返回到scheduler
启用线程调度
thread.h完整代码
#ifndef __THREAD_THREAD_H
#define __THREAD_THREAD_H
#include "../lib/kernel/stdint.h"
#include "../lib/kernel/list.h"
/* 自定义通用函数类型,用来承载线程中函数的类型 */
typedef void thread_func(void*);
/* 进程、线程的状态 */
enum thread_status {
TASK_RUNNING, // 运行中
TASK_READY, // 就绪
TASK_BLOCKED, // 阻塞
TASK_WAITING, // 等待
TASK_HANGING, // 挂起
TASK_DIED // 死亡
};
/*********** 中断栈intr_stack ***********
* 此结构用于中断发生时保护程序(线程或进程)的上下文环境:
* 进程或线程被外部中断或软中断打断时,会按照此结构压入上下文
* 寄存器,intr_exit中的出栈操作是此结构的逆操作
* 此栈在线程自己的内核栈中位置固定,所在页的最顶端
********************************************/
/*进入中断后,在kernel.S中的中断入口程序“intr%1entry”所执行的上下
文保护的一系列压栈操作都是压入了此结构中。*/
struct intr_stack {
uint32_t vec_no; // 中断号
uint32_t edi; // 被中断的线程的edi寄存器值
uint32_t esi; // 被中断的线程的esi寄存器值
uint32_t ebp; // 被中断的线程的ebp寄存器值
uint32_t esp_dummy; // 被中断的线程的esp寄存器值
// 虽然pushad把esp也压入,但esp是不断变化的,所以会被popad忽略
uint32_t ebx; // 被中断的线程的ebx寄存器值
uint32_t edx; // 被中断的线程的edx寄存器值
uint32_t ecx; // 被中断的线程的ecx寄存器值
uint32_t eax; // 被中断的线程的eax寄存器值
uint32_t gs; // 被中断的线程的gs寄存器值
uint32_t fs; // 被中断的线程的fs寄存器值
uint32_t es; // 被中断的线程的es寄存器值
uint32_t ds; // 被中断的线程的ds寄存器值
/* 以下由cpu从低特权级到高特权级时压入 */
uint32_t err_code; // 错误码
void (*eip)(void); // 被中断的线程的eip寄存器值
// eip是指向无参数无返回值的函数的指针
uint32_t cs; // 被中断的线程的cs寄存器值
uint32_t eflags; // 被中断的线程的eflags寄存器值
void * esp; // 被中断的线程的esp寄存器值
uint32_t ss; // 被中断的线程的ss寄存器值
};
/*********** 线程栈thread_stack ***********
* 线程自己的栈,用于存储线程中待执行的函数
* 此结构在线程自己的内核栈中位置不固定,
* 仅用在switch_to(任务切换)时保存线程环境。
* 实际位置取决于实际运行情况。
******************************************/
struct thread_stack {
/*:关于下面四个寄存器,在被调函数运行完之后,这4个寄存器的
值必须和运行前一样,它必须在自己的栈中存储这些寄存器的值。*/
uint32_t ebp; // 线程的ebp寄存器值
uint32_t ebx; // 线程的ebx寄存器值
uint32_t edi; // 线程的edi寄存器值
uint32_t esi; // 线程的esi寄存器值
/* 线程第一次执行时,eip指向待调用的函数。其他
时候,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内核线程要执行的函数的参数
};
/* 线程或进程的pcb程序控制块 */
struct task_struct {
uint32_t *self_kstack; // 线程自己的内核栈栈顶指针
enum thread_status status; // 线程的状态
uint8_t priority; // 线程的优先级
uint8_t ticks; // 线程的时间片,在处理器上运行的时间滴答数
uint32_t elapsed_ticks; // 线程的运行时间,也就是这个线程已经执行了多久
struct list_elem general_tag; // 用于线程在一般队列中的节点
struct list_elem all_list_tag; // 用于线程在thread_all_list队列中的节点
uint32_t *pgdir; // 如果是进程,这是进程的页表的虚拟地址,线程则置为NULL
char name[16]; // 线程的名字
uint32_t stack_magic; // 线程栈的魔数,边界标记,用来检测栈溢出
};
struct task_struct *running_thread(void); // 获取当前线程的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); // 创建一个优先级为prio的线程,线程名是name,执行的函数是function(func_arg)
void schedule(void); // 线程调度函数
void thread_init(void); // 线程初始化函数
#endif
thread.c完整代码
#include "thread.h"
#include "../lib/string.h"
#include "../kernel/memory.h"
#include "interrupt.h"
#include "debug.h"
#include "print.h"
#define PAGE_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(void) {
uint32_t esp;
asm volatile("movl %%esp, %0" : "=g"(esp)); // 获取当前线程的栈顶地址
/* 取得esp整数部分 */
return (struct task_struct *)(esp & 0xfffff000); // 返回pcb指针
}
/* 由kernel_thread执行function(func_arg) */
static void kernel_thread(thread_func *function, void *func_arg) {
intr_enable(); // 开中断,避免后面的时钟中断被屏蔽,而无法调度其他线程
function(func_arg);
//...
}
/* 初始化线程栈thread_stack */
void thread_create(struct task_struct *pthread, thread_func function, void *func_arg) {
/* 先预留中断栈空间 */
pthread->self_kstack -= sizeof(struct intr_stack);
/* 再预留线程栈空间 */
pthread->self_kstack -= sizeof(struct thread_stack);
/* 填充线程栈 */
struct thread_stack *kthread_stack = (struct thread_stack *)pthread->self_kstack;
kthread_stack->eip = kernel_thread; // eip指向kernel_thread
kthread_stack->function = function; // 函数指针
kthread_stack->func_arg = func_arg; // 函数参数
//kthread_stack->ebp = (uint32_t*)((uint32_t)pthread->self_kstack + sizeof(struct thread_stack)); // ebp指向线程栈
kthread_stack->ebp = kthread_stack->ebx = \
kthread_stack->esi = kthread_stack->edi = 0; // ebp, ebx, edi, esi寄存器值
}
/* 初始化线程基本信息 */
void init_thread(struct task_struct *pthread, char *name, int prio) {
memset(pthread, 0, sizeof(*pthread)); // 清空线程pcb
strcpy(pthread->name, name); // 线程名字
if(pthread == main_thread) { // 线程状态
pthread->status = TASK_RUNNING;
}
else {
pthread->status = TASK_READY;
}
/* self_kstack 是线程自己在内核态下使用的栈顶地址 */
pthread->self_kstack = (uint32_t *)((uint32_t)pthread + PAGE_SIZE); // 线程内核栈
pthread->priority = prio; // 线程优先级
pthread->ticks = prio; // 线程时间片
pthread->elapsed_ticks = 0; // 线程运行时间
pthread->pgdir = NULL; // 线程页表
pthread->stack_magic = 0x19870916; // 线程栈的魔数,边界标记,用来检测栈溢出
}
/* 创建一个优先级为prio的线程,线程名是name,执行的函数是function(func_arg) */
struct task_struct *thread_start(char *name, int prio, thread_func function, void *func_arg) {
struct task_struct *thread = get_kernel_pages(1); // 分配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;
}
/* 将kernel中的main函数完善成主线程 */
static void make_main_thread(void) {
/* 因为main线程早已运行,
* 咱们在loader.S中进入内核时的mov esp,0xc009f000,
* 就是为其预留pcb的,因此pcb地址为0xc009e000,
* 不需要通过get_kernel_page另分配一页*/
main_thread = running_thread(); // 获取主线程pcb
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); // 将主线程加入就绪队列
}
/* 实现任务调度 */
void schedule(void) {
ASSERT(intr_get_status() == INTR_OFF); // 确保中断关闭
struct task_struct *cur = running_thread(); // 获取当前线程pcb
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; // 重置时间片
cur->status = TASK_READY; // 设置当前线程状态为就绪
}
else {
/* 如果当前线程不是运行状态,则不需要重置时间片 */
}
ASSERT(!list_empty(&thread_ready_list)); // 确保就绪队列不为空
thread_tag = NULL; // 清空线程节点
thread_tag = list_pop(&thread_ready_list); // 从就绪队列中取出一个线程节点
struct task_struct *next = elem2entry(struct task_struct, \
general_tag, \
thread_tag); // 获取下一个线程pcb
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(); // 创建主线程
put_str("thread_init done\n");
}
得把thread_init加入到init.c 中
//完成所有的初始化工作
#include "init.h"
#include "print.h"
#include "interrupt.h"
#include "timer.h"
#include "memory.h"
#include "thread.h"
/*负责初始化所有模块 */
void init_all() {
put_str("init_all\n"); // 打印初始化信息
idt_init(); // 初始化中断
timer_init(); // 初始化定时器
mem_init(); // 初始化内存
thread_init(); // 初始化线程
}
修改main.c
//内核的入口函数
#include "print.h"
#include "init.h"
#include "debug.h"
#include "memory.h"
#include "thread.h"
#include "interrupt.h"
void k_thread_a(void *); //线程函数声明
void k_thread_b(void *); //线程函数声明
/* 主线程 */
int main(void){
put_str("HongBai's OS\n");
init_all();//初始化所有模块
//中断测试,目前只完成了响应时钟中断
//asm volatile("sti");//打开中断
//asm volatile("cli");//关闭中断
//断言测试
//ASSERT(1 == 2); // 断言失败,会调用panic_spin函数
//申请内存测试
//void *addr = get_kernel_pages(3); //申请3页内存
//put_str("get_kernel_pages start vaddr is: ");
//put_int((uint32_t)addr); //输出虚拟地址
//put_str("\n");
//线程测试
thread_start("k_thread_a", 31, k_thread_a, "threadA ");
thread_start("k_thread_b", 8, k_thread_b, "threadB ");
intr_enable(); //打开中断
while(1) {
//主线程循环
put_str("main ");
}
return 0;
}
/* 在线程中运行的函数 */
void k_thread_a(void *arg){
char *para = arg;
while(1){
put_str(para);
}
}
/* 在线程中运行的函数 */
void k_thread_b(void *arg){
char *para = arg;
while(1){
put_str(para);
}
}
运行测试
可以看到三个线程同时交错运行。
结语
调试代码真的让人心力憔悴。6点钟初步完成这部分的代码,先是编译连接出若干报错,解决后运行出错,一直调试3个小时到现在。累得够呛,懒得再写测试过程了,前面的代码也不一定全部正确,仅供参考吧。