计算机和OS启动过程:https://www.cnblogs.com/z-sm/p/5657914.html

CPU中断:https://www.cnblogs.com/z-sm/p/15162076.html

OS进程调度:https://www.cnblogs.com/z-sm/p/15200785.html

OS多路复用IO:https://www.cnblogs.com/z-sm/p/6680141.html

关于Linux的历史和内部原理的入门书籍,推荐阅读 Linux内核v0.1.2完全注释 一书,前面几章对Linux的发展过程、版本变化等做了详细的介绍。

 

20170104

冯诺依曼计算机(遵循冯诺依曼结构设计的计算机:存储器、运算器、控制器、输入设备、输出设备)之前也有计算机,不过在那之前的计算机是专用的,不可编程,只能干特定的事情没法干其他事。与之前计算机的一个不同在于冯诺依曼计算机是通用计算机可以干很多事情,并且程序是可存储的(“存储程序”),多个程序可以存储在计算机中。

 

20170328

1、典型的系统硬件组成结构

Casual Note of OSCasual Note of OS

现代计算机组成中,IO bridge分为北、南桥两部分,分别称为MCH(Memory Controller Hub)、ICH(IO Controller Hub),分别用来管理高速(如内存等)、中低速组件(如磁盘、USB等)。

Casual Note of OS

 

 

 

2、存储设备层次结构

Casual Note of OS

 

 

Casual Note of OS

 

典型 PC 机上通常含有三种类型的存储器,一种是用来运行程序和临时保存数据的内存存储器,一种是存放着系统开机诊断和初始化硬件程序(即BIOS)的 ROM,另一种是用来存放存计算机实时时钟信息和系统硬件 配置信息的少量 CMOS 存储器。

为什么叫主存(main memory)?因为CPU地址总线控制的不止是通常所认为的内存,还有显存等,这里面最常用的是通常认为的内存,故叫主存?

速度级别:

硬盘:毫秒级

内存:纳秒级

Casual Note of OS

 

 3、OS提供的抽象表示

Casual Note of OS

 

1、进程、线程。并行化:线程级并行(多核或超线程)、指令级并行、单指令多数据(SIMD)

2、虚拟存储器

Casual Note of OS ————Casual Note of OS

(从上可见,栈从上向下增长,故亦称下推表)

 

Casual Note of OS

 

 

20170427

精简指令集/复杂指令集的CPU架构:

RISC: MIPS架构PowerPC架构(IBM)、ARM架构(ARM)、SPARC架构(Sun,后被Oracle收购) 等的CPU用RISC

CISC:X86架构(Intel、AMD、VIA等) 等的CPU用CISC

题外话:Intel 1代为8位机(8080、8085)、2代16位机(8086)、3代为32位机(80386)、4代及目前最新为64位机。64位已经远远够用了,所以位长应该不会再增了。

 

20170428

计算机内部负整数为什么用补码?

原因是:只有这种形式,计算机才能实现正确的加减法。

计算机其实只能做加法,1-1其实是1+(-1)。如果用原码表示,计算结果是不对的。比如说:

1   -> 00000001

-1 -> 10000001

+ ------------------

-2 -> 10000010

用符合直觉的原码表示,1-1的结果是-2。

如果是补码表示:

1   -> 00000001

-1 -> 11111111

+ ------------------

0  ->  00000000

结果是正确的。

 

 

计算机浮点数运算为什么不准确?

如C下 printf("%.10f\n",0.1f*0.1f); 为0.0100000007,Java下 System.out.println(0.1f* 0.1f); 为0.010000001,虽然保留位数少的话可得期望值,但其实真正的计算结果有很多位,所以计算结果不是我们期望的。

原因:运算本身没错,而是计算机不能精确表示很多数,比如0.1,数都不能精确表示当然运算结果也不准确了。为什么不能精确表示呢——与十进制只能表示10的若干次方和的数一样,采用二进制的计算机只能表示2的若干次方和的数,前者不能准确表示无限小数如1/3,后者除不能表示无限小数外还有一些数也不能准确表示如0.1。

