🐳
uCore OS(on RISC-V64)实验指导书
  • Introduction
  • LAB0:ready~go!
    • 实验目的
    • 实验内容
    • 前导知识
      • 了解uCore
      • 了解RISC-V
      • 了解OS实验
      • 了解实验环境
      • 了解开发调试基本工具
      • 了解硬件模拟器
    • 配置环境
      • 安装虚拟环境
      • 安装开发工具
      • 安装硬件模拟器
      • 安装调试工具
  • LAB0.5:最小可执行内核
    • 实验目的
    • 实验内容
    • 练习
    • 内存布局
    • 链接脚本
    • 真正的入口点
    • 从SBI到stdio
    • 编译运行
    • 项目组成与执行流
  • LAB1:中断机制
    • 实验目的
    • 实验内容
    • 练习
    • RISC-V中断相关
    • 上下文处理
    • 中断处理程序
    • 时钟中断
    • 项目组成与执行流
  • LAB2:物理内存管理
    • 实验目的
    • 实验内容
    • 练习
    • 地址与页表
    • 物理内存探测
    • 以页为单位管理物理内存
    • 页面分配算法
    • 项目组成与执行流
  • LAB3:虚拟内存管理
    • 实验目的
    • 实验内容
    • 练习
    • 页面置换
    • PageFault
    • 使用多级页表
    • 页面置换机制
    • FIFO置换算法
    • 项目组成与执行流
  • LAB4:进程管理
    • 实验目的
    • 实验内容
    • 练习
    • 进程与线程
    • 相关结构体
    • 进程模块初始化
    • 进程切换
    • 项目组成与执行流
  • LAB5:用户程序
    • 实验目的
    • 实验内容
    • 练习
    • 用户进程
    • 用户程序
    • 创建并执行用户进程
    • 系统调用
    • 用户进程的退出和等待
    • 项目组成与执行流
  • LAB6:进程调度
    • 实验目的
    • 实验内容
    • 练习
    • 进程状态
    • 再次认识进程切换
    • 调度算法框架
    • 项目组成与执行流
  • LAB7:同步互斥
    • 实验目的
    • 实验内容
    • 练习
    • 同步互斥的基本概念
    • 信号量
    • 条件变量与管程
    • 项目组成与执行流
  • LAB8:文件系统
    • 实验目的
    • 实验内容
    • 练习
    • 文件系统介绍
    • 文件系统抽象层VFS
    • 硬盘文件系统SFS
    • 设备即文件
    • 从中断到终端
    • 项目组成与执行流
由 GitBook 提供支持
在本页

这有帮助吗?

  1. LAB5:用户程序

创建并执行用户进程

我们在proc_init()函数里初始化进程的时候, 认为启动时运行的ucore程序, 是一个内核进程("第0个"内核进程), 并将其初始化为idleproc进程。然后我们新建了一个内核进程执行init_main()函数。

我们比较lab4和lab5的init_main()有何不同。

// kern/process/proc.c (lab4)
static int init_main(void *arg) {
    cprintf("this initproc, pid = %d, name = \"%s\"\n", current->pid, get_proc_name(current));
    cprintf("To U: \"%s\".\n", (const char *)arg);
    cprintf("To U: \"en.., Bye, Bye. :)\"\n");
    return 0;
}

// kern/process/proc.c (lab5)
static int init_main(void *arg) {
    size_t nr_free_pages_store = nr_free_pages();
    size_t kernel_allocated_store = kallocated();

    int pid = kernel_thread(user_main, NULL, 0);
    if (pid <= 0) {
        panic("create user_main failed.\n");
    }

    while (do_wait(0, NULL) == 0) {
        schedule();
    }

    cprintf("all user-mode processes have quit.\n");
    assert(initproc->cptr == NULL && initproc->yptr == NULL && initproc->optr == NULL);
    assert(nr_process == 2);
    assert(list_next(&proc_list) == &(initproc->list_link));
    assert(list_prev(&proc_list) == &(initproc->list_link));

    cprintf("init check memory pass.\n");
    return 0;
}

注意到,lab5新建了一个内核进程,执行函数user_main(),这个内核进程里我们将要开始执行用户进程。

do_wait(0, NULL)等待子进程退出,也就是等待user_main()退出。

我们来看user_main()和do_wait()里做了什么

// kern/process/proc.c
#define __KERNEL_EXECVE(name, binary, size) ({                          \
            cprintf("kernel_execve: pid = %d, name = \"%s\".\n",        \
                    current->pid, name);                                \
            kernel_execve(name, binary, (size_t)(size));                \
        })

#define KERNEL_EXECVE(x) ({                                             \
            extern unsigned char _binary_obj___user_##x##_out_start[],  \
                _binary_obj___user_##x##_out_size[];                    \
            __KERNEL_EXECVE(#x, _binary_obj___user_##x##_out_start,     \
                            _binary_obj___user_##x##_out_size);         \
        })

#define __KERNEL_EXECVE2(x, xstart, xsize) ({                           \
            extern unsigned char xstart[], xsize[];                     \
            __KERNEL_EXECVE(#x, xstart, (size_t)xsize);                 \
        })

#define KERNEL_EXECVE2(x, xstart, xsize)        __KERNEL_EXECVE2(x, xstart, xsize)

// user_main - kernel thread used to exec a user program
static int
user_main(void *arg) {
#ifdef TEST
    KERNEL_EXECVE2(TEST, TESTSTART, TESTSIZE);
#else
    KERNEL_EXECVE(exit);
#endif
    panic("user_main execve failed.\n");
}

于是,我们在user_main()所做的,就是执行了

kern_execve("exit", _binary_obj___user_exit_out_start,_binary_obj___user_exit_out_size)

这么一个函数。

如果你熟悉execve()函数,或许已经猜到这里我们做了什么。

实际上,就是加载了存储在这个位置的程序exit并在user_main这个进程里开始执行。这时user_main就从内核进程变成了用户进程。我们在下一节介绍kern_execve()的实现。

我们在user目录下存储了一些用户程序,在编译的时候放到生成的镜像里。

上一页用户程序下一页系统调用

最后更新于4年前

这有帮助吗?