2021-2022-1 20212812《Linux内核原理与分析》第七周作业

一、分析 Linux 内核创建一个新进程的过程实验

1、在MenuOS中增加fork命令,使用help查看现有命令,步骤如下:

cd menu 
mv test_fork.c test.c//在MenuOS中增加fork命令,并覆盖掉test.c文件
make rootfs  
MenuOS>>help  
MenuOS>>fork

在这里插入图片描述
2、在gdb中调试

//shell1中启动内核
cd ~/LinuxKernel
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -S -s
// shell2中使用gdb调试
cd ~/LinuxKernel
gdb
file linux-3.18.6/vmlinux
target remote:1234

然后在sys_clonedo_forkdup_task_structcopy_processcopy_threadret_from_fork处设置断点:

b sys_clone
b do_fork
b dup_task_struct
b copy_process
b copy_thread
b ret_from_fork

在这里插入图片描述

二、分析代码

1、fork()函数

fork()在父、子进程各返回一次。在父进程中返回子进程的 pid,在子进程中返回0。fork一个子进程的代码如下:

int Fork(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");
   }
}

2、进程创建流程:

  • fork 通过0x80中断(系统调用)来陷入内核,由系统提供的相应系统调用来完成进程的创建。PCB包含了一个进程的重要运行信息,所以我们将围绕在创建一个新进程时,如何来建立一个新的PCB的这一个过程来进行分析,在Linux系统中,PCB主要是存储在一个叫做task_struct这一个结构体中,创建新进程仅能通过fork,vfork,clone等系统调用的形式来进行。
//fork
#ifdef __ARCH_WANT_SYS_FORK
SYSCALL_DEFINE0(fork)
{
#ifdef CONFIG_MMU
return do_fork(SIGCHLD, 0, 0, NULL, NULL);
#else
/* can not support in nommu mode */
return -EINVAL;
#endif
}
#endif

//vfork
#ifdef __ARCH_WANT_SYS_VFORK
SYSCALL_DEFINE0(vfork)
{
return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, 0,
        0, NULL, NULL);
}
#endif

//clone
#ifdef __ARCH_WANT_SYS_CLONE
#ifdef CONFIG_CLONE_BACKWARDS
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
     int __user *, parent_tidptr,
     int, tls_val,
     int __user *, child_tidptr)
#elif defined(CONFIG_CLONE_BACKWARDS2)
SYSCALL_DEFINE5(clone, unsigned long, newsp, unsigned long, clone_flags,
     int __user *, parent_tidptr,
     int __user *, child_tidptr,
     int, tls_val)
#elif defined(CONFIG_CLONE_BACKWARDS3)
SYSCALL_DEFINE6(clone, unsigned long, clone_flags, unsigned long, newsp,
    int, stack_size,
    int __user *, parent_tidptr,
    int __user *, child_tidptr,
    int, tls_val)
#else
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
     int __user *, parent_tidptr,
     int __user *, child_tidptr,
     int, tls_val)
 #endif
{
return do_fork(clone_flags, newsp, 0, parent_tidptr,   child_tidptr);
}
 #endif

通过看上边的代码,我们可以清楚的看到,不论是使用 fork 还是 vfork 来创建进程,最终都是通过 do_ fork() 方法来实现的。

  • do_ fork()的代码中可以看出过程是通过copy_ process来复制进程描述符,返回新创建的子进程的task_ struct的指针(即PCB指针),将新创建的子进程放入调度器的队列中,让其有机会获得CPU,并且要确保子进程要先于父进程运行。

  • copy_process函数主要完成以下工作:用 dup_ task_ struct 复制当前的 task_ struct,并且检查进程数是否超过限制,初始化自旋锁、挂起信号、CPU 定时器等,调用 sched_ fork 初始化进程数据结构,并把进程状态设置为 TASK_ RUNNING,复制所有进程信息:包括文件系统、信号处理函数、信号、内存管理等,最后调用 copy_ thread 初始化子进程内核栈,为新进程分配并设置新的pid。

  • dup_ task_ struct()中可以看出在该函数中,调用了alloc_ task_ struct_ node分配一个 task_ struct 节点,调用alloc_ thread_ info_ node分配一个 thread_ info 节点,其实是分配了一个thread_ union联合体,将栈底返回给 ti。最后将栈底的值 ti 赋值给新节点的栈,最终执行完dup_ task_ struct之后,子进程除了tsk->stack指针不同之外,全部都一样!

三、总结

可以通过fork、vfork和clone来创建一个新进程,而他们又都是通过调用do_ fork方法来实现的。do_ fork函数主要是调用copy_ process函数来为子进程复制父进程信息的。copy_ process函数调用 dup_task_struct为子进程分配新的堆栈;调用sched_ fork 初始化进程数据结构,并把进程状态设置为TASK_ RUNNING。copy_ process函数尤为重要,我们可以看到为什么fork()函数返回值为0,并且fork出的子进程是从哪里开始执行的:将子进程的ip设置为ret_ from_ fork的首地址,子进程从ret_ from_ fork开始执行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值