作者:吴乐 山东师范大学

《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

一、实验过程

1.打开gdb,设置断点

2.跟踪到do_fork处

3.跟踪到copy_process断点处。

4.跟踪到ret_from_fork子进程创建完成。

二、代码部分分析

Fork的系统调用代码在linux/arch/i386/kernel/process.c中:

      asmlinkage int sys_fork(struct pt_regs regs) 

return do_fork(SIGCHLD, regs.esp, ®s, 0, NULL, NULL); 
}

Sys_fork系统调用通过 do_fork()函数实现,通过对do_fork()函数传递不同的clone_flags来实现fork,clone,vfork。

Syn_clone和syn_vfork的系统调用代码如下:

      asmlinkage int sys_clone(struct pt_regs regs) 

unsigned long clone_flags; 
unsigned long newsp; 
int __user *parent_tidptr, *child_tidptr; 
clone_flags = regs.ebx; 
newsp = regs.ecx; 
parent_tidptr = (int __user *)regs.edx; 
child_tidptr = (int __user *)regs.edi; 
if (!newsp) 
newsp = regs.esp; 
return do_fork(clone_flags, newsp, ®s, 0, parent_tidptr, child_tidptr); 

asmlinkage int sys_vfork(struct pt_regs regs) 

return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, ®s, 0, NULL, NULL); 
}

其中clone_flas在includelinuxsched.h中定义

      /* 
* cloning flags: 
*/ 
#define CSIGNAL 0x000000ff /* 进程退出时需要传递的信号*/ 
#define CLONE_VM 0x00000100 /* 父子进程共享地址空间 */ 
#define CLONE_FS 0x00000200 /* 父子进程共享文件系统信息 */ 
#define CLONE_FILES 0x00000400 /* 父子进程共享已打开的文件 */ 
#define CLONE_SIGHAND 0x00000800 /* 父子进程共享信号处理 */ 
#define CLONE_PTRACE 0x00002000 /* 继续调试子进程 */ 
#define CLONE_VFORK 0x00004000 /* 调用vfork(),父进程休眠*/ 
#define CLONE_PARENT 0x00008000 /* 设置一个共有的父进程 */ 
#define CLONE_THREAD 0x00010000 /* 父子进程在同一个线程组 */ 
#define CLONE_NEWNS 0x00020000 /* 为子进程创建一个新的命名空间 */ 
#define CLONE_SYSVSEM 0x00040000 /* 父子进程共享system V SEM_UNDO */ 
#define CLONE_SETTLS 0x00080000 /* 为子进程创建新的TLS */ 
#define CLONE_PARENT_SETTID 0x00100000 /* 设置父进程TID */ 
#define CLONE_CHILD_CLEARTID 0x00200000 /* 清除子进程TID */ 
#define CLONE_DETACHED 0x00400000 /* Unused, ignored */ 
#define CLONE_UNTRACED 0x00800000 /* 不允许调试子进程 */ 
#define CLONE_CHILD_SETTID 0x01000000 /* 设置子进程TID */ 
#define CLONE_STOPPED 0x02000000 /* 设置进程停止状态 */ 
#define CLONE_NEWUTS 0x04000000 /* 创建新的utsname组 */ 
#define CLONE_NEWIPC 0x08000000 /* 创建新的IPC */

Do_fork()在kernel/fork.c中定义,代码如下:

      /* 
* Ok, this is the main fork-routine. 

* It copies the process, and if successful kick-starts 
* it and waits for it to finish using the VM if required. 
*/ 
long do_fork(unsigned long clone_flags, 
unsigned long stack_start, 
struct pt_regs *regs, 
unsigned long stack_size, 
int __user *parent_tidptr, 
int __user *child_tidptr) 

