《奔跑吧linux内核》3.1笔记,不足之处还望咱们批评指正

定义

进程纵使处于执行期的顺序。实际上,进度就是正在实施代码的骨子里结果。
线程是在经过中移动的靶子,每个线程都享有独立的顺序计数器,进程栈以及一组经过寄存器。内核的调度对象是线程,而不是
进程。

linux 新进度的始建

转自:

进程是Linux内核最焦点的架空之一,它是处于执行期的主次。它不仅局限于一段可举行代码(代码段),还包罗进度需求的此外资源。在Linux内核中常被称作任务。

进程的二种虚拟机制

  1. 编造处理器:每个线程独有,不可能共享
  2. 虚拟内存:同一个经过中的线程可以共享

一、背景知识:

1、进程与程序的关系:

进度是动态的,而先后是静态的;从构造上看,每个进程的实业都是由代码断和对应的数额段两有的组成的,那与程序的意义很相近;一个进程能够提到多个程序的实践,一个顺序也足以对应四个进程,即一个程序段可在不相同数额集合上运行,构成不一样的历程;并发性;进程具有开创其他进度的意义;操作系统中的每一个进度都是在一个经过现场中运行的。

linux中用户进度是由fork系统调用创设的。总计机的内存、CPU
等资源都是由操作系统来分配的,而操作系统在分配资源时,大部分情状下是以进度为民用的。

每一个历程唯有一个父进度,可是一个父进度却可以有八个子进度,当进度创制时,操作系统会给子进程成立新的地址空间,并把父进程的地点空间的映射复制到子进程的地方空间去;父进度和子进度共享只读数据和代码段,可是堆栈和堆是分离的。

2、进度的三结合:

进度控制块代码数据

经过的代码和数目由程序提供,而经过控制块则是由操作系统提供。

3、进度控制块的整合:

经过标识符进度上下文环境经过调度新闻进程控制新闻

【澳门金沙国际】Linux内核学习笔记,Linux下进程的创造进度分析。经过标识符:

