fork()详解

本文详细解读了Linux系统下fork函数的源码实现,包括内存管理、进程空间区域写前验证、复制内存页表等关键部分,帮助开发者更好地理解和使用fork函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在Linux系统下学习一个系统函数最好的方法就是阅读其源码,首先,给出fork函数的源码

[cpp]  view plain copy
  1. /* 
  2. *  linux/kernel/fork.c 
  3. *                                //--fork()用于创建子进程 
  4. *  (C) 1991  Linus Torvalds 
  5. */  
  6. /* 
  7. *  'fork.c' contains the help-routines for the 'fork' system call 
  8. * (see also system_call.s), and some misc functions ('verify_area'). 
  9. * Fork is rather simple, once you get the hang of it, but the memory 
  10. * management can be a bitch. See 'mm/mm.c': 'copy_page_tables()' 
  11. */  
  12. #include <errno.h>  
  13. #include <linux/sched.h>  
  14. #include <linux/kernel.h>  
  15. #include <asm/segment.h>  
  16. #include <asm/system.h>  
  17.                                 //--写页面验证,若页面不可写,则复制页面  
  18. extern void write_verify(unsigned long address);  
  19. long last_pid=0;  
  20.                                 //--进程空间区域写前验证函数  
  21. void verify_area(void * addr,int size)  
  22. {  
  23.     unsigned long start;  
  24.     start = (unsigned long) addr;  
  25.     size += start & 0xfff;  
  26.     start &= 0xfffff000;  
  27.     start += get_base(current->ldt[2]);        //--逻辑地址到线性地址的转换  
  28.     while (size>0) {  
  29.         size -= 4096;  
  30.         write_verify(start);  
  31.         start += 4096;  
  32.     }  
  33. }  
  34. int copy_mem(int nr,struct task_struct * p)        //--复制内存页表  
  35. {                                                //--由于采用写时复制技术,这里只复制目录和页表项,不分配内存  
  36.     unsigned long old_data_base,new_data_base,data_limit;  
  37.     unsigned long old_code_base,new_code_base,code_limit;  
  38.     code_limit=get_limit(0x0f);                    //--取段限长  
  39.     data_limit=get_limit(0x17);  
  40.     old_code_base = get_base(current->ldt[1]);  
  41.     old_data_base = get_base(current->ldt[2]);  
  42.     if (old_data_base != old_code_base)  
  43.         panic("We don't support separate I&D");  
  44.     if (data_limit < code_limit)  
  45.         panic("Bad data_limit");  
  46.     new_data_base = new_code_base = nr * TASK_SIZE;  
  47.     p->start_code = new_code_base;  
  48.     set_base(p->ldt[1],new_code_base);  
  49.     set_base(p->ldt[2],new_data_base);  
  50.     if (copy_page_tables(old_data_base,new_data_base,data_limit)) {        //--复制页表  
  51.         free_page_tables(new_data_base,data_limit);  
  52.         return -ENOMEM;  
  53.     }  
  54.     return 0;  
  55. }  
  56. /* 
  57. *  Ok, this is the main fork-routine. It copies the system process 
  58. * information (task[nr]) and sets up the necessary registers. It 
  59. * also copies the data segment in it's entirety. 
  60. */                                    //--fork()子程序,它复制系统进程信息,设置寄存器,复制数据段(代码段)  
  61. int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,  
  62.         long ebx,long ecx,long edx, long orig_eax,   
  63.         long fs,long es,long ds,  
  64.         long eip,long cs,long eflags,long esp,long ss)        //--复制进程  
  65. {  
  66.     struct task_struct *p;  
  67.     int i;  
  68.     struct file *f;  
  69.     p = (struct task_struct *) get_free_page();                //--为新任务数据结构分配内存  
  70.     if (!p)  
  71.         return -EAGAIN;  
  72.     task[nr] = p;  
  73.     *p = *current;    /* NOTE! this doesn't copy the supervisor stack */  
  74.     p->state = TASK_UNINTERRUPTIBLE;  
  75.     p->pid = last_pid;  
  76.     p->counter = p->priority;  
  77.     p->signal = 0;  
  78.     p->alarm = 0;  
  79.     p->leader = 0;        /* process leadership doesn't inherit */  
  80.     p->utime = p->stime = 0;  
  81.     p->cutime = p->cstime = 0;  
  82.     p->start_time = jiffies;  
  83.     p->tss.back_link = 0;  
  84.     p->tss.esp0 = PAGE_SIZE + (long) p;  
  85.     p->tss.ss0 = 0x10;  
  86.     p->tss.eip = eip;  
  87.     p->tss.eflags = eflags;  
  88.     p->tss.eax = 0;  
  89.     p->tss.ecx = ecx;  
  90.     p->tss.edx = edx;  
  91.     p->tss.ebx = ebx;  
  92.     p->tss.esp = esp;  
  93.     p->tss.ebp = ebp;  
  94.     p->tss.esi = esi;  
  95.     p->tss.edi = edi;  
  96.     p->tss.es = es & 0xffff;  
  97.     p->tss.cs = cs & 0xffff;  
  98.     p->tss.ss = ss & 0xffff;  
  99.     p->tss.ds = ds & 0xffff;  
  100.     p->tss.fs = fs & 0xffff;  
  101.     p->tss.gs = gs & 0xffff;  
  102.     p->tss.ldt = _LDT(nr);  
  103.     p->tss.trace_bitmap = 0x80000000;  
  104.     if (last_task_used_math == current)  
  105.         __asm__("clts ; fnsave %0 ; frstor %0"::"m" (p->tss.i387));  
  106.     if (copy_mem(nr,p)) {  
  107.         task[nr] = NULL;  
  108.         free_page((long) p);  
  109.         return -EAGAIN;  
  110.     }  
  111.     for (i=0; i<NR_OPEN;i++)                    //--如果父进程中有文件是打开的,则将对应文件的打开次数增1  
  112.         if (f=p->filp[i])  
  113.             f->f_count++;  
  114.     if (current->pwd)  
  115.         current->pwd->i_count++;  
  116.     if (current->root)  
  117.         current->root->i_count++;  
  118.     if (current->executable)  
  119.         current->executable->i_count++;  
  120.     if (current->library)  
  121.         current->library->i_count++;  
  122.     set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));    //--在GDT表中设置新任务的TSS和LDT  
  123.     set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));  
  124.     p->p_pptr = current;  
  125.     p->p_cptr = 0;  
  126.     p->p_ysptr = 0;  
  127.     p->p_osptr = current->p_cptr;  
  128.     if (p->p_osptr)  
  129.         p->p_osptr->p_ysptr = p;  
  130.     current->p_cptr = p;  
  131.     p->state = TASK_RUNNING;    /* do this last, just in case */  
  132.     return last_pid;  
  133. }  
  134. int find_empty_process(void)                        //--为新进程取得不重复的进程号last_pid  
  135. {  
  136.     int i;  
  137.     repeat:  
  138.         if ((++last_pid)<0) last_pid=1;  
  139.         for(i=0 ; i<NR_TASKS ; i++)  
  140.             if (task[i] && ((task[i]->pid == last_pid) ||  
  141.                         (task[i]->pgrp == last_pid)))  
  142.                 goto repeat;  
  143.     for(i=1 ; i<NR_TASKS ; i++)  
  144.         if (!task[i])  
  145.             return i;  
  146.     return -EAGAIN;  
  147. }  

