第二周 操作系统是如何工作的

第一节 函数调用堆栈

存储程序计算机:是所有计算机基础的框架

堆栈:计算机中基础的部分,在计算机只有机器语言、汇编语言时,就有了堆栈。堆栈机制是高级语言可以运行的基础。

计算机“三宝”:存储程序计算机、函数调用堆栈和中断机制。

  • 堆栈是C语言程序运行时必须的一个记录调用路径和参数的空间
  • 函数调用框架(eg:enter、leave )
  • 传递参数(32位,通过堆栈来传递参数)
  • 保存返回地址(用eax)
  • 提供局部变量空间
  • 等等

  

♦ C语言编译器对堆栈的使用有一套的规则,不同的系统中,编译C代码会有不同的汇编代码。

♦ 了解堆栈存在的目的和编译器对堆栈使用的规则是理解操作系统一些关键性代码的基础。

一、堆栈寄存器和堆栈操作

pop:从高地址向低地址

push:从低地址向高地址

二、利用堆栈实现函数的调用和返回

1.其他关键寄存器

  • cs : eip:总是指向下一条的指令地址
• 顺序执行:总是指向地址连续的下一条指令
• 跳转/分支:执行这样的指令的时候,cs : eip的值会
根据程序需要被修改
• call:将当前cs : eip的值压入栈顶,cs : eip指向被
调用函数的入口地址
• ret:从栈顶弹出原来保存在这里的cs : eip的值,放
入cs : eip中

  中断机制是如何工作的?

call指令:
)将eip中下一条指令的地址A保存在栈顶
)设置eip指向被调用程序代码开始处

三、函数堆栈框架的形成

  1. call xxx
  • 执行call之前
  • 执行call时,cs : eip原来的值指向call下一条指令,该值被保存到栈顶,然后cs : eip的值指向xxx的入口地址

  2. 进入xxx

  • 第一条指令: pushl %ebp
  • 第二条指令: movl %esp, %ebp
  • 函数体中的常规操作,可能会压栈、出栈

  3. 退出xxx

  • movl %ebp,%esp
  • popl %ebp
  • ret

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

当一个中断信号发生时,CPU把当前的eip,esp,ebp压到内核堆栈中去,并把eip指向中断处理程序的入口。

C代码中嵌入汇编代码

第三节 在mykernel基础上构造一个简单的操作系统内核

执行:

mymain.c

myinterrupt.c

实验:完成一个简单的时间片轮转多道程序内核代码

mypcb.h