进程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()。那些体系调用通过一名目繁多的参数标志来指明父、子进度须要共享的资源。Fork()、vfork()和库函数都基于各自必要的参数标志去调用clone(),然后由clone()去调用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_UNINTRRUPTIBLE,以保障它不会被投入运作。
5)、copy_process()调用copy_flags()以更新task_struct
的flags成员。评释进度是或不是富有最佳用户权限的PF_SUPERPRIV标志被清0.评释进度还向来不调用exec()函数的PF_FORKNOEXEC标志被设置。
6)、调用alloc_pid()为新进度分配一个立见成效的PID。
7)、依据传递给clone()
的参数标志,copy_process()拷贝或共享打开的文本、文件系统音信、信号处理函数、进程地址空间和命名空间等。在形似景况下,那一个资源会被给定进度的富有线程共享;否则,那几个资源对每个进程是不相同的所以被拷贝到那里。
8)、最后copy_process()做截至工作并赶回一个指向子进程的指针。
在回到do_fork()函数,如果copy_process()函数成功再次来到,新成立的子进度被唤醒并让其投入运作。内核有意接纳子进度首先实施(即使一而再想子进度先运行,可是并非总能如此)。因为一般子进度都会立刻调用exec()函数,那样可以避免写时拷贝(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_VFORK 调用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()系统调用。

GDB 调试的连带内容可以参照:GDB追踪内核启动 篇
那里不再占用过多篇幅赘述。上边先直接上图,在详细分析代码的运行进程。

澳门金沙国际 3

启动GDB后分别在sys_clone、do_fork、copy_process、copy_thread、ret_from_fork、syscall_exit等岗位设置好断点,见证fork()函数的执行进程(运行环境与GDB追踪内核启动
篇完全一致)

澳门金沙国际 4

可以寓目,当大家在menuos中运行fork
命令的时候,内核会先调用clone,在sys_clone 断点处停下来了。

澳门金沙国际 5

在调用sys_clone()
后,内核依照不一样的参数去调用do_fork()系统调用。进入do_fork()后就去又运行了copy_process().

澳门金沙国际 6

澳门金沙国际 7

在copy_process() 中又运行了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()、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()
成立子进程,那么最后就直接将子进度唤醒,不过只假若通过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、进度与程序的关联:
进度是动态的,而先后是静态的;从布局上看,每个进度的实业都是由代码断和…

前言


Unix标准的复制进度的体系调用时fork(即分叉),可是Linux,BSD等操作系统并不止完毕那些,确切的说linux已毕了三个,fork,vfork,clone(确切说vfork创建出来的是轻量级进度,也叫线程,是共享资源的经过)

系统调用 描述
fork fork创造的子进程是父进程的完整副本,复制了父亲进程的资源,包括内存的内容task_struct内容
vfork vfork创建的子进程与父进程共享数据段,而且由vfork()创建的子进程将先于父进程运行
clone Linux上创建线程一般使用的是pthread库 实际上linux也给我们提供了创建线程的系统调用,就是clone

至于用户空间利用fork, vfork和clone, 请参见

Linux中fork,vfork和clone详解(分裂与沟通)

fork, vfork和clone的种类调用的入口地址分别是sys_fork,
sys_vfork和sys_clone, 而他们的定义是依靠于系统布局的,
因为在用户空间和基础空间之间传递参数的方式因体系布局而异

系统调用的参数传递

系统调用的贯彻与C库不相同,
普通C函数通过将参数的值压入到进程的栈中举行参数的传递。由于系统调用是经过暂停进度从用户态到内核态的一种新鲜的函数调用,没有用户态或者内核态的堆栈可以被用来在调用函数和被调函数之间进行参数传递。系统调用通过CPU的寄存器来进展参数传递。在进展系统调用此前,系统调用的参数被写入CPU的寄存器,而在实质上调用系统服务例程以前,内核将CPU寄存器的情节拷贝到内核堆栈中,完结参数的传递。

所以差其余系统布局可能行使差其余办法仍旧差距的寄存器来传递参数,而地点函数的职分就是从处理器的寄存器中提取用户空间提供的消息,
并调用系统布局无关的_do_fork(或者早期的do_fork)函数,
负责进度的复制

今非昔比的种类布局可能需求采纳差其余主意或者寄存器来储存函数调用的参数,
由此linux在布置系统调用的时候,
将其分割成连串布局有关的层系和连串布局无关的层系,
前者复杂提取出依赖与系统布局的一定的参数,
后者则基于参数的安装举办一定的实在操作

线程被称作轻量级进度,是操作系统调度的矮小单元,平时一个历程可以拥有五个线程。

进度描述符及职务结构

  • 职务队列:存放进度列表的双向循环链表
  • task_struct:进度描述符,包括一个现实经过的持有音讯。2.6后头的版本通过slab动态生成task_struct。
  • thread_info:线程描述符,

fork, vfork, clone系统调用的达成


经过和线程的分别在于进度具有独立的资源空间,而线程则共享进度的资源空间。

PID

唯一的进度标志值。int类型,为了与老版本的Unix和Linux包容,PID的最大值默认设置为32768,那些值最大可增添到400万。进度的PID存放在进程描述符中。

关于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如下表所示

澳门金沙国际 17

标题一:在基本中如何得到当前进度的task_struct数据结构?

进程情况

进程描述符中的state域记录进度如今的景况,进程一共有五中状态,分别为:

  • TASK_RUNNING 运行
  • TASK_INTERRUPTIBLE 可中断
  • TASK_UNINTERRUPTIBLE 不可中断
  • __TASK_TRACED 被此外进度跟踪的进度
  • __TASK_STOPPED 进度为止实施

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作为系统调用的结果再次来到, 否则赶回错误码

  内核有一个常用的常量current用于获取当前进程task_struct数据结构,它使用了内核栈的表征。首先通过sp寄存器获取当前内核栈的地址,对齐后收获struct
thread_info数据社团指针,最终经过thread_info->task成员取得task_struct数据结构。图1为linux内核栈的布局图。

经过上下文

平常经过的代码在用户空间执行,当执行了系统调用或接触了某个极度时,它就沦为了根本空间。此时,大家称基本处于进度上下文中。

sys_vfork的实现


中期达成

架构 实现
arm arch/arm/kernel/sys_arm.c, line 254
i386 arch/i386/kernel/process.c, line 737
x86_64 arch/x86_64/kernel/process.c, line 728
asmlinkage long sys_vfork(struct pt_regs regs)
{
    return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.rsp, &regs, 0);
}

 

