lab4:内核线程管理
文章目录
练习零:填写已有实验
本实验依赖实验1/2/3。请把你做的实验1/2/3的代码填入本实验中代码中 有“LAB1”,“LAB2”,“LAB3”的注释相应部分。
同样使用meld工具,比较lab3和lab4的不同,然后将lab3的代码填入lab4中即可,需要改动的文件如下
default_pmm.c
pmm.c
swap_fifo.c
vmm.c
trap.c
练习一:分配并初始化一个进程控制块
alloc_proc函数(位于kern/process/proc.c中)负责分配并返回一个新的struct proc_struct结 构,用于存储新建立的内核线程的管理信息。ucore需要对这个结构进行最基本的初始化,你需要完成这个初始化过程。
【提示】在alloc_proc函数的实现中,需要初始化的proc_struct结构中的成员变量至少包括state/pid/runs/kstack/need_resched/parent/mm/context/tf/cr3/flags/name。
内核线程是一种特殊的进程,内核线程与用户进程的区别有两个:
内核线程只运行在内核态 用户进程会在在用户态和内核态交替运行
所有内核线程共用ucore内核内存空间,不需为每个内核线程维护单独的内存空间 而用户进程需要维护各自的用户内存空间
实现思路
本练习主要是完成对proc_结构的初始化工作,具体的实现过程为:
①在堆上分配一块内存空间用来存放进程控制块
②初始化进程控制块的各个参数
③返回分配的进程控制块
关键数据结构
在 kern/process/proc.h
中定义了 PCB
,即进程控制块的结构体 proc_struct
,如下:
struct proc_struct { //进程控制块
enum proc_state state; //进程状态
int pid; //进程ID
int runs; //运行时间
uintptr_t kstack; //内核栈位置,分配给该进程/线程的栈的位置
volatile bool need_resched; //是否需要调度
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; //进程哈希表,相同状态相同hash值的PCB
};
其中 state取值:
PROC_UNINIT // 未初始状态
PROC_SLEEPING // 睡眠(阻塞)状态
PROC_RUNNABLE // 运行与就绪态
PROC_ZOMBIE // 僵死状态
alloc_proc实现
static struct proc_struct *alloc_proc(void) {
struct proc_struct *proc = kmalloc(sizeof(struct proc_struct));//分配内存空间
if (proc != NULL) {
proc->state = PROC_UNINIT; //设置进程为初始化状态,表明进程已经“出生”
proc->pid = -1; //未初始化的的进程id为-1,还没有分配pid
proc->runs = 0; //初始化时间片
proc->kstack = 0; //内存栈的地址
proc->need_resched = 0; //是否需要调度设为不需要
proc->parent = NULL; //父节点设为空
proc->mm = NULL; //虚拟内存设为空
memset(&(proc->context), 0, sizeof(struct context));//上下文的初始化
proc->tf = NULL; //中断帧指针置为空
proc->cr3 = boot_cr3; //页目录设为内核页目录表的基址
proc->flags = 0; //标志位
memset(proc->name, 0, PROC_NAME_LEN);//进程名
}
return proc;//返回
}
cr3的作用:内核线程没有用户空间,它执行的只是内核中的一小段代码(通常是一小段函数),所以它没有mm 结构,也就是NULL。当某个进程是一个普通用户态进程的时候,PCB 中的 cr3 就是 mm中页表(pgdir)的物理地址;而当它是内核线程的时候,cr3 等于boot_cr3
回答问题
请说明 proc_struct 中
struct context context
和struct trapframe *tf
成员变量含义和在本实验中的作用是啥?(提示:通过看代码和编程调试可以判断出来)
struct context {
uint32_t eip;
uint32_t esp;
uint32_t ebx;
uint32_t ecx;
uint32_t edx;
uint32_t esi;
uint32_t edi;
uint32_t ebp;
};
struct trapframe {
struct pushregs {
uint32_t reg_edi;
uint32_t reg_esi;
uint32_t reg_ebp;
uint32_t reg_oesp;
uint32_t reg_ebx;
uint32_t reg_edx;
uint32_t reg_ecx;
uint32_t reg_eax;
};
uint16_t tf_gs;
uint16_t tf_padding0;
uint16_t tf_fs;
uint16_t tf_padding1;
uint16_t tf_es;
uint16_t tf_padding2;
uint16_t tf_ds;
uint16_t tf_padding3;
uint32_t tf_trapno;
/* below here defined by x86 hardware */
uint32_t tf_err;
uintptr_t tf_eip;
uint16_t tf_cs;
uint16_t tf_padding4;
uint32_t tf_eflags;
/* below here only when crossing rings, such as from user to kernel */
uintptr_t tf_esp;
uint16_t tf_ss;
uint16_t tf_padding5;
} __attribute__((packed));
二者定义如上:
context作用:
进程的上下文,用于进程切换。主要保存了前一个进程的现场(各个寄存器的状态)。在uCore中,所有的进程在内核中也是相对独立的。使用context 保存寄存器的目的就在于在内核态中能够进行上下文之间的切换。实际利用context进行上下文切换的函数是在kern/process/switch.S中定义switch_to。
tf的作用:
中断帧的指针,总是指向内核栈的某个位置:当进程从用户空间跳到内核空间时,中断帧记录了进程在被中断前的状态。当内核需要跳回用户空间时,需要调整中断帧以恢复让进程继续执行的各寄存器值。除此之外,uCore内核允许嵌套中断。因此为了保证嵌套中断发生时tf 总是能够指向当前的trapframe,uCore 在内核栈上维护了 tf 的链。
练习2:为新创建的内核线程分配资源
创建一个内核线程需要分配和设置好很多资源。kernel_thread函数通过调用do_fork函数完成具体内核线程的创建工作。do_kernel函数会调用alloc_proc函数来分配并初始化一个进程控制块,但alloc_proc只是找到了一小块内存用以记录进程的必要信息,并没有实际分配这些资源。ucore一般通过do_fork实际创建新的内核线程。do_fork的作用是,创建当前内核线程的一个副本,它们的执行上下文、代码、数据都一样,但是存储位置不同。在这个过程中,需要给新内核线程分配资源,并且复制原进程的状态。你需要完成在kern/process/proc.c中的do_fork函数中的处理过程。它的大致执行步骤包括:
①调用alloc_proc,首先获得一块用户信息块。
②为进程分配一个内核栈。
③复制原进程的内存管理信息到新进程(但内核线程不必做此事)
④复制原进程上下文到新进程
⑤将新进程添加到进程列表
⑥唤醒新进程
⑦返回新进程号
函数定义
static int //内核栈复制函数
setup_kstack(struct proc_struct *proc) {
struct Page *page = alloc_pages(KSTACKPAGE);
if (page != NULL) {
proc->kstack = (uintptr_t)page2kva(page);
return 0;
}
return -E_NO_MEM;
}
static int //该函数在本次实验并没有实现
copy_mm(uint32_t clone_flags, struct proc_struct *proc) {
assert(current->mm == NULL); //判断当前函数的虚拟内存非空
return 0;
}
static void
copy_thread(struct proc_struct *proc, uintptr_t esp, struct trapframe *tf) {
proc->tf = (struct trapframe *)(proc->kstack + KSTACKSIZE) - 1;
*(proc->tf) = *tf;
proc->tf->tf_regs.reg_eax = 0;
proc->tf->tf_esp = esp;
proc->tf->tf_eflags |= FL_IF;
proc->context.eip = (uintptr_t)forkret;
proc->context.esp = (uintptr_t)(proc->tf);
}
void
intr_disable(void) { //禁止中断函数
cli(); //禁止中断
}
static inline bool
__intr_save(void) {
if (read_eflags() & FL_IF){ //如果允许屏蔽中断,即IF=1.则中断
intr_disable(); //禁止中断
return 1;
}
return 0;
}
static inline void
__intr_restore(bool flag) { //如果中断被屏蔽,则恢复中断
if (flag) {
intr_enable(); //恢复中断
}
}
do_fork实现
--------------------------------------------------------------------------------------------
/*code*/
/* do_fork - parent process for a new child process
* @clone_flags: used to guide how to clone the child process
* @stack: the parent's user stack pointer. if stack==0, It means to fork a kernel thread.
* @tf: the trapframe info, which will be copied to child process's proc->tf
*/
int do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf) {
int ret = -E_NO_FREE_PROC; //尝试为进程分配内存
struct proc_struct *proc; //定义新进程
if (nr_process >= MAX_PROCESS) { //分配进程数大于 4096,返回
goto fork_out; //返回
}
ret = -E_NO_MEM; //因内存不足而分配失败
//LAB4:EXERCISE2 YOUR CODE
/*
* Some Useful MACROs, Functions and DEFINEs, you can use them in below implementation.
* MACROs or Functions:
* alloc_proc: create a proc struct and init fields (lab4:exercise1)
* setup_kstack: alloc pages with size KSTACKPAGE as process kernel stack
* copy_mm: process "proc" duplicate OR share process "current"'s mm according clone_flags
* if clone_flags & CLONE_VM, then "share" ; else "duplicate"
* copy_thread: setup the trapframe on the process's kernel stack top and
* setup the kernel entry point and stack of process
* hash_proc: add proc into proc hash_list
* get_pid: alloc a unique pid for process
* wakeup_proc: set proc->state = PROC_RUNNABLE
* VARIABLES:
* proc_list: the process set's list
* nr_process: the number of process set
*/
// 1. call alloc_proc to allocate a proc_struct
// 2. call setup_kstack to allocate a kernel stack for child process
// 3. call copy_mm to dup OR share mm according clone_flag
// 4. call copy_thread to setup tf & context in proc_struct
// 5. insert proc_struct into hash_list && proc_list
// 6. call wakeup_proc to make the new child process RUNNABLE
// 7. set ret vaule using child proc's pid
/***************call alloc_proc to allocate a proc_struct*********************/
if ((proc = alloc_proc()) == NULL) { //调用 alloc_proc() 函数申请内存块,如果失败,直接返回处理
goto fork_out;//返回
}
proc->parent = current; //将子进程的父节点设置为当前进程
/***************call setup_kstack to allocate a kernel stack for child process*********************/
if (setup_kstack(proc) != 0) { //调用 setup_stack() 函数为进程分配一个内核栈
goto bad_fork_cleanup_proc; //返回
}
/***************call copy_mm to dup OR share mm according clone_flag*********************/
if (copy_mm(clone_flags, proc) != 0) { //调用 copy_mm() 函数复制父进程的内存信息到子进程
goto bad_fork_cleanup_kstack; //返回
}
/***************call copy_thread to setup tf & context in proc_struct*********************/
copy_thread(proc, stack, tf); //调用 copy_thread() 函数复制父进程的中断帧和上下文信息
/***************insert proc_struct into hash_list && proc_list*********************/
//将新进程添加到进程的 hash 列表中
bool intr_flag;
local_intr_save(intr_flag); //屏蔽中断,intr_flag 置为 1
{
proc->pid = get_pid(); //获取当前进程 PID
hash_proc(proc); //建立 hash 映射
list_add(&proc_list, &(proc->list_link)); //将进程加入到进程的链表中
nr_process ++; //进程数加 1
}
local_intr_restore(intr_flag); //恢复中断
/***************call wakeup_proc to make the new child process RUNNABLE*********************/
wakeup_proc(proc); //一切就绪,唤醒子进程
/***************set ret vaule using child proc's pid*********************/
ret = proc->pid; //返回子进程的 pid
fork_out: //已分配进程数大于 4096
return ret;
bad_fork_cleanup_kstack: //分配内核栈失败
put_kstack(proc);
bad_fork_cleanup_proc:
kfree(proc);
goto fork_out;
}
回答问题
请说明ucore是否做到给每个新fork的线程一个唯一的id?请说明你的分析和理由。
static int
get_pid(void) {
static_assert(MAX_PID > MAX_PROCESS);
struct proc_struct *proc;
list_entry_t *list = &proc_list, *le;
static int next_safe = MAX_PID, last_pid = MAX_PID;
if (++ last_pid >= MAX_PID) {
last_pid = 1;
goto inside;
}
if (last_pid >= next_safe) {
inside:
next_safe = MAX_PID;
repeat:
le = list;
while ((le = list_next(le)) != list) {
proc = le2proc(le, list_link);
if (proc->pid == last_pid) {
if (++ last_pid >= next_safe) {
if (last_pid >= MAX_PID) {
last_pid = 1;
}
next_safe = MAX_PID;
goto repeat;
}
}
else if (proc->pid > last_pid && next_safe > proc->pid) {
next_safe = proc->pid;
}
}
}
return last_pid;
}
从上面的代码可以看出。在使用 fork 或 clone 系统调用时产生的进程均会由内核分配一个新的唯一的PID值。具体来说,就是在分配PID时,设置一个保护锁,暂时不允许中断,这样在就唯一地分配了一个PID。
练习三:阅读代码,理解 proc_run 函数和它调用的函数如何完成进程切换的。
请在实验报告中简要说明你对proc_run函数的分析。并回答如下问题: 在本实验的执行过程中,创建且运行了几个内核线程? 语句 local_intr_save(intr_flag);…local_intr_restore(intr_flag); 在这里有何作用?请 说明理由
过程如下:
① 屏蔽中断
② 修改esp0,页表项和进行上下文切换
③ 允许中断
首先分析schedule函数的源码:
/* 宏定义:
#define le2proc(le, member) \
to_struct((le), struct proc_struct, member)*/
void
schedule(void) {
bool intr_flag; //定义中断变量
list_entry_t *le, *last; //当前list,下一list
struct proc_struct *next = NULL; //下一进程
local_intr_save(intr_flag); //中断禁止函数
{
current->need_resched = 0; //设置当前进程不需要调度
//last是否是idle进程(第一个创建的进程),如果是,则从表头开始搜索
//否则获取下一链表
last = (current == idleproc) ? &proc_list : &(current->list_link);
le = last;
do { //一直循环,直到找到可以调度的进程
if ((le = list_next(le)) != &proc_list) {
next = le2proc(le, list_link);//获取下一进程
if (next->state == PROC_RUNNABLE) {
break; //找到一个可以调度的进程,break
}
}
} while (le != last); //循环查找整个链表
if (next == NULL || next->state != PROC_RUNNABLE) {
next = idleproc; //未找到可以调度的进程
}
next->runs ++; //运行次数加一
if (next != current) {
proc_run(next); //运行新进程,调用proc_run函数
}
}
local_intr_restore(intr_flag); //允许中断
}
可以看到ucore实现的是FIFO调度算法:
1 调度开始时,先屏蔽中断。
2 在进程链表中,查找第一个可以被调度的程序
3 运行新进程,允许中断
再分析switch_to函数
switch_to: # switch_to(from, to)
# save from's registers
movl 4(%esp), %eax #保存from的首地址
popl 0(%eax) #将返回值保存到context的eip
movl %esp, 4(%eax) #保存esp的值到context的esp
movl %ebx, 8(%eax) #保存ebx的值到context的ebx
movl %ecx, 12(%eax) #保存ecx的值到context的ecx
movl %edx, 16(%eax) #保存edx的值到context的edx
movl %esi, 20(%eax) #保存esi的值到context的esi
movl %edi, 24(%eax) #保存edi的值到context的edi
movl %ebp, 28(%eax) #保存ebp的值到context的ebp
# restore to's registers
movl 4(%esp), %eax #保存to的首地址到eax
movl 28(%eax), %ebp #保存context的ebp到ebp寄存器
movl 24(%eax), %edi #保存context的ebp到ebp寄存器
movl 20(%eax), %esi #保存context的esi到esi寄存器
movl 16(%eax), %edx #保存context的edx到edx寄存器
movl 12(%eax), %ecx #保存context的ecx到ecx寄存器
movl 8(%eax), %ebx #保存context的ebx到ebx寄存器
movl 4(%eax), %esp #保存context的esp到esp寄存器
pushl 0(%eax) #将context的eip压入栈中
ret
switch_to函数主要完成的是进程的上下文切换,先保存当前寄存器的值,然后再将下一进程的上下文信息保存到对于寄存器中。
proc_run函数的源码:
void
proc_run(struct proc_struct *proc) {
if (proc != current) {
bool intr_flag; //定义中断变量
struct proc_struct *prev = current, *next = proc;
local_intr_save(intr_flag); //屏蔽中断
{
current = proc; //修改当前进程为新进程
load_esp0(next->kstack + KSTACKSIZE); //修改esp
lcr3(next->cr3); //修改页表项 完成进程间的页表切换
//上下文切换
switch_to(&(prev->context), &(next->context));
}
local_intr_restore(intr_flag); //允许中断
}
}
修改esp:设置任务状态段ts中特权态0下的栈顶指针esp0为next内核线程initproc的内核栈的栈顶, 即next->kstack + KSTACKSIZE ;
回答问题
1、在本实验的执行过程中,创建且运行了几个内核线程?
idleproc:第一个内核进程,完成内核中各个子系统的初始化,之后立即调度,执行其他进程。
initproc:用于完成实验的功能而调度的内核进程。
2、语句local_intr_save(intr_flag);…local_intr_restore(intr_flag);在这里有何作用?请说明理由。
屏蔽中断和打开中断,以免进程切换时其他进程再进行调度。
实验结果
moocos-> cd lab4
[~/moocos/ucore_lab/labcodes/lab4]
moocos-> make qemu
+ cc kern/init/entry.S
+ cc kern/init/init.c
kern/init/init.c: In function ‘kern_init’:
kern/init/init.c:32:5: warning: implicit declaration of function ‘grade_backtrace’ [-Wimplicit-function-declaration]
grade_backtrace();
^
kern/init/init.c: In function ‘grade_backtrace2’:
kern/init/init.c:57:5: warning: implicit declaration of function ‘mon_backtrace’ [-Wimplicit-function-declaration]
mon_backtrace(0, NULL, NULL);
^
kern/init/init.c: At top level:
kern/init/init.c:71:1: warning: conflicting types for ‘grade_backtrace’ [enabled by default]
grade_backtrace(void) {
^
kern/init/init.c:32:5: note: previous implicit declaration of ‘grade_backtrace’ was here
grade_backtrace();
^
kern/init/init.c:104:1: warning: ‘lab1_switch_test’ defined but not used [-Wunused-function]
lab1_switch_test(void) {
^
+ cc kern/libs/rb_tree.c
+ cc kern/libs/readline.c
+ cc kern/libs/stdio.c
+ cc kern/debug/kdebug.c
+ cc kern/debug/kmonitor.c
+ cc kern/debug/panic.c
+ cc kern/driver/clock.c
+ cc kern/driver/console.c
+ cc kern/driver/ide.c
+ cc kern/driver/intr.c
+ cc kern/driver/picirq.c
+ cc kern/trap/trap.c
+ cc kern/trap/trapentry.S
+ cc kern/trap/vectors.S
+ cc kern/mm/default_pmm.c
+ cc kern/mm/kmalloc.c
kern/mm/kmalloc.c: In function ‘__slob_free_pages’:
kern/mm/kmalloc.c:93:3: warning: passing argument 1 of ‘kva2page’ makes pointer from integer without a cast [enabled by default]
free_pages(kva2page(kva), 1 << order);
^
In file included from kern/mm/kmalloc.c:7:0:
kern/mm/pmm.h:106:1: note: expected ‘void *’ but argument is of type ‘long unsigned int’
kva2page(void *kva) {
^
+ cc kern/mm/pmm.c
+ cc kern/mm/swap.c
+ cc kern/mm/swap_fifo.c
+ cc kern/mm/vmm.c
kern/mm/vmm.c: In function ‘check_vmm’:
kern/mm/vmm.c:165:12: warning: unused variable ‘nr_free_pages_store’ [-Wunused-variable]
size_t nr_free_pages_store = nr_free_pages();
^
kern/mm/vmm.c: In function ‘check_vma_struct’:
kern/mm/vmm.c:177:12: warning: unused variable ‘nr_free_pages_store’ [-Wunused-variable]
size_t nr_free_pages_store = nr_free_pages();
^
+ cc kern/fs/swapfs.c
+ cc kern/process/entry.S
+ cc kern/process/proc.c
+ cc kern/process/switch.S
+ cc kern/schedule/sched.c
+ cc libs/hash.c
+ cc libs/printfmt.c
+ cc libs/rand.c
+ cc libs/string.c
+ ld bin/kernel
+ cc boot/bootasm.S
+ cc boot/bootmain.c
+ cc tools/sign.c
+ ld bin/bootblock
'obj/bootblock.out' size: 434 bytes
build 512 bytes boot sector: 'bin/bootblock' success!
10000+0 records in
10000+0 records out
5120000 bytes (5.1 MB) copied, 0.0241909 s, 212 MB/s
1+0 records in
1+0 records out
512 bytes (512 B) copied, 0.000133894 s, 3.8 MB/s
366+1 records in
366+1 records out
187662 bytes (188 kB) copied, 0.000755601 s, 248 MB/s
128+0 records in
128+0 records out
134217728 bytes (134 MB) copied, 0.195327 s, 687 MB/s
(THU.CST) os is loading ...
Special kernel symbols:
entry 0xc010002a (phys)
etext 0xc010b220 (phys)
edata 0xc0128a90 (phys)
end 0xc012bc18 (phys)
Kernel executable memory footprint: 175KB
ebp:0xc0127f38 eip:0xc0101e6a args:0x00010094 0x00000000 0xc0127f68 0xc01000d3
kern/debug/kdebug.c:308: print_stackframe+21
ebp:0xc0127f48 eip:0xc0102159 args:0x00000000 0x00000000 0x00000000 0xc0127fb8
kern/debug/kmonitor.c:129: mon_backtrace+10
ebp:0xc0127f68 eip:0xc01000d3 args:0x00000000 0xc0127f90 0xffff0000 0xc0127f94
kern/init/init.c:57: grade_backtrace2+33
ebp:0xc0127f88 eip:0xc01000fc args:0x00000000 0xffff0000 0xc0127fb4 0x0000002a
kern/init/init.c:62: grade_backtrace1+38
ebp:0xc0127fa8 eip:0xc010011a args:0x00000000 0xc010002a 0xffff0000 0x0000001d
kern/init/init.c:67: grade_backtrace0+23
ebp:0xc0127fc8 eip:0xc010013f args:0xc010b23c 0xc010b220 0x00003188 0x00000000
kern/init/init.c:72: grade_backtrace+34
ebp:0xc0127ff8 eip:0xc010007f args:0x00000000 0x00000000 0x0000ffff 0x40cf9a00
kern/init/init.c:32: kern_init+84
memory management: default_pmm_manager
e820map:
memory: 0009fc00, [00000000, 0009fbff], type = 1.
memory: 00000400, [0009fc00, 0009ffff], type = 2.
memory: 00010000, [000f0000, 000fffff], type = 2.
memory: 07efe000, [00100000, 07ffdfff], type = 1.
memory: 00002000, [07ffe000, 07ffffff], type = 2.
memory: 00040000, [fffc0000, ffffffff], type = 2.
check_alloc_page() succeeded!
check_pgdir() succeeded!
check_boot_pgdir() succeeded!
-------------------- BEGIN --------------------
PDE(0e0) c0000000-f8000000 38000000 urw
|-- PTE(38000) c0000000-f8000000 38000000 -rw
PDE(001) fac00000-fb000000 00400000 -rw
|-- PTE(000e0) faf00000-fafe0000 000e0000 urw
|-- PTE(00001) fafeb000-fafec000 00001000 -rw
--------------------- END ---------------------
use SLOB allocator
check_slab() success
kmalloc_init() succeeded!
check_vma_struct() succeeded!
page fault at 0x00000100: K/W [no page found].
check_pgfault() succeeded!
check_vmm() succeeded.
ide 0: 10000(sectors), 'QEMU HARDDISK'.
ide 1: 262144(sectors), 'QEMU HARDDISK'.
SWAP: manager = fifo swap manager
BEGIN check_swap: count 31950, total 31950
setup Page Table for vaddr 0X1000, so alloc a page
setup Page Table vaddr 0~4MB OVER!
set up init env for check_swap begin!
page fault at 0x00001000: K/W [no page found].
page fault at 0x00002000: K/W [no page found].
page fault at 0x00003000: K/W [no page found].
page fault at 0x00004000: K/W [no page found].
set up init env for check_swap over!
write Virt Page c in fifo_check_swap
write Virt Page a in fifo_check_swap
write Virt Page d in fifo_check_swap
write Virt Page b in fifo_check_swap
write Virt Page e in fifo_check_swap
page fault at 0x00005000: K/W [no page found].
swap_out: i 0, store page in vaddr 0x1000 to disk swap entry 2
write Virt Page b in fifo_check_swap
write Virt Page a in fifo_check_swap
page fault at 0x00001000: K/W [no page found].
swap_out: i 0, store page in vaddr 0x2000 to disk swap entry 3
swap_in: load disk swap entry 2 with swap_page in vadr 0x1000
write Virt Page b in fifo_check_swap
page fault at 0x00002000: K/W [no page found].
swap_out: i 0, store page in vaddr 0x3000 to disk swap entry 4
swap_in: load disk swap entry 3 with swap_page in vadr 0x2000
write Virt Page c in fifo_check_swap
page fault at 0x00003000: K/W [no page found].
swap_out: i 0, store page in vaddr 0x4000 to disk swap entry 5
swap_in: load disk swap entry 4 with swap_page in vadr 0x3000
write Virt Page d in fifo_check_swap
page fault at 0x00004000: K/W [no page found].
swap_out: i 0, store page in vaddr 0x5000 to disk swap entry 6
swap_in: load disk swap entry 5 with swap_page in vadr 0x4000
count is 5, total is 5
check_swap() succeeded!
++ setup timer interrupts
this initproc, pid = 1, name = "init"
To U: "Hello world!!".
To U: "en.., Bye, Bye. :)"
kernel panic at kern/process/proc.c:354:
process exit!!.