实验15.多线程调度

简介

实验.多线程调度

内核线程

text 复制代码
    1.在时钟中断函数中处理中,减少当前线程pcb的tick,tick为0则启动调度
    2.调度,把当前线程pcb放入就绪对立队尾,把就绪线程队首拿出来执行

主要代码

引导

复制代码
省略

内核

list.h

c 复制代码
#ifndef __LIB_KERNEL_LIST_H
#define __LIB_KERNEL_LIST_H

#include "global.h"
#include "stdint.h"

/// 拿到menber相当于struct_type的偏移
/// struct_type 结构体
// member 结构体的属性
#define offset(struct_type, member) (int)(&((struct_type*)0)->member)

///  返回这个属性地址的结构体地址
/// struct_type 结构体
/// struct_member_name 结构体属性
/// elem_ptr 结构体的属性的地址
#define elem2entry(struct_type, struct_member_name, elem_ptr) \
    (struct_type*)((int)elem_ptr - offset(struct_type, struct_member_name))

/// @brief 链表元素
struct list_elem {
    struct list_elem* prev;  // 前躯元素
    struct list_elem* next;  // 后继元素
};

/// @brief 双向链表
struct list {
    // 这两个就如同两堵墙,在墙中间的才是元素
    struct list_elem head;  // 队首 注意: 第一个元素是head.next!!!
    struct list_elem tail;  // 队尾
};

/// @brief 自定义函数类型function
typedef bool(function)(struct list_elem*, int arg);

void list_init(struct list*);
void list_insert_before(struct list_elem* before, struct list_elem* elem);
void list_push(struct list* plist, struct list_elem* elem);
void list_iterate(struct list* plist);
void list_append(struct list* plist, struct list_elem* elem);
void list_remove(struct list_elem* pelem);
struct list_elem* list_pop(struct list* plist);
bool list_empty(struct list* plist);
uint32_t list_len(struct list* plist);
struct list_elem* list_traversal(struct list* plist, function func, int arg);
bool elem_find(struct list* plist, struct list_elem* obj_elem);
#endif

list.c

c 复制代码
#include "list.h"
#include "interrupt.h"

/// @brief 初始化双向链表
/// @param list 链表指针
void list_init(struct list* list) {
    list->head.prev = NULL;
    list->head.next = &list->tail;
    list->tail.prev = &list->head;
    list->tail.next = NULL;
}

/// @brief 把链表元素elem插入在元素before之前
/// @param before 链表元素
/// @param elem  链表元素
void list_insert_before(struct list_elem* before, struct list_elem* elem) {
    // 关闭中断
    enum intr_status old_status = intr_disable();

    // 将before前驱元素的后继元素更新为elem, 暂时使before脱离链表
    before->prev->next = elem;
    // 更新elem自己的前驱结点为before的前驱
    elem->prev = before->prev;
    // 更新elem自己的后继结点为before, 于是before又回到链表
    elem->next = before;
    // 更新before的前驱结点为elem
    before->prev = elem;

    // 恢复原来的中断状态
    intr_set_status(old_status);
}

/// @brief 添加元素到列表队首,类似栈push操作
/// @param plist 队列
/// @param elem 元素
void list_push(struct list* plist, struct list_elem* elem) {
    list_insert_before(plist->head.next, elem);
}

/// @brief 追加元素到链表队尾
/// @param plist
/// @param elem
void list_append(struct list* plist, struct list_elem* elem) {
    list_insert_before(&plist->tail, elem);
}

/// @brief 使元素pelem脱离链表
/// @param pelem 元素
void list_remove(struct list_elem* pelem) {
    // 关闭中断
    enum intr_status old_status = intr_disable();

    pelem->prev->next = pelem->next;
    pelem->next->prev = pelem->prev;

    // 恢复中断状态
    intr_set_status(old_status);
}

/// @brief 将链表第一个元素弹出并返回,类似栈的pop操作
/// @param list 双向链表
struct list_elem* list_pop(struct list* plist) {
    struct list_elem* elem = plist->head.next;
    list_remove(elem);
    return elem;
}

/// @brief 从链表中查找元素obj_elem,
/// @param plist
/// @param obj_elem
/// @return 成功时返回true,失败时返回false
bool elem_find(struct list* plist, struct list_elem* obj_elem) {
    struct list_elem* elem = plist->head.next;
    while (elem != &plist->tail) {
        if (elem == obj_elem) { return true; }
        elem = elem->next;
    }
    return false;
}

