操作系统学习

        • 编译与解释
        • 编译的步骤
        • Gcc 相关
        • 进程
        • 虚拟内存
        • 内存管理
        • 缓存策略
        • 多任务
        • 线程

编译与解释

1. 编译型语言是指:程序被翻译成机器语言、之后由硬件执行
   解释型语言指:程序被软件解释器读取并执行
2. 静态是指在编译时发生的事、动态是指在运行时发生的事
3. 在解释型语言中、变量名会被存储在内存中、
   而在编译型语言中、一般只保存值所在的地址,除非由于调试而被添加

编译的步骤

1. 预处理:生效于编译之前、eg 将#include的内容插入到指定所在位置
2. 解析:编译器读取源代码、构建程序的内部表示、称为抽象语法树(AST)、这一阶段的错误称为语法错误
3. 静态检测:编译器会检测变量和值的类型、函数调用是否带有正确数量和类型的参数等、这一阶段的错误称为静态语义错误
4. 代码生成:编译器读取程序的内部表示、生成机器码或者字节码
5. 链接:若程序使用了定义在库中的值或者函数、编译器需要找到合适的库并保护所需代码
6. 优化:编译器主动修改程序来生成运行更快或者占用更少空间的代码、大多数是一些简单的修改来消除明显的浪费

Gcc 相关

-o 生成可执行文件
-c 编译程序、并生成机器码、但是并不链接它们或者生成可执行文件
gcc hello.c -c
    执行的结果是hello.o 
    可以使用nm命令查看内容
-S 告诉gcc编译程序生成汇编代码、通常是机器代码的可读形式
-E 只运行预处理

预处理器、编译器、链接器(ld)、
这样在编译的过程就进行了大量的检测、在执行时就只进行很少的检测、除非发生了其它的浮点操作、
会得到浮点数异常、若尝试读写内存的不正确位置、会得到段错误

进程

1. 抽象是复杂系统的简单表示
2. 虚拟化:一个抽象出来的概念、可以是一个或多个对象的组合
   eg. 我们说的互联网是一系列网络和协议的组合、将数据包从一个系统传递到另一个网络、从用户和coder的角度来看、整个系统的行为就像是互联网的每台计算机都相互连接、物理连接的数量十分少、但虚拟连接的数量十分庞大
   在虚拟化的语境中、常常把真实发生的事情叫物理的、把虚拟上发生的事情叫逻辑的或者抽象的
3. 进程就是将每个进程和其它的进程隔离、使coder不用考虑每个可能的交互情况、提供这种隔离的软件对象叫进程
  进程表示运行中程序的软件对象、通常一个对象包含数据、并且提供用于操作数据的方法、通常包含以下对象:
  1)程序文本:机器语言的指令序列
  2)程序相关的数据:包含静态数据(编译时分配)和动态数据(运行时的栈和堆)
  3)任何等待中的IO状态:eg. 进程正在等待从磁盘中读取的数据、或者从网络到达的数据包、这些操作的状态也是进程的一部分
  4)程序的硬件状态:包括存储在寄存器中的数据、状态信息以及程序计数器、它表示当前执行了哪个指令
  通常一个进程运行一个程序、对于进程来说、加载并运行新的程序也是可能的
  也可以在多个进程中运行相同的程序、这种情况下哥哥进程共享程序文本、但是拥有不同的数据和硬件状态(线程 ??)

  多任务、虚拟内存、设备抽象
  init 是操作系统启动时首先创建的进程、它会创建许多其它进程、之后会闲置、直到它创建的进程会运行完毕

虚拟内存

1. 物理地址与内存大小有关、eg. 1GB的内存 其物理地址的大小就为  2 ^ 30
   所表示的范围、最高有效地址就是 2^30 - 116进制表示就是0x33ff ffff
   虚拟内存是根据操作系统的寻址空间来确定的
   eg. 32位操作系统、总线为32个、可操作范围就是 2^32 、或者说是4GB
       64位操作系统、总线为64个、可操作范围就是 2^64、 远大于 2^32 

  每次程序访问内存中的值的时候、就使用虚拟地址、硬件在操作系统的帮助下将物理地址翻译成虚拟地址(进程层级完成)、所以即使两个进程访问相同的虚拟地址、所映射的物理地址可能不同
  所以、虚拟内存是操作系统隔离进程的一种重要途径。通常一个进程不能访问其它进程的数据、因为没有任何虚拟地址可以映射到其它进程分配的物理内存

2. 一个运行中的进程的数据组织:
   1)text:程序文本、即程序所组成的机器语言指令
   2static:包含编译器所分配的变量、包括全局变量和static声明的局部变量
   3stack:运行时栈、由栈帧组成。每个栈帧包含函数参数、本地变量以及其它
   4)heap:运行时分配的内存块、常由malloc分配

3. 地址翻译:

内存管理

