ucore Lab4 操作系统实验

这篇博客详述了ucore LAB4内核进程实验,涉及进程与线程的概念,内核线程与用户进程的区别,以及进程控制块(PCB)的重要成员变量。博主介绍了进程状态模型,讨论了内核线程的创建、初始化,以及如何分配资源。通过分析代码,博主解释了进程上下文切换的过程,并讨论了ucore如何为新fork的线程分配唯一ID。实验中,博主实现了分配并初始化进程控制块,以及为内核线程分配资源。最后,博主分享了实验过程中的体会和参考资料。
摘要由CSDN通过智能技术生成

LAB4内核进程实验报告

知识准备

(主要依据理论课知识以及学堂在线上清华大学教学视频)

1.进程与线程定义与区别

.进程是指一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程,通常我们可以将其视为资源的拥有者。线程是进程的一部分,描述指令流的执行状态,是进程中指令执行流的最小单元,CPU调度的基本单位。一个程序至少一个进程,一个进程至少一个线程。进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位。 可以这样认为: 线程=进程-共享资源

个人的通俗理解:如果说程序是一个小品剧表演,则进程就是指包括舞台等各种资源的表演集合,线程就是演员。

内核线程与用户进程的区别:

  • 内核线程只运行在内核态,用户进程会在在用户态和内核态交替运行
  • 所有内核线程共用ucore内核内存空间,不需为每个内核线程维护单独的内存空间;用户进程需要维护各自的用户内存空间
2.进程的一些相关知识

进程包括了正在运行的一个程序的所有状态信息,包括代码、数据、寄存器等。

进程的一些特点:

  • 动态性:可以动态地创建、结束进程
  • 并发行:进程可以被独立调用并占用处理机运行
  • 独立性:不同进程间工作不受影响
  • 制约性:多个进程因访问共享数据、资源或进程间同步而产生制约

进程控制块(PCB)是管理控制进程运行所用信息的集合。PCB是进程存在的唯一标志,每个进程在操作系统中都有一个对应的PCB,操作系统用PCB来描述进程的基本信息以及运行变化情况。PCB通常包含进程标识符、处理机的信息、进程调度信息、进程控制信息。

进程切换(上下文切换):暂停当前的进程,从运行状态变为其他状态,调用另一个进程从就绪状态改为运行状态。在这一过程中,切换前需要保存进程上下文,以便于之后恢复该进程,且尽可能地快速切换(因此通常用汇编写进程切换过程的代码)。CPU给每个任务一定的服务时间,当时间片轮转的时候,需要把当前状态保存下来,同时加载下一个任务,这时候就进行上下文切换。

经典的进程五状态模型(new,ready,waitting,running,terminated):在这里插入图片描述
进程挂起:处于挂起状态的进程映像在磁盘上,目的是减少进程占用内存。与之相对的成为进程激活,将处于挂起状态的进程激活,将进程从外存转到内存

3.线程的一些相关知识

线程的优点:一个进程可以执行多个线程,各线程可以并发执行,各线程可以共享地址空间与文件等资源。

单线程进程与多线程进程:(强调:寄存器不共享)
在这里插入图片描述
进程与线程之间的对应关系(一对一、多对一、一对多、多对多):
在这里插入图片描述
进程线程一对一:

  • 缺点:每创建一个用户线程都需要创建一个相应的内核线程,创建内核线程的开销会影响应用程序的性能
  • 优点:一个线程执行阻塞系统调用时,能允许另一个线程继续执行

进程线程一对多:

  • 缺点:一个用户线程导致waiting将导致其他用户线程不能用
  • 优点:控制用户线程由用户线程库,修改用户线程库比较简单,线程切换在用户空间不会有模式切换

进程线程多对多:

  • 开发人员可以创建任意多的用户线程并且相应内核线程能在多处理器系统上并发执行,并且当一个线程阻塞系统调用时内核能调度另一个线程执行
  • Kernel thread数量最多等于user thread,因为最多一对一。执行时一个内核线程对应一个用户线程

线程分为用户线程和内核线程:

  • 用户线程:由一组用户级的线程库函数来完成线程的管理,包括线程的创建、终止、同步、调度等。不依赖于操作系统内核,用户态的线程切换较快,在用户空间实现线程。但是当线程发起系统调用而阻塞时,整个进程进入等待状态,不支持基于线程的处理机抢占。只能以进程为单位分配CPU时间。
  • 内核线程:由内核通过系统调用实现的线程机制,由内核完成线程的创建、终止和管理。内核线程由内核维护PCB和TCB,线程执行系统调用阻塞时不影响其他线程。但线程创建、终止、切换的开销较大。可以以线程为单位进行分配CPU时间。

实验内容

实验2/3完成了物理和虚拟内存管理,这给创建内核线程(内核线程是一种特殊的进程)打下了提供内存管理的基础。当一个程序加载到内存中运行时,首先通过ucore OS的内存管理子系统分配合适的空间,然后就需要考虑如何分时使用CPU来“并发”执行多个程序,让每个运行的程序(这里用线程或进程表示)“感到”它们各自拥有“自己”的CPU。本次实验将首先接触的是内核线程的管理。

在实验前,非常有必要看一下此次实验增添的内容以及相关数据结构和函数,这将对之后完成实验有着很大帮助。

0.一些新增的非常重要的数据结构与函数:

