深入理解计算机系统(原书第3版)读书笔记,其实就是嚼碎了原文然后把一部分挑了出来摘要,免得读着读着忘了

文章目录

  • 前言
  • 一、物理和虚拟寻址
  • 二、地址空间
  • 三、虚拟内存作为缓存的工具
    • 1、DRAM缓存的组织结构
    • 2、页表
    • 3、页命中
    • 4、缺页
    • 5、分配页面
    • 6、局部性
  • 四、虚拟内存作为内存管理的工具
  • 五、虚拟内存作为内存保护的工具
  • 六、地址翻译
    • 1、结合高速缓存和虚拟内存
    • 2、利用TLB加速地址翻译
    • 3、多级页表
    • 4、端到端的地址翻译
  • 七、 案例研究:Intel Core i7/Linux 内存系统
    • 1、Core i7 地址翻译
    • 2、Linux 虚拟内存系统
  • 八、内存映射
    • 1、再看共享对象
    • 4、使用mmap函数的用户级内存映射
  • 九、动态内存
  • 总结

前言

  • 虚拟内存是硬件异常、硬件地址翻译、主存、磁盘文件和内核软件的完美交互,它为每个进程提供了一个大的、一致的和私有的地址空间。
  • 它将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,它高效地使用了主存。
  • 虚拟内存遍及计算机系统的所有层面,在硬件异常、汇编器、链接器、加载器、共享对象、文件和进程的设计中扮演着重要角色。理解虚拟内存将帮助你更好地理解系统通常是如何工作的。

这一章从两个角度来看虚拟内存。本章的前一部分描述虚拟内存是如何工作的后一部分描述的是应用程序如何使用和管理虚拟内存。好消息就是如果你掌握这些细节,你就能够手工模拟一个小系统的虚拟内存机制,而且虚拟内存的概念将永远不再神秘。第二部分是建立在这种理解之上的,向你展示了如何在程序中使用和管理虚拟内存。你将学会如何通过显式的内存映射和对象 malloc 程序这样的动态内存分配器的调用来管理虚拟内存。你还将了解到 C 程序中的大多数常见的与内存有关的错误,并学会如何避免它们的出现。(饼画的我很喜欢)


一、物理和虚拟寻址

计算机系统的主存被组织成一个由 M 个连续的字节大小的单元组成的数组。都有一个唯一的物理地址(Physical Address)。

将一个虚拟地址转换为物理地址的任务叫做地址翻译(address translation)。CPU 芯片上叫做内存管理单元(Memory Management Unit,MMU)的专用硬件,利用存放在主存中的查询表来动态翻译虚拟地址,该表的内容由操作系统管理。

下面两图展示了如何一步一步地址总线通过物理地址去访问了一个字节。

二、地址空间

  • 地址空间(address space)是一个非负整数地址的有序集合。
  • 如果地址空间中的整数是连续的,那么我们说它是一个线性地址空间(linear address space)。
  • 一个地址空间的大小是由表示最大地址所需要的位数来描述的。例如,一个包含 N=2nN=2^nN=2n个地址的虚拟地址空间就叫做一个n位地址空间(二进制需要n个位数才能表示完)。现代系统通常支持32位或者64位虚拟地址空间。
  • 一个系统还有物理地址空间(physical address space),对应于系统中物理内存的M个字节(M不是求2的幂)。

地址空间清楚地区分了数据对象(字节)和它们的属性(地址)。 一旦认识到了这种区别,那么我们就可以将其推广,允许每个数据对象有多个独立的地址,其中每个地址都选自一个不同的地址空间。这就是虚拟内存的基本思想主存中的每字节都有一个选自虚拟地址空间的虚拟地址和一个选自物理地址空间的物理地址。 这句话的理解应该是首先主存中每个字节都有一个唯一的物理地址;其次不同进程有自己的地址空间,主存中的一个字节可以有进程中的一个虚拟地址。

图片来自这篇写的非常好的博客,它从进程角度讨论了虚拟地址与物理地址,比较概括

三、虚拟内存作为缓存的工具

虚拟内存被组织为一个由存放在磁盘上的M个连续的字节大小的单元组成的数组每字节都有一个唯一的虚拟地址,作为到数组的索引。磁盘上数组的内容被缓存在主存中。(一个新进程建立的时候,将会建立起自己的内存空间,此进程的数据,代码等从磁盘拷贝到自己的进程空间,哪些数据在哪里,都由进程控制表中的task_struct记录,task_struct中记录中一条链表,记录中内存空间的分配情况,哪些地址有数据,哪些地址无数据,哪些可读,哪些可写,都可以通过这个链表记录。)

