作者:武西垚

深入理解函数调用堆栈

堆栈是C语言程序运行时必须的一个记录调用路径和参数的空间

堆栈的作用

  • 函数调用框架
  • 传递参数
  • 保存返回地址
  • 提供局部变量空间

堆栈相关的寄存器

  • esp,堆栈指针,指向栈顶
  • ebp,基址指针,指向栈底,在C语言中用作记录当前函数调用基址。

其他关键寄存器

cs(代码段寄存器) : eip:总是指向下一条的指令地址

  • 顺序执行:总是指向地址连续的下一条指令
  • 跳转/分支:执行这样的指令的时候, cs : eip的值会根据程序需要被修改

参数传递与局部变量

  • 建立框架(相当于 call 指令)
  • push %ebp

movl %esp,%ebp

  • 拆除框架(相当于 ret 指令)
  • movl %ebp,%esp

pop  %ebp

函数返回时一定会拆除框架,建立和拆除是一一对应的。

  • 传递参数

在建立子函数的框架之前,局部变量的值保存在调用者堆栈框架中,所以在子函数框架建立之前可以采用变址寻址的方式将变量值入栈。

函数的返回值通过eax寄存器传递

借助Linux内核部分源代码模拟存储程序计算机工作模型及时钟中断

mykernel实验思想

中断实现了多道程序设计,再各个程序的执行流之间来回切换,CPU将程序的ebp压入栈,并指向一个中断处理程序,从而由CPU和内核代码共同实现了保存现场和恢复现场

C代码中嵌入汇编代码的写法

__asm__(

汇编语句模板:

输入部分:

输出部分:

破坏描述部分:);

实验——mykernel基础上构造一个简单的操作系统内核

实验过程

本次实验是通过分析一个简单的时间片轮转多道程序内核源代码来理解操作系统的工作原理。

首先运行此内核,可以看到提供可一个代码在内核中运行的上下文环境。

然后cd mykernel 找到mymain.c和myinterrupt.c两个源代码,进入https://github.com/mengning/mykernel/blob/master可以找到本次实验需要的几个重要的源代码。然后将上面两个代码修改成网站中找到的代码,除此之外还要加上mypcb.h。

返回之后再次运行,可看到0、1、2、3几个进程相互切换。

源代码分析

mypcb.h

这个代码的目的是定义一个进程控制块(PCB)。

/*

*  linux/mykernel/mypcb.h

*

*  Kernel internal PCB types

*

*  Copyright (C) 2013  Mengning

*

*/

#define MAX_TASK_NUM        4

#define KERNEL_STACK_SIZE   1024*8

/* CPU-specific state of this task */

struct Thread {

unsigned long       ip;//用于eip的保存

unsigned long       sp;//用于esp的保存

};

typedef struct PCB{//用于表示一个进程,定义了进程管理相关的数据结构

int pid;

volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */

char stack[KERNEL_STACK_SIZE];

/* CPU-specific state of this task */

struct Thread thread;

unsigned long   task_entry;

struct PCB *next;

}tPCB;

void my_schedule(void);//调用了my_schedule,表示调度器

mymain.c

/*

*  linux/mykernel/mymain.c

*

*  Kernel internal my_start_kernel

*

*  Copyright (C) 2013  Mengning

*

*/

#include <linux/types.h>

#include <linux/string.h>

#include <linux/ctype.h>

#include <linux/tty.h>

#include <linux/vmalloc.h>

#include "mypcb.h"

tPCB task[MAX_TASK_NUM];

tPCB * my_current_task = NULL;

volatile int my_need_sched = 0;//定义一个标志,用来判断是否需要调度

void my_process(void);

void __init my_start_kernel(void)

{

int pid = 0;//初始化一个进程0

int i;

/* Initialize process 0*/

task[pid].pid = pid;

task[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped */

task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;

//定义进程0的入口为my_process

task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];

task[pid].next = &task[pid];

//因为一开始系统里只有进程0,所以这一行代码表示的是pid的next还是指向自己

/*fork more process */

//创建更多其他的进程,在初始化这些进程的时候可以直接拷贝0号进程的代码

for(i=1;i<MAX_TASK_NUM;i++)

{

memcpy(&task[i],&task[0],sizeof(tPCB));

task[i].pid = i;

task[i].state = -1;

task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1];

//每个进程都有自己的堆栈,把创建好的新进程放到进程列表的尾部,这样就完成了创建

task[i].next = task[i-1].next;

task[i-1].next = &task[i];

}

/* start process 0 by task[0] */

pid = 0;

my_current_task = &task[pid];

