OS笔记整理
只是浅浅整理了一下自己在学习过程中的记录,并没有详细展开指导书中具体内容以及具体功能的实现
lab1
1.lab1的代码结构
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建立的内存管理进程管理示意图
【摘自指导书】
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
线程机制
实现内核级线程,主要思路是仿照进程管理实现线程管理
线程操作
- 为线程控制块分配空间
mm/pmap.c/mips_vm_init()
- 初始化线程控制块并“串”成tcb_free_list
lib/pthread.c/tcb_init()
- 仿照lab3中对进程的操作,实现了对线程的一整套操作
[lib/pthread.c]
内存共享
- 数据结构设计
- 由调度进程控制块变为调度线程控制块
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);
}
拓展功能
- sleep()
根据Gxemul的设备仿真说明:
编写内核态函数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();
}
}
}
- 线程数据共享功能
线程数据共享的功能示例
全部实现在用户态,主要是函数内对变量的改变不会影响主函数中传入参数的值,所以确定了如下数据结构:
点击查看代码
typedef struct {
void *addr;
int status;
void *destructor;
void *ptr; //在create时存储自身pthread_key_t结构体的地址,在get和set时通过此地址访问从而共享函数间对线程内结构体的值的修改
}pthread_key_t;
理解线程运行全过程
在一个进程文件中,线程的代码是一个函数片段,所有的程序代码在创建进程时均被加载进主存(加载二进制镜像),主线程是从entry_point 至 main函数结束 ; 分线程是从函数的开始至结束。
理解变量存储
加载elf文件时,全局变量和静态变量均加载至内存中,临时变量是在栈中,所以,只需要为每个线程分配一个栈帧,就能实现内存空间共享。
区分内核态用户态权限
线程机制的实现中,对线程操作块的操作是操作系统的权限,信号量的操作需要被保证为是原语操作,均需要陷入内核态执行。在sleep()函数中,是采用轮询判断是否满足时间要求,这段循环判断是用户态实现,访问时间是在内核态实现。
线程的共享数据,仅仅是线程内的数据的通信,则只在用户态实现。