和存储器层次结构中其他缓存一样,磁盘(较低层)上的数据被分割成块,这些块作为磁盘和主存(较高层)之间的传输单元。VM系统通过将虚拟内存分割为称为虚拟页(Virtual Page, VP)的大小固定的块来处理这个问题。每个虚拟页的大小为字节。类似地,物理内存被分割为物理页(Physical Page, PP)大小也为P字节(物理页也被称为页帧(page frame))。

总的来说,这一节需要让我们明白的是:进程要知道哪些内存地址上的数据在物理内存上,哪些不在,还有在物理内存上的哪里,需要用页表来记录。页表的每一个表项分两部分,第一部分记录此页是否在物理内存上,第二部分记录物理内存页的地址(如果在的话)。(https://www.jianshu.com/p/b6356e0ec63c)

1、DRAM缓存的组织结构

直写式(WT,Write Through)与回写式(WB,Write Back)指的是缓冲内存的工作方式。直写式缓存方式是当CPU要将数据写入内存除了更新缓冲内存上的数据外也将数据写在 DRAM 中以维持主存与缓冲内存的一致性。当要写入内存的数据一多,速度自然就慢了下来。回写式的缓存方式是每当 CPU 要将数据写入内存时只会先更新缓冲内存上的数据,随后再让缓冲内存在总线不塞车的时候才把数据写回DRAM,所以速度自然快得多。(高速缓存将充当缓冲区)

2、页表

虚拟内存系统必须有某种方法来判定一个虚拟页是否缓存在DRAM中的某个地方。如果是,系统还必须确定这个虚拟页存放在哪个物理页中。如果不命中,系统必须判断这个虚拟页存放在磁盘的哪个位置,在物理内存中选择一个牺牲页,并将虚拟页从磁盘复制到DRAM中,替换这个牺牲页

由软硬件联合提供的,包括操作系统软件、MMU(内存管理单元)中的地址翻译硬件和一个存放在物理内存中叫做页表(page table)的数据结构,页表将虚拟页映射到物理页。每次地址翻译硬件将一个虚拟地址转换为物理地址时,都会读取页表。操作系统负责维护页表的内容,以及在磁盘与DRAM之间来回传送页。

页表就是一个页表条目(Page Table Entry, PTE)的数组。

图的解释:有效位表明了该虚拟页当前是否被缓存在DRAM中。如果设置了有效位,那么地址字段就表示DRAM中相应的物理页的起始置,这个物理页中缓存了该虚拟页。如果没有设置有效位,那么一个空地址表示这个虚拟页还未被分配。否则,这个地址就指向该虚拟页在磁盘上的起始位置。因为 DRAM 缓存是全相联的,所以任意物理页都可以包含任意虚拟页。

3、页命中

当CPU想要读包含在VP2中的虚拟内存的一个字时会发生什么?地址翻译硬件将虚拟地址作为一个索引来定位PTE2, 并从内存中读取它。因为设置了有效位,那么地址翻译硬件就知道VP2是缓存在内存中的了。所以它使用PTE中的物理内存地址(该地址指向 PP1中缓存页的起始位置), 构造出这个字的物理地址。

4、缺页

DRAM缓存不命中称为缺页(page fault)。

  1. CPU引用了VP3中的一个字,VP3并未缓存在DRAM中。地址翻译硬件从内存中读取PTE3, 从有效位推断出VP3未被缓存,并且触发一个缺页异常。
  2. 缺页异常调用内核中的缺页异常处理程序,该程序会选择一个牺牲页,在此例中就是存放在PP3中的VP4。如果VP4已经被修改了,那么内核就会将它复制回磁盘。无论哪种情况,内核都会修改VP4的页表条目,反映出VP4不再缓存在主存中这一事实。
  3. 内核从磁盘复制VP3到内存中的PP3, 更新PTE3,随后返回。
  4. 当异常处理程序返回时,它会重新启动导致缺页的指令,该指令会把导致缺页的虚拟地址重发送到地址翻译硬件。但是现在,VP3已经缓存在主存中了,那么页命中也能由地址翻译硬件正常处理了。图 9-7 展示了在缺页之后我们的示例页表的状态。

虚拟内存系统使用了和 SRAM 缓存不同的术语,即使它们的许多概念是相似的。在虚拟内存的习惯说法中,块被称为页。在磁盘和内存之间传送页的活动叫做交换(swapping) 或者页面调度(paging)。页从磁盘换入(或者页面调入)DRAM和从DRAM 换出(或者页面调出)磁盘。一直等待,直到最后时刻,也就是当有不命中发生时,才换入页面的这种策略称为按需页面调度(demand paging)。也可以采用其他的方法,例如尝试着预测不命中,在页面实际被引用之前就换人页面。然而,所有现代系统都使用的是按需页面调度的方式

5、分配页面

分配一个新的虚拟内存页时对我们示例页表的影响,例如,调用 malloc 的结果。在这个示例中,VP5的分配过程是在磁盘上创建空间,并更新PTE5使它指向磁盘上这个新创建的页面。(page table 是在DRAM里的,这个很有意思,MMU有一个TLB,MMU在CPU里)

6、局部性

程序将趋向于在一个较小的活动页面(active page)集合上工作,这个集合叫做工作集(working set)或者常驻集合(resident set)。只要我们的程序有好的时间局部性,虚拟内存系统就能工作得相当好。但是,当然不是所有的程序都能展现良好的时间局部性。如果工作集的大小超出了物理内存的大小,那么程序将产生一种不幸的状态,叫做抖动(thrashing), 这时页面将不断地换进换出。虽然虚拟内存通常是有效的,但是如果一个程序性能慢得像爬一样,那么聪明的程序员会考虑是不是发生了抖动。

四、虚拟内存作为内存管理的工具

到目前为止,我们都假设有一个单独的页表,将一个虚拟地址空间映射到物理地址空间。实际上,操作系统为每个进程提供了一个独立的页表,因而也就是一个独立的虚拟地址空间。注意,多个虚拟页面可以映射到同一个共享物理页面上

  • 简化链接
  • 简化加载
  • 简化共享
  • 简化内存分配:当一个运行在用户进程中的程序要求额外的堆空间时(如调用 malloc 的结果), 操作系统分配一个适当数字(例如k个)连续的虚拟内存页面,并且将它们映射到物理内存中任意位置的k个物理页面。由于页表工作的方式,操作系统没有必要分配k个连续的物理内存页面。页面可以随机地分散在物理内存中。

五、虚拟内存作为内存保护的工具


在这个示例中,每个PTE中已经添加了三个许可位。SUP 位表示进程是否必须运行在内核(超级用户)模式下才能访问该页。运行在内核模式中的进程可以访问任何页面,但是运行在用户模式中的进程只允许访问那些SUP为0的页面。READ位和WRITE位控制对页面的读和写访问。例如,如果进程i运行在用户模式下,那么它有读VP0和读写VP1的权限。然而,不允许它访问 VP2。如果一条指令违反了这些许可条件,那么 CPU 就触发一个一般保护故障,将控制传递给一个内核中的异常处理程序。Linux shell —般将这种异常报告为“段错误(segmentation fault)” 。

六、地址翻译

了解硬件在支持虚拟内存中的角色,省略了大量的细节,尤其是和时序相关的细节。图 9-11 概括了我们在这节里将要使用的所有符号。

图9-12展示了MMU如何利用页表来实现虚拟地址空间到物理地址空间映射。CPU中的一个控制寄存器,页表基址寄存器(Page Table Base Register, PTBR) 指向当前页表。n位的虚拟地址包含两个部分:一个p位的虚拟页面偏移(Virtual Page Offset, VPO)和一个(n-p)位的虚拟页号(Virtual Page Number, VPN)。MMU利用VPN来选择适当的PTE。例如,VPN0选择PTE0,VPN1选择PTE1,以此类推。将页表条目中物理页号(Physical Page Number, PPN)和虚拟地址中的VPO串联起来,就得到相应的物理地址。 注意,因为物理和虚拟页面都是P字节的,所以物理页面偏移(Physical Page Offset, PPO)和VPO是相同的


(第7步其实就是a)的一次循环了)。

