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

第一节 函数调用堆栈

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

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

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

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

  

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

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

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

pop:从高地址向低地址

push:从低地址向高地址

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

1.其他关键寄存器

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

  中断机制是如何工作的?

  1. call指令:
  2. )将eip中下一条指令的地址A保存在栈顶
  3. )设置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

  1. /*
  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 //定义进程控制块,实际内存叫TASK_STRUCT
  12.  
  13. /* CPU-specific state of this task */
  14. struct Thread { //用来存储eip,esp
  15. unsigned long ip; //保存eip
  16. unsigned long sp; //保存esp
  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

  1. /*
  2. * linux/mykernel/mymain.c
  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. #include "mypcb.h"
  16.  
  17. tPCB task[MAX_TASK_NUM]; //声明一个TASK数组
  18. tPCB * my_current_task = NULL; //声明当前TASK的指针
  19. volatile int my_need_sched = ; //是否需要调度的标志
  20.  
  21. void my_process(void);
  22.  
  23. void __init my_start_kernel(void)
  24. {
  25. int pid = ;
  26. int i;
  27. /* Initialize process 0*/
  28. task[pid].pid = pid; //初始化当前的0号进程
  29. task[pid].state = ;/* -1 unrunnable, 0 runnable, >0 stopped */ //状态:正在运行
  30. task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process; //入口实际上是my_process
  31. task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-]; //栈顶为stack
  32. task[pid].next = &task[pid]; //指向自己本身
  33. /*fork more process */ //初始化更多的进程
  34. for(i=;i<MAX_TASK_NUM;i++)
  35. {
  36. memcpy(&task[i],&task[],sizeof(tPCB));
  37. task[i].pid = i; //复制0号进程的状态
  38. task[i].state = -;
  39. task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-]; //每个进程有自己的堆栈
  40. task[i].next = task[i-].next; //指向下一个进程
  41. task[i-].next = &task[i]; //新fork的进程放在进程列表的尾部
  42. }
  43. /* start process 0 by task[0] */
  44. pid = ; //0号进程开始执行
  45. my_current_task = &task[pid];
  46. asm volatile(
  47. "movl %1,%%esp\n\t" /* set task[pid].thread.sp to esp */
  48. "pushl %1\n\t" /* push ebp */
  49. "pushl %0\n\t" /* push task[pid].thread.ip */
  50. "ret\n\t" /* pop task[pid].thread.ip to eip */
  51. "popl %%ebp\n\t"
  52. :
  53. : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/
  54. );
  55. } //内核初始化完成,启动了0号进程
  56. void my_process(void)
  57. {
  58. int i = ;
  59. while()
  60. {
  61. i++;
  62. if(i% == ) //执行1000万次
  63. {
  64. printk(KERN_NOTICE "this is process %d -\n",my_current_task->pid); //输出,主动调度
  65. if(my_need_sched == ) //执行1000万次调度1次
  66. {
  67. my_need_sched = ;
  68. my_schedule();
  69. }
  70. printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid);
  71. }
  72. }
  73. }

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

嵌入式汇编

