首页 > 其他分享 >实验15.多线程调度

实验15.多线程调度

时间:2024-07-25 18:25:19浏览次数:20  
标签:include 15 struct thread void list 调度 线程 多线程

简介

实验.多线程调度

内核线程

    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

在这里插入图片描述

标签:include,15,struct,thread,void,list,调度,线程,多线程
From: https://blog.csdn.net/laidene/article/details/140697008

相关文章

  • 代码随想录 day8|| 151 翻转单词 28 字符串匹配 459 重复子串
    151翻转单词funcreverseWords(sstring)string{ //思考:判断单词条件是从0或者空格开始到终或者空格结尾,最简单方式strings.split之后变成切片,然后反转就行了 //考虑双指针,左指针指向单词首位,右指针指向单词末尾 varres[]byte varleft,rightint forright<len......
  • CF1015F 题解
    题面考虑这样的匹配问题,可以想如何确定第一次匹配,这样可以不重不漏地计数。考虑dp的时候同时维护有几个括号没有匹配,匹配到\(s\)的第几位,所以令\(f(i,j,k)\)表示dp到(要计数的序列的)第\(i\)个字符,有\(j\)个左括号没有匹配,匹配到\(s\)的第\(k\)位。转移很容易,考......
  • 程序设计:C++入门教程(速成) + 15道经典例题(附带例题解析)
    本文章以实用为主,若实在是不明白文字所表达的内容,无脑复制代码,自己动手运行一下,实验一下即可理解文章内容,放心,代码是全的,全选复制粘贴即可不废话,直接开整数据类型常用数据类型int:整数类型,用于表示整数值。例如:1,2,-3,0等。float:单精度浮点数类型,用于表示带有小数点的数......
  • 钢铁百科:15#钢材质解析,15号钢四切,NB二探保材质保性能
    一、15#钢执行标准:   -GB/T711-2017:这是目前关于15#钢板的主要执行标准,该标准确保了钢板的质量、生产流程和性能均达到国家规定的要求。二、15#钢化学成分:*碳(C):0.12~0.18%*硅(Si):0.17~0.37%*锰(Mn):0.35~0.65%*硫(S)和磷(P)的含量均不超过0.035%此外,还可能含有少量的铬(Cr)、......
  • leetcode 1555 银行账号概要(postgresql)
    需求用户表:Users±-------------±--------+|ColumnName|Type|±-------------±--------+|user_id|int||user_name|varchar||credit|int|±-------------±--------+user_id是这个表的主键。表中的每一列包含每一个用户当前的额度信息。交易......
  • leetcode 1549 每件商品的最新订单(postgresql)
    需求表:Customers±--------------±--------+|ColumnName|Type|±--------------±--------+|customer_id|int||name|varchar|±--------------±--------+customer_id是该表主键.该表包含消费者的信息.表:Orders±--------------±--------+|......
  • P2671 [NOIP2015 普及组] 求和 题解
    题目:P2671NOIP2015普及组求和题意给定一个带有颜色和数字的序列,我们要寻找三元组\((x,y,z)\)满足以下条件:\(y\)为\(x\)和\(z\)的中点且都为整数。\(color[x]=color[z]\)。我们命这样一个三元组对答案的贡献为\((x+z)*(num[x]+num[z])\)。整个序列的总价值为每个......
  • 【多线程】单例模式
    ......
  • C#十种多线程模式介绍/对比
     一、Thread类:最直接的方式开启线程最直接的方式是使用System.Threading.Thread类。这种方式简单明了,适合快速启动线程执行简单任务。Threadthread=newThread(()=>Console.WriteLine("Hellofromanewthread!"));thread.Start();使用场景:快速启动执行简单任务。优点:简......
  • 华为路由器漏洞CVE-2017-17215
    固件获取https://github.com/Vu1nT0tal/IoT-vulhub/tree/master/HUAWEI/CVE-2017-17215/firmware提取binwalk-MerHG532eV100R001C01B020_upgrade_packet.bin启动qemu-systemsudoqemu-system-mips-Mmalta-kernelvmlinux-3.2.0-4-4kc-malta-hdadebian_wheezy_mips......