一.进程控制块PCB-stack_struct

进程在操作系统中都有一个结构,用于表示这个进程。这就是进程控制块(PCB),在Linux中具体实现是task_struct数据结构,它主要记录了以下信息:

  • 状态信息,例如可执行状态、就绪状态、阻塞状态等。
  • 性质,由于unix有很多变种,进行有自己独特的性质。
  • 资源,资源的链接比如内存,还有资源的限制和权限等。
  • 组织,例如按照家族关系建立起来的树(父进程、子进程等)。

task_struct结构体内容非常庞大,暂时没有去分析源代码,以后有时间再去研究。

二.Linux fork执行的过程

在menu中添加一个fork的系统调用,然后用gdb开始调试.执行以下命令

  1. qemu -kernel linux-3.18./arch/x86/boot/bzImage -initrd rootfs.img -s -s
  2. gdb
  3. file linux-3.18./vmlinux
  4. target remote:

然后在sys_fork、sys_clone处设置断点,再逐步调试,观察fork系统调用的执行过程。

具体分析fork系统调用执行过程.

1.fork、vfork和clone三个系统调用都可以创建一个新进程,而且它们都是通过调用do_fork来实现进程的创建,do_fork通过传递不同的clone_flags来实现fork、clone、vfork。

  1. long do_fork(unsigned long clone_flags,
  2. unsigned long stack_start,
  3. unsigned long stack_size,
  4. int __user *parent_tidptr,
  5. int __user *child_tidptr)
  6. {
  7. struct task_struct *p;
  8. int trace = ;
  9. long nr;
  10.  
  11. /*
  12. 1634 * Determine whether and which event to report to ptracer. When
  13. 1635 * called from kernel_thread or CLONE_UNTRACED is explicitly
  14. 1636 * requested, no event is reported; otherwise, report if the event
  15. 1637 * for the type of forking is enabled.
  16. 1638 */
  17. if (!(clone_flags & CLONE_UNTRACED)) {
  18. if (clone_flags & CLONE_VFORK)
  19. trace = PTRACE_EVENT_VFORK;
  20. else if ((clone_flags & CSIGNAL) != SIGCHLD)
  21. trace = PTRACE_EVENT_CLONE;
  22. else
  23. trace = PTRACE_EVENT_FORK;
  24.  
  25. if (likely(!ptrace_event_enabled(current, trace)))
  26. trace = ;
  27. }
  28. 1650
  29. p = copy_process(clone_flags, stack_start, stack_size,
  30. child_tidptr, NULL, trace); #进程复制,核心函数
  31. /*
  32. 1654 * Do this prior waking up the new thread - the thread pointer
  33. 1655 * might get invalid after that point, if the thread exits quickly.
  34. 1656 */
  35. if (!IS_ERR(p)) {
  36. struct completion vfork;
  37. struct pid *pid;
  38.  
  39. trace_sched_process_fork(current, p);
  40.  
  41. pid = get_task_pid(p, PIDTYPE_PID);
  42. nr = pid_vnr(pid);
  43.  
  44. if (clone_flags & CLONE_PARENT_SETTID)
  45. put_user(nr, parent_tidptr);
  46.  
  47. if (clone_flags & CLONE_VFORK) {
  48. p->vfork_done = &vfork;
  49. init_completion(&vfork);
  50. get_task_struct(p);
  51. }
  52.  
  53. wake_up_new_task(p);
  54.  
  55. /* forking complete and child started to run, tell ptracer */
  56. if (unlikely(trace))
  57. ptrace_event_pid(trace, pid);
  58.  
  59. if (clone_flags & CLONE_VFORK) {
  60. if (!wait_for_vfork_done(p, &vfork))
  61. ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
  62. }
  63.  
  64. put_pid(pid);
  65. } else {
  66. nr = PTR_ERR(p);
  67. }
  68. return nr;
  69. }

