lyqqq

导航

 

OS笔记整理

只是浅浅整理了一下自己在学习过程中的记录,并没有详细展开指导书中具体内容以及具体功能的实现

lab1

1.lab1的代码结构
image
2.对elf文件的理解分析在理论课中讲得更详细

lab2

链表宏的使用

链表宏的用法
LIST_INIT(&head)
LIST_EMPTY(&head) //if(LIST_EMPTY(&head)) {/*do something*/}
LIST_HEAD(headstruct, Type) head;
LIST_HEAD_INITIALIZER(&head); //{head->lh_first = NULL;}
LIST_ENTRY(Type) entry; //链表的索引
Type* first = LIST_FIRST(head); // get first elm
LIST_FOREACH(item, &head, field) {/*do something*/}
LIST_INSERT_AFTER(listelm, elm, field)
LIST_INSERT_BEFORE(listelm, elm,field) // field 是访问时用到的域
LIST_INSERT_HEAD(head, elm, field)
LIST_INSERT_TAIL(head, elm, field)
LIST_NEXT(elm, field)
LIST_REMOVE(elm, field)

物理地址、虚拟地址、页面控制块转换相关函数

地址转化相关函数
static inline u_long page2ppn(struct Page *pp)
static inline u_long page2pa(struct Page *pp)
static inline struct Page * pa2page(u_long pa)
static inline u_long page2kva(struct Page *pp)
static inline u_long va2pa(Pde *pgdir, u_long va)
PADDR(kva)
KADDR(pa)
地址转化相关函数的使用需要注意区分kva和va

映射建立的理解

在建立页表后,pgdir = 页目录基地址的虚拟地址,pgdir_entry 是页目录的页表项以指针作为映射途径,*pgdir 即表示存在该虚拟地址的内容,即*pgdir_entry = 二级页表基地址的物理地址和访问权限,由于页表是在内核态,可通过 KADDR 找到对应的二级页表基地址的虚拟地址,即 pgtab。pgtab_entry = 二级页表页表项的虚拟地址,同样是以指针为途径的映射关系,*pgtab_entry = va所对应的物理地址

页表的遍历

点击查看代码
Pde * pgdir, * pgdir_entry;
Pte * pgtab, * pgtab_entry;
for (i = 0; i < PTE2PT; i++)
{
	pgdir_entry = pgdir + i;
	if((*pgdir_entry)&PTE_V) //注意判断是否有效!!!
	{
		pgtab = KADDR(PTE_ADDR(*pgdir_entry));
		for (j = 0; j < PTE2PT; j++)
		{
			pgtab_entry = pgtab + j;
			if((*pgtab_entry)&PTE_V)//注意判断是否有效!!!
			{
				/* {do something} */
			}
		}
	}
}

lab3

lab2 & lab3建立的内存管理进程管理示意图

image
【摘自指导书】

lab3实现进程调度需要填写的函数

实现进程调度填写的函数
[boot/start.S]
except_vec3: warp distribute code

[tools/scse0_3.lds]
"except_vec3" <===> 0x80000080

[include/stackframe.h]
get_sp:according to CP0_CAUSE find sp
SAVE_ALL:save registers to stack (get_sp)

[lib/kclock.c]
kclock_init( set_timer( ) ):设置时钟机制

[lib/genex.S]
handle_int:异常处理的一种方式

[lib/sched.c]
sched_yield():切换进程

lab4

系统调用机制

user/syscall_lib.c:void syscall_*() :用户态可以调用的特权函数 ······· 调用↓
user/syscall_warp.S: msyscall:执行syscall ······ 调用↓
lib/syscall.S: handle_sys():根据参数 ······ 调用对应的内核态函数↓
lib/syscall_all.c: void sys_*():同系统调用对应的内核态函数

新增一个系统调用需要的修改

新增一个系统调用需要的修改
[include/unistd.h]
#define SYS_*  xx  //定义异常分发号

[lib/syscall.S]
sys_call_table:
	.word sys_* //按照上一步定义的顺序申请空间

