进程笔记

针对于神奇而又让人心驰神往的fork,在研读并且实现一个之后,我有必要讨论下自己的理解。这个fork实在是神奇地很。

fork()在linux下顾名思义是用来产生一个子进程的。而且事实上,针对于linux 0.26而言,除了fork之外,还有一个do_fork函数。虽然名字好像,而前者其实“调用”了后者,但是其实二者有截然的差别~其实在我的观察下,do_fork只是进行了copy并[设置]成和current的各种进程上下文一模一样而已,并没有真正地进行启动新的进程。而fork仅仅只直接调用了do_fork,而其他什么也没干!那么为什么fork()会一下子变成两个进程呢?还有两个返回值呢?

其实乍一看好像仅仅在“设置”,而没有进行“调度”,但是其实在暗中已经进行调度了。秘密就在于,fork()函数虽然只调用了do_fork()函数,但是用的是0x80系统调用。也就是说,整个调用链是这样的:fork->(int 0x80中断)sys_fork->do_fork。由于fork就调用了一个sys_fork,而sys_fork就调用了一个do_fork,因而后边在不必要的地方,我们只说fork,而不说sys_fork了。

而且,虽然只有一层调用关系,但是这两个函数的意义却是截然不同的。fork()函数是在用户空间调用的,而do_fork()函数却是在内核空间调用的。而联系用户空间和内核空间的纽带,就是0x80系统调用。因此,在fork()asm volatile ("int $0x80;"::....)函数中,父进程,也就是current进程所做的事情只有一个:系统调用中断发生,自己切换到了内核态,栈由用户栈变成了内核栈;然后用do_fork设置了一波子进程的所有东西,(仅仅是设置,并没有调度),然后就原路中断返回,之后继续执行,没有半点和单进程的不同之处啊。

看一段代码:user_main
仅仅是普普通通的用户态fork调用而已。对于父进程current而言,其实只是正常的返回了。是的!就是普普通通的正常返回了!返回了设置的子进程的pid。为何我要强调设置这么多次呢?因为它确确实实只是设置,其他什么也没有干,没有任何的调度!

那为毛fork竟然有俩返回值呢????

答案就是:因为本来只有一个fork,在进行父进程设置do_fork之后,是fork变成了两个!

啥?

确实没错~C语言仅仅允许一个返回值。返回两个值是根本不可能的。所以,不要听别人“fork返回了两个值!”来忽悠你,那就是在扯淡了。这种言论会让新人越听越懵逼的。

其实在设置do_fork的时候,开辟了子进程的pcb块,然后重点的就是,此父进程的eip会被复制一份。也就是,调度到子进程之后,子进程就会直接在父进程设置好的eip处苏醒过来。

那么在int $0x80中断的时候,其实idtframe中断帧已经由CPU自动压入了下一条指令,也就是把当前的eip+4变成了中断返回应该恢复的eip了。那么子进程将不会执行fork,而是跳过了int中断来直接从int的下一条语句执行!!这里一度困扰了我好久啊。更详细的分析见这里:sys_fork

上边那一段说的是子进程的苏醒。那么我们可以知道,子进程苏醒之后,也是差不多从和父进程一样的地点苏醒的。也就是:又执行了一遍fork的后半部分。这个后半部分,具体指的是“fork的返回部分”。因为fork中其实只有一条int 0x80指令,那么子进程还跳过了这个指令,从下一条开始执行,那么这所谓的下一条,自然就是return语句了。

是了~~现在我们可以看出,子进程直接就是从父进程所执行的fork跳过了设置函数do_fork而开始执行的,也就是只进行了一个返回。那么这个返回值就必然是子进程fork的返回值了。由于整个fork过程中,eax寄存器的值并没有改变!因此,只要在一开始系统调用0x80之前,把eax强制清空一次就好了~所以子进程因为直接从fork的返回开始执行,因此直接返回了eax~~所以这就是返回0的秘密啦!!而刚才也说过了,父进程是一直稳稳地执行并返回,非常正常,所以其实他返回的是do_fork的值,也就是子进程的pid咯!

总结一下,因此,fork返回两个值的真正秘密,其实就是因为子进程是直接从fork的返回处开始执行,所以其实父子加起来,共计执行了两次fork。而不是fork有两个返回值啊。

完~~