以下给出说明:

 fork函数
#include <sys/types.h> 
#include <unistd.h>  pid_t fork(void);

fork调用失败则返回-1,调用成功的返回值见下面的解释。我们通过一个例子来理解fork是怎样创建新进程的。

这个程序的运行过程如下图所示。


  1. 父进程初始化。

  2. 父进程调用fork,这是一个系统调用,因此进入内核。

  3. 内核根据父进程复制出一个子进程,父进程和子进程的PCB信息相同,用户态代码和数据也相同。因此,子进程现在的状态看起来和父进程一样,做完了初始化,刚调用了fork进入内核,还没有从内核返回

  4. 现在有两个一模一样的进程看起来都调用了fork进入内核等待从内核返回(实际上fork只调用了一次),此外系统中还有很多别的进程也等待从内核返回。是父进程先返回还是子进程先返回,还是这两个进程都等待,先去调度执行别的进程,这都不一定,取决于内核的调度算法。

  5. 如果某个时刻父进程被调度执行了,从内核返回后就从fork函数返回,保存在变量pid中的返回值是子进程的id,是一个大于0的整数,因此执下面的else分支,然后执行for循环,打印"This is the parent\n"三次之后终止。

  6. 如果某个时刻子进程被调度执行了,从内核返回后就从fork函数返回,保存在变量pid中的返回值是0,因此执行下面的if (pid == 0)分支,然后执行for循环,打印"This is the child\n"六次之后终止。fork调用把父进程的数据复制一份给子进程,但此后二者互不影响,在这个例子中,fork调用之后父进程和子进程的变量messagen被赋予不同的值,互不影响。

  7. 父进程每打印一条消息就睡眠1秒,这时内核调度别的进程执行,在1秒这么长的间隙里(对于计算机来说1秒很长了)子进程很有可能被调度到。同样地,子进程每打印一条消息就睡眠1秒,在这1秒期间父进程也很有可能被调度到。所以程序运行的结果基本上是父子进程交替打印,但这也不是一定的,取决于系统中其它进程的运行情况和内核的调度算法,如果系统中其它进程非常繁忙则有可能观察到不同的结果。另外,读者也可以把sleep(1);去掉看程序的运行结果如何。

  8. 这个程序是在Shell下运行的,因此Shell进程是父进程的父进程。父进程运行时Shell进程处于等待状态,当父进程终止时Shell进程认为命令执行结束了,于是打印Shell提示符,而事实上子进程这时还没结束,所以子进程的消息打印到了Shell提示符后面。最后光标停在This is the child的下一行,这时用户仍然可以敲命令,即使命令不是紧跟在提示符后面,Shell也能正确读取。

fork函数的特点概括起来就是“调用一次,返回两次”,在父进程中调用一次,在父进程和子进程中各返回一次。从上图可以看出,一开始是一个控制流程,调用fork之后发生了分叉,变成两个控制流程,这也就是“fork”(分叉)这个名字的由来了。子进程中fork的返回值是0,而父进程中fork的返回值则是子进程的id(从根本上说fork是从内核返回的,内核自有办法让父进程和子进程返回不同的值),这样当fork函数返回后,程序员可以根据返回值的不同让父进程和子进程执行不同的代码。

fork的返回值这样规定是有道理的。fork在子进程中返回0,子进程仍可以调用getpid函数得到自己的进程id,也可以调用getppid函数得到父进程的id。在父进程中用getpid可以得到自己的进程id,然而要想得到子进程的id,只有将fork的返回值记录下来,别无它法。

fork的另一个特性是所有由父进程打开的描述符都被复制到子进程中。父、子进程中相同编号的文件描述符在内核中指向同一个file结构体,也就是说,file结构体的引用计数要增加。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值