已完成实验
简介
实验 16.用锁实现终端输出
总结
注意:gcc一定不能用11.4,换成书上环境,我是用的4.4.7能过正确。
使用gcc11一直错误,怀疑人生了都。
主要代码
引导
省略
内核
console.c
// 文件: console.c
// 时间: 2024-07-29
// 来自: ccj
// 描述: 控制台打印相关,用一个锁保证打印正常
#include "console.h"
#include "print.h"
#include "stdint.h"
#include "sync.h"
#include "thread.h"
static struct lock console_lock; // 控制台锁
/* 初始化终端 */
void console_init() { lock_init(&console_lock); }
/* 获取终端 */
void console_acquire() { lock_acquire(&console_lock); }
/* 释放终端 */
void console_release() { lock_release(&console_lock); }
/* 终端中输出字符串 */
void console_put_str(char* str) {
console_acquire();
put_str(str);
console_release();
}
/* 终端中输出字符 */
void console_put_char(uint8_t char_asci) {
console_acquire();
put_char(char_asci);
console_release();
}
/* 终端中输出16进制整数 */
void console_put_int(uint32_t num) {
console_acquire();
put_int(num);
console_release();
}
sync.h
// 文件: sync.h
// 时间: 2024-07-29
// 来自: ccj
// 描述: 信号量、琐数据结构
#ifndef __THREAD_SYNC_H
#define __THREAD_SYNC_H
#include "list.h"
#include "stdint.h"
#include "thread.h"
/* 信号量结构 */
struct semaphore {
uint8_t value;
struct list waiters;
};
/* 锁结构 */
struct lock {
struct task_struct* holder; // 锁的持有者
struct semaphore semaphore; // 用二元信号量实现锁
uint32_t holder_repeat_nr; // 锁的持有者重复申请锁的次数
};
void sema_init(struct semaphore* psema, uint8_t value);
void sema_down(struct semaphore* psema);
void sema_up(struct semaphore* psema);
void lock_init(struct lock* plock);
void lock_acquire(struct lock* plock);
void lock_release(struct lock* plock);
#endif
sync.c
// 文件: sync.c
// 时间: 2024-07-29
// 来自: ccj
// 描述: 锁的实现
// 1.二元信号亮的p操作会尝试获取信号量,获取失败阻塞线程
// 2.二元信号亮的v操作释放信号量,把所里等待队列队首加入就绪队列队首
#include "sync.h"
#include "list.h"
#include "global.h"
#include "debug.h"
#include "interrupt.h"
/* 初始化信号量 */
void sema_init(struct semaphore* psema, uint8_t value) {
psema->value = value; // 为信号量赋初值
list_init(&psema->waiters); // 初始化信号量的等待队列
}
/* 初始化锁plock */
void lock_init(struct lock* plock) {
plock->holder = NULL;
plock->holder_repeat_nr = 0;
sema_init(&plock->semaphore, 1); // 信号量初值为1
}
/// @brief 信号量down操作,获取信号量,获取失败阻塞
/// @param psema 信号量
void sema_down(struct semaphore* psema) {
/* 关中断来保证原子操作 */
enum intr_status old_status = intr_disable();
// 若value为0,表示已经被别人持有
while (psema->value == 0) {
/* 当前线程不应该已在信号量的waiters队列中 */
ASSERT(!elem_find(&psema->waiters, &running_thread()->general_tag));
if (elem_find(&psema->waiters, &running_thread()->general_tag)) {
PANIC("sema_down: thread blocked has been in waiters_list\n");
}
/* 若信号量的值等于0,则当前线程把自己加入该锁的等待队列,然后阻塞自己 */
list_append(&psema->waiters, &running_thread()->general_tag);
thread_block(TASK_BLOCKED); // 阻塞线程,直到被唤醒
}
/* 若value为1或被唤醒后,会执行下面的代码,也就是获得了锁。*/
psema->value--;
ASSERT(psema->value == 0);
/* 恢复之前的中断状态 */
intr_set_status(old_status);
}
/// @brief 信号量的up操作
/// @param psema 释放信号量,把所里等待队列队首加入就绪队列队首
void sema_up(struct semaphore* psema) {
/* 关中断,保证原子操作 */
enum intr_status old_status = intr_disable();
// 唤醒等待队列中的一个线程
ASSERT(psema->value == 0);
if (!list_empty(&psema->waiters)) {
struct task_struct* thread_blocked =
elem2entry(struct task_struct, general_tag, list_pop(&psema->waiters));
thread_unblock(thread_blocked);
}
psema->value++;
ASSERT(psema->value == 1);
/* 恢复之前的中断状态 */
intr_set_status(old_status);
}
/* 获取锁plock */
void lock_acquire(struct lock* plock) {
if (plock->holder != running_thread()) {
sema_down(&plock->semaphore); // 对信号量P操作,原子操作
plock->holder = running_thread();
ASSERT(plock->holder_repeat_nr == 0);
plock->holder_repeat_nr = 1;
} else { /* 排除曾经自己已经持有锁但还未将其释放的情况。*/
plock->holder_repeat_nr++;
}
}
/* 释放锁plock */
void lock_release(struct lock* plock) {
ASSERT(plock->holder == running_thread());
if (plock->holder_repeat_nr > 1) { /* 排除曾经自己已经持有锁但还未将其释放的情况。*/
plock->holder_repeat_nr--;
return;
}
ASSERT(plock->holder_repeat_nr == 1);
plock->holder = NULL; // 把锁的持有者置空放在V操作之前
plock->holder_repeat_nr = 0;
sema_up(&plock->semaphore); // 信号量的V操作,也是原子操作
}
thread.h
// 文件: thread.h
// 时间: 2024-07-29
// 来自: ccj
// 描述: 定义线程pcb、线程栈、线程中断栈数据结构
#ifndef __THREAD_THREAD_H
#define __THREAD_THREAD_H
#include "stdint.h"
#include "list.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; // 状态
char name[16]; //
uint8_t priority; // 线程优先级
uint8_t ticks; // 每次在处理器上执行的时间嘀嗒数
uint32_t elapsed_ticks; // 处理器上执行的时间嘀嗒总数
struct list_elem general_tag; // 放入就绪队列
struct list_elem all_list_tag; // 放入全部队列
uint32_t* pgdir; // pcb的虚拟地址
uint32_t stack_magic; // pcb魔数,用于检测栈的溢出
};
#endif
thread.c
// 文件: thread.c
// 时间: 2024-07-23
// 来自: ccj
// 描述: 线程相关定义
// 1.添加线程,申请pcb,加入就绪队列
// 2.线程调度,切换pcb
// 3.线程阻塞,设置状态非就绪,不加入就绪队列,启动调度
// 4.线程唤醒,设置状态就绪,加入就绪队列
#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 当前线程将自己阻塞,标志其状态为stat
/// @param 状态
void thread_block(enum task_status stat) {
/* stat取值为TASK_BLOCKED,TASK_WAITING,TASK_HANGING,也就是只有这三种状态才不会被调度*/
ASSERT(((stat == TASK_BLOCKED) || (stat == TASK_WAITING) || (stat == TASK_HANGING)));
enum intr_status old_status = intr_disable();
struct task_struct* cur_thread = running_thread();
cur_thread->status = stat; // 置其状态为stat
schedule(); // 将当前线程换下处理器
/* 待当前线程被解除阻塞后才继续运行下面的intr_set_status */
intr_set_status(old_status);
}
/// @brief 将线程pthread解除阻塞
/// @param pthread pcb
void thread_unblock(struct task_struct* pthread) {
enum intr_status old_status = intr_disable();
ASSERT(((pthread->status == TASK_BLOCKED) || (pthread->status == TASK_WAITING) ||
(pthread->status == TASK_HANGING)));
// 放到队列的最前面,使其尽快得到调度
if (pthread->status != TASK_READY) {
ASSERT(!elem_find(&thread_ready_list, &pthread->general_tag));
if (elem_find(&thread_ready_list, &pthread->general_tag)) {
PANIC("thread_unblock: blocked thread in ready_list\n");
}
list_push(&thread_ready_list, &pthread->general_tag);
pthread->status = TASK_READY;
}
intr_set_status(old_status);
}
/// @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");
}
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(); // 初始化线程
console_init(); // 控制台初始化最好放在开中断之前
}
main.c
// 文件: main.c
// 时间: 2024-07-19
// 来自: ccj
// 描述: 内核从此处开始
#include "print.h"
#include "init.h"
#include "thread.h"
#include "interrupt.h"
#include "console.h"
void k_thread(void*);
int main(void) {
put_str("I am kernel\n");
init_all();
thread_start("k_thread_a", 4, k_thread, "argA\n");
thread_start("k_thread_a", 16, k_thread, "argB\n");
thread_start("k_thread_a", 1, k_thread, "argC\n");
thread_start("k_thread_a", 1, k_thread, "argD\n");
thread_start("k_thread_a", 1, k_thread, "argE\n");
intr_enable(); // 打开中断,使时钟中断起作用
while (1) { console_put_str("Main\n"); };
return 0;
}
void k_thread(void* arg) {
char* para = arg;
while (1) { console_put_str(para); }
}
编译
makefile
# 文件: makefile
# 时间: 2024-07-20
# 来自: ccj
# 描述: 自动化构建
# 1.创建硬盘文件
# 2.编译bootloader、kernel
# 3.复制bootloader、kernel到硬盘
BUILD_DIR = build
KERNEL_BIN ?= ${BUILD_DIR}/kernel.bin
HARD_DISK ?= /home/c/tityos/hd60M.img
#---编译kernel begin---
# 汇编器、编译器、链接器
AS = nasm
CC = gcc
LD = ld
# 头文件目录
INCDIRS := lib/kernel \
lib \
kernel \
device \
thread \
# 遍历头文件目录并且加上-I 前缀 include -> -I include
INCLUDE := $(patsubst %, -I %, $(INCDIRS))
# 源文件目录
SRCDIRS := kernel \
lib/kernel \
device \
lib \
thread \
# 拿到所有的源文件相对路径
SFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.s))
CFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))
# 去除源文件的目录,只保留文件名
SFILENDIR := $(notdir $(SFILES))
CFILENDIR := $(notdir $(CFILES))
# 遍历源文件并添加上前缀 xxx.s -> build/xxx.o xxx.c -> build/xxx.o
SOBJS := $(patsubst %, ${BUILD_DIR}/%, $(SFILENDIR:.s=.o))
COBJS := $(patsubst %, ${BUILD_DIR}/%, $(CFILENDIR:.c=.o))
OBJS := $(SOBJS) $(COBJS)
# 重要!!! 给编译器指定.c文件的目录,否则会编译时找不到源文件
VPATH := $(SRCDIRS)
# 编译和链接选项
ASFLAGS = -f elf
CFLAGS = -Wall -m32 -fno-stack-protector -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes
LDFLAGS = -m elf_i386 -Ttityos.lds -e main -Map $(BUILD_DIR)/kernel.map
# 编译全部源文件
$(SOBJS) : ${BUILD_DIR}/%.o : %.s
$(AS) $(ASFLAGS) $< -o $@
$(COBJS) : ${BUILD_DIR}/%.o : %.c
$(CC) $(CFLAGS) $(INCLUDE) -o $@ $<
# 链接全部.o文件,生成内核二进制
${KERNEL_BIN} : $(OBJS)
$(LD) $(LDFLAGS) $^ -o $@
#---编译kernel end---
# 以下为make的操作
.PHONY : print mk_dir create_harddisk bootloader kernel all
# 打印变量
print:
@echo $(OBJS)
@echo $(HARD_DISK)
# 创建生成文件目录
mk_dir:
if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fi
# 创建硬盘 注意:bochs2.6.8版本可以
create_harddisk:
./bin/bximage -mode=create -hd=60M -imgmode=flat -q $(HARD_DISK)
# 编译 bootloader
bootloader:
nasm -I boot/include/ -o $(BUILD_DIR)/mbr.bin boot/mbr.s
nasm -I boot/include/ -o $(BUILD_DIR)/loader.bin boot/loader.s
dd if=/home/c/tityos/${BUILD_DIR}/mbr.bin of=${HARD_DISK} bs=512 count=1 conv=notrunc
dd if=/home/c/tityos/${BUILD_DIR}/loader.bin of=${HARD_DISK} bs=512 count=3 seek=2 conv=notrunc
# 编译 kernel
kernel: ${KERNEL_BIN}
dd if=${KERNEL_BIN} of=${HARD_DISK} bs=512 count=200 seek=9 conv=notrunc
# 删除生成文件
clean:
rm $(HARD_DISK)
cd $(BUILD_DIR) && rm -f ./*
rmdir $(BUILD_DIR)
all: mk_dir create_harddisk bootloader kernel
运行
start.sh
#/bin/bash
# 文件: start.sh
# 描述: 启动bochs
# 时间: 2024-07-19
# 来自: ccj
set -x
bin/bochs -f bochsrc.disk