/// @brief 遍历列表内所有元素,逐个判断是否有符合条件的元素
/// @param list 链表
/// @param function 判断函数,返回true位满足条件,false位不满足条件
/// @param int 参数个数
struct list_elem* list_traversal(struct list* plist, function func, int arg) {
    struct list_elem* elem = plist->head.next;
    if (list_empty(plist)) return NULL;

    while (elem != &plist->tail) {
        if (func(elem, arg)) {  // func返回ture则认为该元素在回调函数中符合条件,直接返回
            return elem;
        }
        elem = elem->next;
    }
    return NULL;
}

/// @brief 返回链表长度
/// @param plist 链表
/// @return 链表长度
uint32_t list_len(struct list* plist) {
    struct list_elem* elem = plist->head.next;
    uint32_t length = 0;
    while (elem != &plist->tail) {
        length++;
        elem = elem->next;
    }
    return length;
}

/// @brief 判断链表是否为空
/// @param plist 链表
/// @return 空时返回true,否则返回false
bool list_empty(struct list* plist) {  // 判断队列是否为空
    return (plist->head.next == &plist->tail ? true : false);
}

switch.s

x86asm 复制代码
; switch.s
; 时间: 2024-07-25
; 来自: ccj
; 描述: 定义切换pcb的函数

;---切换pcb(cur_pcb,next_pcb) begin---
section .text
global switch_to
switch_to:
    ; 保存当前环境
    push esi
    push edi
    push ebx
    push ebp

    ; 保存当前环境到cur_pcb
    mov eax, [esp + 20] ; 得到栈中的参数cur_pcb, cur_pcb = [esp+20]
    mov [eax], esp      ; 保存esp到pcb的 self_kstack =
                        ; 结果 curpub.self_kstack = esp

    ; 切换当前环境到nex_pcb
    mov eax, [esp + 24] ; 得到栈中的参数next_pcb, next_pcb = [esp+24]
    mov esp, [eax]      ; esp = next_pcb.self_kstack

    ; 恢复当前环境
    pop ebp
    pop ebx
    pop edi
    pop esi

    ret
;---切换pcb(cur_pcb,next_pcb) end---

thread.h

c 复制代码
#ifndef __THREAD_THREAD_H
#define __THREAD_THREAD_H
#include "stdint.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;  // 状态
    uint8_t priority;         // 线程优先级
    char name[16];            //
    uint32_t stack_magic;     // 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);
#endif

thread.c

c 复制代码
// 文件: thread.c
// 时间: 2024-07-23
// 来自: ccj
// 描述: 申请了一个物理页做pcb,在pcb中定义了下一步kernel_thread,然后切换到该pcb的环境,运行

#include "thread.h"
#include "stdint.h"
#include "string.h"
#include "global.h"
#include "memory.h"

#define PG_SIZE 4096

/// @brief 由kernel_thread去执行function(func_arg)
/// @param function 函数指针
/// @param func_arg 函数参数
static void kernel_thread(thread_func* function, void* func_arg) { function(func_arg); }

/// @brief 初始化pcb,将待执行的函数和参数放到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 初始化线程基本信息
/// @param pthread pcb
/// @param name 线程名
/// @param prio 优先级
void init_thread(struct task_struct* pthread, char* name, int prio) {
    // 清0
    memset(pthread, 0, sizeof(*pthread));

    // 设置名字、状态、优先级
    strcpy(pthread->name, name);
    pthread->status = TASK_RUNNING;
    pthread->priority = prio;

    // self_kstack是线程自己在内核态下使用的栈顶地址
    pthread->self_kstack = (uint32_t*)((uint32_t)pthread + PG_SIZE);
    pthread->stack_magic = 0x19870916;  // 自定义的魔数
}

/// @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);

    // 切换到thread的上下文,并执行thread
    asm volatile("movl %0, %%esp; pop %%ebp; pop %%ebx; pop %%edi; pop %%esi; ret"
                 :
                 : "g"(thread->self_kstack)
                 : "memory");
    // movl %0, %%esp;
    // 把thread->self_kstack的值给esp

    // pop %%ebp; pop %%ebx; pop %%edi; pop %%esi;
    // 从thread->self_kstack中把这几个属性值给到寄存器

    // ret
    // 由于堆栈和这些关键寄存器已被设置为新线程的值,这会导致执行流跳转到新线程的上下文中继续执行
    return thread;
}

}

