进程1的创建与轮转

本文详细阐述了进程1的创建过程,包括由进程0通过`fork()`调用,`int 0x80`中断进入系统调用,`copy_process()`函数的执行,涉及任务结构复制、内存分配、页表共享等。在进程创建完成后,文章讨论了进程的轮转,通过`pause()`函数触发`sys_pause()`,进而调用`schedule()`和`switch_to()`实现进程切换。在切换过程中,CPU自动从TSS段加载寄存器内容,执行新进程的任务。
摘要由CSDN通过智能技术生成

一. 进程1的创建

进程1是由进程0创建的,创建过程如下:

main() { fork() } ---> _syscall0(int,fork) ---> int 0x80中断进入系统调用(特权级由3切换到0) ---> _sys_call --->sys_fork() ---> find_empty_process()为进程申请一个可用的进程号pid ---> copy_process(),在该函数中初始化创建的进程1的TSS结构,将tss段插入到gdt描述符表中,并设置好TSS的段基址和段长度。

  进程1的创建主要是通过copy_process()函数来完成,copy_process()函数主要完成以下任务:

  (1)调用get_free_page()函数,在主内存申请一个空闲页面,并将申请的空闲页面清零,用于进程1的task_struct及内核栈。

  (2)将父进程0的task_struct的内容复制给进程1的task_struct

  (3)为进程1的task_struct、tss做个性化设置:特别要注意,将进程1的tss.eip设置为创建过程中int 0x80中断程序执行时,保护现场压入栈中的EIP了,这一点会影响后面轮转后,进程1的执行。

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

  (5)进程1共享进程0的文件

  (6)设置进程1的GDT项

  (7)最后将进程1设置为就绪状态,使其可以参与进程间的轮转

代码如下:

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)
        //参数是int 0x80、system_call、sys_fork调用时多次累积压栈的结果
{
    struct task_struct *p;
    int i;
    struct file *f;

    p = (struct task_struct *) get_free_page();
    if (!p)
        return -EAGAIN;
    task[nr] = p;
    //将父进程的task_struct内容拷贝到子进程
    *p = *current;    /* NOTE! this doesn't copy the supervisor stack */ //curent是task_struct类型,并未包含task_union联合体中的stack部分,此处的赋值相当于拷贝内容
    //设置子进程
    p->state = TASK_UNINTERRUPTIBLE;
    p->pid = last_pid;
    p->father = current->pid;
    p->counter = p->priority;
    p->signal = 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; //esp0是内核栈指针
    p->tss.ss0 = 0x10; //0x10,段选择符:0特权级、GDT、数据段
    p->tss.eip = eip; //EIP指向_syscall0(int,fork)调用中的if(__res>0),会影响后面进程切换后(switch_to(n)执行完ljmp %0)的程序的执行
    p->tss.eflags = eflags;
    p->tss.eax = 0; //决定main()函数中if(!fork())后面的分支语句
    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;
    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); //挂接子进程的LDT
    p->tss.trace_bitmap = 0x80000000;
    if (last_task_used_math == current)
        __asm__("clts ; fnsave %0"::"m" (p->tss.i387));
    if (copy_mem(nr,p)) { //设置子进程的代码段、数据段及创建、复制子进程的第一个页表
        task[nr] = NULL;
        free_page((long) p);
        return -EAGAIN;
    }
    for (i=0; i<NR_OPEN;i++)
        if (f=p->filp[i])
            f->f_count++;
    if (current->pwd)
        current->pwd->i_count++;
    if (current->root)
        current->root->i_count++;
    if (current->executable)
        current->executable->i_count++;
    set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss)); //FIRST_TSS_ENTRY = 4对应GDT表中TSS0
    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_process()输入参数都是之前入栈的数据,按照顺序如下:

1)内联函数_syscall0(int,fork) ---> 执行int 0x80中断服务程序:为中断返回保护现场,CPU硬件自动将ss esp eflags cs eip压栈

2)_system_call ---> sys_fork():程序将ds es fs edx ecx ebx压栈

3)sys_fork() ---> copy_process(): 将gs esi edi ebp eax压栈,其中eax将会作为nr


二. 进程1的轮转

进程跳转流程:

当进程1创建完成后会回到main()中,执行main()函数中的pause()函数 --- >_syscall0(int,pause) --->int 0x80中断进入系统调用 ---> _sys_call --->sys_pause() ---> schedule() --- switch_to() 完成进程切换。


下面解析一下进程调度部分的代码:

引用两篇文章:http://blog.csdn.net/fukai555/article/details/41912735

                            http://blog.csdn.net/smallmuou/article/details/6837087

