第1章_温故而知新

1.1 从Hello World说起

问题

  1. 程序为什么要编译了之后才能运行
    • 程序设计语言一般是高级语言,与具体机器无关,所以需要编译成为机器知道的机器码
  2. 编译器在把c语言转换成可以执行的机器码的过程中做了什么,怎么做的?
    • Compiler:
      • 词法分析:读取标识符,确定词法错误
      • 语法分析:确定标识符间关系,确定语法错误
      • 语义分析:进行类型审查
    • Linker:
      • 将第三方库等与源文件链接
  3. 最后编译出来的可执行文件里面是什么?除了机器码还有什么?它们怎么存放的,怎么组织的?
  4. include是什么意思?C语言库又是什么?它怎么实现的?

  5. 不同的编译器在不同硬件平台和操作系统上最终编译出来的结果一样吗?为什么?
    • 不一样,识别的机器码不同
  6. Hello World程序是怎么运行起来的?操作系统是怎么装载它的?它从哪里开始执行,又从哪里结束?main函数之前发生了什么,main函数结束之后又发生了什么
    • 操作系统将程序代码装入内存,生成对应的进程线程,给其分配时间,然后找到主程序段开始执行,到待机指令结束
  7. 如果没有操作系统,Hello World程序可以运行吗?要在一台没有操作系统的机器上运行Hello World需要什么,怎么实现
    • 可以实现,但是Hello World要起到驱动程序的作用与硬件交互
  8. printf是怎么实现的?它为什么可以有不定数量的参数,为什么它能在终端输出字符串
  9. Hello World程序在运行前,他在内存是什么样子的

1.2 万变不离其宗

  1. 三个关键部件
    1. CPU
    2. Main Memory
    3. I/O
  2. Bus
    1. PCI BUS,PCI Bridge:北桥,高速设备
    2. ISA BUS,ISA Bridge:南桥,低速设备
  3. SMP与多核
    1. SMP:Symmetrical Multi-Processing
    2. 多核:共享比较昂贵的缓存部件,保留多个处理器核心

1.3 站的高望的远

Any problem in computer can be solved by another layer of indirection.

1.4 操作系统做什么

1.4.1 不要让CPU打盹

  1. Multiprogramming
  2. Time-Sharing System
  3. Multi-tasking
  4. Preemptive

1.4.2 设备驱动

  1. 操作系统是对硬件的管理和抽象
  2. Device Driver:
    • 往往和操作系统一起运行在特权级
    • 一般通过读写I/O端口寄存器实现

1.5 内存不够怎么办

  1. LBA Logical Block Address:现代硬盘使用一种叫做LBA的方式,分配逻辑扇区号,电子设备会将逻辑扇区号自动转化为物理扇区号
  2. 问题
    1. 地址空间不隔离
    2. 内存使用效率低
    3. 运行地址不确定

1.5.1 关于隔离

虚拟地址空间Virtual Address Space

MMU: Memory Management Unit使用该硬件进行页映射

1.5.2 分段

Segmentation + Virtual Address Space

1.5.3 分页

  1. 一般每页为4KB
  2. VP Virtual Page, PP Physical Page, DP Disk Page
  3. 当某页需要用到而又不在内存中时会引发页错误Page Fault,操作系统接管进程并将页装入内存

1.6 众人拾柴火焰高

1.6.1 线程基础

线程一般由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。通常一个进程由一个或多个线程组成,各个线程之间共享内存空间以及一些进程级的资源

线程的私有存储空间

  1. 线程局部存储Thread Local Store TLS
  2. 寄存器

线程调度

Time Slice,Priority Schedule, Round Robin,Thread Priority

为了避免Starvation, 调度系统常常会逐步提升那些等待了过长时间的得不到执行的线程的优先级

Preemption

  1. 不可抢占线程
    • 线程必须主动进入就绪状态
      1. 试图等待某事件(I/O)
      2. 主动放弃时间片

Linux的多线程

  1. 并不存在真正意义上的线程,所有执行实体都称为任务,不同任务可以选择共享内存空间
  2. 常用方法
    1. fork 复制当前进程
      • fork 和原任务一起共享==Copy On Write==的内存空间
    2. exec 使用新的可执行映像覆盖当前可执行映像
    3. clone 创建子进程并从指定位置开始运行

1.6.2 线程安全

竞争和原子操作

  1. Windows 的 Interlocked API

同步与锁

  1. Lock
  2. Binary Semaphore
  3. Semaphore
  4. Mutex:与二元信号量相似,但是只允许获得Mutex的线程负责释放这个锁
  5. Critical Section:比互斥量严格,其他进程无法获取该锁
  6. Read-Write Lock,三种状态,自由,Shared,Exclusive
  7. Conditional Variable

Reentrant与线程安全

这个函数还没有执行完成但是由于外部或者内部调用又一次进入该函数

可能原因

  1. 多线程同时执行
  2. 函数自身递归

==过度优化==

  1. 编译器可能交换两条不相干指令,而多线程下出问题
    • volatile变量能够阻止变量被缓存到寄存器而不写回,也能阻止编译器调整volatile变量的指令顺序,但是还会被CPU调整
  2. CPU优化
    • EXample:单例模式可能返回没有构造完毕却不是空指针的地址
    • barrier 阻止CPU将该指令之前的指令交换到barrier之后,也阻止之后的指令交换到barrier之前

多线程内部情况

  1. 内核线程与用户线程的对应
    1. 一对一
    2. 多对一
    3. 多对多