今晚和一位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 plaincopy
  1. int find_empty_process(void)
  2. {
  3. int i;
  4. repeat:
  5. if ((++last_pid)<0) last_pid=1;
  6. for(i=0 ; i<NR_TASKS ; i++)
  7. if (task[i] && task[i]->pid == last_pid) goto repeat;
  8. for(i=1 ; i<NR_TASKS ; i++)
  9. if (!task[i])
  10. return i;
  11. return -EAGAIN;
  12. }

2、

[html] view plaincopy
  1. int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,
  2. long ebx,long ecx,long edx,
  3. long fs,long es,long ds,
  4. long eip,long cs,long eflags,long esp,long ss)
  5. {
  6. struct task_struct *p;
  7. int i;
  8. struct file *f;
  9. p = (struct task_struct *) get_free_page();
  10. if (!p)
  11. return -EAGAIN;
  12. task[nr] = p;
  13. *p = *current;  /* NOTE! this doesn't copy the supervisor stack */
  14. p->state = TASK_UNINTERRUPTIBLE;
  15. p->pid = last_pid;
  16. p->father = current->pid;
  17. p->counter = p->priority;
  18. p->signal = 0;
  19. p->alarm = 0;
  20. p->leader = 0;       /* process leadership doesn't inherit */
  21. p->utime = p->stime = 0;
  22. p->cutime = p->cstime = 0;
  23. p->start_time = jiffies;
  24. p->tss.back_link = 0;
  25. p->tss.esp0 = PAGE_SIZE + (long) p;
  26. p->tss.ss0 = 0x10;
  27. p->tss.eip = eip;
  28. p->tss.eflags = eflags;
  29. p->tss.eax = 0;
  30. p->tss.ecx = ecx;
  31. p->tss.edx = edx;
  32. p->tss.ebx = ebx;
  33. p->tss.esp = esp;
  34. p->tss.ebp = ebp;
  35. p->tss.esi = esi;
  36. p->tss.edi = edi;
  37. p->tss.es = es & 0xffff;
  38. p->tss.cs = cs & 0xffff;
  39. p->tss.ss = ss & 0xffff;
  40. p->tss.ds = ds & 0xffff;
  41. p->tss.fs = fs & 0xffff;
  42. p->tss.gs = gs & 0xffff;
  43. p->tss.ldt = _LDT(nr);
  44. p->tss.trace_bitmap = 0x80000000;
  45. if (last_task_used_math == current)
  46. __asm__("clts ; fnsave %0"::"m" (p->tss.i387));
  47. if (copy_mem(nr,p)) {
  48. task[nr] = NULL;
  49. free_page((long) p);
  50. return -EAGAIN;
  51. }
  52. for (i=0; i<NR_OPEN;i++)
  53. if (f=p->filp[i])
  54. f->f_count++;
  55. if (current->pwd)
  56. current->pwd->i_count++;
  57. if (current->root)
  58. current->root->i_count++;
  59. if (current->executable)
  60. current->executable->i_count++;
  61. set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));
  62. set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
  63. p->state = TASK_RUNNING; /* do this last, just in case */
  64. return last_pid;
  65. }

find_empty_process()这个函数你一看就明白了,实在太简单。我们分析下copy_process这个函数吧。

在copy_process这个函数里,有一行比较牛逼的语句。这一句相对比较难一点,需要重点说明下。

p = (struct task_struct *) get_free_page();

这里的新task_struct为什么会指向一个free page呢?

明白了吧?

task_struct结构体是按page分配的,多余的部分作为该进程的内核堆栈,从底向task_struct延伸。

之后就是对task_struct的属性进行设置了,包括“智能”与CPU相关部分属性。