新版本

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

 

可以看出sys_vfork的落到实处与sys_fork只是略微分化,
前者使用了额外的标志CLONE_VFORK | CLONE_VM

澳门金沙国际 18图1
内核栈

经过创设

  1. 写时拷贝,父子进度共享同一个地方空间,将页的正片推迟到实在暴发写入时才进行。这么些优化可以幸免创制进程时拷贝大量不被拔取的数量。
  2. 在经过中调用fork()会因此复制一个共处进度来创设一个新过程,调用fork()的历程是父进度,成立的历程是子进度。fork()函数从基础再次回到五回,一次是回到父进度,另一遍重返子进度。Linux通过
    clone(SIGCHLD, 0);系统调用完结fork()。
  3. vfork()
    不拷贝父进度的页表项,其他与fork功效雷同。系统贯彻:clone(CLONE_VFORK
    | CLONE_VM | SIGCHLD, 0);
  4. exec()那组函数可以创制新的地址空间,并把新的次第载入其中。

sys_clone的实现


最初落成

架构 实现
arm arch/arm/kernel/sys_arm.c, line 247
i386 arch/i386/kernel/process.c, line 715
x86_64 arch/x86_64/kernel/process.c, line 711

sys_clone的贯彻格局与上述系统调用类似, 但实际差别在于do_fork如下调用

casmlinkage int sys_clone(struct pt_regs regs)
{
    /* 注释中是i385下增加的代码, 其他体系结构无此定义
    unsigned long clone_flags;
    unsigned long newsp;

    clone_flags = regs.ebx;
    newsp = regs.ecx;*/
    if (!newsp)
        newsp = regs.esp;
    return do_fork(clone_flags, newsp, &regs, 0);
}

 

新版本

#ifdef __ARCH_WANT_SYS_CLONE
#ifdef CONFIG_CLONE_BACKWARDS
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
                 int __user *, parent_tidptr,
                 unsigned long, tls,
                 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,
                 unsigned long, tls)
#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,
                unsigned long, tls)
#else
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
                 int __user *, parent_tidptr,
                 int __user *, child_tidptr,
                 unsigned long, tls)
#endif
{
        return _do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr, tls);
}
#endif

 

俺们可以观望sys_clone的标识不再是硬编码的,
而是通过种种寄存器参数传递到系统调用, 因此大家要求领取那几个参数。

除此以外,clone也不再复制进度的栈, 而是可以指定新的栈地址, 在生成线程时,
可能需求如此做, 线程可能与父进度共享地址空间,
可是线程自身的栈可能在此外一个地方空间

其余还吩咐了用户空间的三个指针(parent_tidptr和child_tidptr),
用于与线程库通讯

 

线程完结

在Linux内核中线程看起来就是一个平淡无奇的长河,只是和任何一些历程共享某些资源,如地址空间。

  1. 开创线程同样选拔clone已毕,只是须要传递一些参数标志来指明须求共享的资源:clone(CLONE_VM
    | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);
  2. 水源线程从没独立的地点空间,只在基础空间运行,不切换来用户空间上去,只好由基础线程创造。

创制子进度的流程


标题二:上面程序会打印多少个“_”?

进度终结

当一个经过终结时必须释放它所占据的资源。进程积极终结暴发在经过调用exit()系统调用时,当然它还有可能被动终结。

  • 剔除进度描述符:在调用do_exit()之后,就算线程已经僵死不可能再运行了,但系统还保留了它的经过描述符,在父进度取得已了结的子进程的音信或通知内核它不关怀那个信息后,子进程的task_struct结构才获释。
  • 孤儿进度造成的难堪:由于经过退出时索要父进度通告父进程释放子进程的task_struct,倘使一个进程找不到父进度就会在脱离时永远地处僵死状态。因而要在父进度退出时为每一个子经过找到一个新的生父,方法是给子进度在现阶段线程组内找一个线程作为小叔,如若那一个就让init做它们的父进度。

