程序员的自我修养-第一章 笔记

1.计算机硬件的三个部件最为重要:中央处理器CPU,内存和IO控制芯片。

2.主板上北桥芯片处理高速设备。南桥芯片处理低速设备,然后汇总后连接到北桥上。

3.多个处理之间共享比较昂贵的缓冲部件,只保留报个核心,并且以一个处理器的外包装出售,售价比单核心的处理器只贵了一点,这就是多核处理器(Multi-core Processor)的基本想法。

4.操作系统内核层对于硬件层来说是硬件接口的使用者,而硬件是接口的定义者,硬件的接口定义决定了操作系统内核,具体来讲就是驱动程序如何操作硬件,如何与硬件进行通信。这种接口往往被叫做硬件规格(HardWare Specification),硬件的生产厂商负责提供硬件规格,操作系统和驱动程序的开发者通过阅读硬件规格所规定的各种硬件编程接口标准来编写操作系统和驱动程序。

5.早期的计算机中,程序是直接运行在物理内存上的,多个程序运行时也都被分配在物理内存上,这样的分配策略引发了3个重要问题:程序之间的地址空间不隔离,内存使用效率低,程序运行的地址不确定。解决这几个问题的思路就是增加中间层,即使用一种间接的地址访问方法。整个想法是这样的,我们把程序给出的地址看作是一种虚拟地址(Virtual Address),然后通过某些映射的方法,将这个虚拟地址转换成实际的物理地址。这样,只要我们能够妥善地控制这个虚拟地址到物理地址的映射过程,就可以保证任意一个程序所能够访问的物理内存区域跟另外一个程序相互不重叠,以达到地址空间隔离的效果,同时也可以做到每个程序的地址是相同的。而内存使用效率低这个问题人,们很自然地想到了更小粒度的内存分割和映射的方法,使得程序的局部性原理得到充分的利用,大大提高了内存的使用率。这种方法就是分页(Paging)。

6.虚拟存储的实现需要依靠硬件的支持,对于不同的CPU来说是不同的,但是几乎所有的硬件都采用一个叫做MMU(Memory Management Unit)的部件来进行页映射。一般MMU都集成在CPU内部了,不会以独立的部件存在。

7.线程(Thread),有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID、当前指令指针(PC),寄存器集合和堆栈组成。通常意义上,一个进程由一个到多个线程组成。各个线程之间共享程序的内存空间(包括代码段,数据段,堆等)及一些进程级的资源(如打开文件和信号)。

8.一般把频繁等待的线程称之为IO密集型线程(IO Bound Thread),把很少等待的线程成为CPU密集型线程(CPU Bound Thread)。IO密集型线程总是比CPU密集型线程容易得到优先级的提升。在线程优先级调度下,存在一种饿死(Starvation)的现象,一个线程被饿死,是说它的优先级较低,在它执行之前,总是有较高优先级的线程试图执行,因此这个低优先级线程始终无法执行。当一个CPU密集型的线程获得较高的优先级时,许多低优先级的线程很可能饿死。而一个高优先级的IO密集型线程由于大部分时间都处于等待状态,因此相对不容易造成其他线程饿死。为了避免饿死现象,调度系统常常会逐步提升那些等待了过长时间的得不到执行的线程的优先级。在这样的手段下,一个线程只要等待足够长的时间,其优先级一定会提高到足够让它执行的程序。

9.fork产生新任务的速度非常快,因为fork并不复制原任务的内存空间,而是和原任务一起共享一个写时复制(Copy On Write,COW)的内存空间。所谓写时复制,指的是两个任务可以同时自由地读取内存,单任意一个任务试图对内存进行修改时,内存就会复制一份提供给修改方单独使用,以免影响到其他的任务使用。

10.互斥量和二元信号量很类似,资源仅同时允许一个线程访问,但和信号量不同的是,信号量在整个系统可以被任意线程获取并释放,也就是说,同一个信号量可以被系统中的一个线程获取之后由另一个线程释放。而互斥量则要求那个线程获取了互斥量,哪个线程就要负责释放这个锁,其他线程越俎代庖去释放互斥量是无效的。

11.临界区是比互斥量更加严格的同步手段。互斥量和信号量在系统的任何进程都是可见的,也就是说,一个进程创建了一个互斥量或信号量,另一个进程试图去获取该锁是合法的。然而,临界区的作用范围仅限于本进程,其他的进程无法获取该锁,除此之外,临界区具有和互斥量相同的性质。

12.一个函数要成为可重入的,必须具有如下几个特点:
  1.不使用任何(局部)静态或全局的非const变量;
  2.不返回任何(局部)静态或全局的非const变量的指针;
  3.仅依赖于调用方提供的参数;
  4.不依赖任何单个资源的锁(mutex等);
  5.不调用任何不可重入的函数。
  可重入是并发安全的强力保障,一个可重入的函数可以在多线程环境下放心使用。

13.volatile关键字试图阻止过度优化,volatile基本可以做到两件事情:
  1.阻止编译器为了提高速度将一个变量缓存到寄存器内而不写回;
  2.阻止编译器调整操作volatile变量的指令顺序(即使volatile能够阻止编译器调整顺序,也无法阻止CPU动态调度换序);

14.CPU的乱序执行能力让我们对多线程的安全保障的努力变得异常困难。因此要保证线程安全,阻止CPU换序是必需的。遗憾的是,现在并不存在可移植的阻止换序的方法。通常情况下是调用CPU提供的一条指令,这条指令常常被称为barrier。一条barrier指令会阻止CPU将该指令之前的指令交换到barrier之后,反之亦然。换句话说,barrier指令的作用类似于一个拦水坝,阻止换序“穿透”这个大坝。
  关于barrier()宏实际上也是优化屏障:

  #define barrier() __asm__ volatile (”lwsync”)

  CPU越过内存屏障后,将刷新自己对存储器的缓冲状态。这条语句实际上不生成任何代码,但可使gcc在barrier()之后刷新寄存器对变量的分配。

  1.set_mb(),mb(),barrier()函数追踪到底,就是__asm__ __volatile__("":::"memory"),而这行代码就是内存屏障;
  2.__asm__用于指示编译器在此插入汇编语句;
  3.__volatile__用于告诉编译器,严禁将此处的汇编语句与其它的语句重组合优化。

 

 

posted @ 2018-11-10 18:47  空水  阅读(283)  评论(0编辑  收藏  举报