您当前的位置: 首页 >  网络

phymat.nico

暂无认证

  • 4浏览

    0关注

    1967博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Linux内核源代码分析——fork()原理&多进程网络模型

phymat.nico 发布时间:2015-01-26 17:23:42 ,浏览量:4


 
 

 今晚和一位500强的leader喝喝小酒吃吃烤鱼,生活乐无边。这位兄弟伙才毕业2年,已经做到管理层了,机遇和能力不可谓不好。喝酒之余,聊到Linux内核的两个问题——fork()、exec()的原理。

        兄弟伙:fork()的原理是什么呢?

        我:其实一句话就概括了——copy on write。

        兄弟伙:copy on wirte我懂,书上介绍的一抓一大把,但是没几本书是能说明白的。我想从你这里得到通俗的解释。

        我:我在《口述程序员如何意淫进程》的三篇文章里详细介绍过进程是什么样子的。你应该得到启发的。

        兄弟伙:我明白进程是什么样子的了。但是fork()与exec()的原理还不甚明了。

        我:从你的角度,你觉得进程需要具备哪些东西呢?

        兄弟伙:至少具备四个东西。

                        1、task_struct结构体。这玩意儿好比是进程的身份证。(线程则没有)

                        2、进程还必须要有一段可执行代码。

                        3、进程必须具备它独立的内存空间(线程则没有)。

                        4、进程必须具备独立的内核堆栈。

       我:是的。我顺便补充一下。之所以必须具备内核堆栈,是因为代码从内核态进入用户态时(从0级切换到3级),必须保护内核态“现场”,使其能够恢复。

       兄弟伙:那fork()与这4点是什么关系呢?

       我:理解这一点,必须分2种情况。

                         1、调用fork()之后立即调用exec()执行新的程序,生成一个全新的进程。

                          2、调用fork()之后不调用exec(),仅仅是为将当前进程生成多个,以提升软件并发能力——典型的是Web Server,如Apache,nginx等。

        兄弟伙:对于第一点,有什么需要关注的吗?

        我:我们先聊第二点吧。

        兄弟伙:好。

        我:调用fork()之后,操作系统会复制一个全新的task_struct结构体,这个结构体除了id号不一样外,其余的都完全一样——这意味着,两个进程的内存空间也是映射到相同的地址。

         兄弟伙:这种情况应该是最简单,也最完美的情况。

         我:是的。这种情况下,一般fork的进程数只要与CPU数量一致,整个server的性能就不会太差——至少不会因为context switch而变差。而且,具备一个优点——如果每个进程都使用了IO多路复用,比如最典型的epoll,每一个进程都会因为fork而具备独立的数据结构,这相对与多线程模型来说,实在太简单了。

        兄弟伙:啊。你不是说“两个进程的内存空间也是映射到相同的地址”吗?这岂不是互相矛盾?

        我:这个问题提得很好。这并不矛盾。在fork时,两个进程是共享想同的内存的。但是,当其中一个进程试图去修改其中一个数据结构时(写时复制),Linux内核就会产生“缺页中断”为该数据结构分配全新的空间。

        兄弟伙:为什么Unix采用写时复制会大幅度提升内存管理性能呢?

        我:如果不采用写时复制,那么调用fork时,就会为进程分配全新的、独立的内存空间地址,而事实上,其中很大一部分内容可能与父进程是相同的——也就是说,大部分内存其实被重复浪费了。而采用写时复制之后,只有当真的需要分配独立的内存空间的时候,才会发生缺页中断,分配全新的内存空间,这个copy on write是基于page的,而不是基于进程的。

         兄弟伙:明白了。通过代码分析下fork()吧。

         我:首先,你需要明白,fork()其实做了些什么。我选一些早期的Linux内核代码给你看看吧。fork()其实做了2步。1、找到空闲的进程号。2、从父进程拷贝进程信息。

          1、

          

[html]  view plain copy
  1. int find_empty_process(void)  
  2. {  
  3.     int i;  
  4.   
  5.     repeat:  
  6.         if ((++last_pid)pid = last_pid;  
  7.     p->father = current->pid;  
  8.     p->counter = p->priority;  
  9.     p->signal = 0;  
  10.     p->alarm = 0;  
  11.     p->leader = 0;       /* process leadership doesn't inherit */  
  12.     p->utime = p->stime = 0;  
  13.     p->cutime = p->cstime = 0;  
  14.     p->start_time = jiffies;  
  15.     p->tss.back_link = 0;  
  16.     p->tss.esp0 = PAGE_SIZE + (long) p;  
  17.     p->tss.ss0 = 0x10;  
  18.     p->tss.eip = eip;  
  19.     p->tss.eflags = eflags;  
  20.     p->tss.eax = 0;  
  21.     p->tss.ecx = ecx;  
  22.     p->tss.edx = edx;  
  23.     p->tss.ebx = ebx;  
  24.     p->tss.esp = esp;  
  25.     p->tss.ebp = ebp;  
  26.     p->tss.esi = esi;  
  27.     p->tss.edi = edi;  
  28.     p->tss.es = es & 0xffff;  
  29.     p->tss.cs = cs & 0xffff;  
  30.     p->tss.ss = ss & 0xffff;  
  31.     p->tss.ds = ds & 0xffff;  
  32.     p->tss.fs = fs & 0xffff;  
  33.     p->tss.gs = gs & 0xffff;  
  34.     p->tss.ldt = _LDT(nr);  
  35.     p->tss.trace_bitmap = 0x80000000;  
  36.     if (last_task_used_math == current)  
  37.         __asm__("clts ; fnsave %0"::"m" (p->tss.i387));  
  38.     if (copy_mem(nr,p)) {  
  39.         task[nr] = NULL;  
  40.         free_page((long) p);  
  41.         return -EAGAIN;  
  42.     }  
  43.     for (i=0; ifilp[i])  
  44.             f->f_count++;  
  45.     if (current->pwd)  
  46.         current->pwd->i_count++;  
  47.     if (current->root)  
  48.         current->root->i_count++;  
  49.     if (current->executable)  
  50.         current->executable->i_count++;  
  51.     set_tss_desc(gdt+(nr
关注
打赏
1659628745
查看更多评论
立即登录/注册

微信扫码登录

0.1585s