_do_fork和早起do_fork的流程


_do_fork和do_fork在进度的复制的时候并从未太大的分歧,
他们就只是在进度tls复制的长河中贯彻有细微差距

具有进程复制(创制)的fork机制最后都调用了kernel/fork.c中的_do_fork(一个系统布局非亲非故的函数),

其定义在

_do_fork以调用copy_process初步, 后者执行生成新的进程的其实工作,
并按照指定的注明复制父进度的数额。在子进度生成后,
内核必须举行下列收尾操作:

  1. 调用 copy_process 为子进程复制出一份进度消息

  2. 如果是 vfork(设置了CLONE_VFORK和ptrace标志)早先化达成处理消息

  3. 调用 wake_up_new_task 将子进度进入调度器,为之分配 CPU

  4. 倘假若 vfork,父进度等待子进程达成 exec 替换自己的地址空间

相比,大家从《深切linux内核架构》中找到了早期的do_fork流程图,基本一致,可以用来参考学习和对照

澳门金沙国际 19

long _do_fork(unsigned long clone_flags,
      unsigned long stack_start,
      unsigned long stack_size,
      int __user *parent_tidptr,
      int __user *child_tidptr,
      unsigned long tls)
{
    struct task_struct *p;
    int trace = 0;
    long nr;

    /*
     * Determine whether and which event to report to ptracer.  When
     * called from kernel_thread or CLONE_UNTRACED is explicitly
     * requested, no event is reported; otherwise, report if the event
     * for the type of forking is enabled.
     */
    if (!(clone_flags & CLONE_UNTRACED)) {
    if (clone_flags & CLONE_VFORK)
        trace = PTRACE_EVENT_VFORK;
    else if ((clone_flags & CSIGNAL) != SIGCHLD)
        trace = PTRACE_EVENT_CLONE;
    else
        trace = PTRACE_EVENT_FORK;

    if (likely(!ptrace_event_enabled(current, trace)))
        trace = 0;
    }
    /*  复制进程描述符,copy_process()的返回值是一个 task_struct 指针  */
    p = copy_process(clone_flags, stack_start, stack_size,
         child_tidptr, NULL, trace, tls);
    /*
     * 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;
    struct pid *pid;

    trace_sched_process_fork(current, p);
    /*  得到新创建的进程的pid信息  */
    pid = get_task_pid(p, PIDTYPE_PID);
    nr = pid_vnr(pid);

    if (clone_flags & CLONE_PARENT_SETTID)
        put_user(nr, parent_tidptr);

    /*  如果调用的 vfork()方法,初始化 vfork 完成处理信息 */
    if (clone_flags & CLONE_VFORK) {
        p->vfork_done = &vfork;
        init_completion(&vfork);
        get_task_struct(p);
    }
    /*  将子进程加入到调度器中,为其分配 CPU,准备执行  */
    wake_up_new_task(p);

    /* forking complete and child started to run, tell ptracer */
    if (unlikely(trace))
        ptrace_event_pid(trace, pid);

    /*  如果是 vfork,将父进程加入至等待队列,等待子进程完成  */
    if (clone_flags & CLONE_VFORK) {
        if (!wait_for_vfork_done(p, &vfork))
        ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
    }

    put_pid(pid);
    } else {
    nr = PTR_ERR(p);
    }
    return nr;
}

 