通过这部分源代码的分析,你应该明白了吧——最初的“口述程序员如何意淫进程”这样的吹牛B的话看似随意,其实是理解Linux内核的基础与根本,如果真把那些文章当成吹牛逼了,这里的源代码分析对你来说就是天书了——如何才能轻松看懂源代码分析?

          答案是——多看几遍吹牛逼的对话,直到你明白这其中的深意。

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

  1. Linux内核源代码分析-目录

    第一部分 Linux 内核源代码 arch/i386/kernel/entry.S 2 arch/i386/kernel/init_task.c 8 arch/i386/kernel/irq.c 8 ...

  2. linux内核源代码分析----内核基础设施之klist

    概述 klist是list的线程安全版本,他提供了整个链表的自旋锁,查找链表节点,对链表节点的插入和删除操作都要获得这个自旋锁.klist的节点数据结构是klist_node,klist_node引入 ...

  3. Linux内核源代码分析——可执行文件header处理(二进制文件读写范例,写DUL工具入门指引)...

    在把Linux内核源代码生成Image之前,需要把执行文件头结构信息剔除出来.这个过程对理解Linux内核具有很大的帮助.同时,由于是对可执行文件进行直接读写操作,想写DUL工具的童鞋可以在这里学习到 ...

  4. Linux内核源代码分析——中断(一鞭一条痕)(下)

    写在代码前: Jack:上次你说的8个问题.我都想过了.不过不是特别清楚. 我:那我从头介绍一下吧. Jack:好.你开始吧. 中断在几乎所有的CPU里都分为三类:中断(interrupt).异常(e ...

  5. Linux内核源代码分析经验

      Linux的最大的好处之一就是它的源码公开.同时,公开的核心源码也吸引着无数的电脑爱好者和程序员:他们把解读和分析Linux的核心源码作为自己的 最大兴趣,把修改Linux源码和改造Linux系统 ...

  6. Linux内核源代码分析-第三章 内核体系结构概述-3

    3.5 设计和实现的关系 接下来的部分将介绍一些内核设计和实现之间的关系.本部分最重要的内容是对于内核源 程序目录结构的概述,这一点随后就会提到.本章最后以实现中体系结构无关代码和体系 结构相关代码的 ...

  7. Linux内核源代码分析-第三章 内核体系结构概述-1

    第3章 内核体系结构概述 本章从较高层次上对内核进行说明.从顺序上来说,本章首先介绍内核设计目标,接下来 介绍内核体系结构,最后介绍内核源程序目录结构. 3.1 内核设计目标 Linux 的内核展现出 ...

  8. Linux内核源代码分析——Linux内核的入口

    Jack:hi,淫龙,在Linux内核的源代码里,有几段汇编代码,那几段代码是负责Linux内核引导的. 我:是的.早期的Linux内核引导代码只有bootsect.s.setup.s.head.s这 ...

  9. Linux 内核源代码分析 chap 2 存储管理 (5)

    物理页面分配 linux 内核 2.4 中有 2 个版本号的物理页面分配函数 alloc_pages(). 一个在 mm/numa.c 中, 还有一个在 mm/page_alloc.c 中, 依据条件 ...

最新文章

  1. Flink学习笔记:Operators之CoGroup及Join操作
  2. 蓝桥杯-本质上升序列-java
  3. 【攻防世界001】Guess-the-Number
  4. 10万人的大场馆如何“画座位”?
  5. 计算机学校教学大纲,中等职业学校计算机应用基础教学大纲
  6. redhat linux7.0的安装
  7. 掌握常见的内部排序方法(插入排序,冒泡排序,选择排序,快速排序,堆排序,希尔排序,归并排序,基数排序等)...
  8. php写else老是报错,调试PHP错误经常用到的一些
  9. 结构体指针memcpy出错_关于memset和memcpy的使用,尤其对结构体进行初始化和拷贝的问题 | 学步园...
  10. 浪潮服务器u盘安装系统未找到任何驱动器,u盘重装win10时找不到任何驱动器
  11. input file 上传图片判断图片的宽高尺寸
  12. 360n7手机断网没信号_你的手机信号为什么总比别人差?原因没那么简单
  13. java经典sql笔试题
  14. lammps输出模拟结果的4种方法
  15. matlab画对数直方图,matlab中函数bar绘制直方图
  16. 微信小程序之移动端适配
  17. 面对众多客户/用户需求,每个都紧急,怎么排期?
  18. java 只保留字母_java编程问题,急急急!输入一个字符串,如果字符串中存在字母a的次数大于1,则只保留第一个a,...
  19. 【离散数学III】命题逻辑——命题符号及联结词
  20. 微信公众号模板消息推送(附上完整代码)

热门文章

  1. D - 数据结构实验之排序四:寻找大富翁
  2. Centos6.5 下安装opencv
  3. 【Linux】27.Markdown的用法
  4. Python-OpenCV 杂项(二): 鼠标事件
  5. Code Review最佳实践
  6. LinkedList 源码分析(JDK 1.8)
  7. 熟悉Java String的使用,熟悉String的各种函数,Java中各种变量类型
  8. 通过JDBC进行简单的增删改查(以MySQL为例)
  9. Python dict dictionaries Python 数据结构——字典
  10. Hadoop学习笔记—4.初识MapReduce