Linux内核源码浅析——进程创建&切换

 

6. 进程创建

a)   COW机制。子进程刚创建时父子进程共享页表,并设置为只读,只有在某个进程对某个页需要写操作时,才单独复制该页。

b)   系统调用fork(),vfork()【特性:父进程阻塞直到子进程结束获调用execve()加载一个可执行文件】,clone()最终都是调用do_fork()函数。

c)   进程创建的fork_flags标志比较重要的有:CLONE_VM共享内存信息及页表,CLONE_FS共享fs_struct结构,CLONE_FILES共享打开的文件,CLONE_SIGHAND共享信号处理信息,CLONE_VFORK用于实现vfork()函数的特性等。

d)   Kernel/Fork.c下的Do_fork()函数流程:

a)   权限检查

b)   调用copy_process(),复制父进程信息到子进程。

c)   Fork标志检查:如果含有CLONE_VFORK,建立一个completion等待队列,把父进程加入,在函数最后还需要调用wait_for_completion()让父进程睡眠;如果含有CLONE_STOPPED,把子进程设置为TASK_STOPPED,否则唤醒子进程。

下的copy_process()函数流程:

a)   Fork标志检查,如果出现一些不允许同时出现的标志组合,返回错误。

b)   调用dup_task_struct()为子进程分配task_struct结构和thread_info结构,复制服task_struct结构信息,更新子进程的栈指针。

c)   调用copy_creds()复制证书。(应该是复制权限及身份信息)

d)   初始化子进程中的children, sibling等队列头;初始化自旋锁,信号处理;初始化进程统计信息;初始化POSIX时钟;初始化调度相关的统计信息;。

e)   初始化审计信息。

f)   调用进程相关模块部分的函数,根据clone_flags决定是否拷贝,如下

 

 

g)   为子进程分配PID。根据Fork标志设置tgid等,设置进程父子关系。

下的copy_mm()函数流程:

 

a)   把子进程mmactive_mm设置为NULL,如果当前进程的mm为空,即当前进程为内核线程,则不需要复制内存描述符,返回。否则继续

b)   如果设置了CLONE_VM标志,则共享父进程的页,增加父进程(当前进程)mmmm_users计数,设置子进程的mmactive_mm为当前进程的mm。返回。

c)   如果没设置CLONE_VM,调用dup_mm()进行复制页表,但是把可写的用户空间对应的页表设置为只读,这样当有进程对它写入时,产生页错误,do_page_fault()将为这个进程复制该页的一份私有的拷贝(COW机制)。Dup_mm()流程如下:

                                      i.   为子进程分配一个mm_struct,并把当前进程的内容全部复制过来。

                                    ii.   初始化mm_struct资源,并调用分配页表,mm_allco_pgd()实际上调用了pgd_alloc()pgd_alloc()中线获得一个页来保存pgd,然后再分配pmd,最后初始化pgdpmd

                                   iii.  调用dup_mmap()拷贝vm_area_struct结构。Dup_mmap()流程如下。

1.   初始化新mm结构的mmapmmap_cachemap_countmm_rb等成员。

2.   复制。根据vm_area_struct结构的属性标志设置页表项,把可写入的页设置为只读。调用函数,为vm_area_struct指定的内存区域分配并设置页表。copy_page_range()支持多级分页,不断调用copy_pud_range()——copy_pmd_range()设置全局、中间页表及页表项。copy_one_pte()中判断如果是可写区域,则设置为只读,COW机制得以实现

g)   Arch/x86/kernel/Process_32.c下的copy_thread()函数的重要代码:

a)   childregs = task_pt_regs(p);复制父进程的用户空间进程状态pt_regs(寄存器值)

b)   childregs->ax = 0;所以fork调用子进程返回0

c)   childregs->sp = sp;

p->thread.sp = (unsigned long) childregs;

p->thread.sp0 = (unsigned long) (childregs+1);

调整子进程堆栈,需要同时调整thread_info中的还有pt_regs中的。

d)   p->thread.ip = (unsigned long)ret_from_fork;设置新建的子进程第一次被调度后所执行的代

码,这就与普通的进程被调度完所执行代码不同。当子进程被调度时,从ret_from_fork()开始

执行,然后跳到syscall_exit(),最后根据保存在内核态堆栈的pt_regs来返回用户空间。0号进程

是内核启动时手工创建的。在start_kernel()最后,rest_init()调用kernel_thread()函数,以0号进程为模板创

kernel_init进程,然后这个进程在建立init进程来执行/sbin/init,/bin/init,/etc/init,/bin/sh

 

 

7.  进程切换:

a)   Kernel/Sched.c中的Schedule()函数完成进程切换。Schedule()中主要调用了context_switch()

b)   Context_switch()函数流程如下:

                                                  i.      调用prepare_task_switch()获得rq的锁,关中断等切换准备工作。

                                                  ii.      如果nextmm为空,即为内核线程,则把nextactive_mm设为当前active_mm,并增加当前mm_count的计数。否则调用switch_mm()来切换地址空间。

a)   Switch_mm()中重新加载cr3寄存器,如果ldt不同的话重新加载ldt(一般都相同)

                                                  iii.      如果当前进程的mmNULL,则把active_mm设为NULL

                                                  iv.      调用switch_to()函数切换寄存器和内核栈。

                                                   v.      调用finish_task_switch()函数。与之前的prepare_task_switch()配对,释放一些之前占用的资源,释放被换出进程的相关资源:调用mmdrop()判断mm_struct的引用计数,如果为0则调用__mmdrop()释放。如果进程状态为TASK_DEAD>调用put_task_struct(),如果pcb不再使用,调用__put_task_struct()释放thread_infotask_struct()

c)   Switch_to()函数采用GCC内联汇编(先执行第二个“:”标记开始的输入段获得值,然后执行代码,最后执行第一个“:”标记的输出段写出值)。函数参数为上一个进程prev,下一个进程next,以及上一个进程lastlastprev同样为前一个进程的pcb,但是如果没有last,进程在被多次调度后,会丢失前一个进程的引用,即不知道自己的上一个进程是睡),切换流程为:

1.   保存标志flagsprev的栈中。

2.   保存prevebpprev栈中。

3.   保存prevespprev->thread.sp中。

4.   next->thread.sp获得sp恢复esp,此时esp已经切换到新的next的内核栈上。

5.   把标签“1”的地址保存到prev->thread.ip中。下次prev进程被调度时,将继续从1标签位置开始执行。

6.   next->thread.ip保存到next的栈上。

7.   跳转到__switch_to去执行C语言调用,完成其他的切换。当这个调用返回时,会从栈中popeip,此时弹出的eip就是next->thread.ip如果这个next进程已经被调度过,那么这个eip就是1标签的位置,如果是新建的进程,那么就是ret_from_fork()函数的起始地址

8.   next的栈中popebp,这个ebp就是之前next被调度出去时保存的,对应于第二步。

9.   next栈中pop,恢复标志寄存器,对应于第一步。

10.  最后把__switch_to返回的prev(在EAX中)写到last中,此时的last同样通过ebp+N寻址,但是此时是在next的栈中寻址,因此新的next栈中保存了以前的prev

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值