本文主要是介绍xv6源码剖析 009,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
xv6源码剖析 009
我们继续昨天的内容。proc.h
和proc.c
growproc(int n)
int
growproc(int n)
{uint sz;struct proc *p = myproc();sz = p->sz;if(n > 0){if((sz = uvmalloc(p->pagetable, sz, sz + n)) == 0) {return -1;}} else if(n < 0){sz = uvmdealloc(p->pagetable, sz, sz + n);}p->sz = sz;return 0;
}
当我们的进程使用sbrk()
系统调用申请或者释放内存时,内核会在底层调用这个函数。
fork(void)
我们重点看看这个函数,我们在写代码的时候也会常常用到这个函数,有些时候我们会和另一个系统调用exec
一起调用。
int
fork(void)
{int i, pid;struct proc *np;struct proc *p = myproc();// Allocate process.if((np = allocproc()) == 0){return -1;}// 将父进程的页表拷贝到子进程中,// 所以子进程是父进程的一个副本if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){freeproc(np);release(&np->lock);return -1;}// 设置进程栈np->sz = p->sz;// 标识父进程np->parent = p;// 复制父进程trapframe// trapframe保存着父进程的寄存器的状态*(np->trapframe) = *(p->trapframe);// Cause fork to return 0 in the child.// 设置返回值np->trapframe->a0 = 0;// increment reference counts on open file descriptors.// 增加父进程打开的文件描述符的引用计数for(i = 0; i < NOFILE; i++)if(p->ofile[i])np->ofile[i] = filedup(p->ofile[i]);np->cwd = idup(p->cwd);// 复制父进程的名字safestrcpy(np->name, p->name, sizeof(p->name));// 标识子进程pid = np->pid;// 设置子进程的状态,能够被调度器调度np->state = RUNNABLE;// 释放子进程的进程锁release(&np->lock);// 从父进程中返回子进程的进程idreturn pid;
}
有几个点值得我们注意的:
-
一般情况下,我们是这样使用fork的
int main() {do_something();int pid;if ((pid = fork()) == 0){do_something_child();}else if (pid > 0){do_something_parent();}else{exit(0);} }
从上面的fork的代码我们可以很好地理解为什么要这样使用,而不是像线程一样直接使用就好了。
子进程和父进程是相互独立的,它们并不共享相同的资源,这是由操作系统的隔离机制决定的,但是线程不一样,线程是运行在进程中的,而且一个进程中的不同进程是共享一部分进程的内存的;但是不同进程之间的线程就像进程一样,它们的交互是通过进程间通信(ipc)的。
从代码中,我们知道,当调用fork的时候,内核会完全复制父进程的页表,进程地址空间,trapframe page和相应的文件描述符,等等,唯一不同的就是,内核会直接将父进程的返回值返回(在代码中),而对于子进程则是将返回值保存在一个寄存器中;其实本质上,它们都是相同的,只是在代码中的体现有所不同,因为这个调用是由父进程引起的。
-
增减描述符的引用计数
这个小小的细节也值得我们注意。看下面的ipc通信的小例子。
void test() {int pipe[2];// 初始化一个匿名管道::pipe(pipe);char buff[512];int pid;if ((pid = fork()) == 0){memset(buff, 0, sizeof(buff));read(pipe[0], buff, sizeof(buff));do_something(buff);}else if (pid > 0){write(pipe[1], buff, sizeof(buff));do_something();}else {exit(1);} }
上面是一个常规匿名管道的应用。我们可以思考一下,为什么可以通过一个pipe来进行进程间的通信,明明一个进程在fork的时候,会将父进程的所有内容都进行拷贝?
文件描述符是文件系统(file system)的一个高级抽象,它的底层是不同类型的数据存储对象,我们在后面文件系统的时候会讲到。现在我们只需要将文件描述符看成一个c语言中的指针,虽然进行了拷贝,但是父进程和子进程指向的内容是一样的,并且操作系统还会在内部维护一个指向该文件的引用计数(reference count),每当用户进程调用close()的时候,就会将这个引用计数减一,当计数为零并且连接数(link)为零时就会关闭这个文件。
-
copy-on write
这个机制也属于xv6实验的范畴,所以我只简单讲述一下,
在内核拷贝父进程的页表时,函数uvmcopy()需要递归地遍历整个页表,以复制整个父进程的进程地址空间。当父进程的空间过大的,扫描就非常耗时,所以,我们一般是直接拷贝父进程页表中的映射(mapping),相当于拷贝一个指针到子进程中,并将每一个pte都设置为只读(read only),当父进程或子进程尝试写这个页面的时候,就会引发缺页中断(page fault)从而陷入(trap)内核,内核将一部分的内存进行复制并重新建立映射关系。然后返回用户空间,重新执行代码。
今晚时间比较少,写的可能少了点,sorry!!!
这篇关于xv6源码剖析 009的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!