myinterrupt.c

  1. /*
  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]; //extern一些全局的东西
  18. extern tPCB * my_current_task;
  19. extern volatile int my_need_sched;
  20. volatile int time_count = ; //时间计数
  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% == && my_need_sched != ) //时间中断一千次,并且my_need_sched不等于1时,把my_need_sched赋为1
  31. {
  32. printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");
  33. my_need_sched = ;
  34. }
  35. time_count ++ ;
  36. #endif
  37. return;
  38. }
  39.  
  40. void my_schedule(void)
  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; //把当前进程的下一个进程赋给prev
  53. prev = my_current_task;
  54. if(next->state == )/* -1 unrunnable, 0 runnable, >0 stopped */
  55. {
  56. /* switch to next process */ //两个正在运行的进程之间做进程上下文切换
  57. asm volatile(
  58. "pushl %%ebp\n\t" /* save ebp */ //保存当前进程的ebp
  59. "movl %%esp,%0\n\t" /* save esp */ //当前进程的esp赋到0,即thresd.sp
  60. "movl %2,%%esp\n\t" /* restore esp */ //把下一个进程的sp放入esp中
  61. "movl $1f,%1\n\t" /* save eip */ //保存eip
  62. "pushl %3\n\t" //把下一个进程的eip push到栈里
  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)
  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 = ; //进程设置为运行时状态
  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" /* save ebp */ //保存当前ebp
  80. "movl %%esp,%0\n\t" /* save esp */ //保存当前esp
  81. "movl %2,%%esp\n\t" /* restore esp */ //重新记录要跳转进程的esp,%2为next->thread.sp
  82. "movl %2,%%ebp\n\t" /* restore ebp */ //重新记录要跳转进程的ebp,%2为next->thread.sp
  83. "movl $1f,%1\n\t" /* save eip */ //保存当前eip,%1为next->thread.ip,%1f指标号:1的代码在内存中存储的地址
  84. "pushl %3\n\t"
  85. "ret\n\t" /* restore eip */ //重新记录要跳转进程的eip,%3为next->thread.ip
  86. : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
  87. : "m" (next->thread.sp),"m" (next->thread.ip)
  88. );
  89. }
  90. return;
  91. }

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. python第二十九课——文件读写(读取数据操作)

    演示读取数据操作:path=r'a.txt' 1.打开文件f1=open(path,'r') 2.读取数据content1=f1.read(3)print(content1) content1=f1. ...

  2. [JLOI2013]删除物品

    嘟嘟嘟 只要每一次将优先级最高的上面的物品移走,就一定能保证是最优解. 所以我们只要想办法简化这个模拟移物品的过程,看完了题解后,发现可以这么想,我们可以把两个栈头碰头的挨在一起,然后设一个指针代表两 ...

  3. 列表操作方法,元祖,for循环嵌套

    li = ['alex','wusir''女神']增 1.增加到列表末位 li.append() 连续增加,输入q停止 li = ['alex','wusir''女神'] while 1: s = i ...

  4. docker-compose运行Rails

    1.新建空目录,名字可以叫Rails 2.新建Dockerfile并添加如下内容 FROM ruby:2.5 RUN apt-get update -qq && apt-get ins ...

  5. python下载安装搭建

    python官网下载python运行环境(https://www.python.org/downloads/),建议下载稳定版本,不推荐使用最新版本 安装 然后我们打开CMD,在里面输入python, ...

  6. Python 函数(三)

    Python 3 函数 (闭包.装饰器.递归.高阶函数) 一.闭包 内部函数可以引用外部函数的参数和局部变量,当外部函数返回内部函数时,相关参数和变量 都保存在返回的函数中,简单的说,这种内部函数可以 ...

  7. Leetcode——338. 比特位计数

    题目描述:题目链接 对于求解一个十进制数转化为二进制时里面1的个数,可以先看一下概况: 十进制数 二进制数 1的个数 1 1    1 2 10 1 3 11   2 4 100 1 5 101 2 ...

  8. (转)公有云vr客户端tcp连接数太多造成 系统卡顿问题 [bittorrent tracker优化] -公有云常见网络问题及思路

    在公有云服务器 发现使用tcp(http)的tracker连接数太多 用户太多会造成windows系统卡顿 特此发表一下修改配置和路由器的方法 解决卡顿问题 解决方法1(参考内容): 修改 /etc/ ...

  9. JQuery radio单选框应用

    转载:JQuery判断radio(单选框)是否选中和获取选中值方法总结 一.利用获取选中值判断选中 直接上代码,别忘记引用JQuery包 复制代码 代码如下: < !DOCTYPE html P ...

  10. 运放参数的详细解释和分析-part3,输入失调电压Vos及温漂

    运放参数的详细解释和分析-part3,输入失调电压Vos及温漂 在运放的应用中,不可避免的会碰到运放的输入失调电压Vos问题,尤其对直流信号进行放大时,由于输入失调电压Vos的存在,放大电路的输出端总 ...