Lab 4总结与讨论

387 views
Skip to first unread message

Lin Yuning

unread,
Jan 11, 2011, 9:02:58 AM1/11/11
to xv6...@googlegroups.com
代码:

或者以前有取过的直接更新:
git pull

老样子,只弄好了基本问题

Q1:In your implementation of env_run() you should have called lcr3(). Before and after the call to lcr3(), your code makes references (at least it should) to the variable e, the argument to env_run. Upon loading the %cr3 register, the addressing context used by the MMU is instantly changed. But a virtual address (namely e) has meaning relative to a given address context--the address context specifies the physical address to which the virtual address maps. Why can the pointer e be dereferenced both before and after the addressing switch?
A1:因为e本身是在envs数组中,而envs是在内核的那段虚拟地址上的。内核那段虚拟地址在每一个env的环境中都是一样的,所以不会受影响

E1-E10:基本按照网页上面的说明和源代码里面的Hint来,做好一步就要跑一下对应的测试程序,确定没有问题。

说下E5这里面的_pgfault_upcall,主要做的事情就是首先把trapframe结构体里面的tf_esp(原始的esp)取出来,然后将栈临时切换到进入中断之前的状态上,将原来的eip压到进入缺页中断之前的那个栈上(不是缺页中断在跑的UXSTACKTOP这个栈)。然后切换回缺页中断的栈,回复寄存器、在切换到原始栈上、直接ret指令,此时原始的eip已经添加到原始的栈上了,就可以不变动其他寄存器,回复到缺页中断发生的时候的环境继续跑下去了。

也应为原始栈上要追加一个eip、所以如果在缺页中断中又发生缺页中断的话,需要在UXSTACKTOP这个栈上多预留4个字节,否则会破坏栈空间的数据。这一点在kern/trap.c的page_fault_handler的提示文字里面也有提到。

罗罗嗦嗦的说了半天,自己也觉得说的不怎么样,直接看我代码里面的应该更好理解些。
有些地方我自己也没弄得很清楚,大家一起讨论。

另:Lab4的Question好像少了很多啊,Challenge倒是有一大堆……

mei shaoming

unread,
Jan 11, 2011, 9:29:39 AM1/11/11
to xv6...@googlegroups.com
赞, 可惜我才刚开始lab4, 无法开始讨论. 

Sunus Lee

unread,
Jan 15, 2011, 8:13:11 AM1/15/11
to xv6...@googlegroups.com
我今天完成了pfentry.s
很折磨 不知道这样理解对不对。
utf_esp之前指向用户栈的某处,这里假设是ADDR3.
        ****beginning:
        addl $8, %esp

        UXSTACKTOP:                             USSTACKTOP:
        utf_esp     ------------------------            (ADDR0)
        utf_eflags                           \          (ADDR1)
        utf_eip                                 \        (ADDR2)
        utf_regs <____current_ESP    \---->(ADDR3)
        utf_err                                          (ADDR4)
        utf_fault_va                                   (ADDR5)

        ****step1:
        movl 40(%esp),%eax
        movl 48(%esp),%ebx
        movl eax,-4(%ebx)

        UXSTACKTOP:                             USSTACKTOP:
        utf_esp     ------------------------            (ADDR0)
        utf_eflags                           \          (ADDR1)
        utf_eip                                 \        (ADDR2)
        utf_regs <____current_ESP    \---->(ADDR3)
        utf_err                                          (utf_eip)
        utf_fault_va                                   (ADDR5)

        ****step2:
        popal

        UXSTACKTOP:                             USSTACKTOP:
        utf_esp          ------------------------          (ADDR0)
        utf_eflags                               \         (ADDR1)
        utf_eip  <____current_ESP        \       (ADDR2)
        utf_regs                                     \---->(ADDR3)
        utf_err                                             (utf_eip)
        utf_fault_va                                      (ADDR5)

        ****step3:
        addl $4,esp
        popfl
        SAME AS step2

        UXSTACKTOP:                             USSTACKTOP:
        utf_esp (<____current_ESP)------         (ADDR0)
        utf_eflags                                  \       (ADDR1)
        utf_eip                                        \     (ADDR2)
        utf_regs                                       \-->(ADDR3)
        utf_err                                               (utf_eip)
        utf_fault_va                                        (ADDR5)

        ****step4
        popl %esp  (current_ESP = utf_esp)           //http://topic.csdn.net/u/20090425/11/fa943533-9c56-4900-8c9f-9f1a5e9d74ff.html  关于popl %esp讨论
        subl $4,%esp(switch to USSTACKTOP)

        UXSTACKTOP:                    USSTACKTOP:
        utf_esp ------------------------        (ADDR0)
        utf_eflags                      \       (ADDR1)
        utf_eip                           \      (ADDR2)
        utf_regs                          \---->(ADDR3)
        utf_err                                   (utf_eip)(<____current_ESP)
        utf_fault_va                            (ADDR5)

        ****step5
        ret

        UXSTACKTOP:                             USSTACKTOP:
        utf_esp ------------------------         (ADDR0)
        utf_eflags                      \       (ADDR1)
        utf_eip                           \      (ADDR2)
        utf_regs                          \---->(ADDR3)(<____current_ESP)
        utf_err                                  (utf_eip)
        utf_fault_va                            (ADDR5)

        //popl utf_eip
        //run instruction from UTF_EIP;
1:
不知这样理解这个过程对不对
2:
popl %esp
之后 新的esp是否会+4?

谢谢!

Lin Yuning

unread,
Jan 15, 2011, 9:06:14 AM1/15/11
to xv6...@googlegroups.com
1:我理解和你的差不多XD

2:popl %esp之前我都没注意,只是觉得理论上这个指令应该要能够保证最后esp上面的数据应该是在执行这条指令的时候的栈头上面的数据。
而实际操作结果也符合我的推测。个人感觉popl的指令做的工作是 栈上数据取出来——》esp+4——》取出来的数据放到对应的寄存器上。所以既然最后要放在esp上,中间esp+4对结果并无影响。
当然只是我的推测,希望有熟悉这方面的大牛能给个确定的答案,3x~

贴一下我的实现:

Step1实现的有点繁琐,
主要是写的时候不会用movl 40(%esp),%eax这种可以相对寄存器内容加上偏移寻址,再放到另一个寄存器里面的指令(压根么想到,就想着pop、push……)
功能和你的step1相同的

//取出进入Trap之前的用户进程的eip(utf_eip),暂时存在eax上
        addl $0x28, %esp;
        popl %eax;