此次实验主要增加了proc.h,proc.c,entry.S,switch.S四个文件

在proc.h中,我们可以看到新增了一些数据结构:

  • 表示进程状态的 enum 常量 proc_state:

    //存取进程的状态
    enum proc_state {
         
        PROC_UNINIT = 0,  //未初始状态
        PROC_SLEEPING,    // 分配了物理页,此时处于睡眠状态或等待状态
        PROC_RUNNABLE,    // 运行与就绪状态
        PROC_ZOMBIE,      // 死亡状态
    };
    

    需要强调的是进程创建后进入为初始状态,分配物理页后进程进入睡眠状态。进程处于PROC_RUNNABLE状态不一定在运行。当程序指令执行完毕,由操作系统回收进程所占用的资源时,进程进入了“死亡”状态。

  • 进程管理信息proc_struct结构体:

//进程管理信息proc_struct
struct proc_struct {
   
    enum proc_state state;                      // 进程状态
    int pid;                                    // 进程 ID
    int runs;                                   // 运行时间
    uintptr_t kstack;                           // 内核栈位置
    volatile bool need_resched;                 // 是否需要重新调度释放CPU 
    struct proc_struct *parent;                 // 父进程控制块
    struct mm_struct *mm;                       // 进程内存描述符
    struct context context;                     // 进程上下文
    struct trapframe *tf;                       // 当前中断帧的指针
    uintptr_t cr3;                              // 页表地址
    uint32_t flags;                             // 反应进程状态的信息,但不是运行状态,
                                                // 用于内核识别进程当前的状态,以备下一步操作
    char name[PROC_NAME_LEN + 1];               //进程名字
    list_entry_t list_link;                     // 进程的链表
    list_entry_t hash_link;                     // Process hash list
};

结合gitbook以及自己的理解对部分成员变量做详解:

  • mm:内存管理的信息,包括内存映射列表、页表指针等。内核线程常驻内存,不需要考虑swap page问题,因此在lab4中mm对于内核线程就没有用了,这样内核线程的proc_struct的成员变量mm=0是合理的,mm里有个很重要的项pgdir,记录的是该进程使用的一级页表的物理地址。
  • state:表示进程所处的状态
  • parent:用户进程的父进程(创建它的进程)。内核根据这个父子关系建立一个树形结构,用于维护一些特殊的操作,例如确定某个进程是否可以对另外一个进程进行某种操作等等。
  • context:进程的上下文,用于进程切换。在 uCore中,所有的进程在内核中也是相对独立的(例如独立的内核堆栈以及上下文等)。使用 context 保存寄存器的目的就在于在内核态中能够进行上下文之间的切换。
  • tf:当前中断帧的指针。当进程从用户空间跳到内核空间时,中断帧记录了进程在被中断前的状态。当内核需要跳回用户空间时,需要调整中断帧以恢复让进程继续执行的各寄存器值。
  • cr3:cr3 保存页表的物理地址,目的就是进程切换的时候方便直接使用 lcr3实现页表切换,避免每次都根据 mm 来计算 cr3。**当某个进程是一个普通用户态进程的时候,PCB 中的 cr3 就是 mm 中页表(pgdir)的物理地址;而当它是内核线程的时候,cr3 等于boot_cr3。**而boot_cr3指向了uCore启动时建立好的内核虚拟空间的页目录表首地址
  • kstack: kstack记录分配给该进程/线程的内核栈的位置。每个线程都有一个内核栈,并且位于内核地址空间的不同位置。对于内核线程,该栈就是运行时的程序使用的栈;而对于普通进程,该栈是发生特权级改变的时候使保存被打断的硬件信息用的栈。

我们具体查看一下context变量:

//主要用于上下文切换保存寄存器状态
struct context {
   
    uint32_t eip;   //存储CPU要读取指令的地址
    uint32_t esp;   //栈指针寄存器,指向栈顶
    uint32_t ebp;   //基址指针寄存器,指向栈底
    uint32_t ebx;   //数据寄存器
    uint32_t ecx;    //计数寄存器
    uint32_t edx;    //数据寄存器
    uint32_t esi;   //变址寄存器,主要用于存放存储单元在段内的偏移量
    uint32_t edi;   //变址寄存器,主要用于存放存储单元在段内的偏移量
 
};
//需要强调的是不需要保存所有的段寄存器,因为这些都是跨内核上下文的常量
//保存的是上下文切换时前一个进程的状态现场。
//保存上下文的函数在switch.S中

查看switch.S文件,查看操作系统是如何进行上下文切换的:在这里插入图片描述
容易看出**switch_to函数主要完成的是进程的上下文切换,先保存当前寄存器的值,然后再将下一进程的上下文信息保存到对于寄存器中**。

最后再强调一下几个全局变量(操作系统用来管理系统中所有的进程控制块设置的全局变量)

//所有进程控制块的双向线性列表,proc_struct中的成员变量list_link将链接入这个链表中。
extern list_entry_t proc_list;
//current:当前占用CPU且处于“运行”状态进程控制块指针
//initproc:本实验指向一个内核线程,本实验以后,此指针将指向第一个用户态进程。
extern struct proc_struct *idleproc, *initproc, *current;
//所有进程控制块的哈希表,proc_struct中的成员变量hash_link将基于pid链接入这个哈希表中。
static list_entry_t hash_list[HASH_LIST_SIZ
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值