如果数据存储在易失性存储中的话,我们就可以随机访问任一位置。且访问速度基本一致。可对于非易失性的,则只能具有块寻址能力,而不是字节可寻址。字节可寻址指的是去读取存储位置的64bit的数据。且只能读取64bit。所以关键是*非易失的话无法准确得到64bit。必需先得到一个块,(一个4kb的页),然后从中获取。我们的关键是如何进行磁盘存取。我们需要给数据库一种错觉。最底层,我们有磁盘,并且其中我们有数据库文件。数据库文件由块组成。然后我们在内存中有一个buffer pool。我们实际上是从磁盘中获取文件,然后告诉os如何将文件映射到我的进程的地址空间中。如果是操作系统的话,我们可以读写内存地址。因为这个内存目前还不是os导致的。接着,最终我可以让操作系统将之写出,通过执⾏⼀个 Sync并将其写回到磁盘上,这个流程是os操作的流程,但是问题也来了,这样的话当我们要进行页面移除时,不得不让数据库的线程停止。因为我们要进行os的磁盘调度。实现这种虚拟内存的方法之一是使用mmap来映射进程地址空间中的文件内容,这使得操作系统负责在磁盘和内存之间来回移动页面。但是如果是写会有问题,因为现在操作系统并不知道某些pages必须要在其他pages执⾏之前先从内存刷到磁盘中你可以通过给它⼀些提示来解决该问题,例如使⽤madvise告诉它如何访问某些⻚⾯
但不幸的是,如果mmap遇到页面故障,进程将会被阻塞
-
但不幸的是,如果mmap遇到页面故障,进程将会被阻塞。
- 如果你需要写入,你永远不想在你的DBMS中使用mmap。
- DBMS(几乎)总是想自己控制事情,而且可以做得更好,因为它知道更多关于被访问的数据和被处理的查询。
- 操作系统不是你的朋友。
-
可以通过使用操作系统:
madvise
: 告诉操作系统你打算何时读某些页面。mlock
: 告诉操作系统不要把内存范围换到磁盘上。msync
: 告诉操作系统将内存范围刷新到磁盘。
出于正确性和性能的考虑,我们不建议在DBMS中使用mmap
。
现在是关键:如何表示磁盘上数据,如何实际管理内存,以及在硬盘间来回移动数据。如何用磁盘上的文件表示数据库,毕竟数据库就是一堆文件,但事实有组织的文件。当然了sqlite是将数据库看做一个大文件,但一般不是。因为一般数据库很大。存储引擎的作用是维护文件,维护磁盘中数据库文件。我们将文件以页的形式展示,然后存储管理器在这个页中进行读写,但是读写还是使用的os,因为我们有自己的操作系统的文件系统。
因为我们有pageid,所以我们的page可以随意的来进行变换,还是可以定位,因为我们有page的id的表。对硬件来说,page是我们执行原子写入存储设备的最低底层的东西,通常为4kb。page的一种存储方式是堆方式,当然里边是索引还是tuple不重要。So,数据库中的heap⽂件是⼀个⽆序的page集合,因为关系模型并没有任何排序 ,所以无需确保有序。在内部,我们可以通过⼀系列不同的⽅式来表示heap⽂件 在最低层,我们将它们组织为page
通过对流进行抽象,可以实现在文件中的读写。流是一种抽象,为的是使用的时候可以不用考虑怎么读取的。流就是一个缓冲,从文件中读入,再读出到控制台。而字符串流就是这其中没有连接外部设备时使用的那个流。有两种流,输出流是可以输入的。使用cout时,是将输入到流的
首先讨论ostream,这是一个输出流。我可以将信息输出到输出流中
同时我们通过使用.str将流内内容输出。
立即数只有12个bit,只有12位,所以只有+-2048位,这是使用补码的-2048~2047,然后我们有一些伪指令,neg是取反,让0-rs,放到rd中,mv rd,rs就是addi rd,rs,0 nop就是addi x0,x0,0是什么也不做的意思。
riscv怎么构造一个大数呢,可以将32位分解成高20位和低12位,我们用addi提高12位。这就是lui命令,会构造32位立即数,会将低12位清零。但是使用lui会有符号扩展,所以如果最高位是1,则前边都是1.0xfff是一个负数,所以lui,12345 addi x1,fff不行,因为这是个负数,所以不一样哦。所以会符号扩展。因此我们先在前边进位1,即lui12346,然后加上负一
而且很有意思,我写0xfff时,用的是十六进制格式,他就是111111111111(计算机中的原始位,就是11111111111,我们不用将这个数字转换为补码),然而这是一个补码,所以是一个负数。而我写下-1的时候,我是让-1是计算机中的,我们在计算机种看到的将会是他的补码,而不是-1,0xfff我们看到的和写下的一样,这就是计算机中的样子。而-1则不一样,记住哦。-1源码是10000001(为什么是8位,因为是一个字节)反码是11111110,补码是11111111。这是根源,但是影响是什么呢,答案是,如果是-1,扩展后他还是-1,但是是0xfff,也就是说,我们看似他是-1,但其实在addi的时候
记录原码反码补码:
先写一些概念:
机器数,数值在计算机内部的编码
真值:机器数想表示的实际的数值(也就是我们人为的有正负号的值)
原码由1个符号位和数值部分组成,数值是真值的绝对值,但是0存在+-0情况,且加减法复杂。
反码由1位符号位与真值部分组成,当表示负数时,符号位不变,数值位取反。
现在讲一下补码的本质:
先是补码,所谓补码就是二进制中的正数的负,
比方说十进制的4 ,应该是100 让100 + xxx = 000(但是这个000可以进位),则xxx = 100,最后变成了100,但是也可以是取反,因为可以是x+y+1= x00,可以看到这就是取反加1的本质了。取反是为了和全1,加1是为了除了多出来的全0,这样的话符号位必定是相反的了,人们研究这个数字,发现我靠,这一位可以表示符号,但是注意,是他可以表示,他的本质不是符号位。!!!!
所以8只可以用5位表示01000,则01000+x = 100000,x=11000
我们可以看到这样的问题,如果
至于venus,如果是0xfff,编译器只会认为是原始代表的111111111111的原码,也就是4096,而不会认为是负数,这就是venus的问题了。
补码定义是用模减去正数的
需要注意的是,我们写下类似10 ,12,15,由于没有加0x,0b这样的前缀,因此他们都是十进制表示法。我们写的15这样的数,其实实际上都涉及到了进制转换,即将10进制转换为二进制,而十进制只是为了我们查看方便。
~(A-1) = 111111.111-(A-1) = 111.11111-A + 1 = ~A+1
所以补码取负就是连同符号位一起,各位取反,末位加1,即从右到左第一个1不变,其他取反。当然这个方法只有在表示没有超出的时候可以用,如果是四位的-8的补码会变成(1000)1111也就是-1.
-8为什么补码,因为如果用原码写出是1000,数值取反是1111,加1是1000
- 计算机里面,只有加法器,没有减法器,所有的减法运算,都必须用加法进行。
- 用补数代替原数,可把减法转变为加法。出现的进位就是模,此时的进位,就应该忽略不计。
- 二进制下,有多少位数参加运算,模就是在 1 的后面加上多少个 0。
- 补码就是按照这个要求来定义的:正数不变,负数即用模减去绝对值。
补数 和 补码的定义式 里面,根本就没有什么符号位。这最高位的1、0是自然出现的,并不是由人来规定的。的确,符号位在补码运算里面是“模”,本身并不带符号的意义。因为计算机将加法转换成加上一个“负数”,而负数又以补码的形式表现。补码比源码多一位,从这多出来的一位可以推断出原来数字的正负号,所以成为了符号位。也可以这样认为,留出一位(不全部占满)的原因是要用“模”来表示正负数。
也就是说,不是特意留出一个符号位,用1和0来表示正负号。而是补码运算可以用最高位来表示正负,所以符号位诞生了。
100000000 - 10000000 = 10000000