int
main(void){

copy_process流程


  1. 调用 dup_task_struct 复制当前的 task_struct

  2. 反省进程数是不是当先限定

  3. 初始化自旋锁、挂起信号、CPU 定时器等

  4. 调用 sched_fork 初阶化进度数据结构,并把经过情形设置为
    TASK_RUNNING

  5. 复制所有进度新闻,包蕴文件系统、信号处理函数、信号、内存管理等

  6. 调用 copy_thread_tls 开头化子进程内核栈

  7. 为新历程分配并设置新的 pid

对照,大家从《深刻linux内核架构》中找到了初期的do_fork流程图,基本一致,可以用来参考学习和对待

澳门金沙国际 20

器重的分别其实就是最后的copy_thread更改成为copy_thread_tls

/*
 * This creates a new process as a copy of the old one,
 * but does not actually start it yet.
 *
 * It copies the registers, and all the appropriate
 * parts of the process environment (as per the clone
 * flags). The actual kick-off is left to the caller.
 */
static struct task_struct *copy_process(unsigned long clone_flags,
                    unsigned long stack_start,
                    unsigned long stack_size,
                    int __user *child_tidptr,
                    struct pid *pid,
                    int trace,
                    unsigned long tls)
{
    int retval;
    struct task_struct *p;

    retval = security_task_create(clone_flags);
    if (retval)
        goto fork_out;
    //  复制当前的 task_struct
    retval = -ENOMEM;
    p = dup_task_struct(current);
    if (!p)
        goto fork_out;

    ftrace_graph_init_task(p);

    //初始化互斥变量
    rt_mutex_init_task(p);

#ifdef CONFIG_PROVE_LOCKING
    DEBUG_LOCKS_WARN_ON(!p->hardirqs_enabled);
    DEBUG_LOCKS_WARN_ON(!p->softirqs_enabled);
#endif

    //检查进程数是否超过限制,由操作系统定义
    retval = -EAGAIN;
    if (atomic_read(&p->real_cred->user->processes) >=
            task_rlimit(p, RLIMIT_NPROC)) {
        if (p->real_cred->user != INIT_USER &&
            !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))
            goto bad_fork_free;
    }
    current->flags &= ~PF_NPROC_EXCEEDED;

    retval = copy_creds(p, clone_flags);
    if (retval < 0)
        goto bad_fork_free;

    /*
     * If multiple threads are within copy_process(), then this check
     * triggers too late. This doesn't hurt, the check is only there
     * to stop root fork bombs.
     */
    //检查进程数是否超过 max_threads 由内存大小决定
    retval = -EAGAIN;
    if (nr_threads >= max_threads)
        goto bad_fork_cleanup_count;

    delayacct_tsk_init(p);  /* Must remain after dup_task_struct() */
    p->flags &= ~(PF_SUPERPRIV | PF_WQ_WORKER);
    p->flags |= PF_FORKNOEXEC;
    INIT_LIST_HEAD(&p->children);
    INIT_LIST_HEAD(&p->sibling);
    rcu_copy_process(p);
    p->vfork_done = NULL;

    //  初始化自旋锁
    spin_lock_init(&p->alloc_lock);
    //  初始化挂起信号
    init_sigpending(&p->pending);

    //  初始化 CPU 定时器
    posix_cpu_timers_init(p);
    //  ......

    /* Perform scheduler related setup. Assign this task to a CPU. 
        初始化进程数据结构,并把进程状态设置为 TASK_RUNNING
    */
    retval = sched_fork(clone_flags, p);
    if (retval)
        goto bad_fork_cleanup_policy;
    retval = perf_event_init_task(p);

    /*  复制所有进程信息,包括文件系统、信号处理函数、信号、内存管理等
       形式类似于copy_xxx的形式   */
    if (retval)
        goto bad_fork_cleanup_policy;
    retval = audit_alloc(p);
    if (retval)
        goto bad_fork_cleanup_perf;
    /* copy all the process information */
    shm_init_task(p);
    retval = copy_semundo(clone_flags, p);
    if (retval)
        goto bad_fork_cleanup_audit;
    retval = copy_files(clone_flags, p);
    if (retval)
        goto bad_fork_cleanup_semundo;
    retval = copy_fs(clone_flags, p);
    if (retval)
        goto bad_fork_cleanup_files;
    retval = copy_sighand(clone_flags, p);
    if (retval)
        goto bad_fork_cleanup_fs;
    retval = copy_signal(clone_flags, p);
    if (retval)
        goto bad_fork_cleanup_sighand;
    retval = copy_mm(clone_flags, p);
    if (retval)
        goto bad_fork_cleanup_signal;
    retval = copy_namespaces(clone_flags, p);
    if (retval)
        goto bad_fork_cleanup_mm;
    retval = copy_io(clone_flags, p);
    if (retval)
        goto bad_fork_cleanup_namespaces;
    /*    初始化子进程内核栈
        linux-4.2新增处理TLS
        之前版本是   retval = copy_thread(clone_flags, stack_start, stack_size, p);
        */
    retval = copy_thread_tls(clone_flags, stack_start, stack_size, p, tls);
    if (retval)
        goto bad_fork_cleanup_io;

    /*  为新进程分配新的pid  */
    if (pid != &init_struct_pid) {
        pid = alloc_pid(p->nsproxy->pid_ns_for_children);
        if (IS_ERR(pid)) {
            retval = PTR_ERR(pid);
            goto bad_fork_cleanup_io;
        }
    }

    /*  设置子进程的pid  */
    /* ok, now we should be set up.. */
    p->pid = pid_nr(pid);
    if (clone_flags & CLONE_THREAD) {
        p->exit_signal = -1;
        p->group_leader = current->group_leader;
        p->tgid = current->tgid;
    } else {
        if (clone_flags & CLONE_PARENT)
            p->exit_signal = current->group_leader->exit_signal;
        else
            p->exit_signal = (clone_flags & CSIGNAL);
        p->group_leader = p;
        p->tgid = p->pid;
    }

    p->nr_dirtied = 0;
    p->nr_dirtied_pause = 128 >> (PAGE_SHIFT - 10);
    p->dirty_paused_when = 0;

    p->pdeath_signal = 0;
    INIT_LIST_HEAD(&p->thread_group);
    p->task_works = NULL;

    /*
     * Make it visible to the rest of the system, but dont wake it up yet.
     * Need tasklist lock for parent etc handling!
     */
    write_lock_irq(&tasklist_lock);

    /*  调用fork的进程为其父进程  */
    /* CLONE_PARENT re-uses the old parent */
    if (clone_flags & (CLONE_PARENT|CLONE_THREAD)) {
        p->real_parent = current->real_parent;
        p->parent_exec_id = current->parent_exec_id;
    } else {
        p->real_parent = current;
        p->parent_exec_id = current->self_exec_id;
    }

    spin_lock(&current->sighand->siglock);

    // ......

    return p;
}

 

  int
