实验5-基于内核栈切换的进程切换

实验报告

1. 实验内容

修改进程切换方式,由TSS模式,切换到栈模式。

2. 实验步骤

  • 修改schd.c 中的switch_to 函数;

    原先此函数中使用ljmp到tr寄存器指定的描述符中获取tss结构体的的地址,并map的寄存器中(包括CS:IP,SS:SP,AX,CX,DX…);
    现在使用栈模式即需使在代码中手动将esp由旧的内核栈地址切换为新栈地址,然后pop出相应寄存器,直到完成ret指令,从新栈中获取新进程的eip,cpu才执行新进程。另外定义了一个tss的全局结构体变量,用以在cpu执行int 80 时寻找到内核栈,因为cpu总是根据当前tr获取tss描述符,再找到内存中tss位置获得栈,因为switch时,需要重置tss结构体中内核栈地址。 具体步骤为

    • Push BP,CX,BX,AX寄存器
    • 切换PCB
    • TSS中的内核栈指针的重写
    • 切换内核栈
    • 切换LDT
    • POP AX,BX,CX,BP寄存器
    • Ret
  • 修改Fork函数

    • 增加Kernal Stack 指针
    • 去除task中tss结构体的赋值
    • 拷贝将父进程中的用户态寄存器信息[SS,ESP,EFLAGS,CS,IP]
    • Push first_return_from_kernel 标号地址
    • push AX,BX,CX,BP寄存器
  • 修改task 0 初始值,增加结构体中对应的stack的地址

  • 增加tss0全局结构体变量

3. 代码修改

switch_to

stack_switch_to:
    pushl %ebp
    movl %esp,%ebp
    pushl %ecx
    pushl %ebx
    pushl %eax
    pushl %es
    pushl %gs
    pushl %esi
    pushl %edi
    pushl %edx
    movl 8(%ebp),%ebx
    cmpl %ebx,current
    je 1f
    movl %ebx,%eax #exchange pcb
    xchgl %eax,current
    movl tss,%ecx
    addl $4096,%ebx
    movl %ebx,ESP0(%ecx)# rewrite the  tss0->sp
    movl %esp,KERNEL_STACK(%eax) # eax pointer to previous stack 
    movl 8(%ebp),%ebx 
    movl KERNEL_STACK(%ebx),%esp # #exahcge stack 
    movl 12(%ebp),%ecx # load ldt addr 
    lldt %cx #load ldt to ldtr
    movl $0x17,%ecx 
    mov %cx,%fs
    cmpl %eax,last_task_used_math 
    jne 1f
    clts
    1:popl %edx
    popl %edi
    popl %esi
    popl %gs
    popl %es
    popl %eax
    popl %ebx
    popl %ecx
    popl %ebp
    ret

fork.c

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)
{
    struct task_struct *p;
    int i;
    struct file *f;
    long* krnstack;
    p = (struct task_struct *) get_free_page();
    if (!p)
        return -EAGAIN;
    fprintk(3, "%ld\t%c\t%ld\n", last_pid, 'N', jiffies);
    task[nr] = p;
    *p = *current;    /* NOTE! this doesn't copy the supervisor 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;
    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;
    }
    // rearrange stack for usermode
    krnstack = (long*)(PAGE_SIZE + (long)p);//stack pionter
    *(--krnstack) = ss & 0xffff;
    *(--krnstack) = esp;
    *(--krnstack) = eflags;
    *(--krnstack) = cs & 0xffff;
    *(--krnstack) = eip;
    // stack for schedule
    *(--krnstack) = ds & 0xffff;
    *(--krnstack) = es & 0xffff;
    *(--krnstack) = fs & 0xffff;
    *(--krnstack) = gs & 0xffff;
    *(--krnstack) = esi;
    *(--krnstack) = edi;
    *(--krnstack) = edx;
    *(--krnstack) = first_return_from_kernel;
    *(--krnstack) = ebp;
    *(--krnstack) = ecx;
    *(--krnstack) = ebx;
    *(--krnstack) = 0; //0 for res
    *(--krnstack) = es;
    *(--krnstack) = gs;
    *(--krnstack) = esi;
    *(--krnstack) = edi;
    *(--krnstack) = edx;
    p->kernelstack = krnstack;
    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));
    set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
    p->state = TASK_RUNNING;    /* do this last, just in case */
    fprintk(3, "%ld\t%c\t%ld\n", p->pid, 'J', jiffies);
    return last_pid;
}

