第一章 温故而知新
一、从Hello World说起
#include<stdio.h> int main() { printf("Hello world\n"); return 0; }
1、程序为什么要被编译器编译了之后才可以运行?
2、编译器在把C语言程序转换成可以执行的机器码的过程中做了什么?怎么做的?
3、最后编译出来的可执行文件里面是什么?除了机器码还有什么?他们怎么存放的,怎么组织的?
4、#include<stdio.h>是什么意思?把stdio.h包含进来是意味这什么?C语言库又是什么?他怎么实现的?
5、不同的编译器和不同的硬件平台以及不同的操作系统,最终编译出来的结果一样吗?为什么?
6、Hello World程序是怎么运行起来的?操作系统是怎么装载它的?它从哪儿开始执行,到哪儿结束?main函数之前发生了什么?main函数结束以后又发生了什么?
7、如果没有操作系统,Hello World可以运行吗?如果要在一台没有操作系统的机器上运行Hello World需要什么?应该怎么实现?
8、printf是怎么实现的?他为什么可以有不定数量的参数?为什么他能够在终端上输出字符串?
9、Hello World程序运行时,他在内存中是什么样子?
二、万变不离其踪
1、北桥芯片主要协调CPU、内存和高速的图形设备;南桥芯片主要协磁盘、USB等低速设备
2、对称多处理器(SMP:Symmetrical Multi-Processing):简单地讲就是每个CPU在系统中所处的地位和所发挥的功能都是一样的,是相互对称的。
3、多核处理器(Multi-Core Processer):将多个处理器合并在一起打包出售,这些被打包的处理器之间共享比较昂贵的缓存部件,只保留多个核心,可以视作SMP简化版。
4、操作系统作用:CPU调度(多道程序-->分时系统-->多任务系统);设备驱动;内存管理(直接使用物理内存-->分段-->分页)
三、线程基础
线程(Thread),有时被称为轻量级进程(Lightweight Process, LWP)是程序执行流的最小单元。一个标准的线程是由线程ID、当前指令指针(PC)、寄存器集合和堆栈组成。通常意义上,一个进程是由一个到多个线程组成,各个线程之间共享程序的内存空间(包括代码段、数据段、堆等)及一些进程级的资源(如打开文件和信号)。
1、多线程使用场景:a、某个操作可能会陷入长时间等候;b、某个操作是CPU密集型;c、程序逻辑是并发(下载);d、相比多进程,多线程共享效率更高
2、线程的私有空间:a、栈;b、线程局部存储;c、寄存器
3、线程的共享空间:a、全局变量;b、堆上的数据;c、函数里的静态变量;d、程序代码,任何线程都有权限读取并执行任何代码;e、打开的文件等资源
4、原子操作:单指令的操作称之为原子操作,因为无论如何,单条指令的执行都不会被打断。为了避免出错,很多体系结构都提供了一些常用的原子指令。
四、线程安全
为了避免多个线程同时读写同一个数据而产生不可预料的后果,我们要将各个线程对同一个数据的访问同步(Synchronization)。所谓同步,即指在一个线程访问数据未结束的时候,其他线程不得对同一个数据进行访问。
常用的线程同步方式:a、二元信号量(Binay Semaphore)/信号量(Semaphore);b、互斥量(Mutex);c、临界区(Critical Section);d、读写锁(Read-Write Lock);e、条件变量(Condition Variable)。
可重入函数必须满足几个特点:a、不使用任何静态或局部的非const变量;b、不返回任何静态或全局的非const变量的指针;c、仅依赖与调用方提供的参数;d、不依赖任何单个资源的锁;e、不调用任何不可重入的函数。
五、Double-Check
volatile T*pInst = 0; T* GetInstance() { if(pInst == NULL) { lock(); if(pInst == NULL) { pInst = new T; } unlock(); } return pInst; }
上面的代码不是线程安全的,原因有二:1、编译乱序;2、CPU执行乱序