struct task_struct *p; 
int trace = 0; 
struct pid *pid = alloc_pid(); 
long nr; 
if (!pid) 
return -EAGAIN; 
nr = pid->nr; 
if (unlikely(current->ptrace)) { 
trace = fork_traceflag (clone_flags); 
if (trace) 
clone_flags |= CLONE_PTRACE; 

p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, pid); 
/* 
* Do this prior waking up the new thread - the thread pointer 
* might get invalid after that point, if the thread exits quickly. 
*/ 
if (!IS_ERR(p)) { 
struct completion vfork; 
if (clone_flags & CLONE_VFORK) { 
p->vfork_done = &vfork; 
init_completion(&vfork); 

if ((p->ptrace & PT_PTRACED) || (clone_flags & CLONE_STOPPED)) { 
/* 
* We'll start up with an immediate SIGSTOP. 
*/ 
sigaddset(&p->pending.signal, SIGSTOP); 
set_tsk_thread_flag(p, TIF_SIGPENDING); 

if (!(clone_flags & CLONE_STOPPED)) 
wake_up_new_task(p, clone_flags); 
else 
p->state = TASK_STOPPED; 
if (unlikely (trace)) { 
current->ptrace_message = nr; 
ptrace_notify ((trace << 8) | SIGTRAP); 

if (clone_flags & CLONE_VFORK) { 
freezer_do_not_count(); 
wait_for_completion(&vfork); 
freezer_count(); 
if (unlikely (current->ptrace & PT_TRACE_VFORK_DONE)) { 
current->ptrace_message = nr; 
ptrace_notify ((PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP); 


} else { 
free_pid(pid); 
nr = PTR_ERR(p); 

return nr; 
}

Do_fork()函数的核心是copy_process()函数,该函数完成了进程创建的绝大部分工作并且也在fork.c定义,copy_process函数较长,逐段往下看:

      static struct task_struct *copy_process(unsigned long clone_flags, 
unsigned long stack_start, 
struct pt_regs *regs, 
unsigned long stack_size, 
int __user *parent_tidptr, 
int __user *child_tidptr, 
struct pid *pid) 

int retval; 
struct task_struct *p = NULL; 
if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS)) 
return ERR_PTR(-EINVAL); 
/* 
* Thread groups must share signals as well, and detached threads 
* can only be started up within the thread group. 
*/ 
if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND)) 
return ERR_PTR(-EINVAL); 
/* 
* Shared signal handlers imply shared VM. By way of the above, 
* thread groups also imply shared VM. Blocking this case allows 
* for various simplifications in other code. 
*/ 
if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM)) 
return ERR_PTR(-EINVAL); 
retval = security_task_create(clone_flags); 
if (retval) 
goto fork_out; 
retval = -ENOMEM; 
p = dup_task_struct(current); 
if (!p) 
goto fork_out; 
rt_mutex_init_task(p); 
#ifdef CONFIG_TRACE_IRQFLAGS 
DEBUG_LOCKS_WARN_ON(!p->hardirqs_enabled); 
DEBUG_LOCKS_WARN_ON(!p->softirqs_enabled); 
#endif

这段代码首先对传入的clone_flag进行检查,接着调用了dup_task_struct()函数,该函数的主要作用是:为子进程创建一个新的内核栈,复制task_struct结构和thread_info结构,这里只是对结构完整的复制,所以子进程的进程描述符跟父进程完全一样。跟进dup_task_struct()函数看代码:

      static struct task_struct *dup_task_struct(struct task_struct *orig) 

struct task_struct *tsk; 
struct thread_info *ti; 
prepare_to_copy(orig); 
tsk = alloc_task_struct(); 
if (!tsk) 
return NULL; 
ti = alloc_thread_info(tsk); 
if (!ti) { 
free_task_struct(tsk); 
return NULL; 

*tsk = *orig; 
tsk->stack = ti; 
setup_thread_stack(tsk, orig); 
#ifdef CONFIG_CC_STACKPROTECTOR 
tsk->stack_canary = get_random_int(); 
#endif 
/* One for us, one for whoever does the "release_task()" (usually parent) */ 
atomic_set(&tsk->usage,2); 
atomic_set(&tsk->fs_excl, 0); 
#ifdef CONFIG_BLK_DEV_IO_TRACE 
tsk->btrace_seq = 0; 
#endif 
tsk->splice_pipe = NULL; 
return tsk; 
}

三、总结linux创建新进程的过程

  系统允许一个进程创建新进程,新进程即为子进程,子进程还可以创建新的子进程,形成进程树结构模型。整个linux系统的所有进程也是一个树形结构。树根是系统自动构造的,即在内核态下执行的0号进程,它是所有进程的祖先。由0号进程创建1号进程(内核态),1号负责执行内核的部分初始化工作及进行系统配置,并创建若干个用于高速缓存和虚拟主存管理的内核线程。随后,1号进程调用execve()运行可执行程序init,并演变成用户态1号进程,即init进程。它按照配置文件/etc/initab的要求,完成系统启动工作,创建编号为1号、2号...的若干终端注册进程getty。每个getty进程设置其进程组标识号,并监视配置到系统终端的接口线路。当检测到来自终端的连接信号时,getty进程将通过函数execve()执行注册程序login,此时用户就可输入注册名和密码进入登录过程,如果成功,由login程序再通过函数execv()执行shell,该shell进程接收getty进程的pid,取代原来的getty进程。再由shell直接或间接地产生其他进程。

上述过程可描述为:0号进程->1号内核进程->1号内核线程->1号用户进程(init进程)->getty进程->shell进程