//让esp指向utf_esp,跳过utf_eflags的位置
        addl $0x4, %esp;
//将当前的esp(在UXSTACKTOP栈中)暂时备份在ebp上,取出进入trap之前的用户程序栈地址(utf_esp),pop到esp上(相当于临时切换到用户程序的栈上)
        movl %esp, %ebp;
        popl %esp;
//将用户的eip(暂时存在eax寄存器中)增加到用户栈上(此时已经在用户进程的Stack而非UXSTACKTOP上了)
        pushl %eax;
//切换回trap中断的栈(UXSTACKTOP)
        movl %ebp, %esp;
//然后我多做了一部将utf结构体里面的utf_esp的数据加了4,这样在后面用到这个数据的时候就不需要再加4了,原因下面再说
//将utf_esp数据取出来,+4,再放回去
        popl %eax;
        subl $0x4, %eax;
        pushl %eax;
//esp切换到utf_regs位置,为下面的popal做准备
        subl $0x28, %esp;

Step 2:
//popal,么啥好说的了
        popal;

Step 3:
//popfl,也没啥好说的了
        addl $0x4, %esp;
        popfl;

Step 4:
//这里你的代码里面是将esp取出来之后再-4,但是之前已经将eflags回复到了用户进程的eflags了,执行subl可能会影响eflags的标志位
//而Step3的Hint里面也有如下的描述:
        // Restore eflags from the stack.  After you do this, you can
        // no longer use arithmetic operations or anything else that
        // modifies eflags.
//所以我觉得这步减4还是放到Step1里面解决掉会更好些?
//还是说这里操作不会有结果溢出,所以不会影响eflags,放这里也没问题(eflags的细节我也不是很清楚……)
//直接切换到用户栈
        popl %esp;

//恢复用户进程执行
Step 5:
        ret

2011/1/15 Sunus Lee <sunu...@gmail.com>

Sunus Lee

unread,
Jan 15, 2011, 11:38:09 AM1/15/11
to xv6...@googlegroups.com
fix1:
先前发的step1里
movl 40(%esp),%eax
movl 48(%esp),%ebx

该是:
movl 32(%esp),%eax
movl 40(%esp),%ebx

http://topic.csdn.net/u/20090425/11/fa943533-9c56-4900-8c9f-9f1a5e9d74ff.html 
这有关于popl %esp,pushl %esp讨论

step4:
影响标志位我的确疏忽了..

Sunus Lee

unread,
Jan 18, 2011, 2:17:31 PM1/18/11
to xv6...@googlegroups.com
在lab4 user/faultalloc中 出现了个诡异的问题
大家帮看看是什么可能引起的:

正常结果 :

Run user/faultalloc. You should see:

[00000000] new env 00001000
[00000000] new env 00001001
fault deadbeef
this string was faulted in at deadbeef
fault cafebffe
fault cafec000
this string was faulted in at cafebffe
[00001001] exiting gracefully
[00001001] free env 00001001
我的结果:

[00000000] new env 00001000
[00000000] new env 00001001
fault deadbeef
this string was faulted in at deadbeef
fault cafebffe
fault cafec000
在这down掉了.

根据调试结果down之前
sys_page_alloc函数是成功返回0的。
换句话说,在if判断之后,snprintf之前 系统down掉了。大家可以说说是什么原因可能造成这种情况?
faultalloc好似是有2个case,我处理了第一个之后就down.

谢谢!


Sunus Lee

unread,
Jan 19, 2011, 3:15:00 AM1/19/11
to xv6...@googlegroups.com
 解决了 是我SB代码的问题..

Sunus Lee

unread,
Jan 19, 2011, 4:22:50 AM1/19/11
to xv6...@googlegroups.com
Step 4:
//这里你的代码里面是将esp取出来之后再-4,但是之前已经将eflags回复到了用户进程的eflags了,执行subl可能会影响eflags的标志位
//而Step3的Hint里面也有如下的描述:
        // Restore eflags from the stack.  After you do this, you can
        // no longer use arithmetic operations or anything else that
        // modifies eflags.
//所以我觉得这步减4还是放到Step1里面解决掉会更好些?
//还是说这里操作不会有结果溢出,所以不会影响eflags,放这里也没问题(eflags的细节我也不是很清楚……)

这个问题 的确用他的测试程序faultregs可以检测出了,eflags会被subl操作影响。
更正如下 subl这一步该在popf之前执行 修正代码如下;

 37     pushl %esp          // function argument: pointer to UTF
 38     movl _pgfault_handler, %eax
 39     call *%eax
 40     addl $4, %esp           // pop function argument

STEP 1:
 69         addl $8, %esp
 70         movl 32(%esp), %eax
 71         movl 40(%esp), %ebx
 72         movl %eax, -4(%ebx)

STEP 2:
79         popal

STEP 3:
 86         addl $4, %esp //sikp eip
 87         subl $4, 4(%esp) 
 88         popfl

STEP 4:
 91         popl %esp
 92         //Do Not subl %4,%esp here.It will effect eflag bit.

这样可以通过faultregs监测程序了。

dave

unread,
Feb 22, 2011, 8:58:02 PM2/22/11
to xv6...@googlegroups.com
LAB4 Part A davelv详细版,哇卡卡


Exercise 1.Implement round-robin scheduling in sched_yield() as described above. Don't forget to modify syscall() to dispatch sys_yield().

修改kern/sched.c文件的sched_yield()函数
注意:
1、注意起始env的选择一定是当前env的下一个,如果没有env或者env到了最后就从第1个env开始,第0个env作为idle使用的。
2、循环次数,由于不遍历第0个env,所以总共最多循环NENV-1次,这时候整好循环到当前env(如果有的话)

// Choose a user environment to run and run it.
void
sched_yield(void)
{
int i;
//find next env;
struct Env *e = (curenv==NULL || curenv>=envs+NENV-1) ? envs+1: curenv+1;
//find next runnable env;
for(i=1; i<NENV; i++)
{
if (e->env_status == ENV_RUNNABLE)
{//got it
env_run(e);
}
// next env
e = (e == envs+NENV-1) ? envs+1:e+1;
}
// Run the special idle environment when nothing else is runnable.
if (envs[0].env_status == ENV_RUNNABLE)
env_run(&envs[0]);
else {
cprintf("Destroyed all environments - nothing more to do!\n");
while (1)
monitor(NULL);
}
}


