计算机硬件设备有三个最为关键:中央处理器CPU,内存、I/O控制芯片。
连接高速芯片的是北桥;南桥专门处理低速设备;
PCI结构,低速设备采用ISA总线,,采用PCI/ISA以及南北桥设计的硬件图如下:
SMP与多核
对称多处理器(SMP),简单说,就是每个CPU在系统中所处的地位和发挥的作用的功能都是一样的,是相互对称的。
多核处理器是一个处理器有多个核心,是SMP的简化版。
系统软件分为两块,一块是平台性的,操作系统内核、驱动链接、运行库和数以万计的系统工具;另外一块是用于程序开发,比如编译器、汇编器、链接器等开发工具和开发库。
每个层次之间都必须要相互通信,既然要通信就必须有一个通信的协议,我们称其为接口,接口下面那层是接口提供者,由它定义接口;接口上面那层是接口的使用者,它使用该接口来实现所需的功能。
运行库使用操作系统提供的系统调用接口,系统调用接口在实际中往往以软件中断的方式提供。
操作系统内核层对于硬件层来说是硬件接口的使用者,而硬件是接口的定义者,硬件的接口定义决定了操作系统内核,具体来说就是驱动程序如何操作硬件,如何与硬件进行通信,这种接口往往被叫做硬件规格。
操作系统的一个功能是提供抽象的接口,另外一个主要的功能就是管理硬件资源。
一个监控程序,当某个程序暂时无需使用cpu时,监控程序就把另外的正在等待CPU资源的程序启动,使得CPU能够充分利用,这种程序成为多道程序。
经过稍微改进,程序运行模式变成了一种协作的模式,即每个程序运行一段时间后都会主动让出CPU给其他程序,使得一段时间内每个程序都有机会运行一段时间,这叫做分时系统。
多任务系统:操作系统接管了所有硬件资源,并且本身运行在一个受硬件保护的级别。所有的应用程序都以进程的方式在比操作系统权限更低级别,每个进程都有自己独立的地址空间,使得进程之间的地址相互隔离。CPU由操作系统统一分配,每个进程根据进程优先级的高低都有机会得到CPU,但是如果运行一段时间超出一定的时间,操作系统会暂停该进程,将CPU资源分配给其他等待运行的进程。这种CPU分配方式就是抢占式,操作系统可以强制剥夺CPU资源并且分配给它认为目前最需要的进程。
给固定的程序分配固定的空间。
带来以下问题:
1、地址空间不隔离
2、内存使用效率低
3、程序运行的地址不确定。
解决方法就是:增加中间层,使用一种间接的地址访问方法:把程序给出的地址看作一种虚拟地址,然后通过某些映射方法,将这个虚拟地址转换成实际的物理地址。
分段的基本思路是把一段与程序所需的内存空间大小的虚拟空间映射到某个地址空间。
如果程序A访问虚拟空间的地址超出了0x00A00000这个范围,那么硬件就会判断这是一个非法的访问,拒绝这个请求,并将这个请求报告给操作系统或者监控程序,由它来决定如何处理。
分段没有解决内存使用效率的问题。
分页的基本方法是把地址空间人为地分成固定大小的页,每一页的大小又硬件决定,或者硬件支持多种大小的页,由操作系统选择决定页的大小。虚拟空间的有些页被映射到同一个物理页,这样就可以实现内存共享。
保护也是页映射的目的之一,简单地说就是每个页可以设置权限属性,谁可以修改,谁可以访问等,而只有操作系统有权限修改这些属性,那么操作系统就可以做到保护自己和保护进程。
虚拟内存的实现需要依靠硬件的支持,对于不同的CPU来说是不同的。但是几乎所有的硬件采用一个叫做 MMU(内存管理单元)的部件来进行页映射。
什么是线程
线程,被称为轻量级进程,是程序执行流的最小单元。一个标准的线程由线程ID、当前指令指针(PC)、寄存器集合和堆栈组成。通常,一个进程由一个到多个线程组成,各个线程之间共享程序的内存空间(包括代码段、数据段、堆等)及一些进程的资源(如打开文件和信号)。一个经典的线程与进程的关系如下图
采用多线程的原因:
1、多线程执行可以有效利用等待的时间。
2、多线程可以让一个线程负责交互、另一个线程负责计算。
3、程序逻辑本身就要求并发操作
4、全面地发挥计算机的全部计算能力。
5、多线程在数据共享方面效率要高很多。
线程的访问权限:
线程可以访问进程内存里的所有数据,甚至包括其他线程的堆栈,但实际中线程也有自己的私有存储空间,包括以下几方面:
1、栈
2、线程局部存储(TSL)。线程局部存储是某些操作系统为线程单独提供的私有空间,但通常只具有很有限的容量。
3、寄存器(包括PC寄存器),寄存器是执行流的基本数据,因此为线程私有。
线程调度与优先级
不断在处理器上切换不同的线程的行为称为线程调度。
在线程调度中,线程通常拥有至少三种状态:
1、运行,此时线程正在执行
2、就绪,此时线程可以立刻运行,但CPU已经被占用
3、等待,此时线程正在等待某一事件(通常是I/O或者同步)发生,无法执行。
处于运行中线程拥有一段可以执行的时间,这段时间称为时间片,当时间片用尽时候,该进程将进入就绪状态。如果在时间片用尽之前进程就开始等待某事件,那么它将进入等待状态。
线程调度方式:优先级调度和轮转法。
轮转法:让各个线程轮流地执行一段时间。
优先级调度法:决定了线程按照什么顺序轮流执行。
把频繁等待的线程称之为IO密集型线程,而把很少等待的线程称为CPU密集型线程。IO密集型线程总比CPU密集型线程容易得到优先级的提升。
线程优先级改变的三种方式:
1、用户指定优先级
2、根据进入等待状态的频繁程度提升或降低优先级
3、长时间得不到执行而被提升优先级。
可抢占线程和不可抢占线程
线程在用尽时间片之后会被强制剥夺继续执行的权力,而进入就绪状态,这个过程称为抢占。
在不可抢占线程主动放弃执行有两种情况:
1、当线程试图等待某事件时(I/O等)
2、线程主动放弃时间片。
linux的多线程
Linux将所有实体(无论是线程还是进程)都称为任务,每一个任务概念上都类似于一个单线程的进程,具有内存空间、执行实体、文件资源等。
fork产生新任务的速度非常快,因为fork并不复制原任务的内存空间,而是和原任务一起共享一个写时复制的内存空间。写时复制就是指两个任务可以同时自由地读取内存,但是任意一个新的任务试图对内存进行修改时,内存就会复制一份提供给修改方单独使用,以免影响到其他的任务使用。
同步:即在一个线程访问数据未结束的时候,其他线程不得对同一个数据进行访问。
同步的最常见方法是使用锁。锁是一种非强制机制,每一个线程在访问数据或者资源之前首先试图获取锁,并在访问结束之后释放锁。在锁已经被占用的时候试图获取锁时,线程会等待,直到锁重新可用。
二元信号量是最简单的一种锁,它只有两种状态:占用和非占用。它适合只能被唯一一个线程独占访问的资源。
对于允许多个线程并发访问的资源,多元信号量简称信号量。
互斥量和二元信号量很类似,资源仅同时允许一个线程访问,但是和信号量不同的是,信号量在整个系统可用被任意线程获取并释放。
临界区是比互斥量更加严格的同步手段,把临界区的锁的获取称为进入临界区,而把锁的释放称为离开临界区。
读写锁:写时复制,读时共享。
对于同一个锁,读写锁有两种获取方式,共享的或者独占的。。
条件变量作为一种同步手段,作用类似于一个栅栏。
一个函数被重入,表示这个函数没有执行完成,由于外部因素或者内部调用,又一次进入该函数。一个函数要被重入,只有两种情况:
1、多个线程同时执行这个函数
2、函数自身(可能经过多层调用)调用自身。
一个函数被称为可重入,表明该函数被重入之后不会产生任何不良后果。
一个函数要成为可重入的,必须具有如下几个特点:
1、不使用任何(局部)静态或全局的非const变量
2、不返回任何(局部)静态或全局的非const变量的指针
3、仅依赖于调度方提供的参数
4、不依赖任何单个资源的锁。
5、不调用任何不可重入的函数。
可重入是并发安全的强力保障,一个可重入函数可以放心在多线程环境下放心使用。
volatile关键字试图阻止过度优化,volatile基本可以做到两件事:
(1)阻止编译器为了提高速度将一个变量缓存到寄存器内而不写回,
(2)阻止编译器调整操作volatile变量的指令顺序(但是无法阻止CPU动态调度顺序)
三种线程模型
1、一对一模型
一对一模型:一个用户线程对应一个内核线程;优点:线程之间的并发是真正的并发,一个线程阻塞不会影响其他线程。一对一模型可以让多线程程序在多处理器系统有更好表现。
2、多对一模型
多个用户线程映射到一个内核线程上,线程之间的切换由用户态的代码来进行,相对于一对一模型,多对一的线程切换快速许多。
缺点:一个用户线程阻塞会造成一个内核线程的所有用户线程阻塞。
优点:高效的上下文切换和无限制的线程数量
多对多模型结合了多对一和一对多的特点,将给多个用户线程映射到少数但不止一个内核线程。
在多对多模型中,一个用户线程阻塞不会使得所有的用户线程阻塞,因为此时还有别的线程可以被调度来执行。另外,多对多模型对用户线程数量无限制;在多处理器系统上,性能提升效果不如一对一模型高。
来源:《程序员的自我修养》——链接、装载与库。
仅供学习、非商业用途、侵删