程序员的自我修养—2014-10-9
2014-10-09 22:57 想打架的蜜蜂 阅读(189) 评论(0) 收藏 举报计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决
1、每个层次之间都需要相互通信,既然需要通信就必须有一个通信的协议,我们将其称为接口,接口的下面那层是接口的提供者,由它定义接口,接口的上面那层是接口的使用者,他使用该接口来实现所需要的功能。每个层都是对他下面的那层的包装和扩展
2、位于上面的应用程序,使用一个接口,即操作系统应用程序编程接口(Application Programming Interface).应用程序接口的提供者是运行库,运行库使用操作系统提供的系统调用接口,系统调用接口在实现中往往以软件中断的方式提供。
3、操作系统作什么:操作系统的一个功能是提供抽象的接口,另外一个主要功能是管理硬件资源。
程序员使用硬件资源时,不需要关注具体的硬件,这些硬件繁琐的细节全都交给操作系统,具体的讲就是操作系统的硬件驱动程序来完成。驱动程序可以看作是操作系统的一部分,它往往跟操作系统内核一起运行的特权级,但他又与操作系统之间有一定的独立性,使得驱动程序有比较好的灵活性。
4、文件在磁盘上的存储结构:硬盘基本存储单位为扇区,每个扇区一般为512字节。一个硬盘往往有多个盘片,每个盘片分两面,每面按照同心圆分为若干个磁道,每个磁道划分成若干个扇区。
要读取文件的前n字节时,我们会使用一个read的系统调用来实现。文件系统收到read请求之后,判断出文件的这n个字节位置,然后文件系统就向硬盘驱动发出一个读取逻辑扇区为多少(就是文件前n字节存放的位置)的请求,磁盘驱动程序收到这个请求以后就向硬盘发出硬件命令。
5、关于隔离:所谓的地址空间是一个比较抽象的概念,可以把它想象成一个很大的数组,每个数组的元素是一个字节,而这个数组大小由地址空间的地址长度决定。地址空间分为两种,虚拟地址空间和物理地址空间,物理地址空间是实实在在的,假如32位的机器,计算机地址线就是32条,那么物理地址空间就是4G。但是如果计算机只装512MB的内存,那么物理地址的有效部分只有0x00000000~0x1FFFFFFF,其他部分都是无效的,但是如果你装内存超过4G,那么有效的也就4G。
6、分段方法思路,把一段与程序所需要的内存空间大小的虚拟空间映射到物理地址空间,从实际的物理内存中分配一个相同大小的物理地址,然后相同大小的地址空间一一映射,即虚拟空间中的每个字节相对应于物理空间中的每个字节。这个映射过程由软件来设置,实际的地址转换由硬件开完成。分段方法做到了地址隔离,因为不同程序映射到不同的物理空间区域。如果一个程序访问虚拟地址超出了该程序所在的范围,硬件就会判断这是一个非法的访问。但是这种访问方式,造成的问题就是内存使用效率问题。分段对内存区域的映射还是按照程序为单位,如果内存不足,被换入唤出到磁盘的都是整个程序,造成大量的磁盘访问操作。重要的是,根据程序的局部性原理,当一个程序在运行时,在某个时间段内,它只是频繁地用到了一个小部分数据,也就是说程序的很多数据其实在一段时间内不会被访问,故为了提高内存的利用率,引出了分页。
7、分页是把地址空间人为的等分成固定大小的页,由操作系统决定页的大小。对于整个系统来说页的大小是固定的,把虚拟空间的页叫做虚拟页,把物理内存的页成为物理页,不同进程的页可能指向相同的物理页,这样就实现内存共享。
保护也是页映射的目的之一,简单地说,就是每个页可以设置权限属性,谁可以修改,谁可访问,而且只有操作系统有权限修改这些属性。
8、线程被称为轻量级的进程,是程序执行流的最小单元,一个标准的线程由线程ID,当前指令指针PC、寄存器集合和堆栈组成。同一个进程中的各个线程之间共享程序的内存空间(代码段、数据段、堆栈段)以及资源。
使用多线程原因:1)某个操作可能会陷入长时间等待,等待的线程会进入睡眠状态,无法继续执行。多线程执行可以有效利用等待的时间。2)某个操作会消耗大量的时间,如果只有一个线程,程序和用户之间的交互会中断。多线程可以让一个进程做花费时间的操作。3)程序逻辑本身就要求并发性操作。4)多cpu或者多核计算机,本身就具备同时执行多个线程的能力。5)相对于多进程应用,多线程在数据共享方面效率要高的多。
9、线程可以访问进程内存中所有的数据,假如它知道同一个进程中其他线程的堆栈地址,也是可以访问的。但是也拥有自己的私有存储空间,包括:栈、线程局部存储、寄存器(寄存器是执行流的基本数据,因此为线程私有)、
当线程数量小于等于处理器数量时就是真正的并发。线程通常有至少三种状态:
运行:此时线程正在执行
就绪:此时线程可以立即运行,但是cpu已经被占用
等待:此时线程正在等待某一事件发生,无法执行
把频繁等待的线程称之为IO密集型线程,而把很少等待的线程称为CPU密集型线程,IO密集型线程总比CPU密集型线程容易得到优先级的提升。
Linux的多线程
Linux将所有的执行实体都成为任务Task,每一个任务概念上都类似于一个单线程的进程,具有内存空间、执行实体、文件资源等。
同步和锁:避免多个线程同时读写同一个数据而产生不可预料的后果,需要将各个线程对同一个数据的访问同步,所谓同步,既是指在一个线程访问数据未结束的时候,其他线程不得对同一个数据进行访问。
10同步实现方法:同步实现的方法常见的是使用锁,每一个线程在访问数据或资源之前首先试图获取锁,并在访问结束之后释放锁,锁被占用的时候只能等待。
二元信号量是一种最简单的锁,它只处于两种状态,占用和非占用,它适合只能被唯一一个纤尘独占访问的资源,当二元信号量处于非占用状态时,第一个试图获取该二元信号量的线程会获得概锁,并将二元信号量置为占用状态,此后其他的所有试图获取该二元信号量的线程将会等待,直到该锁被释放。、
对于允许多个线程并发访问的资源,多元信号量简称为信号量,他是一个很好的选择,一个初始值为N的信号量允许N个线程并发访问。线程访问资源的时候首先获取信号量,进行如下操作:
将信号量的值减1
如果信号量的值小于0,则进入等待状态,否则继续执行。访问完资源之后,线程释放信号量,进行如下操作
将信号量的值加1
如果信号量的值小于1,唤醒一个等待中的线程
互斥量:资源仅同时允许一个进程访问,但和信号量不同的是,信号量在整个系统可以被任意线程获取并释放,即同一个信号量被系统中的一个线程获取之后由另外一个线程释放,而互斥量则要求哪个线程获取了互斥量,哪个线程就要负责释放这个锁
临界区:临界区的作用只适合于本进程,其他进程无法获得该锁
读写锁:对于一段数据,假如操作都不是原子型,对于同一个锁,读写锁有两种获取方式,共享的或者独占的,当锁处于自由的状态时,试图以任何一种方式获得锁都能成功,并将锁至于对应的状态。如果锁处于共享的状态,其他线程以共享的方式获取锁仍然会成功,此时这个锁分配给了多个线程。然而,如果其他线程试图以独占的方式获取已经处于共享状态的锁,那么它将必须等待锁被所有的线程释放。相应的,处于独占状态的锁将阻止任何其他线程获得该锁,不论他们以何种方式获取。
可重入与线程安全
一个函数被重入,表示这个函数没有执行完成,由于外部因素或内部调用,有一次进入该函数执行,一个函数被重入,有:多个线程同时执行这个函数或者函数自身调用自身
一个函数可重用必须具有以下特点:
不使用任何(局部)静态或者全局的非const变量
不返回任何(局部)静态或者全局的非const变量的指针
仅依赖于调用方提供的参数
不依赖任何单个资源的锁
不调用任何不可重入的函数