/// @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");
}

timer.h

c 复制代码
#ifndef __DEVICE_TIME_H
#define __DEVICE_TIME_H

#include "stdint.h"

void timer_init(void);
#endif

timer.c

c 复制代码
// 文件: timer.c
// 时间: 2024-07-23
// 来自: ccj
// 描述: 调快时钟,调快时钟、注册时钟中断来调度线程

#include "timer.h"
#include "io.h"
#include "print.h"
#include "interrupt.h"
#include "thread.h"
#include "debug.h"

#define IRQ0_FREQUENCY   100
#define INPUT_FREQUENCY  1193180
#define COUNTER0_VALUE   INPUT_FREQUENCY / IRQ0_FREQUENCY
#define CONTRER0_PORT    0x40
#define COUNTER0_NO      0
#define COUNTER_MODE     2
#define READ_WRITE_LATCH 3
#define PIT_CONTROL_PORT 0x43

uint32_t ticks;  // 中断开始,开始计数

/* 把操作的计数器counter_no、读写锁属性rwl、计数器模式counter_mode写入模式控制寄存器并赋予初始值counter_value
 */
static void frequency_set(uint8_t counter_port,
                          uint8_t counter_no,
                          uint8_t rwl,
                          uint8_t counter_mode,
                          uint16_t counter_value) {
    /* 往控制字寄存器端口0x43中写入控制字 */
    outb(PIT_CONTROL_PORT, (uint8_t)(counter_no << 6 | rwl << 4 | counter_mode << 1));
    /* 先写入counter_value的低8位 */
    outb(counter_port, (uint8_t)counter_value);
    /* 再写入counter_value的高8位 */
    outb(counter_port, (uint8_t)counter_value >> 8);
}

/* 时钟的中断处理函数 */
static void intr_timer_handler(void) {
    struct task_struct* cur_thread = running_thread();

    ASSERT(cur_thread->stack_magic == 0x19870916);  // 检查栈是否溢出

    cur_thread->elapsed_ticks++;  // 记录此线程占用的cpu时间嘀
    ticks++;                      // 记录总时钟数

    if (cur_thread->ticks == 0) {  // 若进程时间片用完就开始调度新的进程上cpu
        schedule();
    } else {  // 将当前进程的时间片-1
        cur_thread->ticks--;
    }
}

/* 初始化PIT8253 */
void timer_init(void) {
    put_str("[timer] timer_init start\n");

    /* 设置8253的定时周期,也就是发中断的周期 */
    frequency_set(CONTRER0_PORT, COUNTER0_NO, READ_WRITE_LATCH, COUNTER_MODE, COUNTER0_VALUE);

    // 注册时钟中断,用来调度线程
    register_handler(0x20, intr_timer_handler);
    put_str("[timer] timer_init done\n");
}

interupt.h

c 复制代码
#ifndef __KERNEL_INTERRUPT_H
#define __KERNEL_INTERRUPT_H

#include "stdint.h"

typedef void* intr_handler;

void idt_init(void);

/// @brief 中断机制状态
enum intr_status {
    INTR_OFF,  // 中断关闭
    INTR_ON    // 中断打开
};
enum intr_status intr_enable(void);
enum intr_status intr_disable(void);
enum intr_status intr_get_status(void);
enum intr_status intr_set_status(enum intr_status);
void register_handler(uint8_t vector_no, intr_handler function);

#endif

interupt.c

c 复制代码
// 文件: timer.c
// 时间: 2024-07-23
// 来自: ccj
// 描述: 调快时钟,调快时钟、注册时钟中断来调度线程

#include "timer.h"
#include "io.h"
#include "print.h"
#include "interrupt.h"
#include "thread.h"
#include "debug.h"

#define IRQ0_FREQUENCY   100
#define INPUT_FREQUENCY  1193180
#define COUNTER0_VALUE   INPUT_FREQUENCY / IRQ0_FREQUENCY
#define CONTRER0_PORT    0x40
#define COUNTER0_NO      0
#define COUNTER_MODE     2
#define READ_WRITE_LATCH 3
#define PIT_CONTROL_PORT 0x43