first_return_from_kernel

first_return_from_kernel:
     popl %edx
     popl %edi
     popl %esi
     pop %gs
     pop %fs
     pop %es
     pop %ds
     iret

sched.h

 INIT_TASK \
/* state etc */    { 0,15,15,PAGE_SIZE+(long)&init_task, \
/* signals */    0,{{},},0, \
/* ec,brk... */    0,0,0,0,0,0, \
/* pid etc.. */    0,-1,0,0,0, \
/* uid etc */    0,0,0,0,0,0, \
/* alarm */    0,0,0,0,0,0, \
/* math */    0, \
/* fs info */    -1,0022,NULL,NULL,NULL,0, \
/* filp */    {NULL,}, \
    { \
        {0,0}, \
/* ldt */    {0x9f,0xc0fa00}, \
        {0x9f,0xc0f200}, \
    }, \
/*tss*/    {0,PAGE_SIZE+(long)&init_task,0x10,0,0,0,0,(long)&pg_dir,\
     0,0,0,0,0,0,0,0, \
     0,0,0x17,0x17,0x17,0x17,0x17,0x17, \
     _LDT(0),0x80000000, \
        {} \
    }, \
}

4. 实验问题

问题 1

针对下面的代码片段:

movl tss,%ecx
addl $4096,%ebx
movl %ebx,ESP0(%ecx)

回答问题:

  • (1)为什么要加 4096;
  • (2)为什么没有设置 tss 中的 ss0。

(1)这里ebx指向的是下一个进程的PCB。加上4096后,即为一个进程分配4K的空间,栈顶即为内核堆栈的指针,栈底为进程的PCB。
(2)因为进程的切换不靠tss进行,但CPU的机制造成对每个进程仍然会有TR寄存器、tss的设置等内容,所以可以让所有进程都共用tss0的空间。所以不需要设置tss0。

问题 2

针对代码片段:

*(--krnstack) = ebp;
*(--krnstack) = ecx;
*(--krnstack) = ebx;
*(--krnstack) = 0;

回答问题:

  • (1)子进程第一次执行时,eax=?为什么要等于这个数?哪里的工作让 eax 等于这样一个数?
  • (2)这段代码中的 ebx 和 ecx 来自哪里,是什么含义,为什么要通过这些代码将其写到子进程的内核栈中?
  • (3)这段代码中的 ebp 来自哪里,是什么含义,为什么要做这样的设置?可以不设置吗?为什么?

(1)这里eax等于0,即返回值。这样设置就可以用if(!fork())来判断是不是子进程。最后一行完成了这件事。
(2)ebx、ecx都来自copy_process函数,存放的是父进程的ebx和ecx。通过将父进程的参数压入栈中,可以保证子进程回到用户态运行时具有和父进程相同的环境。
(3)ebp也来自copy_process函数,存放的是父进程的用户栈指针。即在fork刚刚执行完copy_process的时候,它的用户栈是父进程的用户栈,而非自己的用户栈。当子进程进行其他操作时,造成需要的栈将要与父进程不同了,才会创建自己的用户栈。这么做的好处时当一些子进程什么都不做,系统不用分配额外的空间。当然也可以一创建子进程就为它分配一个新的栈,esp指向新的栈顶。

问题 3

为什么要在切换完 LDT 之后要重新设置 fs=0x17?而且为什么重设操作要出现在切换完 LDT 之后,出现在 LDT 之前又会怎么样?

3.切换LDT时,会切换进程使用的用户栈。此时,会利用fs查找它指向的描述符表,并且取出隐藏部分:段基址与段限长放在cs。如果查完表不重新赋值,下次查表的时候就还会查这个表,不能起到切换LDT的作用。放在切完LDT是因为,switch_to之后下一个进程可能会查其他表,不在切完LDT的时候赋值就会影响其他表的查找。

5.实验结果

在这里插入图片描述

6.实验收获

通过本次实验,我基本上了解了如何将TSS切换部分去掉,取而代之的是使用基于堆栈的切换程序,可以在不使用ljmp指令在实现任务切换时大概需要200多个时钟周期。而通过堆栈的切换还可以使用指令流水的并行优化技术,同时还可以使得CPU的设计变得简单。采用堆栈的切换可以更快。

感觉在实验楼上做实验还是要举一反三,感觉提供的样例操作还是存在一些问题,在询问同学和自行百度以后,解决了部分问题,最终完成了实验,得到了实验结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值