其他分享
首页 > 其他分享> > OS Lab3笔记

OS Lab3笔记

作者:互联网

Lab3

进程控制块

PCB记录进程的外部特征,描述进程的运动变化过程。PCB是系统感知进程存在的唯一标志

struct Env {
    struct Trapframe   env_tf;        // Saved registers
    LIST_ENTRY(Env)    env_link;      // Free LIST_ENTRY
    u_int              env_id;        // Unique environment identifier
    u_int              env_parent_id; // env_id of this env's parent
    u_int              env_status;    // Status of the environment
    Pde                *env_pgdir;    // Kernel virtual address of page dir
    u_int              env_cr3;
    LIST_ENTRY(Env)    env_sched_link;
    u_int              env_pri;
};
struct {								\
	struct type *le_next;	/* next element */			\
	struct type **le_prev;	/* address of previous next element */	\
}

ENV_FREE:该进程不活动,处于空闲状态,空闲链表中

ENV_NOT_RUNNABLE:阻塞状态,需要等待条件。(suspend)

ENV_RUNNABLE:就绪状态,等待调度,或者正在运行(running||ready)

PS:进程的管理和物理内存的管理极度相似,都是使用一个结构体进行管理,此结构体只是一个信息标记,并不是我们要管理的对象本身

free_list:空闲对象标记组成的链表

array:标识整个系统中现有的所有的对象,无论状态

进程的标识

每个进程独一无二的标识符,env_id

在env.c文件中找到一个叫做mkenvid的函数,生成一个新的进程id

mkenvid函数

mkenvid函数:传入一个Env指针e,返回进程独一无二的env_id

envid的组成

低位:e指针在envs数组中的下标(0-9位)

高位:调用次数计数器(从1开始)

envid2env函数

传入envid,修改的是指针*penv

返回值:

流程:

设置进程控制块

类比于申请一页的物理内存空间。(page_alloc)

env_setup_vm

初始化进程的地址空间

对于不同的进程而言,ULIM以上,虚拟地址到物理地址的映射关系都是一样的。

不涉及进程管理,由OS内核管理。

我们的内核态和用户态实际是在同一个4G地址空间中的。

进程从内核态提升到了内核态,只是提升了内核的权限!!!,这种权限变化是动态的。

OS中,每一个完整的进程都有成为临时内核的资格,所有的进程都可以发出请求变成临时的内核态。

因此,每一个进程我们都需要拷贝只有内核才能使用的虚页表,在2G/2G模式下,每一个进程都有可能请求后变成内核态来访问上面2G空间,而用户态下,只能访问自己那2G用户态空间。

env_alloc

CP0_status就是MIPSR3000里面的SR寄存器

28bit设置为1,处于用户模式下。

12bit设置为1,4号中断可以被响应。

SR寄存器的低6位是一个二重栈的结构,KUo和IEo是一组,每次当中断发生的时候,硬件将KUp和IEp的数值拷贝到这里。KUp和IEp是一组,当中断发生的时候,硬件会把KUc和IEc的数值拷贝到这里。

KU:标识是否在内核模式下,1:内核模式;0:用户模式

IE:1:中断开启;0:不开启

每当rfe指令调用的时候,就会进行逆操作。

KUo IEo KUp IEp KUc IEc

当rfe指令调用的时候,向右赋值

因此:将status后六位设置为000100

运行到第一个进程之前,也就是ref之后,变为000001

加载二进制镜像

为进程的程序分配空间

加载一个ELF到内存,只需要将ELF文件中所有需要加载的segment加载到对应的虚拟地址上就可以了。

int load_elf(u_char *binary, int size, u_long *entry_point, void *user_data,
             int (*map)(u_long va, u_int32_t sgsize,
             u_char *bin, u_int32_t bin_size, void *user_data))

binary - 待加载的ELF文件,size为该ELF文件的大小

entry_point存放的是解析出的入口地址。

接受一个自定义的函数以及你想传递给自定义函数的额外参数 user_data

load_elf()解析到一个需要加载的segment的时候,会将ELF文件里与加载有关的信息,作为参数传递个map函数,并在map函数中完成加载单个segment的任务

