机器指令里面的内存地址都是虚拟内存地址。程序里面的每个进程,都有一个属于本身的虚拟内存地址空间。咱们能够经过地址转换来得到最终的实际物理地址。
咱们每个指令都存放在内存里面,每一条数据都存放在内存里面。所以,“地址转换”是一个很是高频的动做,
“地址转换”的性能就变得相当重要了。这就是咱们今天要讲的 第一个问题,也就是 性能问题php
由于咱们的指令、数据都存放在内存里面,这里就会遇到咱们今天要谈的 第二个问题,也就是 内存安全问题。若是被人修改了内存里面的内容,
咱们的CPU就可能会去执行咱们计划以外的指令。这个指令多是破坏咱们服务器里面的数据,也多是被人获取到服务器里面的敏感信息。数据库
多级页表虽然节约了咱们的存储空间,可是却带来了时间上的开销,变成了一个“以时间换空间”的策略。本来咱们进行一次地址转换,只须要访问一次内存就能找到物理页号,
算出物理内存地址。可是用了4级页表,咱们就须要访问4次内存,才能找到物理页号。缓存
程序所须要使用的指令,都顺序存放在虚拟内存里面。咱们执行的指令,也是一条条顺序执行下去的。也就是说,咱们对于指令地址的访问,
存在前面几讲所说的“空间局部性”和“时间局部性”,而须要访问的数据也是同样的。咱们连续执行了5条指令。由于内存地址都是连续的,因此这5条指令一般都在同一个“虚拟页”里。
所以,这连续5次的内存地址转换,其实都来自于同一个虚拟页号,转换的结果天然也就是同一个物理页号。那咱们就能够用前面几讲说过的,用一个“加个缓存”的办法。
把以前的内存转换地址缓存下来,使得咱们不须要反复去访问内存来进行内存地址转换。安全
因而,计算机工程师们专门在CPU里放了一块缓存芯片。这块缓存芯片咱们称之为 TLB,全称是 地址变换高速缓冲(Translation-Lookaside Buffer)。bash
这块缓存存放了以前已经进行过地址转换的查询结果。这样,当一样的虚拟地址须要进行地址转换的时候,咱们能够直接在TLB里面查询结果,服务器
而不须要屡次访问内存来完成一次转换。
TLB和咱们前面讲的CPU的高速缓存相似,能够分红指令的TLB和数据的TLB,也就是 ITLB和 DTLB。一样的,咱们也能够根据大小对它进行分级,变成L一、L2这样多层的TLB。
除此以外,还有一点和CPU里的高速缓存也是同样的,咱们须要用脏标记这样的标记位,来实现“写回”这样缓存管理策略。
数据结构
为了性能,咱们整个内存转换过程也要由硬件来执行。在CPU芯片里面,咱们封装了内存管理单元(MMU,Memory Management Unit)芯片,用来完成地址转换。
和TLB的访问和交互,都是由这个MMU控制的。dom
讲完了虚拟内存和物理内存的转换,咱们来看看内存保护和安全性的问题。ide
进程的程序也好,数据也好,都要存放在内存里面。实际程序指令的执行,也是经过程序计数器里面的地址,去读取内存内的内容,而后运行对应的指令,使用相应的数据。
虽然咱们现代的操做系统和CPU,已经作了各类权限的管控。正常状况下,咱们已经经过虚拟内存地址和物理内存地址的区分,隔离了各个进程。
可是,不管是CPU这样的硬件,仍是操做系统这样的软件,都太复杂了,不免仍是会被黑客们找到各类各样的漏洞。
就像咱们在软件开发过程当中,经常会有一个“兜底”的错误处理方案同样,在对于内存的管理里面,计算机也有一些最底层的安全保护机制。函数
这些机制统称为 内存保护(Memory Protection)。我这里就为你简单介绍两个。
第一个常见的安全机制,叫 可执行空间保护(Executable Space Protection)。这个机制是说,咱们对于一个进程使用的内存,
只把其中的指令部分设置成“可执行”的,对于其余部分,好比数据部分,不给予“可执行”的权限。由于不管是指令,仍是数据,在咱们的CPU看来,都是二进制的
数据。咱们直接把数据部分拿给CPU,若是这些数据解码后,也能变成一条合理的指令,其实就是可执行的。
这个时候,黑客们想到了一些搞破坏的办法。咱们在程序的数据区里,放入一些要执行的指令编码后的数据,而后找到一个办法,让CPU去把它们当成指令去加载,
那CPU就能执行咱们想要执行的指令了。对于进程里内存空间的执行权限进行控制,可使得CPU只能执行指令区域的代码。对于数据区域的内容,即便找
到了其余漏洞想要加载成指令来执行,也会由于没有权限而被阻挡掉。
其实,在实际的应用开发中,相似的策略也很常见。我下面给你举两个例子。
好比说,在用PHP进行Web开发的时候,咱们一般会禁止PHP有eval函数的执行权限。这个其实就是惧怕外部的用户,因此没有把数据提交到服务器,
而是把一段想要执行的脚本提交到服务器。服务器里在拼装字符串执行命令的时候,可能就会执行到预计以外被“注入”的破坏性脚本。这里我放了一个例子,用这个办法
script.php?param1=xxx // 咱们的 PHP 接受一个传入的参数,这个参数咱们但愿提供计算功能
$code = eval($_GET["param1"]); // 咱们直接经过 eval 计算出来对应的参数公式的计算结果
script.php?param1=";%20echo%20exec('rm -rf ~/');%20// // 用户传入的参数里面藏了一个命令
$code = ""; echo exec('rm -rf ~/'); //"; // 执行的结果就变成了删除服务器上的数据
还有一个例子就是SQL注入攻击。若是服务端执行的SQL脚本是经过字符串拼装出来的,那么在Web请求里面传输的参数就能够藏下一些咱们想要执行的SQL,
让服务器执行一些咱们没有想到过的SQL语句。这样的结果就是,或者破坏了数据库里的数据,或者被人拖库泄露了数据
第二个常见的安全机制,叫 地址空间布局随机化(Address Space Layout Randomization)。
内存层面的安全保护核心策略,是在可能有漏洞的状况下进行安全预防。上面的可执行空间保护就是一个很好的例子。可是,内存层面的漏洞还有其余的可能性。
这里的核心问题是,其余的人、进程、程序,会去修改掉特定进程的指令、数据,而后,让当前进程去执行这些指令和数据,形成破坏。要想修改这些指令和数据,咱们须要知道这些指令和数据所在的位置才行。
这里的核心问题是,其余的人、进程、程序,会去修改掉特定进程的指令、数据,而后,让当前进程去执行
这些指令和数据,形成破坏。要想修改这些指令和数据,咱们须要知道这些指令和数据所在的位置才行。
原先咱们一个进程的内存布局空间是固定的,因此任何第三方很容易就能知道指令在哪里,程序栈在哪里,数据在哪里,堆又在哪里。这个其实为想要搞破坏的人创造了很大的便利。
而地址空间布局随机化这个机制,就是让这些区域的位置再也不固定,在内存空间随机去分配这些进程里不一样部分所在的内存空间地址,让破坏者猜不出来。猜不出来呢,
天然就无法找到想要修改的内容的位置。若是只是随便作点修改,程序只会crash掉,而不会去执行计划以外的代码。
这样的“随机化”策略,其实也是咱们平常应用开发中一个常见的策略。一个你们都应该接触过的例子就是密码登录功能。网站和App都会须要你设置用户名和密码,
以后用来登录本身的帐号。而后,在服务器端,咱们会把用户名和密码保存下来,在下一次用户登录的时候,使用这个用户名和密码验证。
咱们的密码固然不能明文存储在数据库里,否则就会有安全问题。若是明文存储在数据库里,意味着能拿到数据库访问权限的人,都能看到用户的明文密码。
这个多是由于安全漏洞致使被人拖库,并且网站的管理员也能直接看到全部的用户名和密码信息。
CSDN网站的用户名密码,用户的损失也不会太大。可是不少用户可能会在不一样的网站使用相同的密码,若是拿到这些用户名和密码的人,
可以成功登陆用户的银行、支付、社交等等其余网站的话,用户损失就大了去了。
好比,前几年CSDN就发生过被人拖库的事件。虽然用户名和密码都是明文保存的,别人若是只是拿到了
因而,你们会在数据库里存储密码的哈希值,好比用如今经常使用的SHA256,生成一一个验证的密码哈希值。可是这个每每仍是不够的。由于一样的密码,对应的哈希值都是相同的,
大部分用户的密码又经常比较简单。因而,拖库成功的黑客能够经过彩虹表的方式,来推测出用户的密码。
这个时候,咱们的“随机化策略”就能够用上了。咱们能够在数据库里,给每个用户名生成一个随机的、使用了各类特殊字符的 盐值(Salt)。这样,咱们的哈希值就再也不是仅仅使用密码了,
来生成的而是密码和盐值放在一块儿生成的对应的哈希值。哈希值的生成中,包括了一些相似于“乱码”的随机字符串,因此经过彩虹表碰撞来猜出密码的办法就用不了了。
$password = "goodmorning12345"; // 咱们的密码是明文存储的 $hashed_password = hash('sha256', password); // 对应的 hash 值是 054df97ac847f831f81b439415b2bad05694d16822635999880d7561ee1b77ac // 可是这个 hash 值里能够用彩虹表直接“猜出来”原始的密码就是 goodmorning12345 $salt = "#21Pb$Hs&Xi923^)?"; $salt_password = $salt.$password; $hashed_salt_password = hash('sha256', salt_password); // 这个 hash 后的 slat 由于有部分随机的字符串,不会在彩虹表里面出现。 // 261e42d94063b884701149e46eeb42c489c6a6b3d95312e25eee0d008706035f
能够看到,经过加入“随机”因素,咱们有了一道最后防线。即便在出现安全漏洞的时候,咱们也有了更多的时间和机会去补救这些问题。
虽然安全机制彷佛在平时用不太到,可是在开发程序的时候,仍是要有安全意识。毕竟谁也不想看到,被拖库的新闻里出现的是本身公司的名字,也不但愿用户由于咱们的错误遭受到损失
为了节约页表所须要的内存空间,咱们采用了多级页表这样一个数据结构。可是,多级页表虽然节省空间了,却要花费更多的时间去屡次访问内存。因而,
咱们在实际进行地址转换的MMU旁边放上了TLB这个用于地址转换的缓存。TLB也像CPU Cache同样,分红指令和数据部分,也能够进行L一、L2这样的分层。
而后,我为你介绍了内存保护。不管是数据仍是代码,咱们都要存放在内存里面。为了防止由于各类漏洞,致使一个进程能够访问别的进程的数据或者代码,甚至是执行对应的代码,
形成严重的安全问题,咱们介绍了最经常使用的两个内存保护措施,可执行空间保护和地址空间布局随机化。
经过让数据空间里面的内容不能执行,能够避免了相似于“注入攻击”的攻击方式。经过随机化内存空间的分配,能够避免让一个进程的内存里面的代码,被推测出来,从而不容易被攻击。