i;

dup_task_struct 流程


static struct task_struct *dup_task_struct(struct task_struct *orig)
{
    struct task_struct *tsk;
    struct thread_info *ti;
    int node = tsk_fork_get_node(orig);
    int err;

    //分配一个 task_struct 节点
    tsk = alloc_task_struct_node(node);
    if (!tsk)
        return NULL;

    //分配一个 thread_info 节点,包含进程的内核栈,ti 为栈底
    ti = alloc_thread_info_node(tsk, node);
    if (!ti)
        goto free_tsk;

    //将栈底的值赋给新节点的栈
    tsk->stack = ti;

    //……

    return tsk;

}

 

  1. 调用alloc_task_struct_node分配一个 task_struct 节点

  2. 调用alloc_thread_info_node分配一个 thread_info
    节点,其实是分配了一个thread_union联合体,将栈底重回给 ti

union thread_union {
   struct thread_info thread_info;
  unsigned long stack[THREAD_SIZE/sizeof(long)];
};

 

  • 末尾将栈底的值 ti 赋值给新节点的栈

  • 最后实施完dup_task_struct之后,子进度除了tsk->stack指针不一致之外,全体都如出一辙!

  for(i=0;
i<2; i++){

sched_fork 流程


int sched_fork(unsigned long clone_flags, struct task_struct *p)
{
    unsigned long flags;
    int cpu = get_cpu();

    __sched_fork(clone_flags, p);

    //  将子进程状态设置为 TASK_RUNNING
    p->state = TASK_RUNNING;

    //  ……

    //  为子进程分配 CPU
    set_task_cpu(p, cpu);

    put_cpu();
    return 0;
}

 

俺们能够见到sched_fork大约形成了两项根本工作,

  • 一是将子进度景况设置为 TASK_RUNNING,

  • 二是为其分配 CPU

    fork();

copy_thread和copy_thread_tls流程


咱俩得以观察linux-4.2之后增添了copy_thread_tls函数和CONFIG_HAVE_COPY_THREAD_TLS宏

不过若是未定义CONFIG_HAVE_COPY_THREAD_TLS宏默许则使用copy_thread同时将定义copy_thread_tls为copy_thread

独自将那么些函数是因为那么些复制操作与此外操作都分裂等,
那是一个特定于系统布局的函数,用于复制进度中一定于线程(thread-special)的数据,
紧要的就是填充task_struct->thread的相继成员,那是一个thread_struct类型的布局,
其定义是依靠于系统布局的。它包涵了颇具寄存器(和其它信息),内核在进度之间切换时索要保留和死灰复燃的历程的音讯。

该函数用于设置子进程的推行环境,如子进程运行时各CPU寄存器的值、子进度的内核栈的开局部址(指向内核栈的指针平常也是保留在一个特意保留的寄存器中)

#ifdef CONFIG_HAVE_COPY_THREAD_TLS
extern int copy_thread_tls(unsigned long, unsigned long, unsigned long,
            struct task_struct *, unsigned long);
#else
extern int copy_thread(unsigned long, unsigned long, unsigned long,
            struct task_struct *);

/* Architectures that haven't opted into copy_thread_tls get the tls argument
 * via pt_regs, so ignore the tls argument passed via C. */
static inline int copy_thread_tls(
        unsigned long clone_flags, unsigned long sp, unsigned long arg,
        struct task_struct *p, unsigned long tls)
{
    return copy_thread(clone_flags, sp, arg, p);
}
#endif

 

下边大家来看32位架构的copy_thread_tls函数,他与原先的copy_thread变动并不大,
只是多了背后TLS的设置新闻

int copy_thread_tls(unsigned long clone_flags, unsigned long sp,
    unsigned long arg, struct task_struct *p, unsigned long tls)
{
    struct pt_regs *childregs = task_pt_regs(p);
    struct task_struct *tsk;
    int err;
    /*  获取寄存器的信息  */
    p->thread.sp = (unsigned long) childregs;
    p->thread.sp0 = (unsigned long) (childregs+1);
    memset(p->thread.ptrace_bps, 0, sizeof(p->thread.ptrace_bps));

    if (unlikely(p->flags & PF_KTHREAD)) {
        /* kernel thread
            内核线程的设置  */
        memset(childregs, 0, sizeof(struct pt_regs));
        p->thread.ip = (unsigned long) ret_from_kernel_thread;
        task_user_gs(p) = __KERNEL_STACK_CANARY;
        childregs->ds = __USER_DS;
        childregs->es = __USER_DS;
        childregs->fs = __KERNEL_PERCPU;
        childregs->bx = sp;     /* function */
        childregs->bp = arg;
        childregs->orig_ax = -1;
        childregs->cs = __KERNEL_CS | get_kernel_rpl();
        childregs->flags = X86_EFLAGS_IF | X86_EFLAGS_FIXED;
        p->thread.io_bitmap_ptr = NULL;
        return 0;
    }
    /*  将当前寄存器信息复制给子进程  */
    *childregs = *current_pt_regs();
    /*  子进程 eax 置 0,因此fork 在子进程返回0  */
    childregs->ax = 0;
    if (sp)
        childregs->sp = sp;
    /*  子进程ip 设置为ret_from_fork,因此子进程从ret_from_fork开始执行  */
    p->thread.ip = (unsigned long) ret_from_fork;
    task_user_gs(p) = get_user_gs(current_pt_regs());

    p->thread.io_bitmap_ptr = NULL;
    tsk = current;
    err = -ENOMEM;

    if (unlikely(test_tsk_thread_flag(tsk, TIF_IO_BITMAP))) {
        p->thread.io_bitmap_ptr = kmemdup(tsk->thread.io_bitmap_ptr,
                        IO_BITMAP_BYTES, GFP_KERNEL);
        if (!p->thread.io_bitmap_ptr) {
            p->thread.io_bitmap_max = 0;
            return -ENOMEM;
        }
        set_tsk_thread_flag(p, TIF_IO_BITMAP);
    }

    err = 0;

    /*
     * Set a new TLS for the child thread?
     * 为进程设置一个新的TLS
     */
    if (clone_flags & CLONE_SETTLS)
        err = do_set_thread_area(p, -1,
            (struct user_desc __user *)tls, 0);

    if (err && p->thread.io_bitmap_ptr) {
        kfree(p->thread.io_bitmap_ptr);
        p->thread.io_bitmap_max = 0;
    }
    return err;
}

 

copy_thread_tls 那段代码为大家诠释了八个分外主要的题材!

一是,为何 fork 在子进度中再次回到0,原因是childregs->ax =
0;这段代码将子进度的 eax 赋值为0
二是,p->thread.ip = (unsigned long) ret_from_fork;将子进度的 ip
设置为 ret_form_fork 的首地址,由此子进程是从 ret_from_fork
初叶实践的

    printf(“_\n”);}