/*
* linux/mykernel/mypcb.h
*
* Kernel internal PCB types
*
* Copyright (C) 2013 Mengning
*
*/ #define MAX_TASK_NUM 4
#define KERNEL_STACK_SIZE 1024*8 //定义进程控制块,实际内存叫TASK_STRUCT /* CPU-specific state of this task */
struct Thread { //用来存储eip,esp
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; //定义程序入口,一般是main函数,这里指定了
struct PCB *next; //进程用链表链起来
}tPCB; void my_schedule(void); //调度器

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]; //声明一个TASK数组
tPCB * my_current_task = NULL; //声明当前TASK的指针
volatile int my_need_sched = ; //是否需要调度的标志 void my_process(void); void __init my_start_kernel(void)
{
int pid = ;
int i;
/* Initialize process 0*/
task[pid].pid = pid; //初始化当前的0号进程
task[pid].state = ;/* -1 unrunnable, 0 runnable, >0 stopped */ //状态:正在运行
task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process; //入口实际上是my_process
task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-]; //栈顶为stack
task[pid].next = &task[pid]; //指向自己本身
/*fork more process */ //初始化更多的进程
for(i=;i<MAX_TASK_NUM;i++)
{
memcpy(&task[i],&task[],sizeof(tPCB));
task[i].pid = i; //复制0号进程的状态
task[i].state = -;
task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-]; //每个进程有自己的堆栈
task[i].next = task[i-].next; //指向下一个进程
task[i-].next = &task[i]; //新fork的进程放在进程列表的尾部
}
/* 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号进程
void my_process(void)
{
int i = ;
while()
{
i++;
if(i% == ) //执行1000万次
{
printk(KERN_NOTICE "this is process %d -\n",my_current_task->pid); //输出,主动调度
if(my_need_sched == ) //执行1000万次调度1次
{
my_need_sched = ;
my_schedule();
}
printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid);
}
}
}

mymain.c是一个死循环,一直不断地打印my_start_kernel here。中间加上if判断只是因为,cpu处理一次循环的速度太快,它只每100000次循环打印一次,以便我们观察。

嵌入式汇编

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一些全局的东西
extern tPCB * my_current_task;
extern volatile int my_need_sched;
volatile int time_count = ; //时间计数 /*
* 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)
{
#if 1
if(time_count% == && my_need_sched != ) //时间中断一千次,并且my_need_sched不等于1时,把my_need_sched赋为1
{
printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");
my_need_sched = ;
}
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");
/* schedule */
next = my_current_task->next; //把当前进程的下一个进程赋给prev
prev = my_current_task;
if(next->state == )/* -1 unrunnable, 0 runnable, >0 stopped */
{
/* switch to next process */ //两个正在运行的进程之间做进程上下文切换
asm volatile(
"pushl %%ebp\n\t" /* save ebp */ //保存当前进程的ebp
"movl %%esp,%0\n\t" /* save esp */ //当前进程的esp赋到0,即thresd.sp
"movl %2,%%esp\n\t" /* restore esp */ //把下一个进程的sp放入esp中
"movl $1f,%1\n\t" /* save eip */ //保存eip
"pushl %3\n\t" //把下一个进程的eip push到栈里
"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
{
next->state = ; //进程设置为运行时状态
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 */ //保存当前ebp
"movl %%esp,%0\n\t" /* save esp */ //保存当前esp
"movl %2,%%esp\n\t" /* restore esp */ //重新记录要跳转进程的esp,%2为next->thread.sp
"movl %2,%%ebp\n\t" /* restore ebp */ //重新记录要跳转进程的ebp,%2为next->thread.sp
"movl $1f,%1\n\t" /* save eip */ //保存当前eip,%1为next->thread.ip,%1f指标号:1的代码在内存中存储的地址
"pushl %3\n\t"
"ret\n\t" /* restore eip */ //重新记录要跳转进程的eip,%3为next->thread.ip
: "=m" (prev->thread.sp),"=m" (prev->thread.ip)
: "m" (next->thread.sp),"m" (next->thread.ip)
);
}
return;
}

myinterrupt.c是一个被时间周期调用的函数,也可以看成是一个死循环的功能,一直打印>>>>>my_timer_handler here <<<<<。但与一般的死循环不同是,一般的死循环只是在做循环,而这个是周期性的调用函数。因此,mymain.c中的my_start_kernel只调用了一次,而myinterrupt.c中的my_timer_handler一直被调用。相当于,进程的初始化与进程的调度。

操作系统的两个法宝:

  • 保存现场与恢复现场:中断上下文处理程序
  • 进程上下文的切换

总结:操作系统是如何工作的

工作机制:存储程序计算机、函数调用堆栈和中断机制。

存储程序计算机最早是由著名数学家冯·诺伊曼等人在1946年总结并明确提出来的。任何一个程序通常都包括代码段和数据段,程序要想运行,首先要由操作系统负责为其创建进程,并在进程的虚拟地址空间中为其代码段和数据段建立映射。进程在运行过程中还要有其动态环境,其中最重要的就是堆栈。堆栈中存放的就是与每个函数对应的堆栈帧。当函数调用发生时,新的堆栈帧被压入堆栈;当函数返回时,相应的堆栈帧从堆栈中弹出。引入中断机制使内核可以处理硬件外设I/O。中断来源有I/O请求、时钟以及系统调用。

在单处理系统中,任何时候只有一个进程在运行,它要么处于用户态,要么处于内核态。然而在linux内核是可重入的,这意味着若干个进程可以同时在内核态下执行。用户态切换到内核态有3种方式,系统调用、异常、外围设备的中断。系统调用实际上最终是中断机制实现的,而异常和中断的处理机制基本上也是一致的。

进程是动态执行的实体,内核是进程的管理者。进程不但包括程序的指令和数据,而且包括程序计数器和CPU的所有寄存器以及存储临时数据的进程堆栈。所以,正在执行的进程包括处理器当前的一切活动。进程既可以在用户态下运行,也能在内核下运行,只是内核提供了一些用户态没有的核心服务,因此进程在访问这些服务时会产生中断,必须进行用户态与内核态的切换。