Exercise 2. Implement the system calls described above (sys_exofork, sys_env_set_status, sys_page_alloc, sys_page_map, sys_page_unmap) in kern/syscall.c. You will need to use various functions in kern/pmap.c and kern/env.c, particularly envid2env(). For now, whenever you call envid2env(), pass 1 in the checkperm parameter. Be sure you check for any invalid system call arguments, returning -E_INVAL in that case. Test your JOS kernel with user/dumbfork and make sure it works before proceeding.

修改kern/syscall.c中如下函数
// Allocate a new environment.
// Returns envid of new environment, or < 0 on error.  Errors are:
// -E_NO_FREE_ENV if no free environment is available.
// -E_NO_MEM on memory exhaustion.
static envid_t
sys_exofork(void)
{
// Create the new environment with env_alloc(), from kern/env.c.
// It should be left as env_alloc created it, except that
// status is set to ENV_NOT_RUNNABLE, and the register set is copied
// from the current environment -- but tweaked so sys_exofork
// will appear to return 0.

envid_t ret;
struct Env *e;
ret =  env_alloc(&e, curenv->env_id);
if (ret < 0)
return ret;
e->env_status == ENV_NOT_RUNNABLE;
e->env_tf = curenv->env_tf;
e->env_tf.tf_regs.reg_eax = 0;
return e->env_id;

}
注意返回值的问题,子进程要返回0,所以要把0放到eax中,子进程开始运行的时候从内核返回到用户层就是把eax作为返回值。

// Set envid's env_status to status, which must be ENV_RUNNABLE
// or ENV_NOT_RUNNABLE.
//
// Returns 0 on success, < 0 on error.  Errors are:
// -E_BAD_ENV if environment envid doesn't currently exist,
// or the caller doesn't have permission to change envid.
// -E_INVAL if status is not a valid status for an environment.
static int
sys_env_set_status(envid_t envid, int status)
{
// Hint: Use the 'envid2env' function from kern/env.c to translate an
// envid to a struct Env.
// You should set envid2env's third argument to 1, which will
// check whether the current environment has permission to set
// envid's status.

struct Env *e;
if (status != ENV_RUNNABLE && status != ENV_NOT_RUNNABLE)
return -E_INVAL;
if (envid2env(envid, &e, 1) <0)
return -E_BAD_ENV;
e->env_status = status;
return 0;
}
注意status的值要在限定的范围内。

// Allocate a page of memory and map it at 'va' with permission
// 'perm' in the address space of 'envid'.
// The page's contents are set to 0.
// If a page is already mapped at 'va', that page is unmapped as a
// side effect.
//
// perm -- PTE_U | PTE_P must be set, PTE_AVAIL | PTE_W may or may not be set,
//         but no other bits may be set.  See PTE_USER in inc/mmu.h.
//
// Return 0 on success, < 0 on error.  Errors are:
// -E_BAD_ENV if environment envid doesn't currently exist,
// or the caller doesn't have permission to change envid.
// -E_INVAL if va >= UTOP, or va is not page-aligned.
// -E_INVAL if perm is inappropriate (see above).
// -E_NO_MEM if there's no memory to allocate the new page,
// or to allocate any necessary page tables.
static int
sys_page_alloc(envid_t envid, void *va, int perm)
{
// Hint: This function is a wrapper around page_alloc() and
//   page_insert() from kern/pmap.c.
//   Most of the new code you write should be to check the
//   parameters for correctness.
//   If page_insert() fails, remember to free the page you
//   allocated!

struct Env *e;
struct Page *page;
//check virtual address & perm
if ( (uint32_t)va >= UTOP || PGOFF(va) != 0 || (perm&5) != 5 || (perm & (~PTE_USER)) != 0 )
return -E_INVAL;
// get environment id
if (envid2env(envid, &e, 1) <0)
return -E_BAD_ENV;
//alloc physical page
if (page_alloc(&page) <0)
return -E_NO_MEM;
//insert to page tables
if (page_insert(e->env_pgdir, page, va, perm) <0)
{
page_free(page);
return -E_NO_MEM;
}
//fill with 0
memset(page2kva(page), 0, PGSIZE);
return 0;
}
注意perm的权限,有些一定要置位,有些一定不能有。

// Map the page of memory at 'srcva' in srcenvid's address space
// at 'dstva' in dstenvid's address space with permission 'perm'.
// Perm has the same restrictions as in sys_page_alloc, except
// that it also must not grant write access to a read-only
// page.
//
// Return 0 on success, < 0 on error.  Errors are:
// -E_BAD_ENV if srcenvid and/or dstenvid doesn't currently exist,
// or the caller doesn't have permission to change one of them.
// -E_INVAL if srcva >= UTOP or srcva is not page-aligned,
// or dstva >= UTOP or dstva is not page-aligned.
// -E_INVAL is srcva is not mapped in srcenvid's address space.
// -E_INVAL if perm is inappropriate (see sys_page_alloc).
// -E_INVAL if (perm & PTE_W), but srcva is read-only in srcenvid's
// address space.
// -E_NO_MEM if there's no memory to allocate any necessary page tables.
static int
sys_page_map(envid_t srcenvid, void *srcva,
     envid_t dstenvid, void *dstva, int perm)
{
// Hint: This function is a wrapper around page_lookup() and
//   page_insert() from kern/pmap.c.
//   Again, most of the new code you write should be to check the
//   parameters for correctness.
//   Use the third argument to page_lookup() to
//   check the current permissions on the page.

struct Env *srce, *dste;
pte_t *ppte;
struct Page *page;
//check env_id
if ((envid2env(srcenvid, &srce, 1)<0) || (envid2env(dstenvid, &dste, 1)<0))
return -E_BAD_ENV;
//check vitrual address range and alignment
if (((uint32_t)srcva >= UTOP) || (PGOFF(srcva) != 0) ||
((uint32_t)dstva >= UTOP) || (PGOFF(dstva) != 0))
return -E_INVAL;
//check srcva is mapped
if ((page = page_lookup(srce->env_pgdir, srcva, &ppte)) == NULL)
return -E_INVAL;
//check perm
if ( ((perm&5) != 5) || ((perm & (~PTE_USER)) != 0) ||
((perm & PTE_W)&& !ISPTE_W(*ppte)) )
return -E_INVAL;
//insert page to destination
if (page_insert(dste->env_pgdir, page, dstva, perm) <0)
return -E_NO_MEM;
return 0;

}

主要还是传入参数的安全性/合法性检测

// Unmap the page of memory at 'va' in the address space of 'envid'.
// If no page is mapped, the function silently succeeds.
//
// Return 0 on success, < 0 on error.  Errors are:
// -E_BAD_ENV if environment envid doesn't currently exist,
// or the caller doesn't have permission to change envid.
// -E_INVAL if va >= UTOP, or va is not page-aligned.
static int
sys_page_unmap(envid_t envid, void *va)
{
// Hint: This function is a wrapper around page_remove().

struct Env *e;
if (envid2env(envid, &e, 1) <0)
return -E_BAD_ENV;
if ((uint32_t)va >= UTOP || PGOFF(va) != 0)
return -E_INVAL;

page_remove(e->env_pgdir, va);
return 0;
}
一个page_remove就把活儿轻松搞定

最后还需要注意的是,在syscal()函数里,也要把对应的系统调用安排好。

Challenge 1 Add a less trivial scheduling policy to the kernel, such as a fixed-priority scheduler that allows each environment to be assigned a priority and ensures that higher-priority environments are always chosen in preference to lower-priority environments. If you're feeling really adventurous, try implementing a Unix-style adjustable-priority scheduler or even a lottery or stride scheduler. (Look up "lottery scheduling" and "stride scheduling" in Google.)

Write a test program or two that verifies that your scheduling algorithm is working correctly (i.e., the right environments get run in the right order). It may be easier to write these test programs once you have implemented fork() and IPC in parts B and C of this lab.

给进程调度加入优先级。这个问题实现起来是很轻松的,在内核开辟一块进程调度队列即可。不过原本代码根本没有考虑优先级,所以调度部分sched_yield()要重写,各种进程状态改变的部分也要加入优先级部分,写起来比较麻烦。但是简单的可以用数组来实现,这样每次进程切换的时候都会遍历一次进程数组,这个简单的实现可以参考XV6的进程优先级实现部分。下面说的是进程队列的实现
1、进程结构体要加入优先级成员,并新建一个进程list类型,或者直接用数组。
2、在内核建立进程队列。
3、在进程创建的时候给与初始优先级。
4、在进程切换的时候,当前进程优先级变为初始值入队,其余进程优先级递增(可加权递增),在进程队列中选择最大的一个运行。
5、进程在变为非runnable(阻塞)的时候要从进程队列删除,恢复的时候加入,为了提高程序的响应速度,可以在恢复的时候增加优先级。

这个模型的优点是实现方便,操作灵活,在并发不多的环境下性能也不错。


Challenge 2 The JOS kernel currently does not allow applications to use the x86 processor's x87 floating-point unit (FPU), MMX instructions, or Streaming SIMD Extensions (SSE). Extend the Env structure to provide a save area for the processor's floating point state, and extend the context switching code to save and restore this state properly when switching from one environment to another. The FXSAVE and FXRSTOR instructions may be useful, but note that these are not in the old i386 user's manual because they were introduced in more recent processors. Write a user-level test program that does something cool with floating-point.
给中断加入保存浮点寄存器的功能。
1、给inc/trap.h文件中的Trapframe结构体新增char tf_fpus[512]成员,并新增uint32_t tf_padding0[3]用来对齐。
2、修改kern/trapentry.S文件中的_alltraps函数,加入保存fpu寄存器的功能
_alltraps:
/*make Trapframe*/
pushl %ds;
pushl %es;
pushal;
/*save FPU*/

subl $524, %esp;
fxsave (%esp);
/*load kernel data segment*/
movl $GD_KD, %eax;
movw %ax, %ds;
movw %ax, %es;
/*push trap argument pointer to Trapframe*/
pushl %esp;
call trap;
/*trap fall*/
addl $4, %esp;
fxrstor (%esp);
addl $524, %esp;
popal;
popl %es;
popl %ds;
addl $8, %esp;
iret
3、修改kern/env.c文件env_pop_tf()函数,加入恢复fpu功能
void
env_pop_tf(struct Trapframe *tf)
{
__asm __volatile("movl %0,%%esp\n"
"\tfxrstor (%%esp)\n" /*restore fpu*/
"\taddl $524, %%esp\n"
"\tpopal\n"
"\tpopl %%es\n"
"\tpopl %%ds\n"
"\taddl $0x8, %%esp\n" /* skip tf_trapno and tf_errcode */
"\tiret"
: : "g" (tf) : "memory");
panic("iret failed");  /* mostly to placate the compiler */
}
4、测试用例是修改的user/dumbfork.c的umain()函数。
void
umain(void)
{
envid_t who;
int i;
double a1=2.0,
a=getchar();
//move a to st(0)
asm("fldl %0\n\t"::"m"(a));
who = dumbfork();

if(who)
{

for (i = 0; i < 6; i++)
{
//mull
asm("fmull %0"::"m"(a1));
sys_yield();
}
//move fpu result to a
asm("fstpl %0":"=m"(a));
cprintf("parent a=%d\n",(int) a);
}
else
{
for(i=0; i<6; i++)
{
asm("fmull %0"::"m"(a1));
sys_yield();
}
asm("fstpl %0":"=m"(a));
cprintf ("child a=%d\n",(int)a);
}
}
注意
1、利用父子进程不停的切换来测试浮点寄存器有没有被保存。
2、gcc 4.5.1 x86版本 -03优化好像对于浮点运算提供了保护功能,如果运算后有函数调用会浮点数保存回去(或者是没有达到优化条件?),这样即使函数内部使用了浮点寄存器也不会影响到外面。所以只好用嵌入汇编测试了,不知道有没有编译参数可以调整。当我把上面循环手工展开,把sys_yield()改成直接inline到系统调用时,只用C代码也可以达到测试的效果。

Challenge 3 Add the additional system calls necessary to read all of the vital state of an existing environment as well as set it up. Then implement a user mode program that forks off a child environment, runs it for a while (e.g., a few iterations of sys_yield()), then takes a complete snapshot or checkpoint of the child environment, runs the child for a while longer, and finally restores the child environment to the state it was in at the checkpoint and continues it from there. Thus, you are effectively "replaying" the execution of the child environment from an intermediate state. Make the child environment perform some interaction with the user using sys_cgetc() or readline() so that the user can view and mutate its internal state, and verify that with your checkpoint/restart you can give the child environment a case of selective amnesia, making it "forget" everything that happened beyond a certain point.

增加系统调用,提供进程快照的功能。要提供完整的快照功能,除了要保存进程的全部数据,如果考虑到进程不掉用exec来替换代码文本外,需要保存以下数据
1、进程全局变量
2、进程用户栈
3、进程环境变量Env结构体
内核栈数据不需要保存,因为没有内核抢占功能,内核栈是共用的,如果想保存全局变量的话需要修改载入程序文件处的函数用来保存一些信息,而且不同的全局变量所在段大小是不同的,在用户空间分配也比较麻烦,所以这里暂时只保存用户栈和Env结构体,也可以实现简单的进程快照功能。具体实现如下:
1、在inc/env.h中声明进程状态的结构体
struct proc_status {
        struct Env env;
        char stack[PGSIZE];
};
2、修改inc/syscall.h中的enmu,增加sys_proc_sav和sys_proc_rst两个调用号

3、在kern/syscall.c中新增两个函数sys_proc_save和sys_proc_restore分别用来保存和恢复进程状态,并且修改syscal()函数,使得这两个函数处理新增的两个调用号
//save one process status
static int
sys_proc_save(envid_t envid, struct proc_status *ps)
{
struct Env *olde;
struct Page *pg;
int offset;
//save env
if (envid2env(envid, &olde, 1) <0)
return -E_BAD_ENV;
if (user_mem_check(curenv, ps, sizeof(struct proc_status), PTE_U|PTE_W|PTE_P) <0)
return -E_FAULT;
ps->env = *olde;
//save stack
if ((pg=page_lookup(olde->env_pgdir, (void *)(USTACKTOP-PGSIZE), NULL))==NULL)
return -E_FAULT;
offset = olde->env_tf.tf_esp+PGSIZE-USTACKTOP;
memmove(ps->stack, page2kva(pg), PGSIZE);
cprintf("process %x has been saved\n", envid);
return 0;
}

//restore one process
static int
sys_proc_restore(envid_t envid, const struct proc_status *ps)
{
struct Env *olde;
struct Page *pg;
int offset;
//restore env
if (envid2env(envid, &olde, 1) <0)
return -E_BAD_ENV;
if (user_mem_check(curenv, ps, sizeof(struct proc_status), PTE_U|PTE_P) <0)
return -E_FAULT;
//print_trapframe(&(olde->env_tf));
*olde = ps->env;
//restore stack
if ((pg=page_lookup(olde->env_pgdir, (void *)(USTACKTOP-PGSIZE), NULL))==NULL)
return -E_FAULT;
offset = olde->env_tf.tf_esp+PGSIZE-USTACKTOP;
memmove(page2kva(pg), ps->stack, PGSIZE);
cprintf("process %x has been restored\n",envid);
return 0;
}
4、修改inc/lib.h文件,声明两个系统调用的用户接口
int sys_proc_save(envid_t envid, struct proc_status *ps);
int sys_proc_restore(envid_t envid, const struct proc_status *ps);
5、修改lib/syscall.c文件,实现两个系统调用的用户接口
int
sys_proc_save(envid_t envid, struct proc_status *ps)
{
return syscall(SYS_proc_sav, 1, envid, (uint32_t)ps, 0, 0, 0);
}

int
sys_proc_restore(envid_t envid, const struct proc_status *ps)
{
return syscall(SYS_proc_rst, 1, envid, (uint32_t)ps, 0, 0, 0);
}
6、在user/dumbfork.c的基础上修改umain()函数用来测试。
struct proc_status ps;
void
umain(void)
{
envid_t who;
int i,j,r;
// fork a child process
who = dumbfork();
// print a message and yield to the other a few times
if(who)
{//parent

for (i = 0; i < 10; i++)
{

cprintf("step0:%d I am the parent!\n",i );
if (i==1)
{
if ((r=sys_proc_save(who, &ps)) <0)
panic("sys_env_save: %e", r);
}
else if (i==8)
{
if ((r=sys_proc_restore(who, &ps))<0)
panic("sys_env_restore: %e", r);;
}

sys_yield();
}

}
else
{//child
for(i=0; i<6; i++)
{
cprintf("step1:%d I am the chlid\n",i);
sys_yield();
}
for (j=0; j<6; j++)
{
cprintf("step2:%d I am the chlid\n",j);
sys_yield();
}
}
}

注意:
1、把进程快照保存在用户空间的好处就是父进程可以非常方便的修改用户状态,不好之处就是风险实在太大。也可以把真实数据放入内核,外界用ID代替,就像envid和filedesc一样。
2、这里把进程快照变量定义成了全局变量,不能放在栈上,因为它还要保存的栈数据呢,这样一来就超过栈的大小了。其实也可以实现成在内核分配正好大小的数据返回指针给用户空间,不过由于目前内核对用户空间的内存管理还不够方便(还没实现堆管理),所以就没这么做。

Question 1. In your implementation of env_run() you should have called lcr3(). Before and after the call to lcr3(), your code makes references (at least it should) to the variable e, the argument to env_run. Upon loading the %cr3 register, the addressing context used by the MMU is instantly changed. But a virtual address (namely e) has meaning relative to a given address context--the address context specifies the physical address to which the virtual address maps. Why can the pointer e be dereferenced both before and after the addressing switch?

因为无论是e变量(保存在内核栈上)还是e指向的环境结构体变量(保存在内核数据段)其页映射关系在每个进程的cr3对应的页目录中都是一致的,所以切换cr3不会引发此方面的异常。


Yan Hong

unread,
Feb 23, 2011, 3:02:13 AM2/23/11
to xv6...@googlegroups.com
俺投简历用的也是这个邮箱,一看有新邮件,还以为是哪家公司有回应了Orz。。。。。

dave

unread,
Feb 23, 2011, 3:07:41 AM2/23/11
to xv6...@googlegroups.com
On Wed, 2011-02-23 at 16:02 +0800, Yan Hong wrote:
一看有新邮件,还以为是哪家公司有回应了Orz。。。。。
哇,真是报歉,Yan Hong ,Baidu大佬的名字么 ^^

Yan Hong

unread,
Feb 23, 2011, 3:19:25 AM2/23/11
to xv6...@googlegroups.com
瞎起的。
最近向二十多个小公司投简历,在网上查看一部分公司的评价,清一色负面:老板装B,经常加班,风气不好,管理层浮躁等等等等。看得俺拔凉拔凉的,深怕自己陷入泥潭。。。。。

2011/2/23 dave <dav...@qq.com>

dave

unread,
Feb 23, 2011, 6:05:38 AM2/23/11
to xv6...@googlegroups.com
On Wed, 2011-02-23 at 16:19 +0800, Yan Hong wrote:
瞎起的。
最近向二十多个小公司投简历,在网上查看一部分公司的评价,清一色负面:老板装B,经常加班,风气不好,管理层浮躁等等等等。看得俺拔凉拔凉的,深怕自己陷入泥潭。。。。。