总结


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()。设置子进程调度相关的参数,即子进度的运转CPU、开首时间片长度和静态优先级等。

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

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

大致的说,copy_process()就是将父进度的周转条件复制到子进度并对少数子进度特定的环境做相应的调整。

其余应用程序使用系统调用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()。只要目标是刑满释放平台相关的一些资源。

  5. exit_notify()。在Linux中经过退出时要将其脱离的来头报告父进程,父进程调用wait()系统调用后会在一个守候队列上睡觉。

  6. schedule()。调用进度调度器,因为此进程已经脱离,切换来其余进度。

经过的创导到实践进度如下图所示

澳门金沙国际 21

版权注解:本文为博主原创小说 && 转发请大名鼎鼎出处 @

  wait(NULL);wait(NULL);

  return
0;}

   答案是6个“_”,具体思路如图2所示。(i=0,调用三回fork后,父进程a创制子进度b,此后a和b举办打印,打印七个“_”;后i=1,a和b均调用fork,a创立子进程a_1,b创建子进度b_1,4个经过执行打印操作,打印出两个“_”;i=2,返回)

澳门金沙国际 22图2
fork解题思路

 

难题三:用户空间进度的页表是何等时候分配的,其中一流页表哪天分配?二级页表呢?

   (此问有点难点,暂且认为一流页表为页目录项(pgd),二级页表为也表项(pte))

  对于基本来说,过程的“鼻祖”是idle进程,称为swapper进度;对于用户空间来说,进度“鼻祖”是init进度,所有用户空间进程都由init进度成立或派生。

  在mm_init()函数中,首先给新进度的mm_struct数据结构进行起头化,然后对mm_users,mm_count举办开头化,设置进度空间地址读写信号量,设置保险进程页表的spinlock锁,最终调用pgd_alloc()函数举办pgd页表的分红工作。在pgd_alloc()函数中,调用pte_alloc_map()函数举行第0,第1个页表的分配,此后在dup_mmap()函数中将父进度具有的VMA对应的pte页表项复制到子进度对应的pte页表项中。

难题四:请简述fork,vfork和clone之间的分别?

   fork,vfork,clone的已毕都是通过调用do_fork()函数完毕的,只是函数调用不均等。

  fork函数完结:do_fork(SIGCHLD,0,0,NULL,NULL);只利用SIGCHLD标志位,在子进度终止后发送SIGCHLD信号通告父进程。fork是重量级调用,为子进度建立了一个基于父进度的完好副本,然后子进度基于此运行。为了削减工作量拔取写时复制技术(COW),子进度只复制父进程的页表,不复制页面内容。当子进度须求写入新情节时,才触发写时复制机制,为子进度创建一个副本。

  vfork函数落成:do_fork(CLONE_VFORK | CLONE_VM |
SIGCHLD,0,0,NULL,NULL);它比fork多了七个标志位,分别为CLONE_VFORK和CLONE_VM。CLONE_VFORK代表父进度会被挂起,直至子进度释放虚拟内存资源。CLONE_VM表示父子进度运行在同样的内存空间中。

  clone函数落成:do_fork(clone_flags, newsp, 0, parent_tidptr,
child_tidptr);clone用于创立线程,并且参数通过寄存器从用户空间传递下去,寻常会指定新的栈地址(newsp)。

 

相关文章