Linux内核设计艺术笔记(三)

从前面设备环境的初始化以及进程0的激活,计算机中已经存在一个3特权级的进程,它的第一项工作就是作为父进程调用fork函数创建第一个子进程----进程1。之后的所有进程都是基于父子进程创建机制由父进程创建出来。

三、进程1的创建及执行

1)进程1的创建

       在main函数里面的执行代码只有简短的几行,但是看完这章之后,我是不得不说说Linux博大精深。执行代码如下:

void main()
{
	...
	sti();			        // 所有初始化工作都做完了,开启中断
	move_to_user_mode();	// 进程0翻转到3特权级
	
	if (!fork()) 
	{	
		init();             // 子进程1执行部分
	}

	for(;;) pause();        // 父进程0执行部分
}

        在这里,父进程0调用fork函数实质是执行系统调用sys_fork(),代码如下:

int fork(void) 
{ 
	volatile long __res; 
	__asm__ volatile ("int $0x80"   // int 0x80是所有系统调用函数总入口
		: "=a" (__res)              // 输出部分,将_res赋给eax
		: "0" (__NR_fork)           // 输入部分,"0":同上,寄存器eax
	)
	if (__res >= 0) 		           // int 0x80中断返回后,执行该语句
		return (init) __res; 
	errno = -__res; 	            // 否则置出错号,并返回-1
	return -1;
}

这段代码执行步骤如下:

      先执行输入部分 :"0" (__NR_fork),找出系统调用表中sys_fork函数的位置;接着产生int $0x80中断,然后CPU从3特权级的进程0跳入0特权级的内核代码中执行,在这里就是跳转至_sys_fork处,执行完毕后输出__res值到eax寄存器并返回,然后执行if (__res >= 0)语句。中断int 0x80的执行路线,如下图:

                                    

如图示,在执行_sys_fork过程中,首先,内核需要调用find_empty_process()函数为进程1分配可用的新进程号,然后开始调用copy_process()函数执行。在之前激活进程0的过程中,内核中含有“进程0的task_struct ”和“进程0的页表项”等专属进程0的管理信息,所以,进程0将在copy_process函数中体现父子进程创建机制的工作,具体包括:

      1) 为进程1创建task_struct,将进程0的task_struct的内容复制给进程1;

      2) 为进程1的task_struct、TSS做个性化设置;

      3) 为进程1创建第一个页表,将进程0的页表项内容赋给这个页表;

      4) 进程1共享进程0的文件;

      5) 设置进程1的GDT项;

      6) 最后将进程1设置为就绪态,使其能够参与进程间的轮转。

       进入copy_process()函数(请参考linux0.11源码),首先在主内存申请一空闲页面,用于进程1的task_struct及内核栈;然后,将父进程0的task_struct赋给子进程1,并开始子进程的个性化设置,然后特别需要注意的一点,在这里会将子进程1中TSS的eip赋上fork执行中断时的下一行代码if (__res >= 0)的地址和eax赋初值0,而父进程0的eax将是子进程1的进程号,我们知道,fork调用会返回两个值,父进程返回子进程号,子进程返回0,就是在这体现的!之后,就要开始设置进程1的分页管理了。

       重要:每个进程都要加载属于自己的代码、数据,这些代码、数据的寻址都是用逻辑地址,即段+偏移的形式表示的。CPU首先自动将逻辑地址计算为可寻址的线性地址,然后,根据操作系统对页目录表,页表的设置,自动将线性地址转换为分页的物理地址。所以,进程1的的分页管理设置路线也如此,先在进程的线性地址空间设置代码段、数据段,然后设置页表,页目录,这里是通过调用cpy_mem()函数实现。

       完成分页设置之后,此时,进程1还没有加载自己的程序代码,它的页表又是从父进程0的页表复制而来,所以,他们的管理完全一致,即暂时和父进程共享一套内存页面管理结构。当子进程1有了自己的程序,再将关系解除,并重新组织自己的内存管理结构。这里,会涉及到写时复制的技术,以后再说咯~

       最后,copy_process中会让子进程1会与父进程0文件共享,将进程1的TSS和LDT挂接至GDT中,再设置进程1的状态为就绪态,返回进程号1。至此,进程1的创建工作完成,此时,进程1已经具备父进程0的全部能力,可以在主机中正常运行。

      此时fork()函数中的中断返回eax上的值,为进程号1,也即fork函数的返回值,main()函数检测到if(!fork())为假,不执行init(),进程0执行到for循环,调用pause()函数。
2)进程1的调度

       进程0此刻执行for循环里的pause()函数,pause函数与fork函数一样,都会执行系统调用,在这里,会调用sys_pause函数,代码如下:

int sys_pause (void)
{
	current->state = TASK_INTERRUPTIBLE;
	schedule ();
	return 0;
}

      首先就会对当前进程设置可中断等待状态,然后进行调度函数schedule()。此函数会遍历系统内所有进程,然后根据调度策略找到处在就绪态的相应进程,在这就是切换到进程1执行。

      这个时候进程1从哪开始执行?这是个问题。其实,在schedule里面,首先会将之前的进程1的各寄存器值恢复,然后,进程1就从TSS中eip寄存器的值处开始执行,就是前面讲的fork中的if (__res >= 0)处。此处的__res为之前创建进程1时里的个性化设置的0,所以,在main函数里面,fork函数返回值为假,进入init()。。。

      之后就是进程1的执行了,它会设置硬盘信息,格式化虚拟盘,加载根文件系统等等工作,后续的事反正我就不是太感兴趣了,笔记略过。。。等这些事做完,开始进程2的创建及执行,为系统进入怠速状态做最后准备。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值