asm volatile(

"movl %1,%%esp\n\t"     /* set task[pid].thread.sp to esp */

"pushl %1\n\t"          /* push ebp */

"pushl %0\n\t"          /* push task[pid].thread.ip */

"ret\n\t"               /* pop task[pid].thread.ip to eip */

"popl %%ebp\n\t"

:

: "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)

/* input c or d mean %ecx/%edx*/

);

}

/*  %0表示参数thread.ip,%1表示参数thread.sp。

movl %1,%%esp表示把参数thread.sp放到esp中;

接下来push %1,又因为当前栈为空,esp=ebp,所以等价于push ebp;

然后push thread.ip;ret等价于pop thread.ip;最后pop ebp  */

void my_process(void)//定义所有进程的工作,if语句表示循环1000万次才有机会判断是否需要调度。

{

int i = 0;

while(1)

{

i++;

if(i%10000000 == 0)

{

printk(KERN_NOTICE "this is process %d -\n",my_current_task->pid);

if(my_need_sched == 1)

{

my_need_sched = 0;

my_schedule();

}

printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid);

}

}

}

myinterrupt.c

/*

*  linux/mykernel/myinterrupt.c

*

*  Kernel internal my_timer_handler

*

*  Copyright (C) 2013  Mengning

*

*/

#include <linux/types.h>

#include <linux/string.h>

#include <linux/ctype.h>

#include <linux/tty.h>

#include <linux/vmalloc.h>

#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.

* it runs in the name of current running process,

* so it use kernel stack of current running process

*/

void my_timer_handler(void)

/*  用于设置时间片的大小,时间片用完时设置调度标志。

当时钟中断发生1000次,并且my_need_sched!=1时,把my_need_sched赋为1。

当进程发现my_need_sched=1时,就会执行my_schedule。  */

{

#if 1

if(time_count%1000 == 0 && my_need_sched != 1)

{

printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");

my_need_sched = 1;

}

time_count ++ ;

#endif

return;

}

void my_schedule(void)

{

tPCB * next;

tPCB * prev;

if(my_current_task == NULL //task为空,即发生错误时返回

|| my_current_task->next == NULL)

{

return;

}

printk(KERN_NOTICE ">>>my_schedule<<<\n");

/* schedule */

next = my_current_task->next;//把当前进程的下一个进程赋给next

prev = my_current_task;//当前进程为prev

if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */

{

/* switch to next process */

/*如果下一个进程的状态是正在执行的话,就运用if语句中的代码表示的方法来切换进程*/

asm volatile(

"pushl %%ebp\n\t"       /* save ebp 保存当前进程的ebp*/

"movl %%esp,%0\n\t"     /* save esp 保存当前进程的esp*/

"movl %2,%%esp\n\t"     /* restore  esp 把下一个进程的sp放到esp中*/

"movl $1f,%1\n\t"       /* save eip 保存eip*/

"pushl %3\n\t"

"ret\n\t"               /* restore  eip */

"1:\t"                  /* next process start here */

"popl %%ebp\n\t"

: "=m" (prev->thread.sp),"=m" (prev->thread.ip)

: "m" (next->thread.sp),"m" (next->thread.ip)

);

my_current_task = next;

printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);

}

else

/*  与上一段代码不同的是如果下一个进程为新进程时,就运用else中的这一段代码。

首先将这个进程置为运行时状态,将这个进程作为当前正在执行的进程。    */

{

next->state = 0;

my_current_task = next;

printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);

/* switch to new process */

asm volatile(

"pushl %%ebp\n\t"       /* save ebp */

"movl %%esp,%0\n\t"     /* save esp */

"movl %2,%%esp\n\t"     /* restore  esp */

"movl %2,%%ebp\n\t"     /* restore  ebp */

"movl $1f,%1\n\t"       /* save eip */

"pushl %3\n\t"

"ret\n\t"               /* restore  eip */

: "=m" (prev->thread.sp),"=m" (prev->thread.ip)

: "m" (next->thread.sp),"m" (next->thread.ip)

);

}

return;

}

