以下关于fork()的描述来自于:jason314
首先,在Linux环境下,一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值: 1)在父进程中,fork返回新创建子进程的进程ID; 2)在子进程中,fork返回0; 3)如果出现错误(如系统资源不足),fork返回一个负值。
在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。
现在,我们来写一段程序,使用API调用fork:
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t fpid;
int count = 0;
fpid = fork();
printf("Now pid = %d\n", fpid);
if(fpid < 0)
printf("Error in fork!");
else if(fpid == 0){
printf("I am the child process, my process id is: %d\n", getpid());
count++;
}
else{
printf("I am the parent process, my process id is: %d\n", getpid());
count++;
}
printf("Now count = %d\n", count);
return 0;
}
在第八行执行完fork后,父进程中有count=0、fpid=子进程的pid;子进程中变量为count=0、 fpid=0,这两个进程的变量都是独立的,存在不同的地址中,也不是共用的。可以说,我们就是通过fpid来识别和操作父子进程的。
在x86的系统中,%eax寄存器在进行系统调用前储存系统调用号。另外,由于六个及以上参数的系统调用并不多见,因此一般使用%ebx、%ecx、%edx、%esi和%edi依次存放前五个参数。如果出现六个以上参数的情况,应该用一个单独的寄存器存放指向所有这些参数在用户空间地址的指针。当调用结束后,函数的返回值存放在%eax中。
下面,我们将改写fork.c,直接嵌入汇编语言进行系统调用:
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t fpid;
int count = 0;
asm volatile(
"mov $0, %%ebx\n\t"
"mov $0x2, %%eax\n\t"
"int $0x80\n\t"
"mov %%eax, %0\n\t"
: "=m"(fpid)
);
printf("Now pid = %d\n", fpid);
if(fpid < 0)
printf("Error in fork!");
else if(fpid == 0){
printf("I am the child process, my process id is: %d\n", getpid());
count++;
}
else{
printf("I am the parent process, my process id is: %d\n", getpid());
count++;
}
printf("Now count = %d\n", count);
return 0;
}
总结:API与系统调用并不是一一对应的关系(Linux系统可以参考syscalls),它为程序提供了标准接口。而内核基本只与系统调用打交道;当然,我们也可以直接使用系统调用写程序,但势必会降低程序的可移植性。至于APIs如何进行系统调用,那就是Glibc等标准制定者的事了。
陈政/arc001 原创作品转载请注明出处 《Linux内核分析》MOOC课程