load_icode_mapper函数

加载单个segment到内存

va:该段需要被加载到的内存虚拟地址

sgsize:该段在内存中的大小

bin:该段在ELF文件中的内容

bin_size:该段在文件中的大小。

bin_size可以大于sgsize,其余可以使用0天充

map的本质,按页的大小申请物理空间,并填充对应的页目录项(pgdir_walk),页表项,申请物理空间需要insert

因此对于一个va,按页申请内存,申请到bin_size,这部分是将bin文件的内容填入的

注意va不是4K对齐的,需要先转换到4K对齐,再page_insert建立映射

拷贝关系是(因为有offset)

b-b+4KB-offset pa+offset

b+4KB-offset pa

b+8KB-offset pa

如果segsize大于binsize,继续申请,这部分清0

load_icode

设置用户栈指针(读/写)

加载elf文件

调整PC到entry

load_elf

根据program header table找到phdr项,解析出segment的信息。

加载到各个segment上

创建一个进程(env_create_priority)

申请一个新的Env结构体,

设置进程控制控制块(env_alloc)

设置进程的优先级

将binary加载(load_icode)

插入已经进程调度链表中

PS:创建一个进程不仅需要建立内存管理,还要加载ELF到指定位置,申请堆栈

#define ENV_CREATE_PRIORITY(x, y) \
{ \
    extern u_char binary_##x##_start[];\
    extern u_int binary_##x##_size; \
    env_create_priority(binary_##x##_start, \
    (u_int)binary_##x##_size, y); \
}

x代表user_A或者user_B,不同的用户输入不同的binary

y代表priority

进程的运行和切换

创建结束了,就该运行了

env_run()

异常的分发

每当发生异常的时候,一般来说,处理器会进入一个用于分发异常的程序

作用是检测发生了哪一种异常,并调用相应的异常处理程序

分发程序被要求放在固定的某个物理地址上

    mfc0 k1,CP0_CAUSE
    la k0,exception_handlers
    andi k1,0x7c
    addu k0,k1
    lw k0,(k0)
    nop
    jr k0
    nop

取异常码到k1寄存器

也即取出bit2-bit6 ExcCode

以取得道德异常码作为索引去exception_handlers数组中找到对应的中断处理函数

跳转到对应的中断处理函数中,响应异常,并将异常交给对应的异常处理函数去处理。

.text.exec_vec3这段程序需要放在特定的位置0x80000080处

异常向量组

exception_handlers数组就是所谓的中断向量组

这个数组里面存放的是对应的处理程序的首地址

0 号异常的处理函数为handle_int

1 号异常的处理函数为handle_mod

2 号异常的处理函数为handle_tlb

3 号异常的处理函数为handle_tlb

8 号异常的处理函数为handle_sys

时钟中断

时间片轮转调度:每个进程被分配一个时间段,称为时间片,即该进程允许运行的时间,如果在时间片结束时进程还在运行,那么就挂起,切换到另外一个程序运行。

CPU知道一个进程时间片结束的方法就是通过定时器产生时钟中断,当时一个时钟中断产生的时候,就表明到点了,直接挂起。

初始化时钟主要是在kclock_init函数中,这个函数调用set_timer函数

进程调度

因此,中断的时候最终是调用了sched_yield函数来进行进程的调度

优先级设置为时间片的大小

用两个链表来存储所有就绪状态进程

用一个指针指向当前调度队列

每当一个进程状态变为ENV_RUNNABLE,要将其插入第一个就绪状态进程的链表,调用sched_yield时,先判断当前时间片是否用完,如果用完,就将其插入另外一个就绪进程链表,判断当前就绪状态进程链表是否为空,如果为空,就切换到另外一个就绪状态进程的链表。

指针指向的是当前运行的进程

注意理解一下env_run()的含义,env_run()的意思是关闭当前进程curenv,执行传入的进程next_env,一定要确保传入的进程和当前进程不一样,这个函数由两个动作,先切换,再运行,不是直接运行的

讨论cur是否为NULL!!!

标签:链表,int,笔记,Lab3,中断,env,进程,OS,加载
来源: https://www.cnblogs.com/yyfyyfyyf/p/14705434.html