Linux内核第二节的更多相关文章

  1. linux 内核 第二周 操作系统是如何工作的

    姬梦馨 原创博客 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一:计算机的三个法宝 存储程序计算机工 ...

  2. Linux内核第一节

    存储程序计算机工作模型 存储程序计算机——冯诺依曼体系结构 IP:寄存器,总是指向内存的代码段.IP(16位) 32位(EIP) 64位(RIP). 内存:保存数据和指令. CPU:CPU从IP指向的 ...

  3. linux内核第二周

    chapter 1 知识点梳理 (一)计算机是如何工作的?(总结)——三个法宝 ①存储程序计算机工作模型,计算机系统最最基础性的逻辑结构: ②函数调用堆栈,高级语言得以运行的基础,只有机器语言和汇编语 ...

  4. 『Linux』第二节: 安装Linux系统

    一. 准备工具 1. centOS系统下载 http://isoredirect.centos.org/centos/7/isos/x86_64/CentOS-7-x86_64-DVD-1810.is ...

  5. 《Linux内核精髓:精通Linux内核必会的75个绝技》一HACK #2 如何编译Linux内核

    HACK #2 如何编译Linux内核 本节介绍编译Linux内核的方法.当发现bug而修改源代码或者添加新功能时,就需要对内核进行重新编译,生成二进制映像文件.另外,如果想要使用发布版内核中无效的功 ...

  6. 《Linux内核精髓:精通Linux内核必会的75个绝技》一HACK #1 如何获取Linux内核

    HACK #1 如何获取Linux内核 本节介绍获取Linux内核源代码的各种方法.“获取内核”这个说法看似简单,其实Linux内核有很多种衍生版本.要找出自己想要的源代码到底是哪一个,必须首先理解各 ...

  7. linux内核书籍

    1, 关于操作系统理论的最初级的知识.不需要通读并理解<操作系统概念><现代操作系统>等巨著,但总要知道分时(time-shared)和实时(real-time)的区别是什么, ...

  8. linux内核体系结构

    linux内核第一记   1.linux体系结构   从上图可知,Linux分为:用户空间和内核空间.内核空间和用户空间是程序执行的两种不同的状态,通过系统调用和硬件中断能够完成从用户空间到内核空间的 ...

  9. 《Linux内核分析》 第二节 操作系统是如何工作的

    Linux内核分析 第二周 操作系统是如何工作的 张嘉琪 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/UST ...

随机推荐

  1. C++基础算法学习——逆波兰表达式问题

    例题:逆波兰表达式逆波兰表达式是一种把运算符前置的算术表达式,例如普通的表达式2 + 3的逆波兰表示法为+ 2 3.逆波兰表达式的优点是运算符之间不必有优先级关系,也不必用括号改变运算次序,例如(2 ...

  2. January 31st, 2018 Week 05th Wednesday

    Real love is not just instinct, but intent. 真正的爱不是身体上的一见钟情,而是要用心去经营. What is real love? Honestly, I ...

  3. January 15th, 2018 Week 03rd Monday

    We got things to do. Places to go. People to see. Futures to make. 我们有很多事情要做,有很多地方要去,有很多人要见,有很多美好的未来 ...

  4. String类的常用方法详解

    1:获取字符串的长度length(),下标从1开始 2:将其他类型转换为String类型toStrings() 3:去除字符串首尾的空格trim() 4:分割字符串spilt() 5:比较两个字符串是 ...

  5. vue实例生命周期详解

    每个 Vue 实例在被创建之前都要经过一系列的初始化过程. 例如,实例需要配置数据观测(data observer).编译模版.挂载实例到 DOM ,然后在数据变化时更新 DOM . 在这个过程中,实 ...

  6. php使用curl模拟多线程发送请求

    每个PHP文件的执行是单线程的,但是php本身也可以用一些别的技术实现多线程并发比如用php-fpm进程,这里用curl模拟多线程发送请求.php的curl多线程是通过不断调用curl_multi_e ...

  7. Arduino IDE for ESP8266 项目(2)wifi扫描

    #include "ESP8266WiFi.h" void setup() { Serial.begin(115200); //设定WiFi为STA模式,如果先前已连接上AP,则与 ...

  8. linux下的C语言程序设计

    Linux程序设计基础知识 Linux下C语言编程环境概述 Linux下C语言编程常用的编辑器是vim或emacs,编译器一般用gcc,编译链接程序用make,跟踪调试一般使用gdb,项目管理用mak ...

  9. 3.0+百度地图在地图初始化的时候就弹框展示一个信息框,而不是用户点击poi时才弹出

    有些时候我们会有这样的业务:当用户根据地址跳转到地图后,希望能够立即弹框展示该poi的地址信息. 实现方法: 百度地图3.0+的类BaiduMap中提供了一个方法showInfoWindow(Info ...

  10. OpenGL初学:安装配置与第一个程序

    OpenGL初学:安装配置与第一个程序 2014年10月12日 12:37:03 process-z 阅读数:12413 标签: opengl安装教程 更多 个人分类: OpenGL   计算机图形学 ...