实验15.多线程调度

简介

实验.多线程调度

内核线程

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

主要代码

引导

省略

内核

list.c

// 文件: list.c
// 时间: 2024-07-25
// 来自: ccj
// 描述: 线程pcb双向链表,注意: 在操作链表元素时要关闭中断

/// 拿到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))

剩下省略

switch.s

; 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.c

// 文件: thread.c
// 时间: 2024-07-23
// 来自: ccj
// 描述: 初始化时把主线程加入全局队列,然后在定义开启线程的函数,定义线程调度
#include "thread.h"

#include "stdint.h"
#include "string.h"
#include "global.h"
#include "memory.h"
#include "interrupt.h"
#include "debug.h"
#include "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;  // 用于保存队列中的线程结点

// 引用声明.s的switch_to
extern void switch_to(struct task_struct* cur, struct task_struct* next);

/// @brief 获取当前线程pcb指针
/// @return
struct task_struct* running_thread() {
    // 获取esp指针
    uint32_t esp;
    asm("mov %%esp, %0" : "=g"(esp));

    // esp % 4096就是pcb起始地址
    return (struct task_struct*)(esp & 0xfffff000);
}

/// @brief 由kernel_thread去执行function(func_arg)
/// @param function 函数指针
/// @param func_arg 函数参数
static void kernel_thread(thread_func* function, void* func_arg) {
    // 执行function前要开中断,避免后面的时钟中断被屏蔽,而无法调度其它线程
    intr_enable();
    function(func_arg);
}

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

    // 设置状态
    if (pthread == main_thread) {  // 如果是主线程pcb
        pthread->status = TASK_RUNNING;
    } else {
        pthread->status = TASK_READY;
    }

    // 初始化名字、优先级、时钟、总时钟、魔数
    strcpy(pthread->name, name);
    pthread->priority = prio;
    pthread->ticks = prio;
    pthread->elapsed_ticks = 0;
    pthread->pgdir = NULL;
    pthread->stack_magic = 0x19870916;  // 自定义的魔数

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

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

    // 加入就绪线程队列
    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;
}

/// @brief 将kernel中的main函数完善为主线程
/// @param
static void make_main_thread(void) {
    // 咱们在loader.S中进入内核时的mov esp,0xc009f000,在压入四个4个栈指针,所以主线程的pcb地址为0xc009e000
    // 不需要通过get_kernel_page另分配一页
    main_thread = running_thread();
    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);
}

/// @brief 实现任务调度
void schedule() {
    ASSERT(intr_get_status() == INTR_OFF);

    struct task_struct* cur = running_thread();

    // 若此线程只是cpu时间片到了,将其加入到就绪队列尾
    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再重置为其priority;
        cur->status = TASK_READY;
    } else {
        /* 若此线程需要某事件发生后才能继续上cpu运行,
        不需要将其加入队列,因为当前线程不在就绪队列中。*/
    }

    // 就绪队列队首弹出,准别切换到这个线程
    ASSERT(!list_empty(&thread_ready_list));
    thread_tag = NULL;
    thread_tag = list_pop(&thread_ready_list);

    // 拿到队首的pcb
    struct task_struct* next = elem2entry(struct task_struct, general_tag, thread_tag);
    next->status = TASK_RUNNING;

    // 切换
    switch_to(cur, next);
}

/// @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.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.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.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

// 文件: 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

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


set -x

bin/bochs -f bochsrc.disk

在这里插入图片描述

  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在多线调度算法模拟实验中,通常会研究操作系统如何有效地分配处理器时间片给多个并发执行的线程。实验结论可能会包括以下几个方面: 1. **不同调度算法的效果**:比如,抢占式调度(如轮转、优先级级聝)与非抢占式调度(如时间片轮转)在响应时间和吞吐量上会有显著区别。抢占式调度可能导致较小的CPU利用率,但能更快地响应新任务;非抢占式调度可能提供更好的CPU利用率,但长任务可能会阻塞短任务。 2. **公平性**:实验可能会探讨各种调度策略是否能保证所有线程有大致相等的运行时间,这对于多任务环境中的并发体验至关重要。 3. **死锁和竞争条件**:并发情况下,可能会遇到线程间的资源竞争导致死锁或性能下降。实验结论可能会指出哪些调度算法有助于避免这类问题,或者在发生时如何恢复。 4. **优先级反转**:如果使用了具有优先级的线程,实验可能会研究优先级反转的问题,即低优先级线程长时间占用处理器,高优先级线程被阻塞的情况。 5. **性能瓶颈**:通过模拟,可以识别出哪种类型的线程或任务类型最容易成为系统的瓶颈,从而为优化提供依据。 6. **适应性和可扩展性**:某些调度算法可能对小规模线程组表现良好,但随着线程数量增加,其效率可能会降低。实验结果会分析算法在不同负载下的表现。 **相关问题--:** 1. 在实验中如何衡量调度算法的性能? 2. 抢占式和非抢占式调度在什么情况下更适用? 3. 如何通过调整调度策略来提高系统的整体响应速度?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值