//代码路径:/kernel/sched.c
void schedule(void)
{
	int i,next,c;
	struct task_struct ** p;

	for(p = &LAST_TASK ; p > &FIRST_TASK ; --p) //LAST_TASK = task[63]: task[63]是struct task_struct*类型
		if (*p) {
			if ((*p)->alarm && (*p)->alarm < jiffies) { //如果设置了定时或定时时间片已过
					(*p)->signal |= (1<<(SIGALRM-1)); //设置SIGALRM:SIGALRM是定时器终止时发送给进程的信号
					(*p)->alarm = 0;
				}
			if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&
			(*p)->state==TASK_INTERRUPTIBLE)
				(*p)->state=TASK_RUNNING;
		}

	while (1) {
		c = -1;
		next = 0;
		i = NR_TASKS;
		p = &task[NR_TASKS];
		while (--i) { //找出counter值最大的,state为TASK_RUNNING的进程
			if (!*--p)
				continue;
			if ((*p)->state == TASK_RUNNING && (*p)->counter > c) 
				c = (*p)->counter, next = i;
		}
		if (c) break;
		for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
			if (*p)
				(*p)->counter = ((*p)->counter >> 1) + //设置进程初始时时间片大小为: 进程priority值(初始时counter = 0)
						(*p)->priority;
	}
	switch_to(next);
}

//代码路径:/include/sched.h
#define FIRST_TSS_ENTRY 4
#define FIRST_LDT_ENTRY (FIRST_TSS_ENTRY+1)
#define _TSS(n) ((((unsigned long) n)<<4)+(FIRST_TSS_ENTRY<<3)) //生成TSS的段选择符
#define _LDT(n) ((((unsigned long) n)<<4)+(FIRST_LDT_ENTRY<<3))

#define switch_to(n) {\
struct {long a,b;} __tmp; \                 //为ljmp的CS、EIP准备的数据结构
__asm__("cmpl %%ecx,_current\n\t" \     //比较进程n与当前进程
	"je 1f\n\t" \                           //如果相等,就没必要切换,退出
	"movw %%dx,%1\n\t" \                    //把edx低字部分赋给tmp.b
	"xchgl %%ecx,_current\n\t" \            //task[n]与task[current]交换
	"ljmp %0\n\t" \                         //
	"cmpl %%ecx,_last_task_used_math\n\t" \ //比较上次是否使用过协处理器
	"jne 1f\n\t" \
	"clts\n" \                              //清除CR0中的任务切换标志
	"1:" \
	::"m" (*&__tmp.a),"m" (*&__tmp.b), \     //a对应EIP,b对应CS
	"d" (_TSS(n)),"c" ((long) task[n])); \   //edx是TSSn的索引号,ecx是task[n]
}
说明:

(1)_TSS(n): 生成TSS的段选择符

TI=0 :表示段描述符在GDT中

TI=1 :表示段选择符在LDT中

RPL:表示优先级


(2)GDT表


(3)进程切换过程:switch_to(n)

  首先判断传递进来的任务号n是否是当前任务号,如果是就直接跳转到1,跳出;否则,将current指向task[n]。在将current指向task[n]之前将edx的低字部分即task[n]的TSS的段选择符赋给__tmp.b的低16位,然后将current指向task[n],之后执行ljmp %0\n\t,按照语法格式理解相当于 ljmp *__tmp.a,也就是跳转到地址&__tmp.a中包含的48bit逻辑地址(因为使用内存操作数实现长跳转时:ljmp *mem48,其中内存位置mem48处存放目标逻辑地址: 高16bit存放的是seg_selector,低32bit存放的是offset)。

ljmp %0

  在保护模式下CPU进行长跳转,如果发现段选择符指向TSS段,那么CPU将会忽略偏移,自动将TSS段的内容加载到当前CPU寄存器中,比如eax,ldr,cs,ss,esp,eip等,其实就是104字节的TSS结构的所有成员变量赋值给CPU寄存器(注意这里是ss0和esp0),从而任务切换到该TSS对应的进程。再次强调这个过程是自动完成的。

  注意,执行完ljmp %0后,后面的 cmpl %%ecx,_last_task_used_math还没执行,int 0x80中断没有返回,CPU就开始执行进程1了。现在CPU中的EIP是copy_process()中为进程1的tss.eip,里面的值是:内联函数_syscall0(int,fork) ---> 执行int 0x80中断服务程序:为中断返回保护现场,CPU硬件自动将ss esp eflags cs eip压栈,时的值,因此进程1接下来会去执行fork中int 0x80中断返回时下一行代码,即if(__res>=0),所以之后将从这行代码开始执行,不会像创建进程1那样原路返回。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值