深度分析进程创建过程(linux 2.4) —— 《LINUX内核源代码情景分析》阅读笔记

进程:

实际上是复制出来的,第一个进程是系统固有的(在内核引导下完成初始化),所有其他进程都是其“后代”。

创建:Linux提供三个系统调用函数

pid_t fork(void);是全部复制,父进程的资源全部通过数据结构复制给子进程

int _clone(int(*fn)(void *arg), void * child_stack, int flags, void *arg);选择复制,其余部分通过指针复制

pid_t vfork()除task_struct结构和系统空间堆栈以外的资源全部通过数据结构指针复制“遗传”,是线程。

三个系统调用的实现都是通过do_fork()完成的,其调用参数不同。

 

 

do_fork()剖析:

参数clone_flags由两部分组成:

最低字节为信号类型(规定子进程去世向父进程发出的信号)。对于fork()、vfork()其信号为SIGCHILD,而对于_clone()由调用者决定。

第二部分表示资源为何特性的标志位,对于fork(),这一部分为0,表示对有关资源都要复制。对于vfork(),则为CLONE_VFORK | CLONE_VM,表示父、子进程共用虚存区间,并且当子进程释放其虚存区间要唤醒父进程。至于_clone(),则由调用者设定。其中标志位CLONE_PID,为1时,父、子进程(线程)共用同一个进程号(系统原始进程(0号进程)才允许)。

接着(568行),为子进程分配两个连续的物理页面,低端用作子进程的task_struct结构,高端则用其系统空间堆栈。

低端用作子进程的task_struct结构:

(574行)为整个数据结构赋值(父进程整个task_truct就被复制到了子进程)。

(577行)Task_struct结构中有指针user(指向user_struct结构),每个用户有且只有一个user_struct结构,属于同一用户的进程就可以通过user指针共享这些信息。对于内核线程不属于任何用户,所以其user指引为0;

(577行)task_struct结构有数组rlim数组(对该进程占用各种资源的数量作出限制),rlim[RLIMIT_NPROC]就规定了该进程所属的用户可以拥有的进程数量。当达到限制,不允许fork()了。

(587行)对于不属于任何用户的内核线程进行总量的限制。

(590行)task_struct结构的exec_domain指针(执行域指针,什么系统开发的程序),module(递增具体模块的数据结构中的计数器Linux设备驱动程序设计实现成的“动态安装模块”,使其在运行时动态地安装和拆除。)

(593行)task_struct结构的binfmt指针指向Linux_binfmt数据结构。进程所执行的程序属于某种可执行映像格式(a.out、.elf)。对于不同的格式支持通常是通过动态安装的驱动模块来实现的。

(593行)对有关模块的使用计数器进行操作。

(597行)进程状态设置为TASK_UNINTERRUPTINLE (“深度睡眠”不受“信号”打扰)。因为get_pid()中产生一个新pid的操作必须是独占的(可能因为一时进入不了临界区而暂时进入睡眠状态等待)。

(599行)将clone_flags标志位略补充和转换写入p->flags;

(600行)根据clone_flags标记返回pid(父进程的或新的)。PID_MAX定义为0X8000。即最大进程号为0X7fff(32767)。0~299为系统进程(包括内核线程)。

(602~626行)对各类信息量加以初始化,在task_struct中设置一个队列头部wait_chldexit(接受子进程的信号),子进程的等待队列(611行)。(615~616行)对子进程的待处理信号队列以及有关结构成分的初始化、

(627~637行)task_struct中各种计时变量的初始化。

(638~639行)task_struct中start_time表示进程创建时间,全局变量jiffies的数值就是以时钟中断周期为单位的从系统初始化开始至此的时间。

至此,对task_struct数据结构复制和初始化基本完成。下来是其他资源。

文件描述符:

(643行)copy_files函数,当clone_flags中CLONE_FILES(文件描述符)标志位为0时才真正复制(创建新的files_struct数据结构,一一拷贝),否则只是共享父进程打开的文件(将files_struct数据结构的共享计数+1)。当进程有已打开的文件时task_struct数据结构的指针files指向files_struct数据结构,否则为0(一般与终端设备想联系的用户进程前三个文件stdin、stdout、stderr都是预先打开,所以一般不为0)。

“复制”后,父子进程互不影响(子进程有一个副本),“共享”后父子进程是对一个文件操作。

(645行)copy_fs函数,当clone_flags中CLONE_FS(目录流文件描述符)同样需要通过共享或复制给子进程。

信号:

(647行)copy_sighand()是否复制父进程对信号,当clone_flags中CLONE_SIGHAND标志位为0时才真正进行,否则共享父进程sig指针(signal_struct中共享计数加1)。

用户空间:

(649行)copy_mm(),task_struct数据结构中指针mm指向一个代表这进程的用户空间(内核线程为0)。当clone_flags(文件描述符)中CLONE_VM标志位为0时才真正进行(写时拷贝),否则只是通过复制指针共享父进程的用户空间。

回顾:

fork()通过sys_fork()进入do_fork()时,其clone_flags为SIGCHILD(所有标识为0),所以copy_files()、copy_fs()、copy_sighand()、copy_mm()全部真正执行了,这四项资源全部复制了。

而vfork()通过sys_vfork()进入do_fork()时,其clone_flags为VFORK | CLONE_VM | SIGCHLD,所以vfork()没有copy_mm(),因为CLONE_VM为1,只是通过指针指向父进程的mm_struct,没有自己的副本。或者说vfork()出来的是个线程。

至于_clone(),则取决于调用时的参数(还有父进程的资源)。

 

用作系统空间堆栈的高端:

堆栈指父进程从通过系统调用进入系统空间开始到进入copy_thread()的来历,子进程将要遵循相同的路线返回,所以把他复制给子进程。

(535行)当一个进程因系统调用或中断而进入内核时,其系统空间堆栈的顶部保存着CPU进入内核前夕各个寄存器的内容,并形成一个pt_regs数据结构。

这里的p为子进程的task_struct指针,经过下图偏移得到子进程系统空间堆栈的地址:

(537~538行)然后先将当前进程系统空间堆栈中的pt_regs结构复制过去,再做调整(将该结构的eax置为0(当子进程受调度“恢复”运行,从系统调用返回值为0),将esp置成这里的esp(决定了进程在用户空间的堆栈位置))。

(540~541行)将task_struct结构中thread结构中进程切换时的堆栈指针“返回地址”等信息(复制父进程的)修改为子进程的信息(子进程有了自己的系统间堆栈)。

(543行)p->thread.eip的值(置为ret_fron_fork首次调度开始位置)表示当前进程下一次被切换进入运行时的切入点,类似于函数调用或中断的返回地址。

(545~546行)宏操作,把当前段寄存器fs值保存找p->thread.fs中。

(548~549行)设置i387浮点处理器

接下来配置最后运行信息

至此,新进程的创建完成,并已经挂入可运行队列等待调度(父进程返回前可能会接受调度,所以不一定(父进程在可执行进程的前面,可能性大))。

 

关于v_fork()保证子进程线运行:

因为父子共享用户空间堆栈,并发运行后对于堆栈空间是致命的!

所以在do_frok()中(703~704行)在CLONE_VFORK标志为1并且fork子进程成功时,通过让当前进程(父进程)在一个信号量(共享用户堆栈空间)上执行一次down()操作,进入临界区后资源(信号量)为0,以后的down()都会为阻塞,直到子进程通过execve()执行一个新的可执行程序或通过exit()退出系统时,会执行up()增加信号量来唤醒父进程。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值