1、结合高速缓存和虚拟内存

既使用虚拟内存又使用SRAM髙速缓存的系统中,都有应该使用虚拟地址还是使用物理地址来访问SRAM高速缓存的问题。大多数系统是选择物理寻址的

2、利用TLB加速地址翻译

每次CPU产生一个虚拟地址,MMU就必须查阅一个PTE, 以便将虚拟地址翻译为物理地址。在MMU中包括了一个关于PTE的小的缓存,称为翻译后备缓冲器(Translation Lookaside Buffer, TLB)。TLB 是一个小的、虚拟寻址的缓存,其中每一行都保存着一个由单个 PTE 组成的块。TLB 通常有高度的相联度。当 TLB 命中时(通常情况)所有的地址翻译步骤都是在芯片上的 MMU 中执行的,因此非常快。

3、多级页表

这种方法从两个方面减少了内存要求。第一,如果一级页表中的一个 PTE 是空的,那么相应的二级页表就根本不会存在。这代表着一种巨大的潜在节约,因为对于一个典型的程序,4GB 的虚拟地址空间的大部分都会是未分配的。第二,只有一级页表才需要总是在主存中;虚拟内存系统可以在需要时创建、页面调入或调出二级页表,这就减少了主存的压力;只有最经常使用的二级页表才需要缓存在主存中。

