《操作系统真象还原》第十章(1)——输入输出系统
《操作系统真象还原》第十章(1)——输入输出系统
文章目录
- 《操作系统真象还原》第十章(1)——输入输出系统
- 前言
- 现状
- 同步机制——锁
- 原子操作
- 信号量
- 线程的阻塞与唤醒
- 实现代码thread.c
- 锁的实现
- 声明sync.h
- 实现sync.c
- 用锁实现终端输出
- 声明文件console.h
- 实现文件console.c
- 检验
- init.c
- main.c
- makefile
- 结语
前言
上一章博客:《操作系统真象还原》第九章(2)——线程-CSDN博客
本章参考博客:《操作系统真象还原》第十章 ---- 线程打印尚未成功 仍需此章锁机制完善努力 在前往最终章的路上激流勇进_操作系统真象还原第十章实验不对-CSDN博客
上一章结尾其实有点问题,多线程并没有按期望启动,这章要先实现第九章结尾的结果,再根据第九章结尾的说明开始第十章的学习。
第十章分为两大部分吧,第一部分是10.1和10.2,主要是通过锁完善线程。第二部分主要是和键盘相关。
这周的情况和感悟我都放到结语里面了,我觉得还是挺重要的,如果大家要做这个项目,希望能看看。
现状
贴出来main.c
//内核的入口函数
#include "print.h"
#include "init.h"
#include "debug.h"
#include "memory.h"
#include "thread.h"
#include "interrupt.h"void k_thread_a(void *); //线程函数声明
void k_thread_b(void *); //线程函数声明
/* 主线程 */
int main(void){put_str("HongBai's OS\n");init_all();//初始化所有模块//线程测试thread_start("k_thread_a", 31, k_thread_a, "ArgA ");thread_start("k_thread_b", 8, k_thread_b, "ArgB ");intr_enable(); //打开中断//while(1) ;while(1) {//主线程循环intr_disable(); //关闭中断put_str("Main ");intr_enable(); //打开中断}
}/* 在线程中运行的函数 */
void k_thread_a(void *arg){char *para = arg;while(1){intr_disable(); //关闭中断put_str(para);intr_enable(); //打开中断}
}/* 在线程中运行的函数 */
void k_thread_b(void *arg){char *para = arg;while(1){intr_disable(); //关闭中断put_str(para);intr_enable(); //打开中断}
}
目前的现状是,如果三个线程字符串字母数一致,可以正常打印,一旦不一致,就会报错,多线程目前肯定有误,但是这几天一直没有彻底解决。
我又调试了一下,发现启动多线程后,中断处理似乎无法正常运行,我目前的想法是先把第10章完成,然后再统一调试一下。
同步机制——锁
原子操作
上一章结尾图片遇到的问题,归根结底是,打印字符put_char操作应该是原子操作,它内部可以分为三个微操作——获取光标值、将光标值转化为地址并写入字符、更新光标值。这三个操作要不然都做要不然都不做,不能拆开。
我们发现,在多个访问公共资源时,必须保证线程内部操作的原子性,必须保证它们的操作时序正确。
- 公共资源 可以是公共内存、公共文件、公共硬件等,总之是被所有任务共享的一套资源。
- 临界区 程序要想使用某些资源,必然通过一些指令去访问这些资源,若多个任务都访问同一公共资源,那么 各任务中访问公共资源的指令代码组成的区域就称为临界区。临界区是 指程序中那些访问公共资源的指令代码,即临界区是指令,并不是受访的静态公共资源。
- 互斥 互斥也可称为排他,是指某一时刻公共资源只能被1个任务独享,即不允许多个任务同时出现在自己的临界 区中。公共资源在任意时刻只能被一个任务访问,即只能有一个任务在自己的临界区中执行,其他任务想访问公 共资源时,必须等待当前公共资源的访问者完全执行完他自己的临界区代码后(使用完资源后)再开始访问。
- 竞争条件 竞争条件是指多个任务以非互斥的方式同时进入临界区,大家对公共资源的访问是以竞争的方式并行 进行的,因此公共资源的最终状态依赖于这些任务的临界区中的微操作执行次序。 当多个任务“同时”读写公共资源时,也就是多个任务“同时”执行它们各自临界区中的代码时,它们以 混杂并行的方式访问同一资源,因此后面任务会将前一任务的结果覆盖,最终公共资源的结果取决于所有任务 的执行时序。这里所说的“同时”也可以指多任务伪并行,总之是指一个任务在自己的临界区中读写公共资 源,还没来得及出来(彻底执行完临界区所有代码),另一个任务也进入了它自己的临界区去访问同一资源。
总结:多线程访问公共资源时出问题的原因是产生了竞争条件,也就是多个任务同时出现在自己的临界区。为避免 产生竞争条件,必须保证任意时刻只能有一个任务处于临界区。因此,只要保证各线程自己临界区中的所有代码 都是原子操作,即临界区中的指令要么一条不做,要么一气呵成全部执行完,执行期间绝对不能被换下处理器。
信号量
信号量就是一个计数器(cnt),我们的计数器如果只有两个取值,称之为二元信号量,有两个操作up和down:
增加操作up包括两个微操作。
- 将信号量的值加1。
- 唤醒在此信号量上等待的线程。
减少操作down包括三个子操作。
- 判断信号量是否大于0。
- 若信号量大于0,则将信号量减1。
- 若信号量等于0,当前线程将自己阻塞,以在此信号量上等待。
通过up和down,可以实现互斥,大致流程为: 线程A进入临界区前先通过down操作获得锁(我们有强制通过锁进入临界区的手段),此时信号 量的值便为0。 后续线程B再进入临界区时也通过down操作获得锁,由于信号量为0,线程B便在此信号量上 等待,也就是相当于线程B进入了睡眠态。 当线程A从临界区出来后执行up操作释放锁,此时信号量的值重新变成1,之后线程A将线程B 唤醒。 线程B醒来后获得了锁,进入临界区。
线程的阻塞与唤醒
即实现up/down里面唤醒线程/阻塞线程的功能。
阻塞是线程本身进行的,唤醒往往是锁的持有者唤醒的别的线程。
实现代码thread.c
/*当前线程将自己阻塞,目前的状态变为stat*/
/*stat的取值是blocked、waiting、hanging*/
void thread_block(enum thread_status stat){//先检验stat是否处于这三种状态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; //修改目前正在运行的线程的pcb的状态schedule(); //目前已经不再是运行态,不会重置时间片intr_set_status(old_status); //schedule-swich后,才会执行
}/*锁的拥有者,将名为pthread的线程解除阻塞*/
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){//如果线程因为某些特殊原因还在就绪队列里if(elem_find(&thread_ready_list,&pthread->general_tag)){PANIC("thread_unblock:block thread in ready list");}//正常情况下,即不在就绪队列里list_push(&thread_ready_list,&pthread->general_tag);pthread->status = TASK_READY;}intr_set_status(old_status);
}
目前我们已经完成了锁的基本部件
锁的实现
目录在thread/sync.h .c,我先把代码贴出来,然后说一下一些问题。
声明sync.h
#ifndef __THREAD_SYNC_H
#define __THREAD_SYNC_H#include "../lib/kernel/stdint.h"
#include "list.h"
#include "thread.h"struct semaphore{ //信号量结构体,包含value、waiters两个成员uint8_t value; //信号量的值,是1时允许申请锁,0说明锁被占用struct list waiters;//用来记录在此信号量上等待的线程
};struct lock{ //锁结构体,包括semaphore、holder、holder_repeat_nr三个成员struct semaphore semaphore; //实现锁的结构是二元信号量struct task_struct* holder; //锁目前的持有者uint32_t holder_repeat_nr; //锁的持有者重复申请锁的次数
};
void sema_init(struct semaphore* psema);
void lock_init(struct lock* plock);
void sema_down(struct semaphore* psema);
void sema_up(struct semaphore* psema);
void lock_acquire(struct lock* plock);
void lock_release(struct lock* plock);
#endif
实现sync.c
#include "sync.h"
#include "thread.h"
#include "interrupt.h"
#include "debug.h"
/*初始化信号量psema*/
void sema_init(struct semaphore* psema){psema->value = 0;list_init(&psema->waiters);
}/*初始化锁plock*/
void lock_init(struct lock* plock){plock->holder = NULL;plock->holder_repeat_nr = 0;sema_init(&plock->semaphore);
}/*信号量down操作(V操作)
1. 判断信号量是否大于0。
2. 若信号量大于0,则将信号量减1。
3. 若信号量等于0,当前线程将自己阻塞,以在此信号量上等待。*/
void sema_down(struct semaphore* psema){//关中断保证操作的原子性enum intr_status old_status = intr_disable();//使用while可以反复判断目前有没有锁,如果使用if只会判断一次,可能导致错误while (psema->value == 0){//此时锁被别人持有,其他的线程应该阻塞//目前的线程还不应该在信号量的等待队列里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");}//正常情况下list_append(&psema->waiters,&running_thread()->general_tag); //把当前队列加入到等待队列thread_block(TASK_BLOCKED); //当前线程状态变为阻塞}//当目前value==1,线程可以被唤醒并的获得锁时,会执行下面的代码psema->value--;ASSERT(psema == 0);intr_set_status(old_status);
}/*信号量的up操作(p操作)
1. 将信号量的值加1。
2. 唤醒在此信号量上等待的线程。*/
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 = (struct task_struct*)((uint32_t)list_pop(&psema->waiters) & (0xfffff000));thread_unblock(thread_blocked);}//当value==0时执行下面的代码psema->value++;ASSERT(psema->value == 1);intr_set_status(old_status);
}/*获取锁 参数是待申请的锁plock*如果目前的线程持有锁,将申请次数+1,避免死锁*如果目前线程没有锁,设置目前的信号量为0,然后申请锁*/
void lock_acquire(struct lock* plock){if(plock->holder == running_thread()){plock->holder_repeat_nr++;}else{sema_down(&plock->semaphore);plock->holder = running_thread();ASSERT(plock->holder_repeat_nr == 0);plock->holder_repeat_nr = 1;}
}/*释放锁plock*如果锁的申请次数大于1,次数减一*如果锁的申请次数等于1,将锁的持有者置空,然后信号量+1*/
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;plock->holder_repeat_nr = 0;sema_up(&plock->semaphore);
}
我觉得这部分的要点是搞清楚信号量和锁、锁和线程状态的关系。
我的总结是:
- 信号量为0时,说明某个线程持有锁,其他的线程无法申请锁。
- 信号量为1时,目前的线程都可以尝试申请锁。
- 当锁被其他线程持有,信号量为0时,目前的线程要阻塞自己。
- 当锁被释放,信号量为1时,在0信号量被阻塞的线程被目前的线程释放
用锁实现终端输出
简单来说就是用上面这些函数封装几个功能,看一眼代码就明白了。
顺带补充一下,上面的代码还有这部分的代码都是我自己写的,或许会有一些小错误(丢了分号,=和==用错了),有一些我修改后重新贴回去了,有一些可能忘了改,如果在编译时遇到了问题,可以自己修改。
声明文件console.h
#ifndef __DEVICE_CONSOLE_H
#define __DEVICE_CONSOLE_H
#include "../lib/kernel/stdint.h"
void console_init(void);
void console_acquire(void);
void console_release(void);
void console_put_str(char* str);
void console_put_char(uint8_t char_ascii);
void console_put_int(uint32_t num);
#endif
实现文件console.c
#include "console.h"
#include "../lib/kernel/stdint.h"
#include "../lib/kernel/print.h"
#include "../thread/sync.h"
#include "../thread/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_ascii){console_acquire();put_char(char_ascii);console_release();
}/*终端中输出16进制整型*/
void console_put_int(uint32_t num){console_acquire();put_int(num);console_release();
}
检验
这部分要好好说一下最近3天遇到的一些情况
还是先贴出init.c、main.c、makefile的代码吧,看完代码再说一些问题。
init.c
//完成所有的初始化工作
#include "../kernel/init.h"
#include "../lib/kernel/stdint.h"
#include "../lib/kernel/print.h"
#include "../kernel/interrupt.h"
#include "../device/timer.h"
#include "../kernel/memory.h"
#include "../thread/thread.h"
#include "../device/console.h"/*负责初始化所有模块 */
void init_all() {put_str("init_all\n"); // 打印初始化信息idt_init(); // 初始化中断mem_init(); // 初始化内存timer_init(); // 初始化定时器thread_init(); // 初始化线程console_init(); // 控制台初始化,最好放到开中断之前
}
main.c
//内核的入口函数
#include "print.h"
#include "init.h"
#include "debug.h"
#include "memory.h"
#include "thread.h"
#include "interrupt.h"
#include "../device/console.h"void k_thread_a(void*);
void k_thread_b(void*);
/* 主线程 */
int main(void){put_str("HongBai's OS\n");init_all(); //初始化所有模块thread_start("k_thread_a", 31, k_thread_a, "ArgA ");thread_start("k_thread_b", 8, k_thread_b, "ArgB ");intr_enable();while(1){console_put_str("Main ");}
}void k_thread_a(void* arg){while(1){console_put_str((char*)arg);}
}void k_thread_b(void* arg){while(1){console_put_str((char*)arg);}
}
makefile
BUILD_DIR = ./build
ENTRY_POINT = 0xc0001500
AS = nasm
CC = gcc
LD = ld
LIB = -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -I device/ -I thread/
ASFLAGS = -f elf
CFLAGS = -Wall -m32 -fno-stack-protector $(LIB) -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes
LDFLAGS = -m elf_i386 -Ttext $(ENTRY_POINT) -e main -Map $(BUILD_DIR)/kernel.map
OBJS = $(BUILD_DIR)/main.o $(BUILD_DIR)/init.o $(BUILD_DIR)/interrupt.o \$(BUILD_DIR)/timer.o $(BUILD_DIR)/kernel.o $(BUILD_DIR)/print.o \$(BUILD_DIR)/debug.o $(BUILD_DIR)/string.o $(BUILD_DIR)/memory.o \$(BUILD_DIR)/bitmap.o $(BUILD_DIR)/thread.o $(BUILD_DIR)/list.o \$(BUILD_DIR)/switch.o $(BUILD_DIR)/sync.o $(BUILD_DIR)/console.o################ c代码编译 ##################
$(BUILD_DIR)/main.o: kernel/main.c lib/kernel/print.h \lib/kernel/stdint.h kernel/init.h kernel/debug.h \kernel/memory.h thread/thread.h kernel/interrupt.h \device/console.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/init.o: kernel/init.c kernel/init.h lib/kernel/print.h \lib/kernel/stdint.h kernel/interrupt.h device/timer.h \kernel/memory.h thread/thread.h device/console.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/interrupt.o: kernel/interrupt.c kernel/interrupt.h \lib/kernel/stdint.h kernel/global.h kernel/io.h \lib/kernel/print.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/timer.o: device/timer.c device/timer.h lib/kernel/stdint.h \kernel/io.h lib/kernel/print.h kernel/interrupt.h \thread/thread.h kernel/debug.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/debug.o: kernel/debug.c kernel/debug.h \lib/kernel/print.h kernel/interrupt.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/string.o: lib/string.c lib/string.h \kernel/debug.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/memory.o: kernel/memory.c kernel/memory.h \lib/kernel/stdint.h lib/kernel/bitmap.h kernel/debug.h \lib/string.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/bitmap.o: lib/kernel/bitmap.c lib/kernel/bitmap.h \lib/string.h kernel/interrupt.h lib/kernel/print.h \kernel/debug.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/thread.o: thread/thread.c thread/thread.h \lib/kernel/stdint.h lib/kernel/list.h lib/string.h \kernel/memory.h kernel/interrupt.h kernel/debug.h \lib/kernel/print.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/list.o: lib/kernel/list.c lib/kernel/list.h \lib/kernel/stdint.h kernel/interrupt.h kernel/debug.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/sync.o: thread/sync.c thread/sync.h \lib/kernel/stdint.h thread/thread.h kernel/debug.h \kernel/interrupt.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/console.o: device/console.c device/console.h \lib/kernel/print.h thread/sync.h$(CC) $(CFLAGS) $< -o $@
############## 汇编代码编译 ###############
$(BUILD_DIR)/kernel.o: kernel/kernel.S$(AS) $(ASFLAGS) $< -o $@$(BUILD_DIR)/print.o: lib/kernel/print.S$(AS) $(ASFLAGS) $< -o $@$(BUILD_DIR)/switch.o: thread/switch.S$(AS) $(ASFLAGS) $< -o $@############## 连接所有目标文件 #############
$(BUILD_DIR)/kernel.bin: $(OBJS)$(LD) $(LDFLAGS) $^ -o $@.PHONY : mk_dir hd clean allmk_dir:if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fihd:dd if=$(BUILD_DIR)/kernel.bin \of=/home/hongbai/bochs/bin/c.img \bs=512 count=200 seek=10 conv=notruncclean:cd $(BUILD_DIR) && rm -f ./*build: $(BUILD_DIR)/kernel.binall: mk_dir build hd
先贴一下目前的运行截图吧
中间有数字和ok,是我为了解决下面那个panic写的测试函数,写在thread.c的末尾,贴出代码
void ready_list_len(){ //测试就绪队列是否为空,输出就绪队列的元素数put_int(list_len(&thread_ready_list));put_str("\n");
}void all_list_len(){put_int(list_len(&thread_all_list));put_str("\n");
}
非常简单,就是调用一下list.c里面写好的函数,看一看两个线程list里面有多少线程,这里我发现似乎ready_list里面有线程,但是仍然报了错?很奇怪。
我在时钟中断的中断处理函数(路径是device/timer.c)中加了一行打印,便于我观察,下面是代码
/* 时钟的中断处理函数 */
void intr_timer_handler(void) {put_str("intr_timer_occur!\n");struct task_struct* cur_thread = running_thread();ASSERT(cur_thread->stack_magic == 0x19870916); // 检测栈溢出cur_thread->elapsed_ticks++; // 线程运行的时间加1// 每次时钟中断,ticks加1ticks++;if (cur_thread->ticks == 0) { // 如果当前线程的时间片用完schedule(); // 调度其他线程}else {cur_thread->ticks--; // 否则,当前线程的时间片减1}return;
}
这个方法也不太好,观察不出来什么,贴一下截图吧
这是下面截图的测试代码
void intr_timer_handler(void) {struct task_struct* cur_thread = running_thread();ASSERT(cur_thread->stack_magic == 0x19870916); // 检测栈溢出cur_thread->elapsed_ticks++; // 线程运行的时间加1// 每次时钟中断,ticks加1ticks++;put_int(ticks);put_char(' ');if (cur_thread->ticks == 0) { // 如果当前线程的时间片用完put_str("thread_schedule_in_intr_timer_occur!\n");schedule(); // 调度其他线程}else {cur_thread->ticks--; // 否则,当前线程的时间片减1}return;
}
我捋了一下目前的思路
申请两个线程,目前ready队列里有两个元素->开中断->(理论上此时就要输出Main)->终端console输出、获取锁、信号量down->关中断(目前有1、2次时钟中断)、走thread_block函数使得主线程被阻塞->走block的schedule、不重置时间片(目前运行的main已经被阻塞)、目前len依旧是2、pop操作取出来a线程且len-1、a线程变为running走switch_to->(此时应该输出ArgA)、重新打开中断…
两个小时后,我终于发现我的起点不对!!!!!初始化有误!!!!!
信号量初始化应为1而不是0,否则后面的线程一直认为没有锁可以申请,因为锁被设定为被一个NULL线程持有!显然有误!
好吧,找到问题就好说,修改一下我们的初始化代码。
贴出新的sync.c
#include "sync.h"
#include "thread.h"
#include "interrupt.h"
#include "debug.h"
/*初始化信号量psema*/
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);
}/*信号量down操作(V操作)
1. 判断信号量是否大于0。
2. 若信号量大于0,则将信号量减1。
3. 若信号量等于0,当前线程将自己阻塞,以在此信号量上等待。*/
void sema_down(struct semaphore* psema){//关中断保证操作的原子性enum intr_status old_status = intr_disable();//使用while可以反复判断目前有没有锁,如果使用if只会判断一次,可能导致错误while (psema->value == 0){//此时锁被别人持有,其他的线程应该阻塞//目前的线程还不应该在信号量的等待队列里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");}//正常情况下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);
}/*信号量的up操作(p操作)
1. 将信号量的值加1。
2. 唤醒在此信号量上等待的线程。*/
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 = (struct task_struct*)((uint32_t)list_pop(&psema->waiters) & (0xfffff000));thread_unblock(thread_blocked);}//当value==0时执行下面的代码psema->value++;ASSERT(psema->value == 1);intr_set_status(old_status);
}/*获取锁plock*如果目前的线程持有锁,将申请次数+1,避免死锁*如果目前线程没有锁,设置目前的信号量为0,然后申请锁*/
void lock_acquire(struct lock* plock){if(plock->holder == running_thread()){plock->holder_repeat_nr++;}else{sema_down(&plock->semaphore);plock->holder = running_thread();ASSERT(plock->holder_repeat_nr == 0);plock->holder_repeat_nr = 1;}
}/*释放锁plock*如果锁的申请次数大于1,次数减一*如果锁的申请次数等于1,将锁的持有者置空,然后信号量+1*/
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;plock->holder_repeat_nr = 0;sema_up(&plock->semaphore);
}
删除掉之前的测试代码(否则会出错),再次运行后的结果
尝试换一下字符数
终于!!!!!
结语
现在是2025年4月27日19点26分,上一篇操作系统的博客是2025-04-18 20:55:30 发布的,这将近十天我做了什么?
上周五发布完博客,周六要打天梯赛,就没进行新的学习。这周周二我有一门课要结课,周日简单过了一遍课本。本来周一打算开始继续学习第10章的,这篇博客的开头也是这周一写的,但是被上一章没写好导致的bug搞破防了,遂作罢。周一周二就只学习了数据库和结课的信号与系统。
周三写了“现状”那部分内容,算是有了一点进展。周四本来打算继续突破,后来遇到了一个同学,决定和他交流。周五周六又因为比赛的原因,还有bug的问题,没有什么进展。
今天补5.1调休的课,上课期间有了突破,一直到下午4点开始自习,又调试半天,终于完成这篇博客。
说了这么多,到底要表达什么呢?
首先是节奏方面的问题,第7、8、9章完成的太快了(一会会继续讨论这一点),很多细节没有掌握好,影响了第十章的学习。加上和学校课程、竞赛之间的时间冲突,第十章的学习不太顺利。
其次还有一点很重要,那就是代码补全的问题。学习第七章中断的时候,在ubuntu上下载了vscode,vscode有很多好用的功能,但有一点我不建议大家使用,就是ai代码补全。
诚然,ai代码补全在开发中节省了大量的时间,但是在我们的学习阶段,大量使用代码补全会让我们不那么熟悉我们的代码,这带来了后续debug调试的困难。我就深受其害,因为很多代码是补全出来的,即使尝试理解了这些代码,在code阶段节省了一些时间,但是由于我对它们不够熟悉,debug阶段反而花了更多的时间。
从3.25我写下配置环境的博客算起,现在已经一月有余,我的操作系统举例完成还有相当的一部分内容,在4月结束时彻底完成也不太能实现了。不过我相信功不唐捐,只要我们一点一点往前走,胜利就在前方,与诸位共勉!