1. 实现
   进程启动时、系统为text段、静态分配的数据、栈和堆分配空间、堆中含有动态分配的数据
   并不是所有程序都动态分配数据、所以堆的大小可能很小、或者为0,最开始的时候堆只含有一个空闲块

   malloc调用时、会检查这个空闲块是否足够大、如果不是,它会向系统请求更多内存、这件事的函数叫sbrk,
   它设置 程序中断点(program break),可以将其理解为一个指向堆底部的指针

   sbrk调用时、它分配的新的物理内存页,更新进程的页表、并设置程序断点。
   理论上、程序应该直接调用sbrk而不是malloc,并且自己管理堆区、但是malloc,并且对于大多数内存使用模式,它运行速度快、并且可以高效的使用内存

   malloc通常不依赖块的大小、但是可能取决于空闲块的数量
   free通常很快、因为和空闲块的数量无关
   calloc会清空先有块中的每个字节、执行时间取决于块的大小和空闲块的数量
   realloc有时很快、如果大小比之前更小或者空间可用于扩展现有的内存块
          否则它需要从旧的内存块复制数据到新的内存块、这个情况下速度取决于旧内存块的大小

  空间开销:边界标签和空闲链表指针也占据空间、最小的内存块大小在大多数操作系统上是16字节、对于非常小的内存块、malloc在空间上并不高效、若程序需要大量的小型数据结构、可以将他们分配在数组中

  碎片:若以多种大小分配和释放块、堆区就会变得碎片化、空闲空间会打碎成许多小型片段,碎片非常浪费空间、它也会通过使缓存效率低下来降低程序的速度


缓存策略

1. 谁在移动数据?
    在结构的顶端、寄存器通常由编译器完成分配、CPU上的硬件管理内存的缓存
    在执行程序打开或者文件打开的过程中、用户将存储器上的文件隐式移动到内存、
    操作系统也会将数据从内存移回存储器、
    在层次结构的低端、管理员在磁带和磁盘之间显式的移动数据

多任务

1. 中断:
  1)中断发生时、硬件将程序计数器保存到一个特殊的寄存器中、并且跳转到合适的呃中断处理器
  2)中断处理器将程序计数器和位寄存器、以及任何打算使用的数据寄存器的内存存储到内存中、
  3)中断处理器运行处理中断所需要的代码
  4)恢复保存的寄存器内容、复原被中断进程的程序计数器、跳回被中断进程
  这个过程对于被中断进程无感知

2. 上下文切换
   中断处理器非常快、它们不需要保存整个硬件状态、只需要保存打算使用的寄存器
   通常中断发生时、内核并不总是恢复被中断的进程、它可以选择切换到其它进程、这种机制叫上下文切换

   通常、内核并不知道一个进程会用到哪些寄存器、所以需要全部保存、而且当它切换到新的进程时、它可能需要清除存储在内存管理单元MMU中的数据、上下文切换后需要花费时间为新的进程加载数据、所以上下文切换会比较慢

   在多任务的系统中、每个进程都允许运行一小段时间、叫时间片、在上下文切换的过程中内核会设置一些硬件计数器、在时间片的末尾产生中断、当中断发生时、内核可以切换到另外一个进程、或者允许被中断的进程继续执行,
   做决策的这部分叫调度器

3. 生命周期
   进程被创建时、系统会为进程分配包含进程信息的数据结构、称为进程控制块(PCB)、PCB跟踪进程的状态:
   1)运行Running、如果进程正运行在某个核心上
   2)就绪Ready、进程可以但还没有运行、通常由于就绪进程数量大于内核数量
   3)阻塞Blocked、进程由于正在等待未来的事件、eg 网络通信或者磁盘读取而不能运行
   4)终止Done、进程运行完毕、但是没有读取的退出状态信息

提供优先级调度API、
修改调度器来确保优先级高的进程在固定时间内运行
重新组织中断处理器来保证最大完成时间
修改锁和同步机制、允许优先级搞的任务占用优先级低的任务
选择保证最大完成时间的动态内存分配实现

线程

1. 创建进程时、系统会创建新的地址空间、包含text段、static段和堆区、也会创建新的执行线程、包括程序计数器和其它的硬件状态及运行时栈
   单线程就是每个地址空间中只运行一个执行线程,它在相同地址空间内拥有多个运行中的线程。
   多线程可以在相同地址空间内拥有多个运行中的线程。
   单一进程的多个线程之间共享text段、运行相同的代码、但是通常运行代码的不同部分
   而且它们共享static段、如果一个线程修改了某个全局变量、其它线程可以看到改动
   也共享堆区、所以线程可以共享动态分配的内存块

   但是有自己独特的栈区、所以线程可以调用函数而不相互影响。通常线程不能访问其它线程的局部变量

   编译时加上 gcc -g -O2 -o array array.c -lpthread

你可能感兴趣的:(无事闲翻书)