uint32_t ticks;  // 中断开始,开始计数

/* 把操作的计数器counter_no、读写锁属性rwl、计数器模式counter_mode写入模式控制寄存器并赋予初始值counter_value
 */
static void frequency_set(uint8_t counter_port,
                          uint8_t counter_no,
                          uint8_t rwl,
                          uint8_t counter_mode,
                          uint16_t counter_value) {
    /* 往控制字寄存器端口0x43中写入控制字 */
    outb(PIT_CONTROL_PORT, (uint8_t)(counter_no << 6 | rwl << 4 | counter_mode << 1));
    /* 先写入counter_value的低8位 */
    outb(counter_port, (uint8_t)counter_value);
    /* 再写入counter_value的高8位 */
    outb(counter_port, (uint8_t)counter_value >> 8);
}

/* 时钟的中断处理函数 */
static void intr_timer_handler(void) {
    struct task_struct* cur_thread = running_thread();

    ASSERT(cur_thread->stack_magic == 0x19870916);  // 检查栈是否溢出

    cur_thread->elapsed_ticks++;  // 记录此线程占用的cpu时间嘀
    ticks++;                      // 记录总时钟数

    if (cur_thread->ticks == 0) {  // 若进程时间片用完就开始调度新的进程上cpu
        schedule();
    } else {  // 将当前进程的时间片-1
        cur_thread->ticks--;
    }
}

/* 初始化PIT8253 */
void timer_init(void) {
    put_str("[timer] timer_init start\n");

    /* 设置8253的定时周期,也就是发中断的周期 */
    frequency_set(CONTRER0_PORT, COUNTER0_NO, READ_WRITE_LATCH, COUNTER_MODE, COUNTER0_VALUE);

    // 注册时钟中断,用来调度线程
    register_handler(0x20, intr_timer_handler);
    put_str("[timer] timer_init done\n");
}

init.h

c 复制代码
#ifndef __KERNEL_INIT_H
#define __KERNEL_INIT_H
void init_all(void);
#endif

init.c

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"

/// @brief 内核所有初始化
void init_all() {
    put_str("init all\n");

    idt_init();    // 初始化中断
    timer_init();  // 调快时钟、注册时钟中断来调度线程
    mem_init();  // 初始化内存管理系统
    thread_init();
}

main.c

c 复制代码
// 文件: main.c
// 时间: 2024-07-19
// 来自: ccj
// 描述: 内核从此处开始

#include "print.h"
#include "init.h"
#include "thread.h"
#include "interrupt.h"

void k_thread_a(void*);
void k_thread_b(void*);

int main(void) {
    put_str("I am kernel\n");

    init_all();

    thread_start("k_thread_a", 4, k_thread_a, "argA\n");
    thread_start("k_thread_a", 16, k_thread_b, "argB\n");

    intr_enable();  // 打开中断,使时钟中断起作用
    while (1) { put_str("Main\n"); };
    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); }
}

编译

复制代码
省略

运行

start.sh

shell 复制代码
#/bin/bash
# 文件: start.sh
# 描述: 启动bochs
# 时间: 2024-07-19
# 来自: ccj


set -x

bin/bochs -f bochsrc.disk
相关推荐
tb_first22 分钟前
k8sday13数据存储(1.5/2)
linux·运维·服务器·云原生·容器·kubernetes
逢生博客2 小时前
Ubuntu 安装 gvm 管理 Go 语言开发环境
linux·ubuntu·golang·gvm
多吃蔬菜!!!3 小时前
vscode 搭建C/C++开发环境搭建(linux)
linux·c语言·c++
李李李li3 小时前
Ubuntu 22.04 安装tensorrt
linux·tensorrt
phoenix09815 小时前
Linux入门DAY29
linux·运维
入秋5 小时前
Linux服务器安装部署 Nginx、Redis、PostgreSQL、Docker
linux·前端
Mr. Cao code6 小时前
使用Tomcat Clustering和Redis Session Manager实现Session共享
java·linux·运维·redis·缓存·tomcat
zcz16071278216 小时前
Linux 网络命令大全
linux·运维·网络
the sun346 小时前
Reactor设计模式及其在epoll中的应用
linux·运维·服务器·c++
喜欢你,还有大家6 小时前
Linux笔记7——shell编程基础-1
linux·运维·笔记