Chinaunix首页 | 论坛 | 博客
  • 博客访问: 155973
  • 博文数量: 24
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 225
  • 用 户 组: 普通用户
  • 注册时间: 2014-08-09 13:04
个人简介

人若是没有理想,那跟咸鱼有什么区别!

文章分类

全部博文(24)

文章存档

2018年(1)

2017年(2)

2016年(8)

2015年(11)

2014年(2)

我的朋友

分类: LINUX

2016-03-06 23:01:41

一。堆栈的基本原理
    1.在linux C程序执行过程中,整个堆栈段都在随着数据的压栈、出栈而增长、消减。 堆栈是C语言程序运行时必须的一个记录调用路径和参数 的空间。并且各种编译器构成的堆栈都不一致。
    2.堆栈的寄存器以及图解

    需要注意的是,堆栈是从高地址向低地址增长的。每次压栈,都会讲esp指针下移4个字节(32位机是4个字节)
    其他跟堆栈操作相关的寄存器:    
    cs : eip:总是指向下一条的指令地址
    ? 顺序执行:总是指向地址连续的下一条指令
    ? 跳转/分支:执行这样的指令的时候, cs : eip的值会 根据程序需要被修改
    ? call:将当前cs : eip的值压入栈顶, cs : eip指向被 调用函数的入口地址
    ? ret:从栈顶弹出原来保存在这里的cs : eip的值,放 入cs : eip中

3.堆栈的构造
    堆栈构造时,寄存器的运行流程如下:

点击(此处)折叠或打开

  1. pushl %ebp
  2. movl %esp, %eb
    堆栈结束时

点击(此处)折叠或打开

  1. movl %ebp,%esp
  2. popl %ebp
  3. ret
    此时ret一般是从函数进行返回时有的操作。详见linux内核学习 第一周
例子如下,我们把一段简单的代码进行编译,并且通过反汇编(objdump -S)来得到此段函数的汇编指令。

点击(此处)折叠或打开

  1. #include <stdio.h>

  2. int g(int a)
  3. {
  4. //    printf("a = %d\n", a);
  5.     return a + 3;
  6. }
  7. int main(int argc, char *argv[])
  8. {
  9.     return g(4);
  10. }
通过反汇编之后得到文件为:

点击(此处)折叠或打开

  1.  080483b4 <g>:
  2.  80483b4:    55                    push %ebp
  3.  80483b5:    89 e5                 mov %esp,%ebp
  4.  80483b7:    8b 45 08              mov 0x8(%ebp),%eax
  5.  80483ba:    83 c0 03              add $0x3,%eax
  6.  80483bd:    5d                    pop %ebp
  7.  80483be:    c3                    ret

  8.  080483bf <main>:
  9.  80483bf:    55                    push %ebp
  10.  80483c0:    89 e5                 mov %esp,%ebp
  11.  80483c2:    83 ec 04              sub $0x4,%esp
  12.  80483c5:    c7 04 24 04 00 00 00  movl $0x4,(%esp)
  13.  80483cc:    e8 e3 ff ff ff        call 80483b4 <g>
  14.  80483d1:    c9                    leave
  15.  80483d2:    c3                    ret
对比上下代码即可看到,在调用g函数(call 80483b4 <g>)后,g函数首先进行的是 push 与mov,这两条指令就构成了g 的栈底,知道g函数执行完ret 指令以后,g函数的栈就被清空,重新进入到main函数的栈中。也就是说,函数的调用,本质上是堆栈的建立与销毁的过程。

二。进程调度与堆栈的简易实现
1.LinuxC中内嵌汇编代码的实现:

点击(此处)折叠或打开

  1. #include <stdio.h>

  2. int main(int argc, char *argv[])
  3. {
  4.     int input, output, temp;
  5.     input = 4;
  6.     __asm__ __volatile__ (
  7.     "movl $9, %%eax;\n\t"    
  8.     "movl %%eax, %1;\n\t"
  9.     "movl %2,%%eax;\n\t"
  10.     "movl %%eax, %0;\n\t"
  11.     :"=m"(output),"=m"(temp)
  12.     :"r"(input)
  13.     :"eax"
  14.     );
  15.     printf("%d, %d\n", temp, output);
  16.     return 0;
  17. }
这是个简单的例子。第7行是内嵌汇编的格式。  8行:将立即数9赋值给eax寄存器。9行:将eax寄存器的值放入 参数1中,也就是下面第12行的temp, 10行:将参数2的值放入eax中,通过代码可以看到input的值被初始化成了4,第11行中,eax的值被付给了0号参数:output。
所以这个程序的输出结果为:9,4。
2.在内核中实现简易的进程调度程序,环境搭建可参考: 。
    a.关于中断,中断指当出现需要时,CPU暂时停止当前程序的执行转而执行处理新情况的程序和执行过程。在系统层面上,你能够随意切换当前运行的程序,比如随时从QQ切换到浏览器,但是在这个切换的过程中,就会把指定的程序切换到当前进程中,如何切换呢,这个就得看中断以及进程调度的工作。中断是计算机的一大特点,否则的话,计算机运行程序时就得从头运行到尾,不能被打断。可见中断是一种可以人为参与(软件)或者硬件自动完成的,使CPU发生的一种程序跳转。在linux中,对于中断的处理分为:
    第一步,保护现场就是进入中断程序保存需要用到的寄存器的数据。
    第二部,恢复现场,就是退出中断程序恢复保存寄存器的数据。
    b.根据mykernel的实验结果,内核中对于中断触发的处理,就可以通过上述的汇编指令来实现。
