linux 新进程的开创

我所阅读的内核版本是0.11,所以理解进程的创建对于理解操作系统的原理是非常重要的,进程是动态的,每一个进程只有一个父进程,在Linux系统中fork()通过调用clone系统调用实现其功能,而clone()是通过调用do,vfork和clone的系统调用的入口地址分别是sys,vfork和sys

图片 19

1、引发0×80中断

进程1是由进度0通过fork()创制的,此中的fork代码如下:

init/main.c

#define _syscall0(type,name) /   
type name(void) /  
{ /  
long __res; /  
__asm__ volatile ( "int $0x80" /    // 调用系统中断0x80。   
:"=a" (__res) /     // 返回值??eax(__res)。   
:"0" (__NR_##name)); /           // 输入为系统中断调用号__NR_name。   
      if (__res >= 0) /      // 如果返回值>=0,则直接返回该值。   
      return (type) __res; errno = -__res; /    // 否则置出错号,并返回-1。   
      return -1;}

这么使用int 0×80中断,调用sys_fork系统调用来制程。

一、背景知识:

1、进程与程序的涉嫌:

进程是动态的,而前后相继是静态的;从组织上看,各类进度的实体都是由代码断和对应的数量段两部分组成的,那与程序的意义很周围;一个进度能够提到七个程序的推行,一个前后相继也足以对应三个进程,即多少个程序段可在不一致数额集结上运转,构成差别的进程;并发性;进度具备创建别的进度的成效;操作系统中的每贰个进程都是在三个经过现场中运转的。

linux中客商进程是由fork系统调用创设的。Computer的内部存款和储蓄器、CPU
等能源都是由操作系统来分配的,而操作系统在分配财富时,大大多状态下是以进程为个人的。

每三个进度只有二个父进度,然而两个父进程却得以有多个子进度,当进度创立时,操作系统会给子进度成立新之处空间,并把父进程的地方空间的照射复制到子进度的地点空间去;父进度和子进程分享只读数据和代码段,不过酒馆和堆是分开的。

2、进度的结合:

进程调节块代码数据

进度的代码和数量由程序提供,而经过调节块则是由操作系统提供。

3、进度调节块的三结合:

进程标志符进度上下文意况经过调解消息经过调控音讯

进度标志符:

进度ID进程名经过宗族关系有所该进度的顾客标志

进程的上下文景况:(首要指进程运营时CPU的各存放器的从头到尾的经过)

通用贮存器程序状态在寄存器饭馆指针寄放器指令指针贮存器标记寄放器等

经过调治音信:

经过的气象进程的调治计策进度的预先级进度的运维睡眠时间经过的隔开原因进度的队列指针等

当过程处于不一样的景况时,会被置于不相同的体系中。

进度调节新闻:

进度的代码、数据、酒馆的最早地址进度的财富支配(进度的内部存款和储蓄器描述符、文件描述符、复信号描述符、IPC描述符等)

进度使用的具备能源都会在PCB中汇报。

进度创建时,内核为其分配PCB块,当进度乞求财富时内核会将相应的财富描述消息到场到进度的PCB中,进度退出时内核会释放PCB块。平时来讲进度退出时应当释放它申请的能源,如文件呈报符等。为了堤防进程遗忘有些能源(或是某个恶意进度)进而导致能源泄漏,内核平日会依靠PCB中的音讯回笼进度使用过的能源。

4、task_struct 在内部存款和储蓄器中的存款和储蓄:

在linux中经过调节块定义为task_struct, 下图为task_struct的首要性成员:

图片 1

在2.6原先的基石中,各类进程的task_struct存放在他们内核栈的尾端。那样做是为了让那么些像X86那样存放器超级少的硬件系统构造只要经过栈指针就能够猜度出它的职责,而幸免采纳额外的寄放器来极度记录。由于未来使用slab分配器动态生成task_struct,所以只需在栈底或栈顶创造一个新的结果struct
thread_info(在文件 asm/thread_info.h中定义)
struct thread_info{
struct task_struct *task;
struct exec_domain *exec_domain;
__u32 flags;
__u32 status;
__u32 cpu;
int preempt_count;
mm_segment addr_limit;
struct restart_block restart_block;
void *sysenter_return;
int uaccess_err;
};

图片 2

5、fork()、vfork()的联系:

Fork(卡塔尔在2.6版本的水源中Linux通过clone(卡塔尔系统调用达成fork(State of Qatar。那个系统调用通过一多级的参数标识来指明父、子进度须要分享的财富。Fork(卡塔尔(قطر‎、vfork(卡塔尔(قطر‎和库函数都遵照各自须要的参数标识去调用clone(卡塔尔国,然后由clone(State of Qatar去调用do_fork().
do_fork(卡塔尔实现了创设中的超越八分之四干活,它的定义在kernel/fork.c文件中。该函数调用copy_process()函数,然后经过初步运维。Copy_process(卡塔尔(قطر‎函数完结的办事很风趣:
1)、调用dup_task_struct(卡塔尔国为新历程创制一个根本仓库、thread_info结构和task_struct布局,这几个值与眼前路程的值完全相通。那时子进程和父进度的汇报符是完全相仿的。
2)、检查并确定保障新创立那些子进度后,当前顾客所享有的历程数目未有超越给他分配的能源的界定。
3)、子进度早先是计出万全与父进度不相同开来。进度描述符内的无数成员变量都要被清零或设为开始值。那么些不是后续而来的进度描述符成员,首假若总结音信。Task_struc中的大好些个据都照样未被改变。
4)、子进度的意况棉被服装置为TASK_UNINTLX570RUPTIBLE,以保障它不会被投入运作。
5)、copy_process()调用copy_flags()以更新task_struct
的flags成员。表明进度是还是不是具备最好客户权限的PF_SUPERPENCOREIV标记被清0.标记进度还还未调用exec(卡塔尔函数的PF_FOPRADOKNOEXEC标记棉被服装置。
6)、调用alloc_pid(卡塔尔为新进度分配一个得力的PID。
7)、依照传递给clone(卡塔尔(قطر‎的参数标识,copy_process(卡塔尔拷贝或分享展开的文书、文件系统消息、时限信号管理函数、进度地址空间和命名空间等。在相通情状下,这个能源会被给定进程的有所线程分享;不然,这一个财富对每一种进度是差别的之所以被拷贝到这里。
8)、最后copy_process(卡塔尔(قطر‎做甘休职业并赶回二个指向子进程的指针。
在回到do_fork()函数,如果copy_process(卡塔尔函数成功重回,新成立的子进度被唤醒并让其投入运营。内核有意接受子进度首先实施(即使连年想子进度先运维,可是绝不总能如此)。因为日常子进度都会马上调用exec(State of Qatar函数,那样能够制止写时拷贝(copy-on-write)的额外成本,假使父进度首先实施的话,有希望会起来向地点空间写入。
Vfork(卡塔尔国除了不拷贝父进度的页表项外vfork(卡塔尔和fork(卡塔尔国的功用肖似。子进度作为父进程的二个单独的线程在它的地点空间里运维,父进度被拥塞,直到子进程退出或实施exec(卡塔尔国。子过程无法向地方空间写入(在并没有贯彻写时拷贝的linux版本中,这一优化是很有用的)。

do_fork() –> clone() –> fork() 、vfork() 、__clone()
—–>exec()

clone(卡塔尔函数的参数及其意思如下:

CLONE_FILES 老爹和儿子进程分享展开的文本
CLONE_FS 父子进程共享文件系统音信
CLONE_IDLETASK 将PID设置为0(只供idle进程使用)
CLONE_NEWNS 为子进度创制新的命名空间
CLONE_PARENT 钦点子进度与父进度具有同多个父进度
CLONE_PTRACE 继续调节和测量检验子进程
CLONE_SETTID 将TID写回到顾客空间
CLONE_SETTLS 为子进度创设新的TLS
CLONE_SIGHAND 老爹和儿子进度分享随机信号管理函数以致被阻断的非能量信号
CLONE_SYSVSEM 父亲和儿子进度分享System V SEM_UNDO语义
CLONE_THREAD 老爹和儿子进度放进相似的进度组
CLONE_VFO科雷傲K 调用Vfork(卡塔尔,所以父进度打算就寝等待子进度将其唤醒
CLONE_UNTRACED 制止追踪进度在子进度上强逼试行CLONE_PTRACE
CLONE_STOP 以TASK_SROPPED状态起头试行
CLONE_SETTLS 为子进度创立新的TLS(thread-local storage)
CLONE_CHILD_CLEARTID 清除子进度的TID
CLONE_CHILD_SETTID 设置子进程的TID
CLONE_PARENT_SETTID 设置父进度的TID

CLONE_VM 老爹和儿子进度分享地址空间

二、GDB追踪fork(State of Qatar系统调用。

GDB 调节和测量检验的相干内容能够参谋:GDB追踪内核运维 篇
这里不再占用过多篇幅赘述。上面先直接上海体育场所,在详细分析代码的运作进度。

图片 3

起步GDB后各自在sys_clone、do_fork、copy_process、copy_thread、ret_from_fork、syscall_exit等职责设置好断点,见证fork(State of Qatar函数的执行进度(运转条件与GDB追踪内核运行篇别无二样)

图片 4

能够看见,当我们在menuos中运作fork
命令的时候,内核会先调用clone,在sys_clone 断点处停下来了。

图片 5

在调用sys_clone(State of Qatar后,内核依照分化的参数去调用do_fork(卡塔尔系统调用。步向do_fork(卡塔尔(قطر‎后就去又运营了copy_process().

图片 6

图片 7

在copy_process(State of Qatar 中又运行了copy_thread(卡塔尔(قطر‎,然后跳转到了ret_from_fork
处运营一段汇编代码,再然后就跳到了syscall_exit(这是在arch/x86/kernel/entry_32.S中的多少个标号,是施行系统调用后用于退出根本空间的汇编制程序序。),

图片 8

能够看到,GDB追踪到syscall_exit 后就无法持续追踪了……………..

三、代码解析(3.18.6版本的根本)

在3.18.6版本的内核 kernel/fork.c文件中:

#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
#ifdef __ARCH_WANT_SYS_VFORK
SYSCALL_DEFINE0(vfork)
{
return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, 0, 0, NULL,
NULL);

}
#endif
#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(State of Qatar、vfork(卡塔尔国、clone()的概念能够见见,三者都以依赖不一致的境况传递差别的参数直接调用了do_fork(卡塔尔(قطر‎函数,去掉了中间环节clone()。

进入do_fork 后:

图片 9

在do_fork中第一是对参数做了汪洋的参数检查,然后就进行就实行copy_process将父进度的PCB复制一份到子进度,作为子进度的PCB,再然后依照copy_process的回来值P判定进度PCB复制是还是不是成功,假若成功就先唤醒子进程,让子进程就绪希图运维。

所以在do_fork中最器重的也便是copy_process(卡塔尔(قطر‎了,它做到了子进度PCB的复制与开头化操作。上边就进来copy_process中看看内核是如何完结的:

图片 10

先从完整上看一下,发现,copy_process中开始一部分的代码相通是参数的检查和依靠分裂的参数执行一些连锁的操作,然后创制了一个职分,接着dup_task_struct(current卡塔尔国将前段时间经过的task_struct
复制了一份,并将新的task_struct地址作为指针重临!

图片 11

在dup_task_struct中为子进度创建了多少个task_struct结构体、一个thread_info
构造体,并实行了简要的初始化,不过这是子进度的task_struct如故空的所以接下去的上游一部鲜明是要将老爹和儿子进程task_struct中相同的有个别从父进度拷贝到子进度,然后差异的有的再在子进度中张开最先化。

说起底面包车型地铁一有些则是,现身各个错误后的退出口。

上面来看一下中间那部分:如何将父亲和儿子进度相似的、分化的局地界别开来。

图片 12

能够观察,内核先是将父进度的stask_struct中的内容不管三七四十三全都拷贝到子进度的stask_struct中了(那当中山大学部分的剧情都以和父进度相符,唯有少部分根据参数的不等稍作改革),种种模块拷贝停止后都举行了对应的检查,看是或不是拷贝成功,借使退步就跳到相应的出口处推行复苏操作。最终又举办了贰个copy_thread(),

图片 13

在copy_thread那个函数中做了两件十二分主要的事情:1、便是把子进程的 eax
赋值为 0,
childregs->ax = 0,使得 fork 在子进度中回到
0;2、将子进度唤醒后实行的首先条指令定向到
ret_from_fork。所以那边能够见到子进度的进行从ret_from_fork开始。

借来继续看copy_process中的代码。拷贝完父进程中的内容后,将在对子进度展开“特性化”,

图片 14

从代码也足以看出,这里是对子进度中的其余成员进程早先化操作。然后就退出了copy_process,回到了do_fork()中。

再跟着看一下do_fork(卡塔尔国中“扫尾“工作是如何是好的:

图片 15

眼下植依照参数做一些变量的改变,前边五个操作比较根本,如若是通过fork(State of Qatar创立子进度,那么最终就一向将子进度唤醒,可是假如是经过vfork(卡塔尔(قطر‎来创造子进度,那么快要通告父进程必得等子进度运转甘休技巧在这里在此之前运转。

总结:

总结:内核在开创贰个新进度的时候,首要实践了一晃义务:

1、父进度试行多个系统调用fork(卡塔尔国或vfork(卡塔尔(قطر‎;但最终都以经过调用do_fork(卡塔尔(قطر‎函数来操作,只可是fork(卡塔尔,vfork(卡塔尔传递给do_fork(卡塔尔的参数不一样。

2、在do_fork(卡塔尔函数中,前面做参数检查,前面担负唤醒子进度(若是是vfork则让父进度等待),中间有个别担当创制子进度和子进度的PCB的带头化,这么些专业都在copy_process()中完成。

3、在copy_process(卡塔尔中先是例行的参数检查和依赖参数实行安插;然后是调用大批量的copy_*****
函数将父进度task_struct中的内容拷贝到子进度的task_struct中,然后对于子进度与父进度之间不等的地点,在子进度中初阶化或是清零。

4、完毕子进度的成立和开端化后,将子进度唤醒,优先让子进度先运维,因为假诺让父进程先运维以来,由于linux的写时拷贝机制,父进度超大概会对数码举办写操作,这个时候就须要拷贝数据段和代码断的开始和结果了,但借使先实行子进度来讲,子进度经常都会通过exec(卡塔尔(قطر‎转去推行此外的任务,直接将新职分的数据和代码拷过来就能够了,而无需像前边那么先把父进程的数目代码拷过来,然后拷新职责的代码的时候又将其遮住掉。

5、执行完copy_process(卡塔尔后就回来了do_fork(卡塔尔中,接着父进度回到system_call中执行syscall_exit:
后边的代码,而子进度则先从ret_from_fork:
处开始施行,然后在回去system_call 中去执行syscall_exit:.

ENTRY(ret_from_fork)
CFI_STARTPROC
pushl_cfi %eax
call schedule_tail
GET_THREAD_INFO(%ebp)
popl_cfi %eax
pushl_cfi $0x0202 # Reset kernel eflags
popfl_cfi
jmp syscall_exit
CFI_ENDPROC
END(ret_from_fork)

6、父进度和子进程最终都以由此system_call
的说道从水源空间回到客商空间,回到顾客空间后,由于fork(卡塔尔国函数对父亲和儿子进度的再次回到值分歧,所以依附重临值剖断出回来的是父进程照旧子进度,然后分别推行不一的操作。

图片 16

新历程的创始 一、背景知识:
1、进度与程序的关联:
进度是动态的,而前后相继是静态的;从组织上看,每一个进程的实业都以由代码断和…

fork函数成立新历程经过分析

在Linux系统中fork(State of Qatar通过调用clone系统调用完毕其功用,而clone(State of Qatar是透过调用do_fork()实现的。

do_fork(卡塔尔定义在kernel/fork.c文件中。
该函数调用copy_process(State of Qatar开首创办新进程。工作进度如下:

1.调用dup_task_struct(卡塔尔国为新进度创建二个内核栈、thread_info结构和task_struct(PCB),这么些值与当下历程的值相同。那个时候,子进程和父进度的陈说符是完全相同的。

2.检查并保管新创立那些子进度后,当前客商所全体的进度数目未有超过给它分配的能源的节制。

3.子进度先河使和煦与父进度差距开来。进度描述符内的浩大分子都要被清0或设为伊始值。那二个不是三番两次而来的历程描述符成员,首假诺总括音信。task_struct中的大许多数目都依旧未被改造。

4.子进程的情形棉被服装置为TASK_UNINTEOdysseyRUPTINLE,以确认保障它不会投入运作。(注:TASK_UNINTE君越RUPTIBLE使进程只可以被wake_up(State of Qatar唤醒,即等待情形。等待状态不行被功率信号毁灭。)

5.copy_process()调用copy_flags()以更新task_struct的flags成员。注解进度是不是持有最棒顾客权限的PE_SUPERP凯雷德IV标识被清0。申明进度还还没调用exec(卡塔尔的函数的PF_FOWranglerKNOEXEC标识被设置。

6.调用alloc_pid(State of Qatar为新进度分配贰个立竿见影的PID。

7.根据传递给clone(卡塔尔的参数标记,copy_process(State of Qatar拷贝或共享展开文件、文件系统新闻、非信号管理函数、进度地址空间和命名空间等。在平日景观下,这几个财富会被给定进度的拥有线程分享;不然,那一个能源对各种进程是莫衷一是的,因而被拷贝到这里。

8.最后,copy_process(卡塔尔(قطر‎做得了职业并重临一个指向子进程的指针。

回到do_fork函数,如果copy_process(卡塔尔国函数成功再次来到,新创造的子进度被唤醒并让其投运。

sys_fork的实现


不等类别布局下的fork完结sys_fork重固然通过申明会集区分,
在大多数连串布局上, 标准的fork实现格局与如下

前期达成

架构 实现
arm arch/arm/kernel/sys_arm.c, line 239
i386 arch/i386/kernel/process.c, line 710
x86_64 arch/x86_64/kernel/process.c, line 706
asmlinkage long sys_fork(struct pt_regs regs)
{
    return do_fork(SIGCHLD, regs.rsp, &regs, 0);
}

 

新版本

#ifdef __ARCH_WANT_SYS_FORK
SYSCALL_DEFINE0(fork)
{
#ifdef CONFIG_MMU
        return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
#else
        /* can not support in nommu mode */
        return -EINVAL;
#endif
}
#endif

 

咱俩得以见见独一利用的标识是SIGCHLD。那象征在子进度终止后将发送实信号SIGCHLD频限信号公告父进度,

鉴于写时复制(COW卡塔尔(قطر‎技巧, 最早老爹和儿子进度的栈地址相通,
但是只要操作栈地址闭并写入数据,
则COW机制会为各种进程分别创设一个新的栈别本

如果do_fork成功, 则新建进程的pid作为系统调用的结果回到, 否则赶回错误码

二、进度的创制

系统中的进度是由父进度调用fork(卡塔尔函数来创立的,那么调用fork()函数的时候到底会发出怎么着吗?

linux 新历程的创导

子进度是从哪开头实践的?

当实施到

p->thread.ip = (unsigned long) ret_from_fork;
//调节到子进度时的首先条指令地址。

时,即子过程得到CPU时它从这些地方上马施行的。

而实践那条语句

*childregs = *current_pt_regs(卡塔尔; //复制内核仓库

保障了新进程的实践起源和基本货仓的一致性。
日常来讲图gdb追踪所示。

图片 17

关于do_fork和_do_frok


The commit 3033f14ab78c32687 (“clone: support passing tls argument via
C
rather than pt_regs magic”) introduced _do_fork() that allowed to
pass
@tls parameter.

参见

linux2.5.32以后, 添加了TLS(Thread Local Storage)机制,
clone的标识CLONE_SETTLS接纳二个参数来安装线程的地面存款和储蓄区。sys_clone也由此扩张了几个int参数来传播相应的点tls_val。sys_clone通过do_fork来调用copy_process达成进度的复制,它调用特定的copy_thread和copy_thread把相应的种类调用参数从pt_regs贮存器列表中提收取来,可是会变成意外的情事。

only one code path into copy_thread can pass the CLONE_SETTLS flag,
and
that code path comes from sys_clone with its architecture-specific
argument-passing order.

日前大家说了,
在贯彻函数调用的时候,小编iosys_clone等将一定种类构造的参数从贮存器中领收取来,
然后到达do_fork那步的时候已经应该是系统构造非亲非故了,
不过咱们sys_clone须求设置的CLONE_SETTLS的tls仍为个依据与系统构造的参数,
这里就能现身难题。

因此linux-4.2之后选取引进三个新的CONFIG_HAVE_COPY_THREAD_TLS,和二个新的COPY_THREAD_TLS接受TLS参数为
额外的长整型(系统调用参数大小)的争论。改造sys_clone的TLS参数unsigned
long,并传递到copy_thread_tls。

/* http://lxr.free-electrons.com/source/include/linux/sched.h?v=4.5#L2646  */
extern long _do_fork(unsigned long, unsigned long, unsigned long, int __user *, int __user *, unsigned long);
extern long do_fork(unsigned long, unsigned long, unsigned long, int __user *, int __user *);


/* linux2.5.32以后, 添加了TLS(Thread Local Storage)机制, 
    在最新的linux-4.2中添加了对CLONE_SETTLS 的支持 
    底层的_do_fork实现了对其的支持, 
    dansh*/
#ifndef CONFIG_HAVE_COPY_THREAD_TLS
/* For compatibility with architectures that call do_fork directly rather than
 * using the syscall entry points below. */
long do_fork(unsigned long clone_flags,
              unsigned long stack_start,
              unsigned long stack_size,
              int __user *parent_tidptr,
              int __user *child_tidptr)
{
        return _do_fork(clone_flags, stack_start, stack_size,
                        parent_tidptr, child_tidptr, 0);
}
#endif

 

咱俩会发掘,新本子的种类中clone的TLS设置标记会通过TLS参数字传送递,
因而_do_fork取代了老版本的do_fork。

老版本的do_fork只有在如下情状才会定义

  • 只有当系统不帮助通过TLS参数通过参数字传送递而是接纳pt_regs贮存器列表传递时

  • 未定义CONFIG_HAVE_COPY_THREAD_TLS宏

参数 描述
clone_flags 与clone()参数flags相同, 用来控制进程复制过的一些属性信息, 描述你需要从父进程继承那些资源。该标志位的4个字节分为两部分。最低的一个字节为子进程结束时发送给父进程的信号代码,通常为SIGCHLD;剩余的三个字节则是各种clone标志的组合(本文所涉及的标志含义详见下表),也就是若干个标志之间的或运算。通过clone标志可以有选择的对父进程的资源进行复制;
stack_start 与clone()参数stack_start相同, 子进程用户态堆栈的地址
regs 是一个指向了寄存器集合的指针, 其中以原始形式, 保存了调用的参数, 该参数使用的数据类型是特定体系结构的struct pt_regs,其中按照系统调用执行时寄存器在内核栈上的存储顺序, 保存了所有的寄存器, 即指向内核态堆栈通用寄存器值的指针,通用寄存器的值是在从用户态切换到内核态时被保存到内核态堆栈中的(指向pt_regs结构体的指针。当系统发生系统调用,即用户进程从用户态切换到内核态时,该结构体保存通用寄存器中的值,并被存放于内核态的堆栈中)
stack_size 用户状态下栈的大小, 该参数通常是不必要的, 总被设置为0
parent_tidptr 与clone的ptid参数相同, 父进程在用户态下pid的地址,该参数在CLONE_PARENT_SETTID标志被设定时有意义
child_tidptr 与clone的ctid参数相同, 子进程在用户太下pid的地址,该参数在CLONE_CHILD_SETTID标志被设定时有意义

其中clone_flags如下表所示

图片 18

4、copy_process()

得到进度号同时将有个别存放器的值压栈后,最先实践copy_process(卡塔尔国,该函数根本承当以下的开始和结果。

  • 为子进度成立task_struct,将父进程的task_struct复制给子进度。
  • 为子进程的task_struct,tss做天性化设置。
  • 为子进度创设第三个页表,也将父进程的页表内容赋给那个页表。
  • 子进度分享父进度的文书。
  • 设置子进度的GDT项。
  • 末尾将子进度设置为安妥状态,使其能够加入进程间的滚动。

int  copy_process (int nr, long ebp, long edi, long esi, long gs, long none,  
          long ebx, long ecx, long edx,  
          long fs, long es, long ds,  
          long eip, long cs, long eflags, long esp, long ss)  
{  
  struct task_struct *p;  
  int i;  
  struct file *f;  

  p = (struct task_struct *) get_free_page ();  // 为新任务数据结构分配内存。  
  if (!p)           // 如果内存分配出错,则返回出错码并退出。  
    return -EAGAIN;  
  task[nr] = p;         // 将新任务结构指针放入任务数组中。  
// 其中nr 为任务号,由前面find_empty_process()返回。  
  *p = *current;        /* NOTE! this doesn't copy the supervisor stack */  
/* 注意!这样做不会复制超级用户的堆栈 */ (只复制当前进程内容)。  
    p->state = TASK_UNINTERRUPTIBLE; // 将新进程的状态先置为不可中断等待状态。  
  p->pid = last_pid;     // 新进程号。由前面调用find_empty_process()得到。  
  p->father = current->pid;   // 设置父进程号。  
  p->counter = p->priority;  
  p->signal = 0;     // 信号位图置0。  
  p->alarm = 0;  
  p->leader = 0;     /* process leadership doesn't inherit */  
/* 进程的领导权是不能继承的 */  
  p->utime = p->stime = 0;    // 初始化用户态时间和核心态时间。  
  p->cutime = p->cstime = 0;  // 初始化子进程用户态和核心态时间。  
  p->start_time = jiffies;   // 当前滴答数时间。  
// 以下设置任务状态段TSS 所需的数据(参见列表后说明)。  
  p->tss.back_link = 0;  
  p->tss.esp0 = PAGE_SIZE + (long) p;    // 堆栈指针(由于是给任务结构p 分配了1 页  
// 新内存,所以此时esp0 正好指向该页顶端)。  
  p->tss.ss0 = 0x10;     // 堆栈段选择符(内核数据段)[??]。  
  p->tss.eip = eip;      // 指令代码指针。  
  p->tss.eflags = eflags;    // 标志寄存器。  
  p->tss.eax = 0;  
  p->tss.ecx = ecx;  
  p->tss.edx = edx;  
  p->tss.ebx = ebx;  
  p->tss.esp = esp;  
  p->tss.ebp = ebp;  
  p->tss.esi = esi;  
  p->tss.edi = edi;  
  p->tss.es = es & 0xffff;   // 段寄存器仅16 位有效。  
  p->tss.cs = cs & 0xffff;  
  p->tss.ss = ss & 0xffff;  
  p->tss.ds = ds & 0xffff;  
  p->tss.fs = fs & 0xffff;  
  p->tss.gs = gs & 0xffff;  
  p->tss.ldt = _LDT (nr);    // 该新任务nr 的局部描述符表选择符(LDT 的描述符在GDT 中)。  
  p->tss.trace_bitmap = 0x80000000;   
// 如果当前任务使用了协处理器,就保存其上下文。  
    if (last_task_used_math == current)  
    __asm__ ("clts ; fnsave %0"::"m" (p->tss.i387));  
// 设置新任务的代码和数据段基址、限长并复制页表。如果出错(返回值不是0),则复位任务数组中  
// 相应项并释放为该新任务分配的内存页。  
  if (copy_mem (nr, p))  
    {               // 返回不为0 表示出错。  
      task[nr] = NULL;  
      free_page ((long) p);  
      return -EAGAIN;  
    }  
// 如果父进程中有文件是打开的,则将对应文件的打开次数增1。  
  for (i = 0; i < NR_OPEN; i++)  
    if (f = p->filp[i])  
      f->f_count++;  
// 将当前进程(父进程)的pwd, root 和executable 引用次数均增1。  
  if (current->pwd)  
    current->pwd->i_count++;  
  if (current->root)  
    current->root->i_count++;  
  if (current->executable)  
    current->executable->i_count++;  
// 在GDT 中设置新任务的TSS 和LDT 描述符项,数据从task 结构中取。  
// 在任务切换时,任务寄存器tr 由CPU 自动加载。  
  set_tss_desc (gdt + (nr << 1) + FIRST_TSS_ENTRY, &(p->tss));  
  set_ldt_desc (gdt + (nr << 1) + FIRST_LDT_ENTRY, &(p->ldt));  
  p->state = TASK_RUNNING;   /* do this last, just in case */  
/* 最后再将新任务设置成可运行状态,以防万一 */  
  return last_pid;      // 返回新进程号(与任务号是不同的)。  
}

进入copy_prossess函数后,调用get_free_page()函数,在主内部存储器申请三个空闲页面,并将报名到的页面清0。将以此页面包车型客车指针免强类型转形成task_struct类型的指针,并挂接在task[nr]上,nr就是在find_empty_process中回到的职务号。

接下去的*p=*current将近期经过的指针赋给了子进程的,相当于说子进度继续了父进度一些至关心器重要的质量,当然那是相当不足的,所以接下去的一大堆代码都以为子进度做天性化设置的。

平日来说,每一个过程都要加载归属本人的代码、数据,所以copy_process设置子进度的内部存储器地址。通过copy_mem来安装新职分的代码和数据段基址、限长并复制页表。

int copy_mem (int nr, struct task_struct *p)  
{  
  unsigned long old_data_base, new_data_base, data_limit;  
  unsigned long old_code_base, new_code_base, code_limit;  

  code_limit = get_limit (0x0f);    // 取局部描述符表中代码段描述符项中段限长。  
  data_limit = get_limit (0x17);    // 取局部描述符表中数据段描述符项中段限长。  
  old_code_base = get_base (current->ldt[1]);    // 取原代码段基址。  
  old_data_base = get_base (current->ldt[2]);    // 取原数据段基址。  
  if (old_data_base != old_code_base)   // 0.11 版不支持代码和数据段分立的情况。  
    panic ("We don't support separate I&D");  
  if (data_limit < code_limit)   // 如果数据段长度 < 代码段长度也不对。  
    panic ("Bad data_limit");  
  new_data_base = new_code_base = nr * 0x4000000;   // 新基址=任务号*64Mb(任务大小)。  
  p->start_code = new_code_base;  
  set_base (p->ldt[1], new_code_base);   // 设置代码段描述符中基址域。  
  set_base (p->ldt[2], new_data_base);   // 设置数据段描述符中基址域。  
  if (copy_page_tables (old_data_base, new_data_base, data_limit))  
    {               // 复制代码和数据段。  
      free_page_tables (new_data_base, data_limit); // 如果出错则释放申请的内存。  
      return -ENOMEM;  
    }  
  return 0;  
}

下一场是对文件,pwd等能源的更换,接着要设置子进度在GDT中的表项,最终将经过设置为妥贴状态,并回到进程号。

参谋资料

《Linux内核设计与贯彻》原书第三版

Sawoom原创小说转发请评释出处
《Linux内核解析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

转自:

3、find_empty_process()

// 为新进程取得不重复的进程号last_pid,并返回在任务数组中的任务号(数组index)。  
int  find_empty_process (void)  
{  
  int i;  

repeat:  
  if ((++last_pid) < 0)  
    last_pid = 1;  
  for (i = 0; i < NR_TASKS; i++)  
    if (task[i] && task[i]->pid == last_pid)  
      goto repeat;  
  for (i = 1; i < NR_TASKS; i++) // 任务0 排除在外。  
    if (!task[i])  
      return i;  
  return -EAGAIN;  
}

find_empty_process的效率正是为所要创制的进程分配一个进程号。在基本中用全局变量last_pid来贮存在系统自开机以来累积的经过数,也将此变量用作新建过程的进度号。内核第三遍遍历task[64],要是&&条件创设表达last_pid已经被其他进程使用了,所以++last_pid,直到获取到新的进度号。首回遍历task[64],获得第多个空闲的i,也等于任务号。因为在linux0.11中,最多允许同有时间实施六17个经过,所以一旦当前的历程已满,就能够回去-EAGAIN。

task_struct数据布局解析

struct task_struct {
    volatile long state;//进程运行状态。-1为等待状态,0为运行,>0为停止状态
    void *stack; //进程的内核堆栈
    atomic_t usage;
    unsigned int flags; //每个进程的标识符
    unsigned int ptrace;//进程跟踪标识符

#ifdef CONFIG_SMP //条件编译,即对处理时用到的代码
    struct llist_node wake_entry;
    int on_cpu;
    struct task_struct *last_wakee;
    unsigned long wakee_flips;
    unsigned long wakee_flip_decay_ts;

    int wake_cpu;
#endif
    /*运行队列和进程调度相关的代码*/
    int on_rq;  

    int prio, static_prio, normal_prio;
    unsigned int rt_priority;
    const struct sched_class *sched_class;
    struct sched_entity se;
    struct sched_rt_entity rt;
#ifdef CONFIG_CGROUP_SCHED
    struct task_group *sched_task_group;
#endif
    struct sched_dl_entity dl;

#ifdef CONFIG_PREEMPT_NOTIFIERS
    /* list of struct preempt_notifier: */
    struct hlist_head preempt_notifiers;
#endif

#ifdef CONFIG_BLK_DEV_IO_TRACE
    unsigned int btrace_seq;
#endif

    unsigned int policy;
    int nr_cpus_allowed;
    cpumask_t cpus_allowed;

#ifdef CONFIG_PREEMPT_RCU
    int rcu_read_lock_nesting;
    union rcu_special rcu_read_unlock_special;
    struct list_head rcu_node_entry;
#endif /* #ifdef CONFIG_PREEMPT_RCU */
#ifdef CONFIG_TREE_PREEMPT_RCU
    struct rcu_node *rcu_blocked_node;
#endif /* #ifdef CONFIG_TREE_PREEMPT_RCU */
#ifdef CONFIG_TASKS_RCU
    unsigned long rcu_tasks_nvcsw;
    bool rcu_tasks_holdout;
    struct list_head rcu_tasks_holdout_list;
    int rcu_tasks_idle_cpu;
#endif /* #ifdef CONFIG_TASKS_RCU */

#if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT)
    struct sched_info sched_info;
#endif

    struct list_head tasks;  //进程的链表,将所有进程通过双向循环链表链接在一起。
#ifdef CONFIG_SMP
    struct plist_node pushable_tasks;
    struct rb_node pushable_dl_tasks;
#endif

    struct mm_struct *mm, *active_mm; //与进程的地址空间相关的数据结构
#ifdef CONFIG_COMPAT_BRK
    unsigned brk_randomized:1;
#endif
    /* per-thread vma caching */
    u32 vmacache_seqnum;
    struct vm_area_struct *vmacache[VMACACHE_SIZE];
#if defined(SPLIT_RSS_COUNTING)
    struct task_rss_stat    rss_stat;
#endif
/* task state */
    int exit_state;
    int exit_code, exit_signal;
    int pdeath_signal;  /*  The signal sent when the parent dies  */
    unsigned int jobctl;    /* JOBCTL_*, siglock protected */

    /* Used for emulating ABI behavior of previous Linux versions */
    unsigned int personality;

    unsigned in_execve:1;   /* Tell the LSMs that the process is doing an
                 * execve */
    unsigned in_iowait:1;

    /* Revert to default priority/policy when forking */
    unsigned sched_reset_on_fork:1;
    unsigned sched_contributes_to_load:1;

    unsigned long atomic_flags; /* Flags needing atomic access. */

    pid_t pid;  //进程标识符
    pid_t tgid; //进程标识符

#ifdef CONFIG_CC_STACKPROTECTOR
    /* Canary value for the -fstack-protector gcc feature */
    unsigned long stack_canary;
#endif
    /*
     * pointers to (original) parent process, youngest child, younger sibling,
     * older sibling, respectively.  (p->father can be replaced with
     * p->real_parent->pid)
     */
/*与进程父子关系有关的代码*/
    struct task_struct __rcu *real_parent; /* real parent process */
    struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */
    /*
     * children/sibling forms the list of my natural children
     */
    struct list_head children;  /* list of my children */
    struct list_head sibling;   /* linkage in my parent's children list */
    struct task_struct *group_leader;   /* threadgroup leader */

    /*
     * ptraced is the list of tasks this task is using ptrace on.
     * This includes both natural children and PTRACE_ATTACH targets.
     * p->ptrace_entry is p's link on the p->parent->ptraced list.
     */
    struct list_head ptraced;
    struct list_head ptrace_entry;

    /* PID/PID hash table linkage. */
    struct pid_link pids[PIDTYPE_MAX];
    struct list_head thread_group;
    struct list_head thread_node;

    struct completion *vfork_done;      /* for vfork() */
    int __user *set_child_tid;      /* CLONE_CHILD_SETTID */
    int __user *clear_child_tid;        /* CLONE_CHILD_CLEARTID */

    /*与时间相关的代码*/
    cputime_t utime, stime, utimescaled, stimescaled;
    cputime_t gtime;
#ifndef CONFIG_VIRT_CPU_ACCOUNTING_NATIVE
    struct cputime prev_cputime;
#endif
#ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN
    seqlock_t vtime_seqlock;
    unsigned long long vtime_snap;
    enum {
        VTIME_SLEEPING = 0,
        VTIME_USER,
        VTIME_SYS,
    } vtime_snap_whence;
#endif
    unsigned long nvcsw, nivcsw; /* context switch counts */
    u64 start_time;     /* monotonic time in nsec */
    u64 real_start_time;    /* boot based time in nsec */
/* mm fault and swap info: this can arguably be seen as either mm-specific or thread-specific */
    unsigned long min_flt, maj_flt;

    struct task_cputime cputime_expires;
    struct list_head cpu_timers[3];

/* process credentials */
    const struct cred __rcu *real_cred; /* objective and real subjective task
                     * credentials (COW) */
    const struct cred __rcu *cred;  /* effective (overridable) subjective task
                     * credentials (COW) */
    char comm[TASK_COMM_LEN]; /* executable name excluding path
                     - access with [gs]et_task_comm (which lock
                       it with task_lock())
                     - initialized normally by setup_new_exec */
/* file system info */
    int link_count, total_link_count;
#ifdef CONFIG_SYSVIPC
/* ipc stuff */
    struct sysv_sem sysvsem;
    struct sysv_shm sysvshm;
#endif
#ifdef CONFIG_DETECT_HUNG_TASK
/* hung task detection */
    unsigned long last_switch_count;
#endif
/* 与CPU有关的数据结构*/
    struct thread_struct thread;
/* filesystem information */
    struct fs_struct *fs;//与文件系统有关的数据结构
/* open file information */
    struct files_struct *files; //文件描述符
/* namespaces */
    struct nsproxy *nsproxy;
/* 与信号处理相关的数据结构 */
    struct signal_struct *signal;
    struct sighand_struct *sighand;

    sigset_t blocked, real_blocked;
    sigset_t saved_sigmask; /* restored if set_restore_sigmask() was used */
    struct sigpending pending;

    unsigned long sas_ss_sp;
    size_t sas_ss_size;
    int (*notifier)(void *priv);
    void *notifier_data;
    sigset_t *notifier_mask;
    struct callback_head *task_works;

    struct audit_context *audit_context;
#ifdef CONFIG_AUDITSYSCALL
    kuid_t loginuid;
    unsigned int sessionid;
#endif
    struct seccomp seccomp;

/* Thread group tracking */
    u32 parent_exec_id;
    u32 self_exec_id;
/* Protection of (de-)allocation: mm, files, fs, tty, keyrings, mems_allowed,
 * mempolicy */
    spinlock_t alloc_lock;

    /* Protection of the PI data structures: */
    raw_spinlock_t pi_lock;

#ifdef CONFIG_RT_MUTEXES  //互斥锁
    /* PI waiters blocked on a rt_mutex held by this task */
    struct rb_root pi_waiters;
    struct rb_node *pi_waiters_leftmost;
    /* Deadlock detection and priority inheritance handling */
    struct rt_mutex_waiter *pi_blocked_on;
#endif

#ifdef CONFIG_DEBUG_MUTEXES//互斥锁
    /* mutex deadlock detection */
    struct mutex_waiter *blocked_on;
#endif
#ifdef CONFIG_TRACE_IRQFLAGS //与调试相关的数据结构
    unsigned int irq_events;
    unsigned long hardirq_enable_ip;
    unsigned long hardirq_disable_ip;
    unsigned int hardirq_enable_event;
    unsigned int hardirq_disable_event;
    int hardirqs_enabled;
    int hardirq_context;
    unsigned long softirq_disable_ip;
    unsigned long softirq_enable_ip;
    unsigned int softirq_disable_event;
    unsigned int softirq_enable_event;
    int softirqs_enabled;
    int softirq_context;
#endif
#ifdef CONFIG_LOCKDEP
# define MAX_LOCK_DEPTH 48UL
    u64 curr_chain_key;
    int lockdep_depth;
    unsigned int lockdep_recursion;
    struct held_lock held_locks[MAX_LOCK_DEPTH];
    gfp_t lockdep_reclaim_gfp;
#endif

/* journalling filesystem info */
    void *journal_info;

/* stacked block device info */
    struct bio_list *bio_list;

#ifdef CONFIG_BLOCK
/* stack plugging */
    struct blk_plug *plug;
#endif

/* VM state */
    struct reclaim_state *reclaim_state;

    struct backing_dev_info *backing_dev_info;

    struct io_context *io_context;

    unsigned long ptrace_message;
    siginfo_t *last_siginfo; /* For ptrace use.  */
    struct task_io_accounting ioac;
#if defined(CONFIG_TASK_XACCT)
    u64 acct_rss_mem1;  /* accumulated rss usage */
    u64 acct_vm_mem1;   /* accumulated virtual memory usage */
    cputime_t acct_timexpd; /* stime + utime since last update */
#endif
#ifdef CONFIG_CPUSETS
    nodemask_t mems_allowed;    /* Protected by alloc_lock */
    seqcount_t mems_allowed_seq;    /* Seqence no to catch updates */
    int cpuset_mem_spread_rotor;
    int cpuset_slab_spread_rotor;
#endif
#ifdef CONFIG_CGROUPS
    /* Control Group info protected by css_set_lock */
    struct css_set __rcu *cgroups;
    /* cg_list protected by css_set_lock and tsk->alloc_lock */
    struct list_head cg_list;
#endif
#ifdef CONFIG_FUTEX
    struct robust_list_head __user *robust_list;
#ifdef CONFIG_COMPAT
    struct compat_robust_list_head __user *compat_robust_list;
#endif
    struct list_head pi_state_list;
    struct futex_pi_state *pi_state_cache;
#endif
#ifdef CONFIG_PERF_EVENTS
    struct perf_event_context *perf_event_ctxp[perf_nr_task_contexts];
    struct mutex perf_event_mutex;
    struct list_head perf_event_list;
#endif
#ifdef CONFIG_DEBUG_PREEMPT
    unsigned long preempt_disable_ip;
#endif
#ifdef CONFIG_NUMA
    struct mempolicy *mempolicy;    /* Protected by alloc_lock */
    short il_next;
    short pref_node_fork;
#endif
#ifdef CONFIG_NUMA_BALANCING
    int numa_scan_seq;
    unsigned int numa_scan_period;
    unsigned int numa_scan_period_max;
    int numa_preferred_nid;
    unsigned long numa_migrate_retry;
    u64 node_stamp;         /* migration stamp  */
    u64 last_task_numa_placement;
    u64 last_sum_exec_runtime;
    struct callback_head numa_work;

    struct list_head numa_entry;
    struct numa_group *numa_group;

    /*
     * Exponential decaying average of faults on a per-node basis.
     * Scheduling placement decisions are made based on the these counts.
     * The values remain static for the duration of a PTE scan
     */
    unsigned long *numa_faults_memory;
    unsigned long total_numa_faults;

    /*
     * numa_faults_buffer records faults per node during the current
     * scan window. When the scan completes, the counts in
     * numa_faults_memory decay and these values are copied.
     */
    unsigned long *numa_faults_buffer_memory;

    /*
     * Track the nodes the process was running on when a NUMA hinting
     * fault was incurred.
     */
    unsigned long *numa_faults_cpu;
    unsigned long *numa_faults_buffer_cpu;

    /*
     * numa_faults_locality tracks if faults recorded during the last
     * scan window were remote/local. The task scan period is adapted
     * based on the locality of the faults with different weights
     * depending on whether they were shared or private faults
     */
    unsigned long numa_faults_locality[2];

    unsigned long numa_pages_migrated;
#endif /* CONFIG_NUMA_BALANCING */

    struct rcu_head rcu;

    /*
     * 与管道相关的数据结构
     */
    struct pipe_inode_info *splice_pipe; 

    struct page_frag task_frag;

#ifdef  CONFIG_TASK_DELAY_ACCT
    struct task_delay_info *delays;
#endif
#ifdef CONFIG_FAULT_INJECTION
    int make_it_fail;
#endif
    /*
     * when (nr_dirtied >= nr_dirtied_pause), it's time to call
     * balance_dirty_pages() for some dirty throttling pause
     */
    int nr_dirtied;
    int nr_dirtied_pause;
    unsigned long dirty_paused_when; /* start of a write-and-pause period */

#ifdef CONFIG_LATENCYTOP
    int latency_record_count;
    struct latency_record latency_record[LT_SAVECOUNT];
#endif
    /*
     * time slack values; these are used to round up poll() and
     * select() etc timeout values. These are in nanoseconds.
     */
    unsigned long timer_slack_ns;
    unsigned long default_timer_slack_ns;

#ifdef CONFIG_FUNCTION_GRAPH_TRACER
    /* Index of current stored address in ret_stack */
    int curr_ret_stack;
    /* Stack of return addresses for return function tracing */
    struct ftrace_ret_stack *ret_stack;
    /* time stamp for last schedule */
    unsigned long long ftrace_timestamp;
    /*
     * Number of functions that haven't been traced
     * because of depth overrun.
     */
    atomic_t trace_overrun;
    /* Pause for the tracing */
    atomic_t tracing_graph_pause;
#endif
#ifdef CONFIG_TRACING
    /* state flags for use by tracers */
    unsigned long trace;
    /* bitmask and counter of trace recursion */
    unsigned long trace_recursion;
#endif /* CONFIG_TRACING */
#ifdef CONFIG_MEMCG /* memcg uses this to do batch job */
    unsigned int memcg_kmem_skip_account;
    struct memcg_oom_info {
        struct mem_cgroup *memcg;
        gfp_t gfp_mask;
        int order;
        unsigned int may_oom:1;
    } memcg_oom;
#endif
#ifdef CONFIG_UPROBES
    struct uprobe_task *utask;
#endif
#if defined(CONFIG_BCACHE) || defined(CONFIG_BCACHE_MODULE)
    unsigned int    sequential_io;
    unsigned int    sequential_io_avg;
#endif
};

总结


fork, vfork和clone的系统调用的入口地址分别是sys_fork,
sys_vfork和sys_clone, 而他们的概念是依附于系统布局的,
而他们最终都调用了_do_fork(linux-4.2在此之前的内核中是do_fork),在_do_fork中通过copy_process复制进度的信息,调用wake_up_new_task将子进程步向调解器中

fork系统调用对应的kernel函数是sys_fork,此函数轻巧的调用kernel函数_do_fork。五个简化版的_do_fork施行如下:

  1. copy_process(卡塔尔(قطر‎此函数会做fork的绝大好些个业务,它首要达成讲父进度的运转条件复制到新的子进度,例如实信号管理、文件陈述符和过程的代码数据等。

  2. wake_up_new_task(卡塔尔(قطر‎。计算此进度的优先级和别的调治参数,将新的进程步入到进程调治队列并设此进度为可被调整的,以往那么些进程可以被进程调整模块调解实施。

简化的copy_process()流程

  1. dup_task_struct(卡塔尔国。分配二个新的长河调节块,包括新进程在kernel中的仓库。新的进程序调控制块会复制父进度的进程序调节制块,然而因为各种进程都有一个kernel宾馆,新进程的仓库将被设置成新分配的仓库。

  2. 起头化一些新进度的计算音信,如此过程的运行时刻

  3. copy_semundo(卡塔尔国复制父进度的semaphore undo_list到子进程。

  4. copy_files()、copy_fs(卡塔尔。复制父进度文件系统相关的情状到子进度

  5. copy_sighand()、copy_signal(卡塔尔(قطر‎。复制父进度能量信号处理有关的条件到子进度。

  6. copy_mm(卡塔尔。复制父进度内部存款和储蓄器管理有关的条件到子进程,包罗页表、地址空间和代码数据。

  7. copy_thread()/copy_thread_tls。设置子进程的实市价况,如子进程运转时各CPU存放器的值、子进度的kernel栈的开场合址。

  8. sched_fork(State of Qatar。设置子进度调治相关的参数,即子进度的运转CPU、最早时间片长度和静态优先级等。

  9. 将子进度步入到全局的进度队列中

  10. 设置子进度的长河组ID和对话期ID等。

简轻便单的说,copy_process(State of Qatar便是将父进度的运作碰着复制到子进度并对一些子进度特定的条件做相应的调度。

别的应用程序使用系统调用exit(卡塔尔国来收尾一个进度,此系统调用接收八个退出原因代码,父进度可以动用wait(卡塔尔系统调用来博取此代码,进而知道子进程退出的由来。对应到kernel,此系统调用sys_exit_group(卡塔尔(قطر‎,它的为主流程如下:

  1. 将信号SIGKILL加入到其它线程的非实信号队列中,并提醒这么些线程。

  2. 此线程实施do_exit()来退出。

do_exit(卡塔尔国完毕线程退出的任务,其利害攸关功效是将线程占用的系统能源释放,do_exit(卡塔尔的主干流程如下:

  1. 将经过内部存款和储蓄器管理相关的资源自由

  2. 将经过ICP semaphore相关财富自由

  3. __exit_files()、__exit_fs(卡塔尔。将经过文件管理相关的财富自由。

  4. exit_thread(State of Qatar。只要目标是刑释平台相关的局地能源。

  5. exit_notify(State of Qatar。在Linux中经过退出时要将其脱离的由来告诉父进程,父过程调用wait(卡塔尔系统调用后会在叁个守候队列上睡觉。

  6. schedule(卡塔尔(قطر‎。调用进程调整器,因为此进度已经脱离,切换来其它进度。

进度的成立到实行进度如下图所示

图片 19

版权注解:本文为博主原创作品 && 转发请知名出处 @