linux clone 创建进程,LINUX创建进程过程的理解

copye_process 函数 /linux-3.18.6/kernel/fork.c#copy_process

dup_task_struct 函数 /linux-3.18.6/kernel/fork.c#dup_task_struct

实际就是alloc一个内核堆栈把alloc后返回的地址赋给stack

arch_dup_task_struct 函数 /linux-3.18.6/kernel/fork.c#arch_dup_task_struct

就是把数据结构加*,原来它是数据结构的指针,加*,表示它的值

294return 0;

alloc_thread_info_node 函数

/linux-3.18.6/kernel/fork.c#alloc_thread_info_node

做了实际分配内核堆栈空间的效果,实际的代码是alloc_kmem_pages_node,创建了一定大小的页面,页面有一部分用来存放thread_info,另一部分从高地址向低地址就是内核堆栈

回到dup_task_struct,现在已经把父进程的PCB,也就是task_struct数据结构复制过来了,也就是由p所指向的子进程的PCB(进程描述符)

往后的代码,有大量地修改子进程内容的代码,做初始化,这些都可以抽象掉

copy_thread /linux-3.18.6/arch/x86/kernel/process_32.c#132

从这里可以看到,从子进程的pid,也就是内核堆栈的位置,找到了栈空间,SAVE_ALL的一些内容,SAVE_ALL的地址

139p->thread.sp

= (unsigned

long) childregs;

// 调度到子进程时的内核栈底

把栈底赋上

拷贝内核堆栈数据和指定新进程的第一条指令地址

当前进程,也就是父进程,因为我们这个执行过程还在父进程的执行上下文当中。父进程的内核堆栈的栈底,也就是SAVE_ALL的内容,把它拷贝过来,这个地方实际就是做内核堆栈里已有数据的拷贝

值得注意的是:在复制内核堆栈的时候,只复制了与SAVE_ALL相关的那一部分,只复制了struct

pt_regs

struct pt_regs数据结构的内容/linux-3.18.6/arch/x86/include/asm/ptrace.h

//

SAVE_ALL压到内核堆栈里的内容12unsigned long bx;

13unsigned long cx;

14unsigned long dx;

15unsigned long si;

16unsigned long di;

17unsigned long bp;

18unsigned long ax;

// 传递的系统调用号

19unsigned long ds;

20unsigned long es;

21unsigned long fs;

22unsigned long gs;

23unsigned long orig_ax;

// 原来的eax

//

执行int

0x80指令的时候,CPU自动压到内核堆栈里面的内容24unsigned long ip;

25unsigned long cs;

26unsigned long flags;

27unsigned long sp;

28unsigned long ss;

29};

31#else

在复制内核堆栈的时候,i386只复制了内核堆栈最栈底的那一部分内容,也就是系统调用压栈的过程,int

0x80指令(CPU自动)和SAVE_ALL压到内核堆栈里的内容

为什么子进程的fork返回0,这里就是原因!

返回值存放在eax,pid=0就是在这赋值的。因为子进程的返回值是0,所以拷贝完还需要修改一下内核堆栈里压入的返回值

是传递给copy_thread的第二个参数

包括栈底的数据

调度到子进程时的第一条指令地址

赋值thread.ip的内容为ret_from_fork,子进程得到进程调度,得到CPU的时候,是从这个位置开始执行的

entry_32.S

/linux-3.18.6/arch/x86/kernel/entry_32.S

系统调用总控程序,找到ret_from_fork

syscall_exit

493pushl_cfi �x

# save orig_eax

494SAVE_ALL

// 这里进行SAVE_ALL

501syscall_call:

// 这里进行system_call

502call *sys_call_table(,�x,4)503syscall_after_call:// 这里call返回了,返回到内核堆栈

// 也就是内核堆栈怎么压栈,它就又怎么出来了504movl �x,PT_EAX(%esp)

# store the return value

505syscall_exit:

call返回,返回到内核堆栈,也就是内核堆栈怎么压栈,它就又怎么出来了,所以到syscall_exit的时候,实际上和sys_call之前它的堆栈状态是一样的

所以ret_from_fork跳到syscall_exit来,就可以继续往下执行,就可以正常地返回到用户态

也就是说当子进程获得CPU控制权,开始运行的时候,它的ret_from_fork可以把后面的堆栈出栈出栈,从iret返回到用户态,这时候返回到用户态,就不是原来父进程的进程空间了,而是子进程的进程空间了

4.

总结

系统允许一个进程创建新进程,新进程即为子进程,子进程还可以创建新的子进程。形成进程树型结构。整个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进程

每个进程都有自己

的堆栈,内核在创建一个新的进程时,在创建进程控制块task_struct的同时,也为进程创建自己堆栈。一个进程有2个堆栈,用户堆栈和系统堆栈:用户堆栈的空间指向用户地址空间。内核堆栈的空间指向内核地址空间。

子进程是从ret_ from_ fork开始执行的。子进程在进入sys_

call之前与父进程的堆栈状态相同,ret_ from_ fork中跳转到了syscall_

exit

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值