下面是代码解析,我们只对关键的一部分代码进行分析,其他的可以通过demo自己动手测试。
初始化部分:my_start_kernel函数

点击(此处)折叠或打开

  1. /* start process 0 by task[0] */
  2.     pid = 0;
  3.     my_current_task = &task[pid];
  4.     asm volatile(
  5.         "movl %1,%%esp\n\t"     /* set task[pid].thread.sp to esp。将当前esp指向 新进程的堆栈空间 */
  6.         "pushl %1\n\t"      /* push ebp,将 新进程的esp值进行压栈 */
  7.         "pushl %0\n\t"      /* push task[pid].thread.ip , 将当前进程的ip进行压栈*/
  8.         "ret\n\t"      /* pop task[pid].thread.ip to eip , 将当前进程的ip 赋给eip*/
  9.         "popl %%ebp\n\t"    /*将当前进程的sp赋给ebp,此时构造出了一个新的栈空间,即 代码中的task[pid].thread.sp*/
  10.         :
  11.         : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)    /* input c or d mean %ecx/%edx*/
  12.     );
参考注释可以知道,这段代码构造0号pid 的堆栈空间,这样第一次运行时将直接进入到0号进程。
在my_schedule函数中,切换进程的汇编执行的功能可以参考注释:

点击(此处)折叠或打开

  1. if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */
  2.     {
  3.         /* switch to next process */
  4.         asm volatile(    
  5.             "pushl %%ebp\n\t"      /* save ebp ,将原进程ebp压栈,ebp位于栈顶位置*/
  6.             "movl %%esp,%0\n\t"     /* save esp ,将esp赋值给prev的sp。这一句会将当前进程的栈顶指针保存在sp中*/
  7.             "movl %2,%%esp\n\t" /* restore esp ,将next的sp赋值给esp,也就是将esp指向了新的栈*/
  8.             "movl $1f,%1\n\t" /* save eip ,等同于将eip进行保存,设置prev的ip为 1f,这四句指令执行完毕后,esp会指向新进程的栈空间*/    
  9.             "pushl %3\n\t"     /*将next的ip进行压栈*/
  10.             "ret\n\t"      /* restore eip ,将ip 赋给当前的eip寄存器,接下来将从ip的位置开始执行新的进程。原先的进程已经被压栈处理。*/
  11.             "1:\t" /* next process start here */
  12.             "popl %%ebp\n\t"
  13.             : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
  14.             : "m" (next->thread.sp),"m" (next->thread.ip)
  15.         );
  16.         my_current_task = next;
  17.         printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);     
  18.     }
  19.     else
  20.     {
  21.         next->state = 0;
  22.         my_current_task = next;
  23.         printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);
  24.         /* switch to new process */
  25.         asm volatile(    
  26.             "pushl %%ebp\n\t"      /* save ebp ,将原进程ebp压栈,ebp位于栈顶位置*/
  27.             "movl %%esp,%0\n\t"     /* save esp ,将esp赋值给prev的sp这一句会将当前进程的栈顶指针保存在sp中*/
  28.             "movl %2,%%esp\n\t" /* restore esp ,将next的sp赋值给esp,也就是将esp指向了新的栈*/
  29.             "movl %2,%%ebp\n\t" /* restore ebp ,初始化栈底指针。此时栈为空*/
  30.             "movl $1f,%1\n\t" /* save eip ,保存prev  的ip。*/    
  31.             "pushl %3\n\t"
  32.             "ret\n\t"      /* restore eip */
  33.             : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
  34.             : "m" (next->thread.sp),"m" (next->thread.ip)
  35.         );
  36.     }
当state为0时,说明这个进程之前已经在运行了,此时可以继续执行,就切换到下一个进程。当下一个进程的state不为0时,那么也就是说下一个进程还从来都没有执行过,所以这一段内联汇编的作用是开始执行一个新进程。如此将会完成从一个process切换到另一个process的功能。
上述demo使用的时间片轮转的方式控制着进程的切换,这里主需要了解当进程进行切换时的堆栈的变化。
其中时间片轮转: my_timer_handler  ,此函数注册在  arch/x86/kernel/time.c 中。


作者程大鹏, 转载请注明出处    http://blog.chinaunix.net/blog/post.html
Linux内核分析》MOOC课程 ”




阅读(1960) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~