do_fork()函数的核心是copy_process(),该函数完成了进程创建的绝大部分。

  1. /*
  2. 1175 * This creates a new process as a copy of the old one,
  3. 1176 * but does not actually start it yet.
  4. 1177 *
  5. 1178 * It copies the registers, and all the appropriate
  6. 1179 * parts of the process environment (as per the clone
  7. 1180 * flags). The actual kick-off is left to the caller.
  8. 1181 */static struct task_struct *copy_process(unsigned long clone_flags,
  9. unsigned long stack_start,
  10. unsigned long stack_size,
  11. int __user *child_tidptr,
  12. struct pid *pid,
  13. int trace)
  14. {
  15. int retval;
  16. struct task_struct *p;
  17.  
  18. if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
  19. return ERR_PTR(-EINVAL);
  20.  
  21. if ((clone_flags & (CLONE_NEWUSER|CLONE_FS)) == (CLONE_NEWUSER|CLONE_FS))
  22. return ERR_PTR(-EINVAL);
  23.  
  24. /*
  25. 1199 * Thread groups must share signals as well, and detached threads
  26. 1200 * can only be started up within the thread group.
  27. 1201 */
  28. if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
  29. return ERR_PTR(-EINVAL);
  30.  
  31. /*
  32. 1206 * Shared signal handlers imply shared VM. By way of the above,
  33. 1207 * thread groups also imply shared VM. Blocking this case allows
  34. 1208 * for various simplifications in other code.
  35. 1209 */
  36. if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))
  37. return ERR_PTR(-EINVAL);
  38.  
  39. /*
  40. 1214 * Siblings of global init remain as zombies on exit since they are
  41. 1215 * not reaped by their parent (swapper). To solve this and to avoid
  42. 1216 * multi-rooted process trees, prevent global and container-inits
  43. 1217 * from creating siblings.
  44. 1218 */
  45. if ((clone_flags & CLONE_PARENT) &&
  46. current->signal->flags & SIGNAL_UNKILLABLE)
  47. return ERR_PTR(-EINVAL);
  48.  
  49. /*
  50. 1224 * If the new process will be in a different pid or user namespace
  51. 1225 * do not allow it to share a thread group or signal handlers or
  52. 1226 * parent with the forking task.
  53. 1227 */
  54. if (clone_flags & CLONE_SIGHAND) {
  55. if ((clone_flags & (CLONE_NEWUSER | CLONE_NEWPID)) ||
  56. (task_active_pid_ns(current) !=
  57. current->nsproxy->pid_ns_for_children))
  58. return ERR_PTR(-EINVAL);
  59. }
  60.  
  61. retval = security_task_create(clone_flags);
  62. if (retval)
  63. goto fork_out;
  64.  
  65. retval = -ENOMEM;
  66. p = dup_task_struct(current); #为子进程创建一个新的内核栈,复制task_struct和thread_info结构,此时子进程的进程控制块和父进程完全一致。
  67. if (!p)
  68. goto fork_out;
  69.  
  70. ftrace_graph_init_task(p);
  71.  
  72. rt_mutex_init_task(p);
  73.  
  74. #ifdef CONFIG_PROVE_LOCKING
  75. DEBUG_LOCKS_WARN_ON(!p->hardirqs_enabled);
  76. DEBUG_LOCKS_WARN_ON(!p->softirqs_enabled);
  77. #endif
  78. retval = -EAGAIN;
  79. if (atomic_read(&p->real_cred->user->processes) >=
  80. task_rlimit(p, RLIMIT_NPROC)) {
  81. if (p->real_cred->user != INIT_USER &&
  82. !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))
  83. goto bad_fork_free;
  84. }
  85. current->flags &= ~PF_NPROC_EXCEEDED;
  86.  
  87. retval = copy_creds(p, clone_flags);
  88. if (retval < )
  89. goto bad_fork_free;
  90.  
  91. /*
  92. 1266 * If multiple threads are within copy_process(), then this check
  93. 1267 * triggers too late. This doesn't hurt, the check is only there
  94. 1268 * to stop root fork bombs.
  95. 1269 */
  96. retval = -EAGAIN;
  97. if (nr_threads >= max_threads)
  98. goto bad_fork_cleanup_count;
  99.  
  100. if (!try_module_get(task_thread_info(p)->exec_domain->module))
  101. goto bad_fork_cleanup_count;
  102.  
  103. delayacct_tsk_init(p); /* Must remain after dup_task_struct() */
  104. p->flags &= ~(PF_SUPERPRIV | PF_WQ_WORKER);
  105. p->flags |= PF_FORKNOEXEC;
  106. INIT_LIST_HEAD(&p->children);
  107. INIT_LIST_HEAD(&p->sibling);
  108. rcu_copy_process(p);
  109. p->vfork_done = NULL;
  110. spin_lock_init(&p->alloc_lock);
  111.  
  112. init_sigpending(&p->pending);
  113.  
  114. p->utime = p->stime = p->gtime = ;
  115. ....

