"Linux内核分析"第六周实验报告

时间:2021-06-29 21:13:31

张文俊 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

1、进程的描述

进程控制块PCB——task_struct

为了管理进程,内核必须对每个进程进行清晰的描述,进程描述符(即task_struct)提供了内核所需了解的进程信息。

父子进程关系管理:

"Linux内核分析"第六周实验报告

这是CPU状态,在进程上下文切换的过程中,起到关键作用:

"Linux内核分析"第六周实验报告

2、进程的创建

进程的创建概览和fork一个进程的用户态代码

"Linux内核分析"第六周实验报告

以fork一个子进程的代码为例:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. int main(int argc, char * argv[])
  5. {
  6. int pid;
  7. /* fork another process */
  8. pid = fork();
  9. if (pid < 0)
  10. {
  11. /* error occurred */
  12. fprintf(stderr,"Fork Failed!");
  13. exit(-1);
  14. }
  15. else if (pid == 0)
  16. {
  17. /* child process */
  18. printf("This is Child Process!\n");
  19. }
  20. else
  21. {
  22. /* parent process  */
  23. printf("This is Parent Process!\n");
  24. /* parent will wait for the child to complete*/
  25. wait(NULL);
  26. printf("Child Complete!\n");
  27. }
  28. }

可用fork系统调用在父进程和子进程各返回一次。在fork之后,这段代码实际上就变成了两个进程。

理解进程创建过程复杂代码的方法

系统调用回顾:

"Linux内核分析"第六周实验报告

"Linux内核分析"第六周实验报告

"Linux内核分析"第六周实验报告

创建一个新进程在内核中的执行过程

  • fork、vfork和clone三个系统调用都可以创建一个新进程,而且都是通过调用do_fork来实现进程的创建;

  • Linux通过复制父进程来创建一个新进程,那么这就给我们理解这一个过程提供一个想象的框架:

    • 复制一个PCB——task_struct

      1. err = arch_dup_task_struct(tsk, orig);
    • 要给新进程分配一个新的内核堆栈

      1. ti = alloc_thread_info_node(tsk, node);
      2. tsk->stack = ti;
      3. setup_thread_stack(tsk, orig); //这里只是复制thread_info,而非复制内核堆栈
    • 要修改复制过来的进程数据,比如pid、进程链表等等都要改改吧,见copy_process内部。

  • 从用户态的代码看fork();函数返回了两次,即在父子进程中各返回一次,父进程从系统调用中返回比较容易理解,子进程从系统调用中返回,那它在系统调用处理过程中的哪里开始执行的呢?

这就涉及子进程的内核堆栈数据状态和task_struct中thread记录的sp和ip的一致性问题,这是在哪里设定的?copy_thread in copy_process

  1. *childregs = *current_pt_regs(); //复制内核堆栈
  2. childregs->ax = 0; //为什么子进程的fork返回0,这里就是原因!
  3. p->thread.sp = (unsigned long) childregs; //调度到子进程时的内核栈顶
  4. p->thread.ip = (unsigned long) ret_from_fork; //调度到子进程时的第一条指令地址

浏览进程创建过程相关的关键代码

"Linux内核分析"第六周实验报告

以上为复制数据结构部分代码,dup_task_struct。这段代码相对简单,就是将src其中内容赋值给dst。

"Linux内核分析"第六周实验报告

以上为alloc调用,它功能就是生成了两个页面。

"Linux内核分析"第六周实验报告

重点还是看copy_thread的功能,pt_regs获取了pid指向的堆栈内信息,比如栈底等内容。

"Linux内核分析"第六周实验报告

这是父进程对已有堆栈数据的copy,拷贝内核堆栈数据和指定新进程的第一条指令地址。

因为子进程的返回值为0,所以copy时还需要修改一下内核栈内压入的。

子进程是从哪里开始执行的?

"Linux内核分析"第六周实验报告

子进程是从这个位置开始执行的,就是fork返回值被得到的时候。

对pt_regs内容进行查看,可以看到其中包含int指令和SAVE_ALL压到内核栈的内容。只复制了i32的相关内容。

"Linux内核分析"第六周实验报告

从entry_32中,可以找到ret_from_fork的函数代码部分。

会跳到syscall_exit部分继续执行。正常返回到用户态。

使用gdb跟踪创建新进程的过程

如果用实验楼环境,首先需要rm menu -rf 删除原先menu 然后clone一份新的。

然后进入menu后,mv test_fork.c test.c,把test.c覆盖掉。

然后make rootfs,编译出来。

"Linux内核分析"第六周实验报告

可以发现fork被进入到menu里。

然后使用gdb调试,使用-s -S。

然后设置断点:b sys_clone 和 b do_fork 和 b dup_task_struct 和 b copy_process 和 b copy_thread 和 b ret_from_fork

然后进行n单步调试。

"Linux内核分析"第六周实验报告

可以看到子进程执行的起点。

3、总结

linux 系统创建进程都是用 fork() 系统调用创建子进程。

由 fork() 系统调用创建的新进程被称为子进程。

该函数被调用一次,但返回两次。

如果 fork()进程调用成功,两次返回的区别是子进程的返回值是0,而父进程的返回值则是新子进程的进程号。