[lib/syscall_all.c]
int sys_*()      //内核态函数的内容

[user/syscall_lib.c]
syscall_*();   //用户态函数中的内容

注意:我们的操作系统并不允许中断重入。此外,如果在内核态使用sys_yield(),使当前进程放弃CPU,则当前进程若再被调度时,是从用户态中执行,即上次陷入内核态的指令的下一条指令执行。

ipc

同lab3中的进程调度密切相关

fork()中父子进程的分离的实现

在fork()函数中,首先执行newenvid = syscall_env_alloc();,是在syscall_env_alloc()中陷入内核态,在sys_env_alloc()中创建了子进程,父进程都将在syscall_env_alloc此返回用户态,子进程将从该syscall_env_alloc的返回指令开始调度,只不过此时子进程还处于阻塞状态,需要父进程对子进程完成一系列配置后,子进程才能正式被调度。对于父进程,返回值直接从寄存器读入即可,所以,envid为子进程的进程id。对于子进程,在sys_env_alloc时把子进程的$v0寄存器设置为0,在调度子进程时需要重新加载寄存器值(在sched.c yield()中实现的),此时$v0寄存器被刷新成了0,从而实现,在子进程中newid被赋值为0,进而区分父子进程。

lab4-challenge

线程机制

实现内核级线程,主要思路是仿照进程管理实现线程管理

image

线程操作

  1. 为线程控制块分配空间
    mm/pmap.c/mips_vm_init()
  2. 初始化线程控制块并“串”成tcb_free_list
    lib/pthread.c/tcb_init()
  3. 仿照lab3中对进程的操作,实现了对线程的一整套操作
    [lib/pthread.c]

内存共享

  1. 数据结构设计
    image
  2. 由调度进程控制块变为调度线程控制块lib/sched.c/sched_yield(),当即将被调度的线程与当前线程不属于同一进程时,重新加载地址空间lib/pthread.c/tcb_run()
点击查看代码
if((curtcb == NULL && curenv == NULL) ||  t->env_id != curenv->env_id) {
                envid2env(t->env_id, &curenv, 0);
                lcontext((u_int)(curenv)->env_pgdir);
}

拓展功能

  1. sleep()

    根据Gxemul的设备仿真说明:
    image
    编写内核态函数sys_get_time(),在用户态实现sleep()

点击查看代码
void sleep(int time) {
	int t = syscall_get_time();
	int j = 0;
	while(1) {
		j = syscall_get_time();
		if ((j - t) > time) {
			break;
		} else {
			syscall_yield();
		}
	}
}
  1. 线程数据共享功能
    线程数据共享的功能示例
    全部实现在用户态,主要是函数内对变量的改变不会影响主函数中传入参数的值,所以确定了如下数据结构:
点击查看代码
typedef struct {
	void *addr;
	int status;
	void *destructor;
	void *ptr; //在create时存储自身pthread_key_t结构体的地址,在get和set时通过此地址访问从而共享函数间对线程内结构体的值的修改
}pthread_key_t;

理解线程运行全过程

在一个进程文件中,线程的代码是一个函数片段,所有的程序代码在创建进程时均被加载进主存(加载二进制镜像),主线程是从entry_point 至 main函数结束 ; 分线程是从函数的开始至结束。

理解变量存储

加载elf文件时,全局变量和静态变量均加载至内存中,临时变量是在栈中,所以,只需要为每个线程分配一个栈帧,就能实现内存空间共享。

区分内核态用户态权限

线程机制的实现中,对线程操作块的操作是操作系统的权限,信号量的操作需要被保证为是原语操作,均需要陷入内核态执行。在sleep()函数中,是采用轮询判断是否满足时间要求,这段循环判断是用户态实现,访问时间是在内核态实现。
线程的共享数据,仅仅是线程内的数据的通信,则只在用户态实现。

posted on 2022-07-10 22:40  ~小~禾~  阅读(51)  评论(0编辑  收藏  举报