通过dup_task_struct()函数,为子进程创建一个新的内核栈,复制task_struct和thread_info结构。

  1. ti=alloc_thread_info_node(task,node);
  2. tsk->stack=ti;
  3. setup_thread_stack(tsk,orig); //这里只是复制了thread_info

重点关注下,fork()创建子进程后,父进程从系统调用中返回,而子进程从哪开始返回.

这主要是在copy_process()中copy_thread()代码.

  1. int copy_thread(unsigned long clone_flags, unsigned long sp,
  2. unsigned long arg, struct task_struct *p)
  3. {
  4. struct pt_regs *childregs = task_pt_regs(p);
  5. struct task_struct *tsk;
  6. int err;
  7.  
  8. p->thread.sp = (unsigned long) childregs; #记录进程切换时的堆栈指针
  9. p->thread.sp0 = (unsigned long) (childregs+);
  10. memset(p->thread.ptrace_bps, , sizeof(p->thread.ptrace_bps));
  11.  
  12. if (unlikely(p->flags & PF_KTHREAD)) {
  13. /* kernel thread */
  14. memset(childregs, , sizeof(struct pt_regs));
  15. p->thread.ip = (unsigned long) ret_from_kernel_thread;
  16. task_user_gs(p) = __KERNEL_STACK_CANARY;
  17. childregs->ds = __USER_DS;
  18. childregs->es = __USER_DS;
  19. childregs->fs = __KERNEL_PERCPU;
  20. childregs->bx = sp; /* function */
  21. childregs->bp = arg;
  22. childregs->orig_ax = -;
  23. childregs->cs = __KERNEL_CS | get_kernel_rpl();
  24. childregs->flags = X86_EFLAGS_IF | X86_EFLAGS_FIXED;
  25. p->thread.io_bitmap_ptr = NULL;
  26. return ;
  27. }
  28. *childregs = *current_pt_regs();#复制内核堆栈
  29. childregs->ax = ; #这也是为什么子进程的fork返回0
  30. if (sp)
  31. childregs->sp = sp;
  32.  
  33. p->thread.ip = (unsigned long) ret_from_fork; #子进程开始执行处
  34. task_user_gs(p) = get_user_gs(current_pt_regs());
  35.  
  36. p->thread.io_bitmap_ptr = NULL;
  37. tsk = current;
  38. err = -ENOMEM;
  39.  
  40. if (unlikely(test_tsk_thread_flag(tsk, TIF_IO_BITMAP))) {
  41. p->thread.io_bitmap_ptr = kmemdup(tsk->thread.io_bitmap_ptr,
  42. IO_BITMAP_BYTES, GFP_KERNEL);
  43. if (!p->thread.io_bitmap_ptr) {
  44. p->thread.io_bitmap_max = ;
  45. return -ENOMEM;
  46. }
  47. set_tsk_thread_flag(p, TIF_IO_BITMAP);
  48. }
  49.  
  50. err = ;
  51.  
  52. /*
  53. 184 * Set a new TLS for the child thread?
  54. 185 */
  55. if (clone_flags & CLONE_SETTLS)
  56. err = do_set_thread_area(p, -,
  57. (struct user_desc __user *)childregs->si, );
  58.  
  59. if (err && p->thread.io_bitmap_ptr) {
  60. kfree(p->thread.io_bitmap_ptr);
  61. p->thread.io_bitmap_max = ;
  62. }
  63. return err;
  64. } 

然后回到do_fork()函数中,唤醒子进程并开始运行。至此,一个进程创建就完成了。

三.实验总结

中间虽然的很多细节还不是很清楚,但是对linux 创建子进程的大体流程有了一个宏观的认识,更加深刻地理解了底层Linux 内核进程运行的机制。