Linux内核分析——第二周学习笔记20135308的更多相关文章

  1. Linux内核分析第二周学习笔记

    linux内核分析第二周学习笔记 标签(空格分隔): 20135328陈都 陈都 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.co ...

  2. Linux内核分析——第一周学习笔记20135308

    第一周 计算机是如何工作的 第一节 存储程序计算机工作模型 1.冯·诺依曼结构模型:冯·诺依曼结构也称普林斯顿结构,是一种将程序指令存储器和数据存储器合并在一起的存储器结构.程序指令存储地址和数据存储 ...

  3. 三20135320赵瀚青LINUX内核分析第二周学习笔记

    赵瀚青原创作品转载请注明出处<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.计算机的三个法宝 存储程 ...

  4. Linux内核分析——第二周学习笔记

    20135313吴子怡.北京电子科技学院 chapter 1 知识点梳理 (一)计算机是如何工作的?(总结)——三个法宝 ①存储程序计算机工作模型,计算机系统最最基础性的逻辑结构: ②函数调用堆栈,高 ...

  5. LINUX内核分析第二周学习总结——操作系统是如何工作的

    LINUX内核分析第二周学习总结——操作系统是如何工作的 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/course ...

  6. linux内核分析第一周学习笔记

    linux内核分析第一周学习笔记 标签(空格分隔): 20135328陈都 陈都 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.co ...

  7. Linux内核分析第二周学习博客——完成一个简单的时间片轮转多道程序内核代码

    Linux内核分析第二周学习博客 本周,通过实现一个简单的操作系统内核,我大致了解了操作系统运行的过程. 实验主要步骤如下: 代码分析: void my_process(void) { int i = ...

  8. 20135320赵瀚青LINUX内核分析第一周学习笔记

    赵瀚青原创作品转载请注明出处<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.概述 第一周的学习内容主 ...

  9. Linux内核分析第二周学习总结:操作系统是如何工作的?

    韩玉琪 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.函数调用堆栈 ...

随机推荐

  1. XtraEditors六、ListBoxControl、CheckedListBoxControl、ImageListBoxControl

    ListBoxControl 效果如下: 示例代码: string[] girlArr = { "面码", "Saber", "Mathilda&qu ...

  2. MySQL Error Code文档手册---摘自MySQL官方网站

    This chapter lists the errors that may appear when you call MySQL from any host language. The first ...

  3. os.path.md

    os.path 我们可以利用os.path模块提供的函数更容易地在跨平台上处理文件. 即使我们的程序不是用于夸平台, 也应该使用os.path来让路径名字更加可靠. Parsing Paths os. ...

  4. Spring之Method Injection

    对于Spring的多数用户而言,主要的Bean存在形式都是单例,当一个单例需要结合另一个单例协作或者一个非单例与另一个非单例协作时,典型的做法是通过属性的形式注入,但是当两个Bean的声明周期不同时候 ...

  5. 真实的物理机安装Centos7系统后网卡只有lo没有eno1的解决办法:实际上是物理机未安装网驱动卡

    问题症状: 我真实的物理机安装Centos7系统后,在/etc/sysconfig/目录下查看,发现网卡只有lo没有eno1,出现该问题的实际原因是物理机未安装网驱动卡. 解决办法: 不多说了,让我们 ...

  6. 关于PLC高速计数器使用

    今天去面试问我高速计数器,因为没用过,所以直接说--不会.但是自己感觉自己自学电气,说不会太丢人了,所以今天学了PLC的高速计数器.虽然没有书,但是有度娘,还有现成的PLC设备实际检验程序,更有鹏哥和 ...

  7. 【转】PHP 类与对象

    原文:http://blog.csdn.net/e421083458/article/details/8217650 1.类与对象 对象:实际存在该类事物中每个实物的个体.$a =new User() ...

  8. c++ 模板参数做容器参数迭代器报错 vector<T>::const_iterator,typename const报错

    错误1: template<class T>void temp(std::vector<T>& container){        std::vector<T& ...

  9. Xcode添加全局引用文件pch

    Xcode6之前有PrefixHeader.pch文件在写项目的时候,大部分宏定义.头文件都导入在这个pch文件,虽然方便,但会增加Build的时间,所以Xcode6以及之后的版本去除了PrefixH ...

  10. 允许Ubuntu系统下Mysql数据库远程连接

    第一步: vim /etc/mysql/my.cnf找到bind-address = 127.0.0.1 注释掉这行,如:#bind-address = 127.0.0.1 或者改为: bind-ad ...