深入分析计算机的函数调用与进程切换

作者:奋斗的白杨(杨延生)
注:原创作品转载请注明出处

《Linux内核分析》 MOOC课程http://mooc.study.163.com/course/USTC-1000029000


一、计算机是如何工作的?——三个法宝


1. 存储程序计算机工作模型,计算机系统最最基础性的逻辑结构;
2. 函数调用堆栈,高级语言得以运行的基础,只有机器语言和汇编语言的时候堆栈机制对于计算机来说并不那么重要,但有了高级语言及函数,堆栈成为了计算机的基础功能;
enter  // 进入函数
pushl %ebp
movl %esp,%ebp
leave  // 函数执行结束,离开
movl %ebp,%esp
popl %ebp

3. 中断,多道程序操作系统的基点,没有中断机制程序只能从头一直运行结束才有可能开始运行其他程序。


二、函数调用栈的机制

重要是用到了一个函数堆栈的框架。

一般在call指令(即,调用函数)前,会先eip(eip现在通常指向现在执行指令的下一条指令)压栈,便于当函数调用结束时,从该eip指向的位置继续执行。 如果函数调用有参数,一般也会在保存eip前,先将函数参数压入栈中。

新的eip将指向调用函数的地址,后面便从始从调用的函数处执行代码。

在进入到调用函数内部时,通过要先建立堆栈框架,一般就是先将之前的ebp压栈,再将esp赋给ebp,这样新的函数就有一个空的栈,一切新的局部变量都将在新的栈中保存。

将调用的函数执行完后,会先将esp退回到ebp处,同时将之前存储的ebp的内容又赋给ebp,恢复到函数调用前的样子。


三、中断机制

中断机制是多道程序设计的基础,不然程序就会一直顺序执行直到该程序结束。

中断程序的核心就是对中断的处理,需要处理进程之间的上下文切换。涉及到保护现场和恢复现场。

在进程要进行切换时,都需要将该进程的ebp压入内核栈,同时esp保存在PCB中,eip等都要保存在PCB中。同时将新的进程PCB中的esp赋给esp,

四、实验截图与代码





五、中断分析


#include "mypcb.h"
extern tPCB task[MAX_TASK_NUM];
extern tPCB *my_current_task;
extern volatile int my_need_sched;
volatile int time_count = 0;//计时器

/*
 * Called by timer interrupt.
 */
void my_timer_handler(void)
{
#if 1
    if(time_count%1000 == 0 && my_need_sched != 1)
    {
        printk(KERN_NOTICE">>> my timer handler here <<<\n");
        my_need_sched = 1;//此时置my_need_sched为1
    }
    time_count++;
#endif
    return;
}
void my_schedule(void)
{
    tPCB *next;
    tPCB *prev;//定义即将要发生切换的任务
    //错误处理
     if(my_current_task == NULL||my_current_task->next == NULL)
    {
        return;
    }
    printk(KERN_NOTICE ">>>my_schedule<<< \n");
    next = my_cueernt_task->next;
    prev = my_current_task;
    if(next.state==0)//两个正在运行的进城之间进行切换
    {
        my_current_task = next;
        printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);
        //切换进程,内嵌汇编代码
        asm volatile(
                    "pushl %%ebp\n\t" //save ebp
                    "movl %%esp,$0\n\t" //save esp,prev执行到此,将当前的esp保存到prev的thread的sp
                    "movl $2,%%esp" //将next的esp移入esp
                    "movl $1f,$1"   //将prev的eip压栈
                    "pushl %3\n\t"
                    "ret\n\t"
                    "1:\t"
                    :"=m"(prev->thread.sp),"=m"(prev->thread.ip)
                    :"m"(next->thread.sp),"m"(next->thread.ip)
    }
    else//next 是一个新进程,重来没有执行过,所以初始的堆栈是空的
    {
        next.state=0
        my_current_task = next;
        printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);
        //切换进程,内嵌汇编代码
        asm volatile(
                    "pushl %%ebp\n\t" //save ebp
                    "movl %%esp,$0\n\t" //save esp,prev执行到此,将当前的esp保存到
                    "movl $2,%%esp" //将next的esp移入esp
                    "movl $2,%%ebp" //第一次执行,next的堆栈是空的
                    "movl $1f,$1"   //将prev的eip压栈
                    "pushl %3\n\t"
                    "ret\n\t"
                    :"=m"(prev->thread.sp),"=m"(prev->thread.ip)
                    :"m"(next->thread.sp),"m"(next->thread.ip)
    }
}

所有进程第一次执行时

首先,它设置了两个变量,当前进程为prev,下一个进程为next。然后,判断下一个进程的状态是否为可运行(即是否为0),由于除了0号进程以外,其它进程都是第一次执行,因此它们的状态都为不可运行,即为-1,走else路径。
我们看看else路径,首先,它即将执行的1号进程状态置为运行状态,然后用内联汇编进行进程切换了。首先,它保存0号进程的ebp寄存器值到0号进程的栈里,然后把esp寄存器值保存0号进程的thread.ip里。接着,把esp寄存器值指向1号进程的esp值,即1号进程栈的栈顶,接着把ebp值也只想同样的位置。我们知道,当一个函数刚开始执行时(即完成了push ebp;mov esp ,ebp),esp和ebp是指向同样的位置的。接着,执行这句,mov $1f,%1,这是保存0号进程的eip的值,让再度切换到0号进程时可以继续执行,1f是个在if 分支里面的地址,等我们分析到if分支时,我们再详细说明,这里要明白这是保存了0号进程的ip值就好。 再接着,把1号进程的入口值压栈,再ret,弹出给eip,这样1号进行就开始执行了,执行my_process函数。类似地,当1号进程去切换到2号进程时,过程也类似,直到所有进程都执行了一遍,从最后一个进程切换到0号进程时,情况不一样了。

所有进程非第一次执行时

当最后一个进程切换到0号进程时,由于0号进程已经可以运行了,所以它走if分支。那我们来分析一下切换的内联汇编代码。首先,把当前进程的ebp值压栈进行保存,然后把当前进程的esp值存入到对应的trhead.sp里面。再接着,开始切换栈了,把下一个进程也就是1号进程的esp值赋給esp寄存器,也就是让esp切换到0号进程的栈空间。再接着mov 1f,%1,即保存当前进程的eip值,而1f是这段内敛汇编最后一条指令(popl %ebp)的值,这样当再次切换回来时,第一条执行的语句就是pop ebp。再接着,把下一个进程也就是0号进程的ip值压栈,再ret,弹给EIP,这样就切换到下一个进程了,也就是切换回0号进程了,然后0号进程第一句就是popl %ebp。以后,所有的进程切换都会执行if分支了,过程跟上面描述的类似。注意,这段代码,可能有个难点就是 (movl $1f,%1)1f是最后一条指令的地址,这句就是保存eip的地址,这样让所有进程切换回来的时候,第一句就执行popl %ebp。

六、总结


存储程序计算机在运行过程中一个核心的问题便是 保存与恢复,不论是函数调用还是进程切换,在进入新的函数或进程前,一定要对之前的状态进行保存,以便以在回头时继续执行。该思想体现在计算机工作中的方方面面,可以举一反三。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值