通过fork函数创建进程的跟踪,分析linux内核进程的创建的更多相关文章

  1. 关于fork( )函数父子进程返回值的问题

    fork()是linux的系统调用函数sys_fork()的提供给用户的接口函数,fork()函数会实现对中断int 0x80的调用过程并把调用结果返回给用户程序. fork()的函数定义是在init ...

  2. 跟踪分析Linux内核的启动过程--实验报告 分析 及知识重点

    跟踪分析Linux内核的启动过程 攥写人:杨光  学号:20135233 ( *原创作品转载请注明出处*) ( 学习课程:<Linux内核分析>MOOC课程http://mooc.stud ...

  3. 20135202闫佳歆--week3 跟踪分析Linux内核的启动过程--实验及总结

    实验三:跟踪分析Linux内核的启动过程 一.调试步骤如下: 使用gdb跟踪调试内核 qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd r ...

  4. 实验三:跟踪分析Linux内核的启动过程

    实验三:跟踪分析Linux内核的启动过程 学号:20135114 姓名:王朝宪 注: 原创作品转载请注明出处   <Linux内核分析>MOOC课程http://mooc.study.16 ...

  5. 20135239 益西拉姆 linux内核分析 跟踪分析Linux内核的启动过程

    回顾 1.中断上下文的切换——保存现场&恢复现场 本节主要课程内容 Linux内核源代码简介 1.打开内核源代码页面 arch/目录:支持不同CPU的源代码:其中的X86是重点 init/目录 ...

  6. Linux内核分析第三周学习博客——跟踪分析Linux内核的启动过程

    Linux内核分析第三周学习博客--跟踪分析Linux内核的启动过程 实验过程截图: 过程分析: 在Linux内核的启动过程中,一共经历了start_kernel,rest_init,kernel_t ...

  7. 跟踪分析Linux内核的启动过程小解

    跟踪分析Linux内核的启动过程 “20135224陈实  + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029 ...

  8. fork()函数、进程表示符、进程位置

    linux.centos6.5 fork()函数:作用于创建子进程.返回值有两个,一个是向父进程返回它的pid,一个是返回0: eg1: #include<stdio.h> #includ ...

  9. Linux fork函数具体图解-同一时候分析一道腾讯笔试题

    原创blog.转载请注明出处 头文件: #include<unistd.h> #include<sys/types.h> 函数原型: pid_t fork( void); (p ...

随机推荐

  1. 洛谷 1365 WJMZBMR打osu! / Easy

    题目:https://www.luogu.org/problemnew/show/P1365 大水题.记录一下o的期望长度. 关键是(x+1)^2=x^2+2*x+1. #include<ios ...

  2. Spring XML和Annotation混合配置的时候,XML中Bean名称写错会导致启动异常不打印、死循环

    今天做Tomcat迁移Spring Boot,遇到一个坑.启动没有错误,CPU特别高 经过把堆栈kill -3 打印出来,发现堆栈特别长(没有死循环),所有的堆栈信息都集中在org.springfra ...

  3. spring data mongodb 操作

    xml配置(mongo集群方式): <?xml version="1.0" encoding="UTF-8"?> <beans xmlns=& ...

  4. 小程序WXML基本使用

    数据绑定 <!--wxml--> <view> {{message}} </view> // page.js Page({ data: { message: 'He ...

  5. windows环境下,安装zookeeper~

    1.   概述 ZooKeeper是Hadoop的正式子项目,它是一个针对大型分布式系统的可靠协调系统,提供的功能包括:配置维护.名字服务.分布式同步.组服务等.ZooKeeper的目标就是封装好复杂 ...

  6. 卷积神经网络之ResNet网络模型学习

    Deep Residual Learning for Image Recognition 微软亚洲研究院的何凯明等人 论文地址 https://arxiv.org/pdf/1512.03385v1.p ...

  7. python学习笔记(四):函数

    一.函数是什么? 函数一词来源于数学,但编程中的「函数」概念,与数学中的函数是有很大不同的,编程中的函数在英文中也有很多不同的叫法.在BASIC中叫做subroutine(子过程或子程序),在Pasc ...

  8. Angular 6 HMR 热加载配置

    什么是 HMR? ​ HMR 是hot module replacement 的简称,直译:热模块替换,如果不开启HMR模式,angular项目在模块更改的时候会从根节点开始刷新,开启HMR模式以后, ...

  9. Python 中的进程与 锁

    理论知识 操作系统背景知识 顾名思义,进程即正在执行的一个过程.进程是对正在运行程序的一个抽象. 进程的概念起源于操作系统,是操作系统最核心的概念,也是操作系统提供的最古老也是最重要的抽象概念之一.操 ...

  10. Java面向对象-面向对象编程之基本概念

    面向对象这个概念,每本书上的说法定义很多. 我自己根据我的经验,自己归档总结了下, 所谓面向对象,就是 以基于对象的思维去分析和解决问题,万物皆对象: 面向对象经常和面向过程放一起讨论: 这里举例, ...