4、端到端的地址翻译

一个具体的端到端的地址翻译示例。书P609/775

七、 案例研究:Intel Core i7/Linux 内存系统

书P612/775。

1、Core i7 地址翻译

当MMU翻译每一个虚拟地址时,它还会更新另外两个内核缺页处理程序会用到的位。每次访问一个页时,MMU都会设置A位,称为引用位(reference bit)。内核可以用这个引用位来实现它的页替换算法。每次对一个页进行了写之后,MMU都会设置D位,又称修改位或脏位(dirty bit)。修改位告诉内核在复制替换页之前是否必须写回牺牲页。内核可以通过调用一条特殊的内核模式指令来清除引用位或修改位。

2、Linux 虚拟内存系统


一般给每个进程分配4G虚拟内存,最高 1G 的内核空间。当进程运行在内核空间时就处于内核态,而进程运行在用户空间时则处于用户态。(上图大括弧进程虚拟内存,应该是指用户空间,对应上面的内核空间)以下内容参考这篇博客
进程虚拟地址空间中一般都包含如下不同的段:

  • 当前运行代码的二进制代码。该代码通常称之为 text,所处的虚拟内存区域称之为 text 段。ELF(Executable and Linkable Format)二进制文件映射到地址空间后,该区域长度不变,边界由 start_codeend_code 标记。
  • 程序使用的动态库的代码。
  • 存储全局变量和动态产生的数据的堆。start_brk 标识了堆的起始位置,其长度在运行阶段会发生变化。
  • 用于保存局部变量和实现函数过程调用的栈。
  • 环境变量和命令行参数的段。由 arg_startarg_endenv_startenv_end 标识。
  • 将文件内容映射到虚拟地址空间中的内存映射。

    (一个新进程建立的时候,将会建立起自己的内存空间,此进程的数据,代码等从磁盘拷贝到自己的进程空间,哪些数据在哪里,都由进程控制表中的task_struct记录,task_struct中记录中一条链表,记录中内存空间的分配情况,哪些地址有数据,哪些地址无数据,哪些可读,哪些可写,都可以通过这个链表记录。)

任务结构中的一个条目指向 mm_struct,它描述了虚拟内存的当前状态,内存管理记录。我们感兴趣的两个字段是 pgdmmap。其中pgd指向第一级页表(页全局目录)的基址,而 mmap 指向一个vm_area_structs(区域结构)的链表,其中每个vm_area_structs都描述了当前虚拟地址空间的一个区域。当内核运行这个进程时,就将pgd存放在CR3控制寄存器中。在 vm_area_struct 中主要保存了和后背存储器(磁盘)的映射关系,其中主要包括内存区域在虚拟地址空间的起始地址和结束地址,映射到的文件,以及文件内的起始偏移地址,此外还包括该段内存的权限和属性,是否是共享内存等:

  • vm_start: 指向这个区域的起始处。
  • vm_end: 指向这个区域的结束处。
  • vm_port:描述这个区域内包含的所有页的读写许可权限。
  • vm_flags:描述这个区域内的页面是与其他进程共享的,还是这个进程私有的(还描述了其他一些信息)。
  • vm_next: 指向链表中下一个区域结构。