在CNBETA上看到一则很给力的招聘广告,不知道有没有适合你的岗位,可以去看看。

Yan Hong

unread,
Feb 23, 2011, 6:43:27 AM2/23/11
to xv6...@googlegroups.com
谢谢。还不知道这里也有招聘信息,我去看看。

2011/2/23 dave <dav...@qq.com>

dave

unread,
Mar 3, 2011, 7:18:13 AM3/3/11
to xv6...@googlegroups.com
MIT6.828 LAB4 Part B: Copy-on-Write Fork
这里的dave的实验报告:-)


Exercise 3. Implement the sys_env_set_pgfault_upcall system call. Be
sure to enable permission checking when looking up the environment ID of
the target environment, since this is a "dangerous" system call.

修改kern/syscall.c文件,添加注册页故障处理函数的功能:
1、修改sys_env_set_pgfault_upcall()函数
// Set the page fault upcall for 'envid' by modifying the corresponding
struct
// Env's 'env_pgfault_upcall' field. When 'envid' causes a page fault,
the
// kernel will push a fault record onto the exception stack, then branch
to
// 'func'.


//
// Returns 0 on success, < 0 on error. Errors are:
// -E_BAD_ENV if environment envid doesn't currently exist,
// or the caller doesn't have permission to change envid.

static int
sys_env_set_pgfault_upcall(envid_t envid, void *func)
{


struct Env *e;
if (envid2env(envid, &e, 1) <0)
return -E_BAD_ENV;

//if (user_mem_check(curenv, func, sizeof(void *), PTE_U|PTE_P) <0)
// return -E_FAULT;
e->env_pgfault_upcall = func;
return 0;
}
在代码中有两行被注释掉了,刚开始的时候我是用这两行代码检测传入的func是否
合法,但是评分程序过不了才知道这里不要求验证func的有效性。不过我还是觉得
在这里验证最好,毕竟往内核传入指针了阿。
2、修改syscal函数,把sys_env_set_pgfault_upcall()注册给适当的调用号。

Exercise 4. Implement the code in page_fault_handler in kern/trap.c
required to dispatch page faults to the user-mode handler. Be sure to
take appropriate precautions when writing into the exception stack.
(What happens if the user environment runs out of space on the exception
stack?)

修改kern/trap.c,在异常处理中加入调用用户注册的页错误处理函数的功能

//page fault handler
void pf_hdl(struct Trapframe *tf)
{
uint32_t fault_va;

// Read processor's CR2 register to find the faulting address
fault_va = rcr2();

// Handle kernel-mode page faults.

// LAB 3: Your code here.
if (tf->tf_cs == GD_KT)
{
print_trapframe(tf);
panic("Page fault in kernel");
}
// We've already handled kernel-mode exceptions, so if we get here,
// the page fault happened in user mode.

// Call the environment's page fault upcall, if one exists. Set up a
// page fault stack frame on the user exception stack (below
// UXSTACKTOP), then branch to curenv->env_pgfault_upcall.
//
// The page fault upcall might cause another page fault, in which case
// we branch to the page fault upcall recursively, pushing another
// page fault stack frame on top of the user exception stack.
//
// The trap handler needs one word of scratch space at the top of the
// trap-time stack in order to return. In the non-recursive case, we
// don't have to worry about this because the top of the regular user
// stack is free. In the recursive case, this means we have to leave
// an extra word between the current top of the exception stack and
// the new stack frame because the exception stack _is_ the trap-time
// stack.
//
// If there's no page fault upcall, the environment didn't allocate a
// page for its exception stack or can't write to it, or the exception
// stack overflows, then destroy the environment that caused the fault.
// Note that the grade script assumes you will first check for the page
// fault upcall and print the "user fault va" message below if there is
// none. The remaining three checks can be combined into a single
test.

//check page fault user handler && ux stack alloced && ux stack
overflow

if ( (curenv->env_pgfault_upcall != NULL) &&
( (tf->tf_esp >=UXSTACKEND+sizeof(struct UTrapframe)+sizeof(int)) ||
(tf->tf_esp< USTACKTOP)))
{
user_mem_assert(curenv, curenv->env_pgfault_upcall, 1, PTE_U|PTE_P) ;
user_mem_assert(curenv, (void *)UXSTACKEND, PGSIZE, PTE_U|PTE_W|
PTE_P) ;

struct UTrapframe *utf;
//is a trap from ux stack ?
if(tf->tf_esp< UXSTACKTOP && tf->tf_esp>=UXSTACKEND)
{//yes
utf = (struct UTrapframe *)(tf->tf_esp - sizeof(struct UTrapframe) -
sizeof(int));
}
else
{
utf = (struct UTrapframe *)(UXSTACKTOP - sizeof(struct UTrapframe));
}
//prepare UTrapframe
utf->utf_fault_va = fault_va;
utf->utf_err = tf->tf_err;
utf->utf_regs = tf->tf_regs;
utf->utf_eip = tf->tf_eip;
utf->utf_eflags = tf->tf_eflags;
utf->utf_esp = tf->tf_esp;
//set user pf handler entry
tf->tf_eip = (uint32_t)curenv->env_pgfault_upcall;
tf->tf_esp = (uint32_t)utf;
// run user pf handler;
env_run(curenv);
}

// Destroy the environment that caused the fault.
cprintf("[%08x] user fault va %08x ip %08x\n",
curenv->env_id, fault_va, tf->tf_eip);
print_trapframe(tf);
//monitor(tf);
env_destroy(curenv);
}

注意检查用户页错误处理函数是否注册,用户异常栈是否分配,是否溢出,是否页
错误嵌套。并注意给新的UTrapframe和嵌套返回值保留空间。

Exercise 5. Implement the _pgfault_upcall routine in lib/pfentry.S. The
interesting part is returning to the original point in the user code
that caused the page fault. You'll return directly there, without going
back through the kernel. The hard part is simultaneously switching
stacks and re-loading the EIP.

修改lib/pfentry.S文件,在用户层加入用户页错误处理函数的入口功能。
// Page fault upcall entrypoint.

