chyyuu / os_kernel_lab

OS kernel labs based on Rust/C Lang & RISC-V 64/X86-32
https://rcore-os.github.io/rCore-Tutorial-Book-v3/index.html
GNU General Public License v3.0
3.96k stars 1.92k forks source link

uCore Lab6中提供给用户应用程序使用的cprintf函数是非原子的 #95

Open WU-SUNFLOWER opened 1 month ago

WU-SUNFLOWER commented 1 month ago

我是外校的学生,为了上手方便我仍然使用贵校开发的基于c语言实现的uCore进行练习。我不是很清楚贵校开发团队是否还在维护这个项目的c语言版本,也不了解贵校开发团队的rCore是否也存在类似的问题,如有打扰还请谅解

以下是我个人给出的临时修复办法:


uCore官方实现的提供给用户程序使用的cprintf函数实现是非原子的。这会导致什么问题呢?

举个例子,假设有两个进程A和B,如果进程A想打印字符串"Hello World",进程B想打印字符串"Thanks for You",则可能最终会在设备屏幕上输出"Hello Thanks for World"或者其他的混乱结果。这显然会影响我们正常开展本次实验,以及导致无法使用make grade对我们的代码进行评测!

这是因为uCore中用户进程每打印一个ASCII字符,都需要请求一次SYS_putc系统调用,因此虽然每次系统调用的执行是原子的,但这个用户程序在逐个打印字符的过程中仍然可能会被时钟中断,转而执行其他进程,从而导致屏幕输出结果的混乱。

这里我采用最简单粗暴的办法来修复这个bug,即直接给cprintf函数上一把大锁。

首先,由于lab6中uCore还没有实现锁机制,我们需要在内核代码中自行封装一个自旋锁。

代码如下,其中原子操作__sync_lock_test_and_set__sync_lock_release由gcc编译器提供,我们无需关心其具体实现:

// kern/sync/my_spin_lock.h
// 这个结构体用于表示自旋锁
typedef struct {
    volatile int locked;
} spinlock_t;

void spin_lock(spinlock_t* lock);
void spin_unlock(spinlock_t* lock);

extern spinlock_t global_print_lock;

// kern/sync/my_spin_lock.c
#include <my_spin_lock.h>
#include <sched.h>

// 加锁函数
void spin_lock(spinlock_t *lock) {
    while (__sync_lock_test_and_set(&(lock->locked), 1)) {
        schedule();  
    }
}

// 解锁函数
void spin_unlock(spinlock_t *lock) {
    __sync_lock_release(&(lock->locked));
}

// 初始化一个全局的自旋锁
spinlock_t global_print_lock = {0}; // 全局锁初始化为未锁定状态

然后,我们把加锁/解锁函数封装成系统调用,以便用户程序使用:

// libs/unistd.h
#define SYS_print_lock 100
#define SYS_print_unlock 101
// kern/syscall/syscall.c
#include <my_spin_lock.h>

static int sys_print_lock(uint32_t arg[]) {
    spin_lock(&global_print_lock);
    return 0;
}

static int sys_print_unlock(uint32_t arg[]) {
    spin_unlock(&global_print_lock);
    return 0;
}

static int (*syscalls[])(uint32_t arg[]) = {
    // ...
    [SYS_print_lock]        sys_print_lock,
    [SYS_print_unlock]      sys_print_unlock,
};

添加用户程序库中的系统调用接口:

// user/libs/syscall.h
void sys_print_lock(void);
void sys_print_unlock(void);

// user/libs/syscall.c
void sys_print_lock(void) {
    syscall(SYS_print_lock);
}

void sys_print_unlock(void) {
    syscall(SYS_print_unlock);
}

最后,修改cprintf函数的底层代码:

// user/libs/stdio.c
int vcprintf(const char *fmt, va_list ap) {
    int cnt = 0;
    sys_print_lock();  // 加锁
    vprintfmt((void*)cputch, &cnt, fmt, ap);
    sys_print_unlock();  // 解锁
    return cnt;
}