《Linux内核分析》 week6作业-Linux内核fork()系统调用的创建过程的更多相关文章

  1. 《Linux内核分析》 第四节 扒开系统调用的三层皮(上)

    <Linux内核分析> 第四节 扒开系统调用的三层皮(上) 张嘉琪 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com ...

  2. LINUX内核分析第四周学习总结——扒开系统调用的“三层皮”

    LINUX内核分析第四周学习总结--扒开系统调用的"三层皮" 标签(空格分隔): 20135321余佳源 余佳源 原创作品转载请注明出处 <Linux内核分析>MOOC ...

  3. 《Linux内核分析》 第五节 扒开系统调用的三层皮(下)

    <Linux内核分析> 第五节 扒开系统调用的三层皮(下) 20135307 一.给MenusOS增加time和time-asm命令 给MenuOS增加time和time-asm命令需要 ...

  4. windows7内核分析之x86&x64第二章系统调用

    windows7内核分析之x86&x64第二章系统调用 2.1内核与系统调用 上节讲到进入内核五种方式 其中一种就是 系统调用 syscall/sysenter或者int 2e(在 64 位环 ...

  5. 《linux内核分析》作业一:分析汇编代码

    通过汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的(王海宁) 姓名:王海宁                             学号:20135103 课程:<Linux内核分析& ...

  6. 《Linux内核分析》第五周 扒开系统调用的三层皮(下)

    [刘蔚然 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] WEEK FIVE( ...

  7. Linux内核分析——进程的切换和系统的一般执行过程

    进程的切换和系统的一般执行过程 一.进程切换的关键代码switch_to分析 (一)进程调度与进程调度的时机分析 1.不同类型的进程有不同的调度需求 第一种分类: (1)I/O-bound:频繁进行I ...

  8. 20135239 益西拉姆 linux内核分析 进程的切换和系统的一般执行过程

    week 8 进程的切换和系统的一般执行过程 [ 20135239 原文请转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course ...

  9. Linux内核分析— —进程的切换和系统的一般执行过程

    进程调度的时机 linux进程调度是基于分时和优先级的 中断处理过程(包括时钟中断.I/O中断.系统调用和异常)中,直接调用schedule(),或者返回用户态时根据need_resched标记调用s ...

随机推荐

  1. 初窥struts2(二)OGNL表达式

    Struts2总结 Struts2完整的处理流程: 1  客户端发送请求,交给struts2控制器(StrutsPrepareAndExecuteFilter). 2  Filter控制器进行请求过滤 ...

  2. MySQL重置root密码的几种方法(windows+Linux)

    重置root密码的方法: windows系统下:1.停止mysql服务:2.新建文件init-root.txt,写上如下内容: update mysql.user set password = pas ...

  3. 转:eclipse载入extjs4出现内存溢出错误的解决方法

    去掉.project文件中的以下部分:第一部分:<buildCommand>    <name>org.eclipse.wst.jsdt.core.javascriptVali ...

  4. 【Maven实战】archetype的使用和eclipse的配置

    1.之前在进行项目的构建时都是使用手工进行文件夹的建立,maven也给我们提供了一个参数archetype,可以用来进行项目骨架的建立.使用maven archetype:generate进行创建: ...

  5. web client

    http://blog.csdn.net/borishuai/article/details/8676573 http://down.lusongsong.com/soft/696.html http ...

  6. 改进基于Boost.Asio的聊天服务

    Boost.Asio是个非常易用的C++异步网络库,官方文档中一个示例是聊天服务,分为chat_message.chat_client.chat_server三个部分.chat_server的启动代码 ...

  7. CLOUDSTACK HA功能,测试成功

    要注意VM HA和HOST HA两个级别的区别.并且要整合.

  8. GDI+画图类Graphics的使用

    一:基础定义 #region 定义线尾.线头为箭头.字体和笔刷 Pen p = );//定义画笔 蓝色,宽度为1(坐标显示颜色) p.EndCap = LineCap.ArrowAnchor;//定义 ...

  9. Something broke! (Error 500)——reviewboard

    Something broke! (Error 500) 1.什么时候会出现? 不清楚,出现过几次 2.解决手段及方法: 更改/www_rb/conf/settings_local.py文件,将DEB ...

  10. redis取值报错

    > get "all_couriers_on_the_job" (error) ERR Operation against a key holding the wrong k ...