Interface
的概念:计算机中,每个层次之间的通信协议(这里的接口区别于Java等编程语言的接口)。
内存管理
内存的分段管理:
基本思路是把一段与程序所需要的内存空间大小的虚拟空间映射到某个地址空间。给出一个示例图:
但是分段存在一个效率问题,比如我们的硬盘是100M,此时由10M、80M和30M三个程序,不论怎样分段,都无法完成高效的映射。
内存的分页管理:
分页的基本思路是把内存的地址空间人为的分成固定大小的页,操作程序可以有部分在内存中,有部分在磁盘中。根据程序利用的“二八法则”,可以灵活地调整内存分配。
线程、进程与并发
进程拥有独立的地址空间,资源对外封闭,是操作系统调度的单位。线程是CPU执行的单位,是轻量级的继承,有自己的寄存器集合和堆栈,但是资源不封闭。
线程或者进程会有不同的状态,基本的状态转化图:
Linux与windows相比,创建进程消耗的资源更少,可以快速创建大量的进程,创建方式有三种,分别是:
fork
:复制当前进程exec
:使用新的可执行映像覆盖当前可执行映像clone
:创建子进程并从指定位置开始执行
fork
的速度非常快,fork
本身不会立刻复制原来任务的内存空间,而是和原来的任务共享写时复制的内存空间。写时复制是指两个任务可以同时自由读取内存,如果任意一个任务对内存空间进行写的操作,那么内存就会复制一份给修改方单独使用。
fork
只能产生本任务的镜像,我们可以先fork
一个进程,然后调用exec
函数来执行一个新的任务,从而达到多进程执行不同任务的目的。
锁、互斥量和信号量的概念:
- 锁:锁是一个概念,是为了保护某个代码段只能同时被一个线程所访问,这个代码段成为临界区。
- 信号量:访问信号量的值小于0时,线程进入等待状态;在访问资源完成后,信号量的值加1,如果此时信号量的小于1,则唤醒一个等待的线程。二元信号量是最简单的锁的结构。信号量可以被任何的线程释放!!!
- 互斥量
mutex
:资源仅仅允许一个线程访问,但是互斥量只能被获取互斥量的线程释放。
可重入函数:在函数调用结束前,如果有其他的线程调用该函数,函数产生的结果不会发生变化。可重入函数是并发执行的保障。
编译器的过度优化和volatile关键字
首先要明白,线程拥有各自独立的寄存器,线程在读取变量的时候,会先从内存获取数据添加到自己的寄存器中,然后在寄存器中操作完毕后放回内存中。第一个过度优化的情况是,编译器为了提高程序速度,在寄存器操作完后,不把数据立刻放回内存中,而可能过一段时间才放回内存;第二个过度优化是,编译器可能把看似逻辑上不相关的操作指令调换顺序。
第一个操作带来的问题:
x = 0;
Thread1 Thread2
lock(); lock();
x++; x++;
unlock(); unlock();
虽然加锁了,但是程序执行自增操作后,此时某个线程离开了临界区,为了优化速度不把寄存器的x
立刻放回内存,那么另一个程序操作的数据是没有更新的,这样就是造成数据错误。
第二个操作带来的问题:
x = y = 0;
Thread1 Tread2
x = 1; y = 1;
r1 = y; r2 = x;
编译器为了优化速度,可能把看似不相关的程序调换下位置,比如x = 1
和r1 = y
的顺序被调换了,那么r1
和r2
就会出现同时为0的情况。
volatile
指令可以做两件事情:
- 阻止编译器为了提高速度把一个变量缓存到寄存器而不写回内存
- 阻止编译器调整
volatile
变量的指令顺序
但是,volatile
无法阻止CPU动态调度换序,因此还是存在并发的问题