此外进程的地址空间会被分成多个区域,每个区域描述的是一段连续的、具有相同访问属性的虚存空间,该虚存空间的大小为物理内存页面的整数倍。在这里,每个区都会通过一个(vm_area_struct)描述,通常,进程所使用到的虚存空间并不不连续,且各部分虚存空间的访问属性也可能不同。所以一个进程的虚存空间需要多个 vm_area_struct 结构来描述。进程的各个区域按照两种方式维护:

  1. 在一个单链表上(开始于 mm_struct->mmap)
  2. 在一个红黑树中,根节点存储在 mm_rb

八、内存映射

将一组连续的虚拟页映射到任意一个文件中的任意位置的表示法称作内存映射(memory mapping)Linux 提供一个称为 mmap的系统调用,允许应用程序自己做内存映射。下图来自讲内存映射很不错的文章(在内存映射的过程中,并没有实际的数据拷贝,文件没有被载入内存,只是逻辑上被放入了内存,具体到代码,就是建立并初始化了相关的数据结构(struct address_space),这个过程由系统调用mmap()实现,所以建立内存映射的效率很高。)

mmap()会返回一个指针ptr,它指向进程逻辑地址空间中的一个地址,这样以后,进程无需再调用read或write对文件进行读写,而只需要通过ptr就能够操作文件。但是ptr所指向的是一个逻辑地址,要操作其中的数据,必须通过MMU将逻辑地址转换成物理地址,这个过程与内存映射无关。

Linux通过将一个虚拟内存区域与一个磁盘上的对象(object)关联起来,以初始化这个虚拟内存区域的内容,这个过程称为内存映射(memory mapping)虚拟内存区域可以映射到两种类型的对象中的一种:
1) Linux文件系统中的普通文件:一个区域可以映射到一个普通磁盘文件的连续部分,例如一个可执行目标文件。文件区(section)被分成页大小的片,每一片包含一个虚拟页面的初始内容。因为按需进行页面调度,所以这些虚拟页面没有实际交换进入物理内存,直到 CPU 第一次引用到页面(即发射一个虚拟地址,落在地址空间这个页面的范围之内)
2) 匿名文件:一个区域也可以映射到一个匿名文件,匿名文件是由内核创建的,包含的全是二进制零。CPU第一次引用这样一个区域内的虚拟页面时,内核就在物理内存中找到一个合适的牺牲页面,如果该页面被修改过,就将这个页面换出来,用二进制零覆盖牺牲页面并更新页表,将这个页面标记为是驻留在内存中的。注意在磁盘和内存之间并没有实际的数据传送。因为这个原因,映射到匿名文件的区域中的页面有时也叫做请求二进制零的页(demand-zero page)

有两种内存会有后备存储器,其一是文件映射的内存,通过系统调用可以将文件的指定区域映射到进程的虚拟地址空间。另一个是匿名内存换出,当物理内存不足时,会将一部分物理内存换出到磁盘中,这些内存可能是堆内存,栈内存等,因为它们没有显示的对应映射文件,所以系统会在交换分区选择一个合适的文件来暂存内存中的内容。

无论在哪种情况中,一个虚拟页面被初始化了,它就在一个由内核维护的专门的交换文件(swap file)之间换来换去。交换文件也叫做**交换空间(swap space)**或者交换区域(swap area)需要意识到的很重要的一点是,在任何时刻,交换空间都限制着当前运行着的进程能够分配的虚拟页面的总数

1、再看共享对象

一个对象可以被映射到虚拟内存的一个区域,要么作为共享对象,要么作为私有对象。

4、使用mmap函数的用户级内存映射

mmap函数要求内核创建一个新的虚拟内存区域,最好是从地址start开始的一个区域,并将文件描述符fd指定的对象的一个连续的片(chunk)映射到这个新的区域。连续的对象片大小为length字节,从距文件开始处偏移量为offset字节的地方开始。start地址仅仅是一个暗示,通常被定义为 NULL。为了我们的目的,我们总是假设起始地址为NULL。

九、动态内存


总结

内存回收就和进程,反向映射相关。