// This is where we ask the kernel to redirect us to whenever we cause
// a page fault in user space (see the call to sys_set_pgfault_handler
// in pgfault.c).
//
// When a page fault actually occurs, the kernel switches our ESP to
// point to the user exception stack if we're not already on the user
// exception stack, and then it pushes a UTrapframe onto our user
// exception stack:
//
// trap-time esp
// trap-time eflags
// trap-time eip
// utf_regs.reg_eax
// ...
// utf_regs.reg_esi
// utf_regs.reg_edi
// utf_err (error code)
// utf_fault_va <-- %esp
//
// If this is a recursive fault, the kernel will reserve for us a
// blank word above the trap-time esp for scratch work when we unwind
// the recursive call.
//
// We then have call up to the appropriate page fault handler in C
// code, pointed to by the global variable '_pgfault_handler'.

.text
.globl _pgfault_upcall
_pgfault_upcall:
// Call the C page fault handler.
// Step 0:


pushl %esp // function argument: pointer to UTF

movl _pgfault_handler, %eax
call *%eax


addl $4, %esp // pop function argument

// Now the C page fault handler has returned and you must return
// to the trap time state.
// Push trap-time %eip onto the trap-time stack.
//
// Explanation:
// We must prepare the trap-time stack for our eventual return to
// re-execute the instruction that faulted.
// Unfortunately, we can't return directly from the exception stack:
// We can't call 'jmp', since that requires that we load the address
// into a register, and all registers must have their trap-time
// values after the return.
// We can't call 'ret' from the exception stack either, since if we
// did, %esp would have the wrong value.
// So instead, we push the trap-time %eip onto the *trap-time* stack!
// Below we'll switch to that stack and call 'ret', which will
// restore %eip to its pre-fault value.
//
// In the case of a recursive fault on the exception stack,
// note that the word we're pushing now will fit in the
// blank word that the kernel reserved for us.
//
// Throughout the remaining code, think carefully about what
// registers are available for intermediate calculations. You
// may find that you have to rearrange your code in non-obvious
// ways as registers become unavailable as scratch space.
// Step 1:
movl 0x30(%esp), %ebp
subl $0x4, %ebp
movl %ebp, 0x30(%esp)
movl 0x28(%esp), %eax
movl %eax, (%ebp)

// Restore the trap-time registers. After you do this, you
// can no longer modify any general-purpose registers.
// Step 2:
addl $0x8, %esp
popal


// Restore eflags from the stack. After you do this, you can
// no longer use arithmetic operations or anything else that
// modifies eflags.

// Step 3:
addl $0x4, %esp
popfl
// Switch back to the adjusted trap-time stack.
// Step 4:
popl %esp
// Return to re-execute the instruction that faulted.
// Step 5:
ret

step 0是调用用户注册的用户页错误处理函数。step 1 是用来设置返回到发生页
错误(原始)的地址。 step 2是用来弹出通用寄存器。 step 3 是用来弹出状态寄
存器。 step 4用来切换esp到原始栈。 step 5就是返回到原始地址。
各种注意事项代码的注释中已有说明,一定得认真看了再去写代码。

Exercise 6. Finish set_pgfault_handler() in lib/pgfault.c.

修改lib/pgfault.c文件的set_pgfault_handler()函数

/ Set the page fault handler function.
// If there isn't one yet, _pgfault_handler will be 0.
// The first time we register a handler, we need to
// allocate an exception stack (one page of memory with its top
// at UXSTACKTOP), and tell the kernel to call the assembly-language
// _pgfault_upcall routine when a page fault occurs.
//
void
set_pgfault_handler(void (*handler)(struct UTrapframe *utf))
{
int r;

if (_pgfault_handler == 0) {
// First time through!
if((r=sys_page_alloc(0, (void *)UXSTACKEND, PTE_U|PTE_W|PTE_P)) <0)
panic("sys_page_alloc: %e", r);
sys_env_set_pgfault_upcall(0, _pgfault_upcall);
}

// Save handler pointer for assembly to call.
_pgfault_handler = handler;
}
注意这里饶了个弯子,没有直接注册handler而是注册了练习5中的汇编函数
_pgfault_upcall,这样是因为要从handler直接返回到用户页错误发生处,需要汇
编函数_pgfault_upcall来制造栈环境(普通C代码无能为力)。

Challenge! Extend your kernel so that not only page faults, but all
types of processor exceptions that code running in user space can
generate, can be redirected to a user-mode exception handler. Write
user-mode test programs to test user-mode handling of various exceptions
such as divide-by-zero, general protection fault, and illegal opcode.

给系统异常加入注册用户异常处理函数的系统调用。这个最简单的实现方法是只写
一个系统调用,用参数中的异常号来分辨不同的异常,代码可以精简很多。如果觉
得这样写用户调用不方便(还得记异常号)那就给每个异常写一个用户层的lib接
口。这样还可以保持上面写的set_pgfault_handler函数不动。 没啥技术含量和挑
战性,咱就不写了,以后有机会再说。

Exercise 7. Implement fork, duppage and pgfault in lib/fork.c.
Test your code with the forktree program. It should produce the
following messages, with interspersed 'new env', 'free env', and
'exiting gracefully' messages. The messages may not appear in this
order, and the environment IDs may be different.

修改lib/fork.c文件

//
// Custom page fault handler - if faulting page is copy-on-write,
// map in our own private writable copy.
//
static void
pgfault(struct UTrapframe *utf)
{
void *addr = (void *) utf->utf_fault_va;
uint32_t err = utf->utf_err;
int r;

// Check that the faulting access was (1) a write, and (2) to a
// copy-on-write page. If not, panic.
// Hint:
// Use the read-only page table mappings at vpt
// (see <inc/memlayout.h>).

if (!(err&FEC_WR))
panic("Page fault: not a write access.");
if ( !(vpt[VPN(addr)]&PTE_COW) )
panic("Page fualt: not a COW page.");

// Allocate a new page, map it at a temporary location (PFTEMP),
// copy the data from the old page to the new page, then move the new
// page to the old page's address.
// Hint:
// You should make three system calls.
// No need to explicitly delete the old page's mapping.

if ((r=sys_page_alloc(0, PFTEMP, PTE_U|PTE_W|PTE_P)) <0)
panic("Page fault: sys_page_alloc err %e.", r);
memmove(PFTEMP, (void *)PTE_ADDR(addr), PGSIZE);
if ((r=sys_page_map(0, PFTEMP, 0, (void *)PTE_ADDR(addr), PTE_U|PTE_W|
PTE_P))<0)
panic("Page fault: sys_page_map err %e.", r);
if ((r=sys_page_unmap(0, PFTEMP))<0)
panic("Page fault: sys_page_unmap err %e.", r);
}

检查页属性,然后分配页,复制数据。

