超越物理内存的策略
当有足够的内存时,发生页错误,只需要将页加入内存即可,但是如果内存不够,就需要将内存中的一些页换出,放入磁盘中去。那么现在问题就在于,要将那些页面换出呢?
这些存在于内存中的页,可以看作是一个缓存,如果访问该页时,页在内存,就当作缓存命中,不在就是没命中,那么我们的目标就变为让缓存的命中率尽可能地提高。
最优替换策略该替换策略是将最远的将来才会使用到的页替换出去,这样就能使得命中率最高,但是也很难实现,或者换种方式说,几乎无法实现。因为我们无法知道哪个页最远才会被使用,该算法只能作为一个比较使用。
FIFO(先进先出)先进入系统的页就先被换出。优点是实现很简单。缺点是可能导致内存页命中率特别低,极端情况下还会为0。
随机依靠随机数,替换出某些页。该策略实现页比较简单,但是依靠运气。
利用历史数据:LRU该算法考虑历史使用数据,如果一个页被很频繁的使用,那么它应该是比较重要的,不应该被换出。那么那些最近最不经常使用的页,就会被换出内存。
这里使用了局部性原则,包括空间局部性和时间局部性。
面临的问题:
该算法要求我们,每次访问内存,都需要同步的做一些修改,来更新页面位于队列中的位置。为了 ...
超越物理内存的机制
在本章之前,我们所做的假设都是将进程的所有页都可以放入内存当中,但实际情况是,物理内存无法存储这么多的页。而且,为了更大的地址空间,我们也需要更大的物理内存来存储更大的页表。
这里的一个解决办法就是,利用磁盘空间,也就是说将页表取出一部分,放入磁盘当中。而操作系统需要知道这些被换出的页存在于磁盘的哪个位置,以便后续将他们加载到内存当中。
交换空间在磁盘上开辟一块空间,用于物理页的移入和移出,这些空间就称为交换空间。而交换空间的大小,决定了系统在某一时刻能够使用的最大内存页数。而且操作系统需要记得给定页的硬件地址。
存在位按照之前的设定,一个虚拟地址转换为物理地址,会先检查TLB(快速地址转换),如果命中,则直接从中获取到映射,速度很快。
如果未命中,则需要先去内存中去找页目录,然后从页目录中找到对应页表页(页表被分为了很多个页),然后从页表中找到对应的映射,之后再把它加入TLB中。
如果添加了交换空间,这里边就需要考虑页是否在内存中的情况,则需要多一个标记位,即存在位,用于标记该页是否在内存当中。
页错误如果访问的页不在内存当中,就会发生页错误。
发生页错误,可能是因为该页被换出磁盘 ...
Synchronized的使用
考虑如下代码
12345678910111213141516171819202122public class SyncTest implements Runnable { int count = 0; @Override public synchronized void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + ":" + count++); } } public static void main(String[] args) { SyncTest syncTest = new SyncTest(); SyncTest syncTest1 = new SyncTest(); Thread thread = new Thread(syncTe ...
可靠数据传输原理
实现可靠传输是靠可靠数据传输协议来完成的,而该协议却建立在不可靠的下层协议上。就好比TCP提供可靠传输,而TCP却是建立在IP协议这个不可靠协议的基础上。
以下的讨论都是单向数据传输,而且暂且只考虑损坏比特或者整个报文段丢失的情况,而不考虑乱序到达的情况。
rdt1.0:经完全可靠信道传输该协议只考虑最简单的情况,即底层信道完全可信。
因为默认信道传输可信,那么它的发送端只等待来自上层的调用,接受端只等待来自下层的调用,发送端将数据发送出去之后,就不需要再管,默认一定会到达接收端。
而且这里也假定了接收方的接受速率和发送方的一致。
rdt2.0:具有比特差错信道的可靠数据传输在该协议中,我们只假定发送的比特可能会受损,而不考虑它们可能存在乱序到达或者丢失的情况。
这里可以类比打电话,我们说一句,对方听到后就会对其进行响应,如果没听清,一般会说让我们再说一边。
在该协议中,使用了肯定确认和否定确认,发送方收到肯定确认意味着接收方成功收到了数据,而否定确认意味着接收方收到的比特有误,需要重传。基于这种机制的协议叫做自动重传请求协议。
它最主要的是三点:
1、差错检测。它需要能够检测出传送的 ...
UDP
UDP是面向无连接的,它没有拥塞控制。但是这里面存在一个问题,如果大量的UDP进行传输并且不经过控制,那么会导致整个网络的拥堵,造成大量UDP报文溢出,而且还会挤占TCP的通道,影响TCP通信。
UDP可以提供差错的检测,但是对于错误的报文段,却无法对其进行恢复,只能选择丢弃,或者交给应用程序,并发出警告。
分页:较小的表
由于一个地址空间可能会对应很多页,从而导致每一个页表比较大。而每一个进程都有一个页表,则会给内存带来很大的压力。这里就是为了解决这个问题,如何让页表变得更小。
更大的页我们可以让每一个页更大,如果每一个页可以对应更多的地址空间,那么页表需要存储的映射将会变少。这也意味着每一个页表会变得更小。
这种解决办法会带来新的问题,如果每个页太大,则会导致每个页所分配的内存用不完,导致内部碎片。
分页和分段混合在单独采用页表时,一个进程的地址空间映射可能如下图:
在上图中,一个地址空间会分为多个页,它单独对应一个页表。但是真正使用的空间却只有上图那些白色区域,而灰色区域是没有使用的,但是他们也被映射了具体的物理地址,而且这些映射还存在页表当中。
所以我们可以采用分段时的思想,不再将进程的整个空间分配单个页表,而是为进程的每个逻辑分段提供一个页表。
在上图的例子中,我们可以给代码段一个页表,给堆和栈分别一个页表。
在分段中,一个基址寄存器告诉我们每个段对应的物理地址,还有界限寄存器告诉我们段的大小。而在分段和分页混合的方案中,我们的基址寄存器存储的是该段的页表的物理地址。界限寄存器用于指示页表有 ...
分页:快速地址转换
为了解决分页带来的额外内存访问,又引入了地址转换旁路缓冲存储器,又称TLB。它是一个硬件缓存,它记录了频繁发生的虚拟地址到物理地址的转换。
引入该缓存后,每次需要访问内存时,硬件会先检查TLB中是否有期望的映射,如果有就直接转换。
TLB的基本算法硬件会先从虚拟地址中提取页号,然后检查TLB中是否有该页号(在引入TLB之前,这里需要直接访问一次内存,去读取页表),如果有,则可以根据TLB的值,经过计算得到正确的物理地址。
如果没有找到,那么硬件需要去内存访问页表,然后将映射更新到TLB当中,前提是该虚拟地址有效,而且我们有权限访问。
上下文切换时对TLB的处理引入TLB之后,我们在发生上下文切换时,要对TLB进行特殊的处理。因为TLB记录的是某一个进程的地址空间到物理地址的转换,如果切换进程,那么当前的TLB对于另一个进程是不可用的。
一种解决方案:
在发生上下文切换时,直接清空TLB,那么就不会有进程读到错误的TLB。但是也存在问题,每个进程刚运行时,都会发生TLB未命中。
第二种解决方案:
增加一个地址空间标识符,也可以看作是进程标识符,用于标识存储的内容是那个进程的映射。这样一来 ...
MySQL怎么保证主备一致
MySQL在进行主备同步时,主库会直接把binlog拿到从库中去执行。这里就涉及到binlog的一些内容。
binlog的三种格式binlog有三种格式,分别是statement,row和mixed。其中第三种是前两种的混合方式。
这三种日志的区别假如说执行如下sql语句:
1delete from t where a >= 4 and t_modified <= '2018-11-10' limit 1;
如果说binlog的格式为statement,那么binlog中会记录一个事务,事务中会执行这个sql。也就是说,该设置下,相当于把该条sql语句直接拿到从库中去执行。
如果说格式为row,那么binglog中会记录很多的内容,但是总的来说,它能够根据这些内容,去从库中直接定位到主库删除的那一条记录。就比如说,主库删除主键是1的那一行,如果记录为row格式,那么从库一定可以定位到从库中主键是1的那一行。
为什么说要定位到从库主键是1的这一行呢?
因为如果格式为statement,那么直接在从库中执行上述语句,可能会导致一些主备不一致的情况。
上述语 ...
MySQL怎么保证数据不丢失
binlog的写入机制总的流程:先把日志写到binlog cache,事务提交的时候,再把binlog cache写到binlog文件中。
但是需要注意,一个事务的binlog无论多大,都需要一次性写入,这就涉及到binlog cache的保存。
每一个线程都会有一个binlog cache,可以设置其大小。如果超过大小限制,就要暂存在磁盘当中。但是所有的线程共享一个binlog file,也就是说,每个线程的binlog cache,都会存入到同一个文件。
一个具体的例子如下:
上图中的write操作,其实只是把binlog cache的内容写入文件系统的缓存,并没有直接同步到磁盘,而fsync才是真正的将文件系统缓存的内容写入到磁盘上。
write 和fsync的时机,是由参数sync_binlog控制的:
sync_binlog=0的时候,表示每次提交事务都只write,不fsync;
sync_binlog=1的时候,表示每次提交事务都会执行fsync;
sync_binlog=N(N>1)的时候,表示每次提交事务都write,但累积N ...
分页
因为分段采用的是将空间划分为大小不同的部分,这样的话会导致整个空间比较零碎,会给内存维护带来一定的困难。
为了解决这个问题,我们可以把内存空间分割成大小一样的片段,这种思想称为分页。
分页不是将一个进程的地址空间分割成几个不同长度的逻辑段(即代码、堆、段),而是分割成固定大小的单元,每个单元称为一页。
我们把物理内存看成是定长的一个分割过的槽,叫作页帧(page frame)。每个这样的页帧包含一个虚拟内存页。如下图:
当分页出现以后,一个进程地址空间被引入内存就变为下图:
为了记录地址空间的每个虚拟页放在物理内存中的位置,操作系统通常为每个进程保存一个数据结构,称为页表(page table)。
页表的主要作用是为地址空间的每个虚拟页面保存地址转换(address translation),从而让我们知道每个页在物理内存中的位置。
页表中有什么页表中有一个有效位的标识符。该标识符用于标记对应的地址空间是否使用,如果尝试访问标记为未使用的地址空间,那么程序可能会被终止。
通过有效位,我们可以只对标识为已经使用了的地址空间进行分配物理内存,这样就节省了大量内存。
还有一个存在位, ...