简介
实验.多线程调度
内核线程
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