//
// Map our virtual page pn (address pn*PGSIZE) into the target envid
// at the same virtual address. If the page is writable or
copy-on-write,
// the new mapping must be created copy-on-write, and then our mapping
must be
// marked copy-on-write as well. (Exercise: Why we need to mark ours
// copy-on-write again if it was already copy-on-write at the beginning
of
// this function?)


//
// Returns: 0 on success, < 0 on error.

// It is also OK to panic on error.
//
static int
duppage(envid_t envid, unsigned pn)
{
int r;
void *addr = (void *)(pn<<PGSHIFT);

// LAB 4: Your code here.
if (vpt[pn]&(PTE_W|PTE_COW))
{
if ((r = sys_page_map(0, addr, envid, addr, PTE_P | PTE_U |
PTE_COW))<0)
return r;
if ((r = sys_page_map(0, addr, 0, addr, PTE_P | PTE_U | PTE_COW))<0)
return r;
}
else
{
if ((r = sys_page_map(0, addr, envid, addr, PTE_P | PTE_U))<0)
return r;
}
return 0;
//panic("duppage not implemented");
}
注意区别写/写时复制 和 只读页面。在这里想到,其实在kern/env.c的
load_inode()函数里面我们都是用可写的方式处理页面。所以需要注意改进阿。

//
// User-level fork with copy-on-write.
// Set up our page fault handler appropriately.
// Create a child.
// Copy our address space and page fault handler setup to the child.
// Then mark the child as runnable and return.
//
// Returns: child's envid to the parent, 0 to the child, < 0 on error.
// It is also OK to panic on error.
//
// Hint:
// Use vpd, vpt, and duppage.
// Remember to fix "env" in the child process.
// Neither user exception stack should ever be marked copy-on-write,
// so you must allocate a new page for the child's user exception
stack.
//
envid_t
fork(void)
{

envid_t envid;
uint8_t *addr;
int r;
extern unsigned char end[];

set_pgfault_handler(pgfault);
envid = sys_exofork();
if (envid < 0)
panic("sys_exofork: %e", envid);
//child
if (envid == 0) {
//can't set pgh here ,must before child run
//because when child run ,it will make a page fault
env = &envs[ENVX(sys_getenvid())];
return 0;
}
//parent
for (addr = (uint8_t*) UTEXT; addr < end; addr += PGSIZE)
duppage(envid, VPN(addr));
duppage(envid, VPN(&addr));
//copy user exception stack
if ((r = sys_page_alloc(envid, (void *)UXSTACKEND, PTE_P|PTE_U|PTE_W))
< 0)
panic("sys_page_alloc: %e", r);
r = sys_env_set_pgfault_upcall(envid, env->env_pgfault_upcall);

//set child status
if ((r = sys_env_set_status(envid, ENV_RUNNABLE)) < 0)
panic("sys_env_set_status: %e", r);
return envid;

}

这里需要注意,由于新建进程时,自进程会把自己的pgfault_handler设置成空,
所以要重新注册一下。
不过不能在子进程注册,因为运行子进程时候调用函数或者写入变量会导致页错
误,这时候还没有注册pgfault_handler,所以就默认进程销毁了。 只能在父进程
设置子进程的句柄。我有个疑问,为什么不在新建进程的时候直接把
pgfault_handler继承过来呢,不知道设计者怎么想的。

Challenge! Implement a shared-memory fork() called sfork(). This version
should have the parent and child share all their memory pages (so writes
in one environment appear in the other) except for pages in the stack
area, which should be treated in the usual copy-on-write manner. Modify
user/forktree.c to use sfork() instead of regular fork(). Also, once you
have finished implementing IPC in part C, use your sfork() to run
user/pingpongs. You will have to find a new way to provide the
functionality of the global env pointer.

修改lib/fork.c。把sfork()函数变成一个读写共享全局数据和代码的fork()
// Challenge!
int
sfork(void)
{
envid_t envid;
uint8_t *addr;
int r;
extern unsigned char end[];

set_pgfault_handler(pgfault);
envid = sys_exofork();
if (envid < 0)
panic("sys_exofork: %e", envid);
//child
if (envid == 0) {
//can't set pgh here ,must before child run
//because when child run ,it will make a page fault
env = &envs[ENVX(sys_getenvid())];
return 0;
}
//parent
//share pages
for (addr = (uint8_t*) UTEXT; addr < end; addr += PGSIZE)
{
if ((r = sys_page_map(0, addr, envid, addr,
PTE_USER&vpt[VPN(addr)]))<0)
return r;
}
//copy normal stack
duppage(envid, VPN(&addr));
//copy user exception stack
if ((r = sys_page_alloc(envid, (void *)UXSTACKEND, PTE_P|PTE_U|PTE_W))
< 0)
panic("sys_page_alloc: %e", r);
r = sys_env_set_pgfault_upcall(envid, env->env_pgfault_upcall);

//set child status
if ((r = sys_env_set_status(envid, ENV_RUNNABLE)) < 0)
panic("sys_env_set_status: %e", r);
return envid;

}

和fork()函数基本一致,只需要注意共享UTEXT到end之间的数据时老老实实的按照
原来的属性重新映射一个到子进程就可以了,注意权限哦

Challenge! Your implementation of fork makes a huge number of system
calls. On the x86, switching into the kernel using interrupts has
non-trivial cost. Augment the system call interface so that it is
possible to send a batch of system calls at once. Then change fork to
use this interface.

How much faster is your new fork?

You can answer this (roughly) by using analytical arguments to estimate
how much of an improvement batching system calls will make to the
performance of your fork: How expensive is an int 0x30 instruction? How
many times do you execute int 0x30 in your fork? Is accessing the TSS
stack switch also expensive? And so on...

Alternatively, you can boot your kernel on real hardware and really
benchmark your code. See the RDTSC (read time-stamp counter)
instruction, defined in the IA32 manual, which counts the number of
clock cycles that have elapsed since the last processor reset. QEMU
doesn't emulate this instruction faithfully (it can either count the
number of virtual instructions executed or use the host TSC, neither of
which reflects the number of cycles a real CPU would require).

这次的挑战是要求测试下fork的速度,可以通过RDTSC指令阿或者指令计数器来计
算。这次的fork调用了N多个系统调用,每个系统调用都要切换栈空间,压入弹出N
多寄存器,尤其是后来我还加入了浮点寄存器的保护,这都512个字节呢。速度会
奇慢无比,具体代码不写了。RDTSC指令格式满大街都是,大家自己google吧。


Reply all
Reply to author
Forward
0 new messages