《Linux内核分析》 第六节 分析Linux内核创建一个新进程的过程

时间:2021-04-17 15:48:25

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

一、实验过程

1.删除原来的menu,并clone新的menu,用test_fork.c覆盖test.c

2. make rootfs之后新的内核启动,测试fork功能

3.使用-s -S冷冻内核,准备调试

4.设置断点

5.根据断点,进行跟踪,得到结果

1、以下是执行指令

cd LinuxKernel   

  • rm menu -rf         
  • git clone https://github.com/mengning/menu.git   
  • cd menu    
  • mv test_fork.c test.c    
  • make rootfs  

 

《Linux内核分析》 第六节 分析Linux内核创建一个新进程的过程

 《Linux内核分析》 第六节 分析Linux内核创建一个新进程的过程

《Linux内核分析》 第六节 分析Linux内核创建一个新进程的过程

 

2. gdb上述的fork命令

关闭QEMU窗口,中命令行中输入:

qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S

再次启动MenuOS,并暂停等待gdb调试。

依次输入以下命令,启动调试:

gdb

file linux-3.18.6/vmlinux

target remote:1234

《Linux内核分析》 第六节 分析Linux内核创建一个新进程的过程

然后再设置以下断点:

b sys_clone

b do_fork

b dup_task_struct

b copy_process

b copy_thread

b ret_from_fork

继续执行之后,停在了do_fork的位置。然后n单步执行,依次进入copy_process、dup_task_struct。按s进入该函数,可以看到dst =src(也就是复制父进程的struct)

《Linux内核分析》 第六节 分析Linux内核创建一个新进程的过程

《Linux内核分析》 第六节 分析Linux内核创建一个新进程的过程

《Linux内核分析》 第六节 分析Linux内核创建一个新进程的过程

 

二、实验总结

 

 

1、fork系统调用

fork调用创建一个新的进程。新的进程或者说子进程是调用进程的或者说父进程的副本。

《Linux内核分析》 第六节 分析Linux内核创建一个新进程的过程

1.如果fork执行成功,就向父进程返回子进程的PID,并向子进程返回0。这就一起这即使你只调用fork一次,他也会返回两次。

2.Fork创建的新进程是和父进程(除了PID和PPID)一样的副本,包括真实和有效的UID和GID、进程组合会话ID、环境、资源限制、打开的文件以及共享内存段。

父进程和子进程之间有一点区别。子进程没有继承父进程的超市设置(使用alarm调用)父进程创建的文件锁,或者未决信号。要理解的关键概念是fork创建的新进程是父进程的一个准确副本。

3.一般来说,fork之后父进程先执行还是子进程先执行是不确定的。这取决于内核所使用的调度算法。
 
 

本周视频学习内容如下:

一、操作系统内核里有三大功能:进程管理、内存管理、文件系统

 

二、进程的描述:说到进程的描述,首先要分析一下进程描述符:task_struct

进程控制块PCB——task_struct:

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

struct task_struct数据结构很庞大

进程的标示pid

所有进程链表struct list_head tasks;

内核的双向循环链表的实现方法 - 一个更简略的双向循环链表

程序创建的进程具有父子关系,在编程时往往需要引用这样的父子关系。进程描述符中有几个域用来表示这样的关系

Linux为每个进程分配一个8KB大小的内存区域,用于存放该进程两个不同的数据结构:Thread_info和进程的内核堆栈

 

三、进程的创建

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

       iret与int 0x80指令对应,一个是弹出寄存器值,一个是压入寄存器的值

       如果将系统调用类比于fork();那么就相当于系统调用创建了一个子进程,然后子进程返回之后将在内核态运行,而返回到父进程后仍然在用户态运行

       fork一个子进程的代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char * argv[])
{
int pid;
/* fork another process */
pid = fork();
if (pid < 0)
{
/* error occurred */
fprintf(stderr,"Fork Failed!");
exit(-1);
}
else if (pid == 0)
{
/* child process */
printf("This is Child Process!\n");
}
else
{
/* parent process */
printf("This is Parent Process!\n");
/* parent will wait for the child to complete*/
wait(NULL);
printf("Child Complete!\n");
}
}

创建新进程的框架

     fork,vfork,clone都可以创建新进程,他们都是通过调用do_fork来实现的。

     copy_process修改复制的PCB以适应子进程的特点,也就是子进程的初始化

《Linux内核分析》 第六节 分析Linux内核创建一个新进程的过程

分配一个新的内核堆栈(用于存放子进程数据)

 

  内核堆栈的一部分也要从父进程中拷贝

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

1
err = arch_dup_task_struct(tsk, orig);

要给新进程分配一个新的内核堆栈

1
2
3
ti = alloc_thread_info_node(tsk, node);
tsk->stack = ti;
setup_thread_stack(tsk, orig);  //这里只是复制thread_info,而非复制内核堆栈

 

       从用户态的代码看fork();函数返回了两次,即在父子进程中各返回一次,父进程从系统调用中返回比较容易理解,子进程从系统调用中返回,那它在系统调用处理过程中的哪里开始执行的呢?这就涉及子进程的内核堆栈数据状态和taskstruct中thread记录的sp和ip的一致性问题,这是在哪里设定的

copythread in copy_process