《Linux内核分析》第二周笔记 操作系统是如何工作的
操作系统是如何工作的
一、函数调用堆栈
1、三个法宝 计算机是如何工作的?(总结)——三个法宝(存储程序计算机、函数调用堆栈、中断机制)
1)存储程序计算机工作模型,计算机系统最最基础性的逻辑结构;
2)函数调用堆栈,高级语言得以运行的基础,只有机器语言和汇编语言的时候堆栈机制对于计算机来说并不那么重要,但有了高级语言及函数,堆栈成为了计算机的基础功能,是高级语言的起点;
•enter
•pushl %ebp
• movl %esp,%ebp
•leave
•movl %ebp,%esp
•popl %ebp
函数参数传递机制和局部变量存储共同
3)中断,多道程序操作系统的基点,没有中断机制程序只能从头一直运行结束才有可能开始运行其他程序。
早期的计算机没有中断只能在执行完一个程序后执行另一个程序,有了中断有了多道程序设计(在系统中同时执行多个程序),一个程序有自己的执行流,在执行流的过程中,如何切换到另一个程序?CPU可以在当一个中断信号发生的时候,cpu把当前的eip,esb,ebp都压到一个堆栈里面,eip指向中断处理程序的入口,执行中断处理程序(由cpu和内核代码共同实现了保存现场和恢复现场)。
1.1什么是堆栈
1)是C语言程序运行时必须的一个记录调用路径和参数的空间,实际上在CPU内部集成好了功能。
- 函数调用框架
- 传递参数(64位略有不同)
- 保存返回地址(利用eax)
- 提供局部变量空间
2)C语言编译器对堆栈的使用有一套的规则(不同的指令序列可以实现完全相同的功能)
3)了解堆栈存在的目的和编译器对堆栈使用的规则是理解操作系统一些关键性代码的基础
2、深入理解函数调用堆栈
2.1堆栈寄存器和堆栈操作
1)堆栈相关的寄存器
– esp,堆栈指针(stack pointer)
– ebp,基址指针(base pointer)
2)堆栈操作
– push 栈顶地址减少4个字节,并将push的内容放进去(32位)
– pop 高地址栈顶地址增加4个字节 (从高地址向低地址增加)
3)ebp在C语言中用作记录当前函数调用基址
当函数嵌套比较深的话,每一个函数的ebp是不一样的
4)其他关键寄存器
– cs(代码段寄存器) : eip:总是指向下一条的指令地址
• 顺序执行:总是指向地址连续的下一条指令
• 跳转/分支:执行这样的指令的时候,cs : eip的值会根据程序需要被修改
• call:将当前cs : eip的值压入栈顶,cs : eip指向被调用函数的入口地址
• ret:从栈顶弹出原来保存在这里的cs : eip的值,放入cs : eip中
2.2深入理解函数调用堆栈的工作机制
call指令:
1)将eip中下一条指令的地址A保存在栈顶
2)设置eip指向被调用程序代码开始处
2.3函数调用时堆栈的变化
对应cs:eip被压栈
——第二条指令:movl %esp,%ebp
在原来堆栈的存储空间里,创建了一个新的函数调用堆栈,一个新的空的堆栈,可继续向低地址增长
——函数体中的常规操作可能会出现压栈、出栈
退出XXX
——movl%ebp,%esp(清空堆栈)
——popl%ebp(ebp指向原来的基地址)
——ret(cs:eip出栈)
3.参数传递与局部变量
3.1举例分析函数调用堆栈(二级调用)
源文件:test.c
main函数中调用了函数p1和p2
首先使用gcc —g生成test.c的可执行文件test
然后使用objdump —s获得test的反汇编文件
3.1.1观察p2的堆栈框架
3.1.2如何传递参数给p2
1)先将y的值push进来,不是push y而是y的变址寻址(局部变量),是因为在建立当前的框架的时候,把局部变量都保存在堆栈里面,就可以用变址寻址找到y的值。
下图就是将两个局部变量压栈的图示:
2)call:
3)函数调用结束后返回:
4)add esp+8:
5)mov保存返回值。函数的返回值通过eax寄存器传递。
p2的返回值是如何返回给z的?
eax放到当前函数调用堆栈Z的位置。eax赋值给z
6)先push y x z再push字符串
3.1.3观察局部变量的存储机制
2)sub $0x18,%esp(预留出0x18个字节,此空间用来存储局部变量)
以前:写c代码的时候必须把变量的声明放在代码的头部,是因为好把声明的变量在堆栈中预留空间。
现在:编译器智能可以先扫描整个函数,里面一共声明多少变量,可以提前准备好,所以编译器不强制要求局部变量在函数头部。
char c='a'(声明时把字符a的ascll码放在变址寻址中)
3.1.4整个程序运行时堆栈的变化
1)从main函数开始执行,由系统建立堆栈。
2)从main开始向下执行,调用到p1的时候,将c,eip,压栈,p1堆栈
3)将p1 return,回到原来的状态
4)执行p2的时候,将x,y压栈,eip堆栈
5)回到main的堆栈
3.2三级函数调用程序
在这个程序中main函数中调用了函数p2,而在p2的执行过程中,又调用了函数p1
具体过程如下:
二、借助linux内核部分源代码模拟存储程序计算机工作模型及时钟中断
1、利用mykernel实验模拟计算机硬件平台
周期性的每过一个时间发生一次时钟中断,时钟中断会调用一个程序。
在实验楼环境下运行mykernel
mykernel相关的源代码:
操作
系统的入口(开始启动操作系统):
查看myinterrupt.c的代码:
每调用一次时钟中断都打印一个:
三、在mykernel基础上构造一个简单的操作系统内核
1.c代码中嵌入汇编代码的写法
1)内嵌汇编语法
_asm_(
汇编语句模板:
输出部分: (函数调用的参数)
输入部分:
破坏描述部分);
举例:使用嵌入式汇编代码使val1+val2=val3
asm volatile(
"movl $0,%%eax\n\t" /* clear %eax to 0*/ 将eax为0 %%:转义字符
"addl %1,%%eax\n\t" /* %eax += val1 */将val1+0放到eax里 %1指的是下面第五行的val1
"addl %2,%%eax\n\t" /* %eax += val2 */
"movl %%eax,%0\n\t" /* val2 = %eax*/将val1+val2的值放到%0中
: "=m" (val3) /* output =m mean only write output memory variable*/写到内存变量里面去,不使用寄存器
: "c" (val1),"d" (val2) /* input c or d mean %ecx/%edx*/ 用ecx寄存器存储val1的值,'c'指的是ecx
);
printf("val1:%d+val2:%d=val3:%d\n",val1,val2,val3);
嵌入式汇编的时候,每一个输出和输入的部分前面都可以加一个限定符。
=m:只写
r:将输入变量放入通用寄存器
eax:破坏描述部分
把0赋给temp(%1)
%2是input,input是1,把1赋给eax
输出remp=0,output=1
2、一个简单的操作系统源代码
mypcb.h
2 * linux/mykernel/mypcb.h
3 *
4 * Kernel internal PCB types
5 *
6 * Copyright (C) 2013 Mengning
7 *
8 */
9
10 #define MAX_TASK_NUM 4
11 #define KERNEL_STACK_SIZE 1024*8
12
13 /* CPU-specific state of this task */
14 struct Thread {
15 unsigned long ip;
16 unsigned long sp;
17 };
18
19 typedef struct PCB{ /*定义进程管理相关的数据结构*/
20 int pid;
21 volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
22 char stack[KERNEL_STACK_SIZE]; /*内核堆栈*/
23 /* CPU-specific state of this task */
24 struct Thread thread;
25 unsigned long task_entry; /*入口一般是main函数*/
26 struct PCB *next; /*把进程用链表练起来*/
27 }tPCB;
28
29 void my_schedule(void);
mymain.c
/*
2 * linux/mykernel/mymain.c /*在mykernnel基础上构造一个简单的操作系统内核*/
3 *
4 * Kernel internal my_start_kernel
5 *
6 * Copyright (C) 2013 Mengning
7 *
8 */
9 #include <linux/types.h>
10 #include <linux/string.h>
11 #include <linux/ctype.h>
12 #include <linux/tty.h>
13 #include <linux/vmalloc.h>
14
15
16 #include "mypcb.h"
17
18 tPCB task[MAX_TASK_NUM];
19 tPCB * my_current_task = NULL;
20 volatile int my_need_sched = 0; /*内核初始化和0号进程启动*/
21
22 void my_process(void);
23
24
25 void __init my_start_kernel(void)
26 {
27 int pid = 0;
28 int i;
29 /* Initialize process 0*/
30 task[pid].pid = pid;
31 task[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped */
32 task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;
33 task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];
34 task[pid].next = &task[pid];
35 /*fork more process */ /*创建更多的进程*/
36 for(i=1;i<MAX_TASK_NUM;i++)
37 {
38 memcpy(&task[i],&task[0],sizeof(tPCB));
39 task[i].pid = i;
40 task[i].state = -1;
41 task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1];
42 task[i].next = task[i-1].next;
43 task[i-1].next = &task[i];
44 }
45 /* start process 0 by task[0] */ /*启动0号进程*/
46 pid = 0;
47 my_current_task = &task[pid];
48 asm volatile(
49 "movl %1,%%esp\n\t" /* set task[pid].thread.sp to esp */
50 "pushl %1\n\t" /* push ebp */ /*将ebp压栈*/
51 "pushl %0\n\t" /* push task[pid].thread.ip */
52 "ret\n\t" /* pop task[pid].thread.ip to eip */ /*ret之后0号进程启动*/
53 "popl %%ebp\n\t"
54 :
55 : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/
56 );
57 }
58 void my_process(void)
59 {
60 int i = 0;
61 while(1)
62 {
63 i++;
64 if(i%10000000 == 0) /*循环1000万次才有一次机会需要判断是否需要调度*/
65 {
66 printk(KERN_NOTICE "this is process %d -\n",my_current_task->pid);
67 if(my_need_sched == 1)
68 {
69 my_need_sched = 0;
70 my_schedule();
71 }
72 printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid);
73 }
74 }
75 }
myinterrupt.c
/*
2 * linux/mykernel/myinterrupt.c
3 *
4 * Kernel internal my_timer_handler
5 *
6 * Copyright (C) 2013 Mengning
7 *
8 */
9 #include <linux/types.h>
10 #include <linux/string.h>
11 #include <linux/ctype.h>
12 #include <linux/tty.h>
13 #include <linux/vmalloc.h>
14
15 #include "mypcb.h"
16
17 extern tPCB task[MAX_TASK_NUM];
18 extern tPCB * my_current_task;
19 extern volatile int my_need_sched;
20 volatile int time_count = 0;
21
22 /*
23 * Called by timer interrupt.
24 * it runs in the name of current running process,
25 * so it use kernel stack of current running process
26 */
27 void my_timer_handler(void)
28 {
29 #if 1
30 if(time_count%1000 == 0 && my_need_sched != 1) /*设置时间片的大小,时间片用完时设置一下调度标识*/
31 {
32 printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");
33 my_need_sched = 1;
34 }
35 time_count ++ ;
36 #endif
37 return;
38 }
39
40 void my_schedule(void) /*my schedule:把当前进程(prev)的下一进程赋给nest,当下一个进程是0(正在执行)的话,用下面方法来切换进程。*/
41 {
42 tPCB * next;
43 tPCB * prev;
44
45 if(my_current_task == NULL
46 || my_current_task->next == NULL)
47 {
48 return;
49 }
50 printk(KERN_NOTICE ">>>my_schedule<<<\n");
51 /* schedule */
52 next = my_current_task->next;
53 prev = my_current_task;
54 if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */
55 {
56 /* switch to next process */
57 asm volatile( /*两个正在运行的进程之间做上下文切换*/
58 "pushl %%ebp\n\t" /* save ebp */
59 "movl %%esp,%0\n\t" /* save esp */
60 "movl %2,%%esp\n\t" /* restore esp */
61 "movl $1f,%1\n\t" /* save eip */
62 "pushl %3\n\t"
63 "ret\n\t" /* restore eip */
64 "1:\t" /* next process start here */
65 "popl %%ebp\n\t"
66 : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
67 : "m" (next->thread.sp),"m" (next->thread.ip) /*prev sp就是%1 next sp就是%2*/
68 );
69 my_current_task = next;
70 printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);
71 }
72 else
73 {
74 next->state = 0;
75 my_current_task = next;
76 printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);
77 /* switch to new process */ /*切换到一个新进程的方法*/
78 asm volatile(
79 "pushl %%ebp\n\t" /* 将ebp压栈 */
80 "movl %%esp,%0\n\t" /* save esp */
81 "movl %2,%%esp\n\t" /* restore esp */
82 "movl %2,%%ebp\n\t" /* restore ebp */
83 "movl $1f,%1\n\t" /* save eip */ /*$1f是指接下来的标号1:的位置*/
84 "pushl %3\n\t"
85 "ret\n\t" /* restore eip */
86 : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
87 : "m" (next->thread.sp),"m" (next->thread.ip)
88 );
89 }
90 return;
91 }
操作系统的两把剑:中断上下文和进程上下文的切换
3、运行这个精简的操作系统内核
可修改时间片缩小,使调度更加频繁。
四、总结
第二周的课程明显比第一周的课程难许多,重点还是要搞明白函数在调用过程中栈的变化,以及在构造简单的操作系统内核的过程中了解进程的调用,进程的切换,还有在进行嵌入式汇编过程中的限定符的应用。还有要记住计算机工作的三个法宝(存储程序计算机、函数调用堆栈、中断机制),还有函数的返回值通过eax寄存器传递,以及操作系统的两把剑:中断上下文和进程上下文的切换。但是对基于mykernel实现的时间片轮转调度代码我还是没有完全理解,希望通过今后的学习和课堂上老师的讲解可以更加清楚的理解。
《Linux内核分析》第二周笔记 操作系统是如何工作的的更多相关文章
- Linux内核分析第二周:操作系统是如何工作的
第一讲 函数调用堆栈 计算机是如何工作的? (总结)——三个法宝 1,存储程序计算机工作模型,计算机系统最最基础性的逻辑结构: 2,函数调用堆栈,高级语言得以运行的基础,只有机器语言和汇编语言的时候堆 ...
- LINUX内核分析第二周学习总结——操作系统是如何工作的
LINUX内核分析第二周学习总结——操作系统是如何工作的 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/course ...
- Linux内核分析第二周学习笔记
linux内核分析第二周学习笔记 标签(空格分隔): 20135328陈都 陈都 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.co ...
- Linux内核分析第二周--操作系统是如何工作的
Linux内核分析第二周--操作系统是如何工作的 李雪琦 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course ...
- Linux内核分析第二周学习博客——完成一个简单的时间片轮转多道程序内核代码
Linux内核分析第二周学习博客 本周,通过实现一个简单的操作系统内核,我大致了解了操作系统运行的过程. 实验主要步骤如下: 代码分析: void my_process(void) { int i = ...
- linux内核分析第二周
网易云课堂linux内核分析第二周 20135103 王海宁 <Linux内核分析>MOOC课程http://mooc.study.163.com/cours ...
- Linux内核分析第二周学习总结:操作系统是如何工作的?
韩玉琪 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.函数调用堆栈 ...
- linux内核分析 第二周 操作系统是如何工作的
银雪纯 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.计算机是如何工作的 ...
- Linux内核分析——第二周学习笔记
20135313吴子怡.北京电子科技学院 chapter 1 知识点梳理 (一)计算机是如何工作的?(总结)——三个法宝 ①存储程序计算机工作模型,计算机系统最最基础性的逻辑结构: ②函数调用堆栈,高 ...
- 20135327郭皓——Linux内核分析第二周 操作系统是如何工作的
操作系统是如何工作的 上章重点回顾: 计算机是如何工作的?(总结)——三个法宝 存储程序计算机工作模型,计算机系统最最基础性的逻辑结构: 函数调用堆栈,高级语言得以运行的基础,只有机器语言和汇编语言的 ...
随机推荐
- python第四十一课——析构函数
3.析构函数 格式:__del__(self): 作用: 在程序结束前将对象回收,释放资源的行为 演示析构函数的使用: class Animal: #定义构造函数 def __init__(self, ...
- BZOJ3676:[APIO2014]回文串(SAM,Manacher)
Description 考虑一个只包含小写拉丁字母的字符串s.我们定义s的一个子串t的“出 现值”为t在s中的出现次数乘以t的长度.请你求出s的所有回文子串中的最 大出现值. Input 输入只有一行 ...
- C# ActiveX 网页打包验证自动升级
原文地址:http://www.cnblogs.com/yilin/p/csharp-activex.html 注意事项:Win10下需要设置兼容模式,F12仿真切换到IE6-8(版本参考——BT90 ...
- 【转】默认网关有什么用?我应当怎么填写默认网关和DNS呢
默认网关有什么用?我应当怎么填写默认网关和DNS呢? 目前使用的是pppoe方式上网,无猫,只是将一根入户的网线插在无线路由上面,然后在路由中设置ppoe方式上网,输入帐号密码.一般电脑和手机全设成了 ...
- CSS 文本
CSS 文本属性可定义文本的外观. 通过文本属性,您可以改变文本的颜色.字符间距,对齐文本,装饰文本,对文本进行缩进,等等. 缩进文本 把 Web 页面上的段落的第一行缩进,这是一种最常用的文本格式化 ...
- sublime出现 unable download.......
I managed to fix this by changing my package settings. I made my osx downloader preference curl, and ...
- snmpwalk,iptables
-A RH-Firewall-1-INPUT -i lo -j ACCEPT -A INPUT -s 1.1.1.1 -p udp -d 2.2.2.2 --dport 161 -j ACCEPT - ...
- WorldWind源码剖析系列:插件类Plugin、插件信息类PluginInfo和插件编译器类PluginCompiler
插件类Plugin是所有由插件编译器加载的插件子类的抽象父类,提供对插件的轻量级的访问控制功能. 插件信息类PluginInfo用来存储关于某个插件的信息的类,可以理解为对插件类Plugin类的进一步 ...
- OpenCV——图像的深度与通道数讲解
矩阵数据类型: – CV_(S|U|F)C S = 符号整型 U = 无符号整型 F = 浮点型 E.g.: CV_8UC1 是指一个8位无符号整型单通道矩阵, CV_32FC2是指一个32位浮点型双 ...
- C++之强制类型转化
在C++语言中新增了四个关键字static_cast.const_cast.reinterpret_cast和dynamic_cast.这四个关键字都是用于强制类型转换的.我们逐一来介绍这四个关键字. ...