//PAGEBREAK: 17
// Saved registers for kernel context switches.
// Don't need to save all the segment registers (%cs, etc),
// because they are constant across kernel contexts.
// Don't need to save %eax, %ecx, %edx, because the
// x86 convention is that the caller has saved them.
// Contexts are stored at the bottom of the stack they
// describe; the stack pointer is the address of the context.
// The layout of the context matches the layout of the stack in swtch.S
// at the "Switch stacks" comment. Switch doesn't save eip explicitly,
// but it is on the stack and allocproc() manipulates it.
structcontext{uintedi;uintesi;uintebx;uintebp;uinteip;};enumprocstate{UNUSED,EMBRYO,SLEEPING,RUNNABLE,RUNNING,ZOMBIE};// Per-process state
structproc{uintsz;// Size of process memory (bytes)
pde_t*pgdir;// Page table
char*kstack;// Bottom of kernel stack for this process
enumprocstatestate;// Process state
intpid;// Process ID
structproc*parent;// Parent process
structtrapframe*tf;// Trap frame for current syscall
structcontext*context;// swtch() here to run process
void*chan;// If non-zero, sleeping on chan
intkilled;// If non-zero, have been killed
structfile*ofile[NOFILE];// Open files
structinode*cwd;// Current directory
charname[16];// Process name (debugging)
};
#include<stdio.h>#include<stdlib.h>#include<unistd.h>intmain(intargc,char*argv[]){printf("hello world (pid:%d)\n",(int)getpid());intrc=fork();if(rc<0){// fork failed; exit
fprintf(stderr,"fork failed\n");exit(1);}elseif(rc==0){// child (new process)
printf("hello, I am child (pid:%d)\n",(int)getpid());}else{// parent goes down this path (original process)
printf("hello, I am parent of %d (pid:%d)\n",rc,(int)getpid());}return0;}
1
2
3
4
$ ./p1
hello world (pid:29146)hello, I am child (pid:29147)hello, I am parent of 29147(pid:29146)
新创建的进行几乎与调用的进程完全一样,在 OS 看来就好像有两个 p1 的副本在运行,两者都将从 fork() 系统调用返回。
#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<sys/wait.h>intmain(intargc,char*argv[]){printf("hello world (pid:%d)\n",(int)getpid());intrc=fork();if(rc<0){// fork failed; exit
fprintf(stderr,"fork failed\n");exit(1);}elseif(rc==0){// child (new process)
printf("hello, I am child (pid:%d)\n",(int)getpid());sleep(1);}else{// parent goes down this path (original process)
intwc=wait(NULL);printf("hello, I am parent of %d (wc:%d) (pid:%d)\n",rc,wc,(int)getpid());}return0;}
1
2
3
4
$ ./p2
hello world (pid:29266)hello, I am child (pid:29267)hello, I am parent of 29267(rc_wait:29267)(pid:29266)
#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<string.h>#include<sys/wait.h>intmain(intargc,char*argv[]){printf("hello world (pid:%d)\n",(int)getpid());intrc=fork();if(rc<0){// fork failed; exit
fprintf(stderr,"fork failed\n");exit(1);}elseif(rc==0){// child (new process)
printf("hello, I am child (pid:%d)\n",(int)getpid());char*myargs[3];myargs[0]=strdup("wc");// program: "wc" (word count)
myargs[1]=strdup("p3.c");// argument: file to count
myargs[2]=NULL;// marks end of array
execvp(myargs[0],myargs);// runs word count
printf("this shouldn't print out");}else{// parent goes down this path (original process)
intwc=wait(NULL);printf("hello, I am parent of %d (wc:%d) (pid:%d)\n",rc,wc,(int)getpid());}return0;}
这里在子进程中调用 wc p3.c 来统计字符数(源码与书中不同,因此结果不同):
1
2
3
4
$ ./p3
hello world (pid:29383) hello, I am child (pid:29384)291071030 p3.c
hello, I am parent of 29384(rc_wait:29384)(pid:29383)
exec() 会从可执行程序中加载代码和静态数据,并覆盖自身,堆栈等内存空间也会初始化,然后 OS 执行该程序,将参数传递给该进程。
#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<string.h>#include<fcntl.h>#include<assert.h>#include<sys/wait.h>intmain(intargc,char*argv[]){intrc=fork();if(rc<0){// fork failed; exit
fprintf(stderr,"fork failed\n");exit(1);}elseif(rc==0){// child: redirect standard output to a file
close(STDOUT_FILENO);open("./p4.output",O_CREAT|O_WRONLY|O_TRUNC,S_IRWXU);// now exec "wc"...
char*myargs[3];myargs[0]=strdup("wc");// program: "wc" (word count)
myargs[1]=strdup("p4.c");// argument: file to count
myargs[2]=NULL;// marks end of array
execvp(myargs[0],myargs);// runs word count
}else{// parent goes down this path (original process)
intwc=wait(NULL);assert(wc>=0);}return0;}
//PAGEBREAK: 17
// Saved registers for kernel context switches.
// Don't need to save all the segment registers (%cs, etc),
// because they are constant across kernel contexts.
// Don't need to save %eax, %ecx, %edx, because the
// x86 convention is that the caller has saved them.
// Contexts are stored at the bottom of the stack they
// describe; the stack pointer is the address of the context.
// The layout of the context matches the layout of the stack in swtch.S
// at the "Switch stacks" comment. Switch doesn't save eip explicitly,
// but it is on the stack and allocproc() manipulates it.
structcontext{uintedi;uintesi;uintebx;uintebp;uinteip;};
xv6 上下文切换1,实际上是先将原进程上下文切换到当前 CPU 的 scheduler(也是 context 结构体),然后再切换到新进程。
# Context switch
#
# void swtch(struct context **old, struct context *new);
#
# Save the current registers on the stack, creating
# a struct context, and save its address in *old.
# Switch stacks to new and pop previously-saved registers.
.globlswtchswtch:movl4(%esp),%eaxmovl8(%esp),%edx# Save old callee-saved registers
pushl%ebppushl%ebxpushl%esipushl%edi# Switch stacks
movl%esp,(%eax)movl%edx,%esp# Load new callee-saved registers
popl%edipopl%esipopl%ebxpopl%ebpret
swtch() 首先将 old 和 new 分别保存到 %eax 和 %edx,然后将其余寄存器入栈: