《操作系统真象还原》第九章(2)——线程

发布于:2025-04-22 ⋅ 阅读:(18) ⋅ 点赞:(0)

《操作系统真象还原》第九章(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函数,到时候再说。

总结一下,完整的调度过程需要三部分的配合。

  1. 时钟中断处理函数。
  2. 调度器schedule。
  3. 任务切换函数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个小时到现在。累得够呛,懒得再写测试过程了,前面的代码也不一定全部正确,仅供参考吧。


网站公告

今日签到

点亮在社区的每一天
去签到