虚拟内存(深入理解计算机系统原书第3版9节读书笔记)相关推荐

  1. Linux设备驱动程序(第三版)/深入理解计算机系统(原书第2版)/[Android系统原理及开发要点详解].(韩超,梁泉)百度云盘下载

    文档下载云盘连接:http://pan.baidu.com/s/1dDD2sgT 更多其他资料,请关注淘宝:http://shop115376623.taobao.com/ http://item.t ...

  2. 《渗透测试实践指南 必知必会的工具与方法 (原书第2版)》读书摘录

    ----------------------------------------------------------------------------分割线--------------------- ...

  3. 深入理解ElasticSearch(原书第2版)

    为了方便人们从海量信息中快速检索出内容,搜索引擎应运运出.ElasticSearch是一个款基于apache lucene 的开源搜索引擎,它具有开源,分布式,准实时,restful,便于二次开发等特 ...

  4. 读书笔记 摘自:《吃掉那只青蛙:博恩·崔西的高效时间管理法则(原书第3版)》

    <吃掉那只青蛙:博恩·崔西的高效时间管理法则(原书第3版)>的笔记(作者: [美]博恩·崔西) [美]博恩·崔西 出版:2017.08.01 5.6万字 赞誉 管理的首要任务是管理好自己. ...

  5. 《深入理解Elasticsearch(原书第2版)》——第2章 查询DSL进阶 2.1 Apache Lucene默认评分公式解释...

    本节书摘来自华章计算机<深入理解Elasticsearch(原书第2版)>一书中的第2章,第2.1节,作者 [美]拉斐尔·酷奇(Rafal Ku)马雷克·罗戈任斯基(Marek Rogoz ...

  6. 《深入理解Hadoop(原书第2版)》——1.3大数据的编程模型

    本节书摘来自华章计算机<深入理解Hadoop(原书第2版)>一书中的第1章,第1.3节,作者 [美]萨米尔·瓦德卡(Sameer Wadkar),马杜·西德林埃(Madhu Siddali ...

  7. 《深入理解Elasticsearch(原书第2版)》——1.4 小结

    本节书摘来自华章计算机<深入理解Elasticsearch(原书第2版)>一书中的第1章,第1.4节,作者 [美]拉斐尔·酷奇(Rafal Ku)马雷克·罗戈任斯基(Marek Rogoz ...

  8. 《深入理解Elasticsearch(原书第2版)》一1.3 在线书店示例

    本节书摘来自华章出版社<深入理解Elasticsearch(原书第2版)>一书中的第1章,第1.3节,作者[美]拉斐尔·酷奇(Rafal Ku) 马雷克·罗戈任斯基(Marek Rogoz ...

  9. 《机器学习与R语言(原书第2版)》一2.3 探索和理解数据

    本节书摘来自华章出版社<机器学习与R语言(原书第2版)>一书中的第2章,第2.3节,美] 布雷特·兰茨(Brett Lantz) 著,李洪成 许金炜 李舰 译更多章节内容可以访问云栖社区& ...

最新文章

  1. linux 命令详解 二十七
  2. xmemcached发布1.3.4
  3. 11. java 抽象类
  4. Linux学习:gcc 编译工作流程
  5. 小熊维尼项目冲刺 第三天
  6. java电商项目的项目描述_Java电商项目-6.实现门户首页数据展示_Redis数据缓存
  7. [CodePlus2017]晨跑
  8. JSK-16500 金币【模拟】
  9. python问题整理
  10. bmp qimage 保存位_在Qt中保存QImage(Save a QImage in Qt)
  11. 历经18年胡培松创制优良新种质 国稻种芯百团计划行动
  12. php网站安装有密钥,win10安装时跳过密钥
  13. 7、微信小程序-wxs脚本
  14. mysql 索引原理详解
  15. Python编写一个函数,计算一个整数各个数字之和
  16. 安卓html调色器,5个超棒的在线配色神器
  17. 开机hidl报错修改
  18. 【2019CSP-J普及组】T4 加工零件
  19. 模糊查询忽略大小写解决方案
  20. 房屋租赁系统 nodejs+vue微信小程序

热门文章

  1. 超级文献下载工具scihub-cn,教您一行命令下载文献
  2. SQL SERVER 的排序规则
  3. Apache Solr同义词示例
  4. 华中科技大学计算机中德班,华中科技大学中德班介绍
  5. GooglePlay内购In-app Billing 总结~
  6. Qt嵌入浏览器开发——下载编译
  7. 自己动手去除app中谷歌广告
  8. Amazon云计算的一些实用应用
  9. 《Effective C++》学习笔记(条款51:编写 new 和 delete 时需固守常规)
  10. S7-200 SMART 老版本固件更新