《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内核分析第二周 操作系统是如何工作的
操作系统是如何工作的 上章重点回顾: 计算机是如何工作的?(总结)——三个法宝 存储程序计算机工作模型,计算机系统最最基础性的逻辑结构: 函数调用堆栈,高级语言得以运行的基础,只有机器语言和汇编语言的 ...
随机推荐
- kdTree相关原理及c++实现
kdTree概念 kd-tree或者k维树是计算机科学中使用的一种数据结构,用来组织表示k维空间中点的集合.它是一种带有其他约束条件的二分查找树.Kd-tree对于区间和近邻搜索十分有用.一般位于三维 ...
- 本博客已不再更新,新文章将发布在我的个人博客:https://www.tapme.top
如题,本博客已不再更新,请访问个人博客:www.tapme.top
- app的描述-软件的描述
app的描述=需求文档+接口文档+程序架构+工程结构. 程序架构:类结构图: 需求文档:业务逻辑-->时序图.
- 【2018暑假集训模拟一】Day1题解
T1准确率 [题目描述] 你是一个骁勇善战.日刷百题的OIer. 今天你已经在你OJ 上提交了y 次,其中x次是正确的,这时,你的准确率是x/y.然而,你最喜欢一个在[0; 1] 中的有理数p/q(是 ...
- oneinstack远程管理数据库
本篇文章主要内容是本地工具连接数据非网页(网站)连接 如果你想使用网页(网站)连接远程数据库,请看下面的官网教程 OneinStack如何配置MySQL远程连接? 为了安全考虑,OneinStack仅 ...
- Scala学习之路 (九)Scala的上界和下届
一.泛型 1.泛型的介绍 泛型用于指定方法或类可以接受任意类型参数,参数在实际使用时才被确定,泛型可以有效地增强程序的适用性,使用泛型可以使得类或方法具有更强的通用性.泛型的典型应用场景是集合及集合中 ...
- Mac svn使用学习-1-简介
在Windows环境中,可以使用TortoiseSVN来搭建svn环境.但是由于Mac自带了svn的服务器端和客户端功能,因此可以直接使用svn功能. svn即subversion,Subversio ...
- 数据同步canal客户端
1.增量订阅.消费设计 get/ack/rollback协议介绍: ① Message getWithoutAck(int batchSize),允许指定batchSize,一次可以获取多条,每次返回 ...
- Java基础—面向对象
一.什么叫面向对象 万物皆对象(待更正) 二.面向对象三大特征 抽象:把一类对象共同特征进行抽取构造类的过程,包括两种抽象:第一种是数据抽象,也就是对象的属性.第二种是过程抽象,也就是对象的行为 封装 ...
- 20155320 Exp3 免杀原理与实践
20155320 Exp3 免杀原理与实践 免杀 一般是对恶意软件做处理,让它不被杀毒软件所检测.也是渗透测试中需要使用到的技术. [基础问题回答] (1)杀软是如何检测出恶意代码的? 1.通过行为检 ...