处理不精确:如果要求的精度不高,可以四舍五入;否则可以将小数转化为整数进行运算,算完再转化为小数。

 

 20170719

1、CPU视角看计算机启动过程(见 CPU阿甘——码农翻身

2、CPU视角看程序装载运行过程(见 CPU阿甘之烦恼——码农翻身):地址重定位,分页、工作集、页表、缺页中断,分段、段表、段错误

3、进程、线程实现(见 我是一个进程——码农翻身

4、死锁相关(见 我是一个线程——码农翻身

5、同步和锁

同步和互斥问题(见 那些烦人的同步和互斥问题——码农翻身):脱机打印、信号量、生产者消费者问题(通过信号量解决)

锁(见 编程世界的那把锁——码农翻身):CAS、TAS等操作 -> 自旋锁 -> 可重入锁 -> Semaphore、ReentrantReadWriteLock、CountDownLatch、CyclicBarrier
 
 
20170907
死锁的产生与解决:

1、产生死锁的原因主要是:
(1)因为系统资源不足。
(2)进程运行推进的顺序不合适。
(3)资源分配不当等。
如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。
2、产生死锁的四个必要条件:
(1)互斥条件:一个资源每次只能被一个进程使用。
(2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3)不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
  这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
3、死锁的预防:

(1)资源一次性分配:(破坏请求和保持条件)
(2)可剥夺资源:即当某进程新的资源未满足时,释放已占有的资源(破坏不可剥夺条件)
(3)资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏循环等待条件)

4、死锁的避免:(银行家算法)

预防死锁的几种策略,会严重地损害系统性能。因此在避免死锁时,要施加较弱的限制,从而获得较满意的系统性能。由于在避免死锁的策略中,允许进程动态地申请资源。因而,系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入不安全状态,则将资源分配给进程;否则,进程等待。其中最具有代表性的避免死锁算法是银行家算法

5、死锁的检测:进程等待图(wait graph)看是否有环

6、死锁的解除:

(1)剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态;

(2)撤消进程:可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用,死锁状态.消除为止;所谓代价是指优先级、运行代价、进程的重要性和价值等

 
2017.09.08
进程间通信(IPC)的方式:
1、无名管道(Pipe)
管道是一种具有两个端点的通信通道,一个管道实际上就是只存在在内存中的文件,对这个文件操作需要两个已经打开文件进行,他们代表管道的两端,也叫两个句槟,管道是一种特殊的文件,不属于一种文件系统,而是一种独立的文件系统,有自己的数据结构,根据管道的使用范围划分为无名管道和命名管道。是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
2、有名管道(Named Pipe)
是为了解决无名管道只能在父子进程间通信而设计的,命名管道是建立在实际的磁盘介质或文件系统(而不是只存在内存中),任何进程可以通过文件名或路径建立与该文件的联系,命名换到需要一种FIFO文件(有先进先出的原则),虽然FIFO文件的inode节点在磁盘上,但仅是一个节点而已,文件的数据还是存在于内存缓冲页面中,和普通管道相同。
3、高级管道(popen)
将另一个程序当做一个新的进程在当前程序进程中启动,则它算是当前程序的子进程,这种方式我们成为高级管道方式。
4、信号量(semophore )
信号量是一个计数器,可用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
5、信号(Signal)
信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
6、消息队列
消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
7、共享内存
最快的IPC方式。共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量配合使用,来实现进程间的同步和通信。
8、套接字
与其他通信机制不同的是,套接字可用于不同机器间的进程通信。有Socket套接字(TCP/IP)、Unix域套接字等。
 
套接字:Linux、Windows等平台,同一或不同Server上。
有名管道和共享内存:Windows,同一Server上。
Unix域套接字:Linux、Unix,同一Server上。
 
 
RAID(Redundant Arrays of Independent Disk,独立磁盘冗余阵列):RAID0、RAID1、RAID5、RAID10、RAID01、RAID50等
 
 
2017.09.11

页面置换算法:(详见:页面置换算法])

总结:

最佳置换算法

基于队列:FIFO。实现简单,时间效率高;效果差(缺页多),Belady异常

基于栈:LRU。与FIFO比:实现较复杂,时间效率低;效果好,没有Belady异常

LFU,与LRU类似

ClOCK:时间开销比LRU小但效果接近LRU

1、最佳置换算法(OPT)

置换算法所选择的被淘汰页面将是以后永不使用的,或者是在最长时间内不再被访问的页面,这样可以保证获得最低的缺页率。由于人们目前无法预知进程在内存下的若干页面中哪个是未来最长时间内不再被访问的,因而该算法无法实现

示例:(9次缺页中断、6次页面置换)

Casual Note of OS

2、先进先出置换算法(FIFO)

基于队列的算法。优先淘汰最早进入内存的页面,亦即在内存中驻留时间最久的页面。该算法实现简单,只需把调入内存的页面根据先后次序链接成队列,设置一个指针总指向最早的页面。但该算法与进程实际运行时的规律不适应,因为在进程中,有的页面经常被访问。FIFO算法还会产生当所分配的物理块数增大而页故障数不减反增的异常现象(由 Belady于1969年发现,故称为Belady异常)

示例:(15次缺页中断、12次页面置换)

Casual Note of OS

3、最近最久未使用置换算法(LRU)

基于栈的算法。选择最近最长时间未访问过的页面予以淘汰,它认为过去一段时间内未访问过的页面,在最近的将来可能也不会被访问。LRU性能较好,但需要寄存器和栈的硬件支持。LRU是堆栈类的算法。理论上可以证明,堆栈类算法不可能出现Belady异常。FIFO算法基于队列实现,不是堆栈类算法。

示例:(12次缺页中断、9次页面置换) 

Casual Note of OS

基于优先队列实现LRU:假设优先级越高越不应被删除,则对于要访问的目标数据(访问的页)

若缓存(三个物理块)中存在,则从缓存中访问该页,并把该页置为最高优先级;若不存在,则从缓存移除优先级最低的(最长时间未使用的物理块中到)页、把新页加入缓存,并把新页面置为最高优先级。

基于LinkedHashMap实现LRU:实际上就是对上述基于优先队列方案的实现,LinkedHashMap的entry的entry按加入的顺序保存,故“晚加入”相当于上面的“高优先级”。对于某个数据:

若LinkedHashMap中存在该元素,则访问删除该元素然后重新添加到map;若不存在,则直接加入(未满时)或 移除第一个entry并将元素加入(满时)到map。代码:

Casual Note of OS
abstract class DbUtilWithLruCache<T> {
    private final int cacheCapacity = 10;
    private LinkedHashMap<String, T> lruCache = new LinkedHashMap<>();// LinkedHashMap具有FIFO的特点,借助之来维护优先级,越晚添加者(越靠后)优先级越高

    // 添加缓存数据,添加的数据优先级最高
    private void setToLruCache(String key, T val) {
        if (lruCache.size() == cacheCapacity) {
            Iterator<?> it = lruCache.entrySet().iterator();
            lruCache.remove(it.next());

        }
        lruCache.put(key, val);
    }

    // 提高指定缓存数据的优先级
    private void increasePriority(String key, T val) {
        // 缓存中移除
        lruCache.remove(key);
        // 新加到缓存
        lruCache.put(key, val);
    }

    protected abstract T getValFromDB(String key);

    protected abstract void setValToDB(String key, T val);

    /** 获取数据 */
    public synchronized T get(String key) {
        T val = lruCache.get(key);
        
        if (null == val) {// 不存在:从db取、更新缓存
            // 从真实位置取得数据
            val = getValFromDB(key);
            // 加到缓存
            setToLruCache(key, val);
            
        } else {// 存在:提高优先级
            increasePriority(key, val);
        }

        return val;
    }

    /** 存数据 */
    public synchronized void set(String key, T val) {
        T oldVal = lruCache.get(key);
        
        if (oldVal != val) {//数据变化:存到db、更新缓存
            setValToDB(key, val);
            setToLruCache(key, val);
            
        } else {//数据没变化:提高优先级
            increasePriority(key, val);
        }
    }
LRU_with_LinkedHashMap

相关文章: