Linux内存初始化(二)identity mapping和kernel image mapping

一、前言

本文没有什么框架性的东西,就是按照__create_page_tables代码的执行路径走读一遍,记录在初始化阶段,内核是如何创建内核运行需要的页表过程。想要了解一些概述性的、框架性的东西可以参考内存初始化文档。

本文的代码来自ARM64,内核版本是4.4.6,此外,阅读本文最好熟悉ARMv8中翻译表描述符的格式。

二、create_table_entry

这个宏定义主要是用来创建一个中间level的translation table中的描述符。如果用linux的术语,就是创建PGD、PUD或者PMD的描述符。如果用ARM64术语,就是创建L0、L1或者L2的描述符。具体创建哪一个level的Translation table descriptor是由tbl参数指定的,tbl指向了该translation table的内存。virt参数给出了要创建地址映射的那个虚拟地址,shift参数以及ptrs参数是和具体在哪一个entry中写入描述符有关。我们知道,在定位页表描述的时候,我们需要截取虚拟地址中的一部分做为offset(index)来定位描述符,实际上,虚拟地址右移shift,然后截取ptrs大小的bit field就可以得到entry index了。tmp1和tmp2是临时变量。create_table_entry的代码如下:

.macro    create_table_entry, tbl, virt, shift, ptrs, tmp1, tmp2
lsr    \tmp1, \virt, #\shift
and    \tmp1, \tmp1, #\ptrs - 1    // table index-------------------(1)
add    \tmp2, \tbl, #PAGE_SIZE-------------------------(2)
orr    \tmp2, \tmp2, #PMD_TYPE_TABLE---------------------(3)
str    \tmp2, [\tbl, \tmp1, lsl #3]--------------------------(4)
add    \tbl, \tbl, #PAGE_SIZE---------------------------(5)
.endm

(1)tmp1中保存virt地址对应在Translation table中的entry index。

(2)初始阶段的页表页表定义在链接脚本中,如下:

BSS_SECTION(0, 0, 0)

. = ALIGN(PAGE_SIZE);
idmap_pg_dir = .;
. += IDMAP_DIR_SIZE;
swapper_pg_dir = .;
. += SWAPPER_DIR_SIZE;

初始阶段的页表(PGD/PUD/PMD/PTE)都是排列在一起的,每一个占用一个page。也就是说,如果create_table_entry当前操作的是PGD,那么tmp2这时候保存了下一个level的页表,也就是PUD了。

(3)这一步是合成描述符的数值。光有下一级translation table的地址不行,还要告知该描述符是否有效(bit 0),该描述符的类型是哪一种类型(bit 1)。对于中间level的页表,该描述符不可能是block entry,只能是table type的描述符,因此该描述符的最低两位是0b11。

#define PMD_TYPE_TABLE        (_AT(pmdval_t, 3) << 0)

(4)这是最关键的一步,将描述符写入页表中。之所以有“lsl #3”操作,是因为一个描述符占据8个Byte。

(5)将translation table的地址移到next level,以便进行下一步设定。

三、create_pgd_entry

从字面上看,create_pgd_entry似乎是用来在PGD中创建一个描述符,但是,实际上该函数不仅仅创建PGD中的描述符,如果需要下一级的translation table,例如PUD、PMD,也需要同时建立,最终的要求是能够完成所有中间level的translation table的建立(其实每个table中都是只建立了一个描述符),仅仅留下PTE,由其他代码来完成。该函数需要四个参数:tbl是pgd translation table的地址,具体要创建哪一个地址的描述符由virt指定,tmp1和tmp2是临时变量,create_pgd_entry具体代码如下:

.macro    create_pgd_entry, tbl, virt, tmp1, tmp2
    create_table_entry \tbl, \virt, PGDIR_SHIFT, PTRS_PER_PGD, \tmp1, \tmp2-------(1)
#if SWAPPER_PGTABLE_LEVELS > 3------------------------(2)
    create_table_entry \tbl, \virt, PUD_SHIFT, PTRS_PER_PUD, \tmp1, \tmp2--------(3)
#endif
#if SWAPPER_PGTABLE_LEVELS > 2------------------------(4)
    create_table_entry \tbl, \virt, SWAPPER_TABLE_SHIFT, PTRS_PER_PTE, \tmp1, \tmp2
#endif
    .endm

(1)create_table_entry 在上一节已经描述了,这里通过调用该函数在PGD中为虚拟地址virt创建一个table type的描述符。

(2)SWAPPER_PGTABLE_LEVELS这个宏定义和ARM64_SWAPPER_USES_SECTION_MAPS相关,而这个宏在蜗窝已经有一篇文章描述,这里就不说了。SWAPPER_PGTABLE_LEVELS其实定义了swapper进程地址空间的页表的级数,可能3,也可能是2,具体中间的Translation table有多少个level是和配置相关的,如果是section mapping,那么中间level包括PGD和PUD就OK了,PMD是最后一个level。如果是page mapping,那么需要PGD、PUD和PMD这三个中间level,PTE是最后一个level。当然,如果整个page level是3或者2的时候,也有可能不存在PUD或者PMD这个level。

(3)当SWAPPER_PGTABLE_LEVELS > 3的时候,需要创建PUD这一级的Translation table。

(4)当SWAPPER_PGTABLE_LEVELS > 2的时候,需要创建PMD这一级的Translation table。

上面太枯燥了,我们给出一些实例:

例子1:当虚拟地址是48个bit,4k page size,这时候page level等于4,映射关系是PGD(L0)--->PUD(L1)--->PMD(L2)--->Page table(L3)--->page,但是如果采用了section mapping(4k的page一定会采用section mapping),映射关系是PGD(L0)--->PUD(L1)--->PMD(L2)--->section。在create_pgd_entry函数中将创建PGD和PUD这两个中间level。

例子2:当虚拟地址是48个bit,16k page size(不能采用section mapping),这时候page level等于4,映射关系是PGD(L0)--->PUD(L1)--->PMD(L2)--->Page table(L3)--->page。在create_pgd_entry函数中将创建PGD、PUD和PMD这三个中间level。

例子3:当虚拟地址是39个bit,4k page size,这时候page level等于3,映射关系是PGD(L1)--->PMD(L2)--->Page table(L3)--->page。由于是4k page,因此采用section mapping,映射关系是PGD(L1)--->PMD(L2)--->section。在create_pgd_entry函数中将创建PGD这一个中间level。

四、create_block_map

create_block_map的名字起得不错,该函数就是在tbl指定的Translation table中建立block descriptor以便完成address mapping。具体mapping的内容是将start 到 end这一段VA mapping到phys开始的PA上去,代码如下:

.macro    create_block_map, tbl, flags, phys, start, end
    lsr    \phys, \phys, #SWAPPER_BLOCK_SHIFT
    lsr    \start, \start, #SWAPPER_BLOCK_SHIFT
    and    \start, \start, #PTRS_PER_PTE - 1    // table index
    orr    \phys, \flags, \phys, lsl #SWAPPER_BLOCK_SHIFT    // table entry
    lsr    \end, \end, #SWAPPER_BLOCK_SHIFT
    and    \end, \end, #PTRS_PER_PTE - 1        // table end index
9999:    str    \phys, [\tbl, \start, lsl #3]        // store the entry
    add    \start, \start, #1            // next entry
    add    \phys, \phys, #SWAPPER_BLOCK_SIZE        // next block
    cmp    \start, \end
    b.ls    9999b
    .endm

五、__create_page_tables

1、准备阶段

__create_page_tables:
    adrp    x25, idmap_pg_dir------------------------(1)
    adrp    x26, swapper_pg_dir
    mov    x27, lr

mov    x0, x25-----------------------------(2)
    add    x1, x26, #SWAPPER_DIR_SIZE
    bl    __inval_cache_range

mov    x0, x25-----------------------------(3)
    add    x6, x26, #SWAPPER_DIR_SIZE
1:    stp    xzr, xzr, [x0], #16
    stp    xzr, xzr, [x0], #16
    stp    xzr, xzr, [x0], #16
    stp    xzr, xzr, [x0], #16
    cmp    x0, x6
    b.lo    1b

ldr    x7, =SWAPPER_MM_MMUFLAGS-----------------(4)

(1)取idmap_pg_dir这个符号的物理地址,保存到x25。取swapper_pg_dir这个符号的物理地址,保存到x26。这段代码没有什么特别要说明的,除了adrp这条指令。adrp是计算指定的符号地址到run time PC值的相对偏移(不过,这个offset没有那么精确,是以4K为单位,或者说,低12个bit是0)。在指令编码的时候,立即数(也就是 offset)占据21个bit,此外,由于偏移计算是按照4K进行的,因此最后计算出来的符号地址必须要在该指令的-4G和4G之间。由于执行该指令的 时候,还没有打开MMU,因此通过adrp获取的都是物理地址,当然该物理地址的低12个bit是全零的。此外,由于在链接脚本中 idmap_pg_dir和swapper_pg_dir是page size aligned,因此使用adrp指令也是OK的。

(2)这段代码是要进行invalid cache的操作了,具体要操作的范围就是identity mapping和kernel image mapping所对应的页表区域,起始地址是idmap_pg_dir,结束地址是swapper_pg_dir+SWAPPER_DIR_SIZE。

为什么要调用__inval_cache_range来invalidate idmap_pg_dir和swapper_pg_dir对应页表空间的cache呢?根据boot protocol,代码执行到此,对于cache的要求是kernel image对应的那段空间的cache line是clean到PoC的,不过idmap_pg_dir和swapper_pg_dir对应页表空间不属于kernel image的一部分,因此其对应的cacheline很可能有一些旧的,无效的数据,必须要清理掉。

(3)将idmap和swapper页表内容设定为0是有意义的。实际上这些translation table中的大部分entry都是没有使用的,PGD和PUD都是只有一个entry是有用的,而PMD中有效的entry数目是和mapping的地 址size有关。将页表内容清零也就是意味着将页表中所有的描述符设定为invalid(描述符的bit 0指示是否有效,等于0表示无效描述符)。

(4)要创建mapping除了需要VA和PA,还需要memory attribute的参数,这个参数定义如下:

#if ARM64_SWAPPER_USES_SECTION_MAPS
#define SWAPPER_MM_MMUFLAGS    (PMD_ATTRINDX(MT_NORMAL) | SWAPPER_PMD_FLAGS)
#else
#define SWAPPER_MM_MMUFLAGS    (PTE_ATTRINDX(MT_NORMAL) | SWAPPER_PTE_FLAGS)
#endif

为了理解这些定义,需要理解block type和page type的描述符的格式,大家自行对照ARMv8文档,这里就不贴图了。SWAPPER_MM_MMUFLAGS这个flag其实定义了要映射地址的memory attribut。对于kernel image这一段内存,当然是普通内存,因此其中的MT_NORMAL就是表示后续的地址映射都是为normal memory而创建的。其他的flag定义如下:

#define SWAPPER_PTE_FLAGS    (PTE_TYPE_PAGE | PTE_AF | PTE_SHARED)
#define SWAPPER_PMD_FLAGS    (PMD_TYPE_SECT | PMD_SECT_AF | PMD_SECT_S)

PMD_SECT_AF(PTE_AF)中的AF是access flag的缩写,这个bit用来表示该entry是否第一次使用(当程序访问对应的page或者section的时候,就会使用该entry,如果从来没有被访问过,那么其值等于0,否者等于1)。该bit主要被操作系统用来跟踪一个page是否被使用过(最近是否被访问),当该page首次被创建的时候,AF等于0,当代码第一次访问该page的时候,会产生MMU fault,这时候,异常处理函数应该设定AF等于1,从而阻止下一次访问该page的时候产生MMU Fault。在这里,kernel image对应的page,其描述符的AF bit都设定为1,表示该page当前状态是actived(最近被访问),因为只有用户空间进程的page才会根据AF bit来确定哪些page被swap out,而kernel image对应的page是always actived的。

PMD_SECT_S(PTE_SHARED)对应shareable attribute bits,这个两个bits定义了该page的shareable attribute。那么是shareable attribute呢?shareable attribute定义了memory location被多个系统中的bus master共享的属性。具体定义如下:

SH[1:0] Normal memory
00 Non-shareable
01 无效
10 outer shareable
11 inner shareble

这里memory attribute中SH被设定为0b11,即inner shareable。如果一个page被标注为inner shareable,那么在inner shareable domain中,所有的bus mast访问该page中的memory都是coherent的(HW会处理cache coherence问题),软件不需要考虑cache。一般而言,所有cpu core组成了inner shareable domain,也就是说,对于kernel direct mapping而言,其对应的内存对所有的cpu core的访问都是coherent的。

memory attribute中其他的flag都没有显式指定,也就是说它们的值都是0,我们可以简单过一下。AP的值是0,表示该page对kernel mode(EL1)是read/write的,对于userspace(EL0),是不允许访问的。nG bit是0,表示该地址翻译是全局的,不是process-specific的,这也合理,内核page的映射当然是全局的了。

2、建立identity mapping

mov    x0, x25                -------------------------(1)
    adrp    x3, __idmap_text_start        --------------------(2)

#ifndef CONFIG_ARM64_VA_BITS_48---------------------(3)
#define EXTRA_SHIFT    (PGDIR_SHIFT + PAGE_SHIFT - 3)-----------(4)
#define EXTRA_PTRS    (1 << (48 - EXTRA_SHIFT)) ---------------(5)

#if VA_BITS != EXTRA_SHIFT-------------------------(6)
#error "Mismatch between VA_BITS and page size/number of translation levels"
#endif

adrp    x5, __idmap_text_end-------------------------(7)
    clz    x5, x5
    cmp    x5, TCR_T0SZ(VA_BITS)    ----------------------(8)
    b.ge    1f

adr_l    x6, idmap_t0sz---------------------------(9)
    str    x5, [x6]
    dmb    sy
    dc    ivac, x6

create_table_entry x0, x3, EXTRA_SHIFT, EXTRA_PTRS, x5, x6--------(10)
1:
#endif

create_pgd_entry x0, x3, x5, x6-----------------------(11)
    mov    x5, x3                // __pa(__idmap_text_start)
    adr_l    x6, __idmap_text_end        // __pa(__idmap_text_end)
    create_block_map x0, x7, x3, x5, x6---------------------(12)

(1)x0保存了idmap_pg_dir变量的物理地址,也就是identity mapping的PGD。

(2)x3保存了__idmap_text_start的物理地址,对于identity mapping而言,x3也保存了虚拟地址,因为虚拟地址是等于物理地址的。

(3)基本上创建identity mapping是没有什么大问题的,但是,如果物理内存的地址位于非常高的位置,那么在进行identity mapping就有问题了,因为有可能你配置的VA_BITS不够大,超出了虚拟地址的范围。这时候,就需要扩展virtual address range了。当然,如果配置了48bits的VA_BITS就不存在这样的问题了,因为ARMv8最大支持的VA BITS就是48个,根本不可能扩展了。

(4)在虚拟地址地址不是48 bit,而系统内存的物理地址又放到了非常非常高的位置,这时候,为了完成identity mapping,我们必须要扩展虚拟地址,那么扩展多少呢?扩展到48个bit。扩展之后,增加了一个EXTRA的level,地址映射关系是EXTRA--->PGD--->……,其中EXTRA_SHIFT等于(PGDIR_SHIFT + PAGE_SHIFT - 3)。

(5)扩展之后,地址映射多个一个level,我们称之EXTRA level,该level的Translation table中有多少个entry呢?EXTRA_PTRS给出了答案。

(6)其实现行的linux kernel中,对地址映射是有要求的,即要求PGD是满的。例如:48 bit的虚拟地址,4k的page size,对应的映射关系是PGD(9-bit)+PUD(9-bit)+PMD(9-bit)+PTE(9-bit)+page offset(12-bit),对于42bit的虚拟地址,64k的page size,对应的映射关系是PGD(13-bit)+ PTE(13-bit)+ page offset(16-bit)。这两种例子有一个共同的特点就是PGD中的entry数目都是满的,也就是说索引到PGD的bit数目都是PAGE_SIZE-3。如果不满足这个关系,linux kernel会认为你的配置是有问题的。注意:这是内核的要求,实际上ARM64的硬件没有这么要求。

正因为正确的配置下,PGD都是满的,因此扩展之后EXTRA_SHIFT一定是等于VA_BITS的,否则一定是你的配置有问题。我们延续上一个实例来说明如何扩展虚拟地址的bit数目。对于42bit的虚拟地址,64k的page size,扩展之后,虚拟地址是48个bit,地址映射关系是EXTRA(6-bit)+ PGD(13-bit)+ PTE(13-bit)+ page offset(16-bit)。

(7)x5保存了__idmap_text_end的物理地址,之所以这么做是因为需要确定identity mapping的最高的物理地址,计算该物理地址的前导0有多少个,从而可以判断该地址是否是位于物理地址空间中比较高的位置。

(8)宏定义TCR_T0SZ可以计算给定虚拟地址数目下,前导0的个数。如果虚拟地址是48的话,那么前导0是16个。如果当前物理地址的前导0的个数(x5的值)还有小于当前配置虚拟地址的前导0的个数,那么就需要扩展。

(9)OK,现在进入需要扩展的分支,当然,具体要配置虚拟地址是通过TCR_EL1寄存器中的T0SZ域进行的,现在还不是时候(具体的设定在__cpu_setup函数中),这里,我们只要设定idmap_t0sz这个变量值就OK了,在__cpu_setup函数中会从该变量取值并设定到TCR_EL1寄存器中的。代码中,x6是idmap_t0sz变量的物理地址,x5是物理地址前导0的个数,将其保存到idmap_t0sz变量中。

(10)创建extra translation table的entry。具体传递的参数如下:

x0:页表地址idmap_pg_dir

x3:准备映射的虚拟地址(虽然x3保存的是物理地址,但是identity mapping嘛,VA和PA都是一样的)

EXTRA_SHIFT:正常建立最高level mapping的时候, shift是PGDIR_SHIFT,但是,由于物理地址位置太高,需要额外的映射,因此这里需要再加上一个level的mapping,因此shift需要PGDIR_SHIFT + (PAGE_SHIFT - 3)。

EXTRA_PTRS:增加了一个level的Translation table,我们需要确定这个增加level的Translation table中包含的描述符的数目,EXTRA_PTRS给出了这个参数。

(11)create_pgd_entry这个函数上面解释过了,建立各个中间level的table描述符。

(12)创建最后一个level translation table的entry。该entry可能是page descriptor,也可能是block descriptor,具体传递的参数如下:

x0:指向最后一个level的translation table

x7:要创建映射的memory attribute

x3:物理地址

x5:虚拟地址的起始地址(其实和x3一样)

x6:虚拟地址的结束地址

3、创建kernel direct mapping

mov    x0, x26                ------------------------(1)
mov    x5, #PAGE_OFFSET-----------------------(2)
create_pgd_entry x0, x5, x3, x6---------------------(3)
ldr    x6, =KERNEL_END            // __va(KERNEL_END)
mov    x3, x24                // phys offset
create_block_map x0, x7, x3, x5, x6 -------------------(4)

mov    x0, x25
add    x1, x26, #SWAPPER_DIR_SIZE
dmb    sy
bl    __inval_cache_range

mov    lr, x27
ret

(1)swapper_pg_dir其实就是swapper进程(pid等于0的那个,其实就是idle进程)的地址空间,这时候,x0指向了内核地址空间的PGD的基地址。

(2)PAGE_OFFSET是kernel image的首地址,对于48bit的VA而言,该地址是0xffff8000-00000000

(3)创建PAGE_OFFSET(即kernel image首地址,虚拟地址)对应中间level的table描述符。

(4)创建PAGE_OFFSET~KERNEL_END之间地址映射的最后一个level的描述符。

参考文献:

1、ARMv8技术手册

2、Linux 4.4.6内核源代码

原文地址:https://www.cnblogs.com/alantu2018/p/8447579.html

时间: 2024-11-07 13:02:27

Linux内存初始化(二)identity mapping和kernel image mapping的相关文章

Linux内存初始化(一)

一.前言 一直以来,我都非常着迷于两种电影拍摄手法:一种是慢镜头,将每一个细节全方位的展现给观众.另外一种就是快镜头,多半是反应一个时代的变迁,从非常长的时间段中,截取几个典型的snapshot,合成在十几秒的镜头中,可以让观众很快的了解一个事物的发展脉络.对应到技术层面,慢镜头有点类似情景分析,把每一行代码都详细的进行解析,了解技术的细节.快镜头类似数据流分析,勾勒一个过程中,数据结构的演化.本文采用了快镜头的方法,对内存初始化部分进行描述,不纠缠于具体函数的代码实现,只是希望能给大家一个概略

linux文件系统 - 初始化(二)

一.目的 本文主要讲述linux3.10文件系统初始化过程的第二阶段:加载initrd. initrd是一个临时文件系统,由bootload负责加载到内存中,里面包含了基本的可执行程序和驱动程序.在linux初始化的初级阶段,它提供了一个基本的运行环境.当成功加载磁盘文件系统后,系统将切换到磁盘文件系统并卸载initrd. 如果是嵌入式设备,那么最终的文件系统就是initrd. 二.cpio文件格式 initrd常用的的文件格式是cpio,cpio格式记录了文件系统的结构和内容. cpio格式具

Linux内存初始化(三) 内存布局

一.前言 同样的,本文是内存初始化文章的一份补充文档,希望能够通过这样的一份文档,细致的展示在初始化阶段,Linux 4.4.6内核如何从device tree中提取信息,完成内存布局的任务.具体的cpu体系结构选择的是ARM64. 二.memory type region的构建 memory type是一个memblock模块(内核初始化阶段的内存管理模块)的术语,memblock将内存块分成两种类型:一种是memory type,另外一种是reserved type,分别用数组来管理系统中的

高端内存映射之kmap持久内核映射--Linux内存管理(二十)

1 高端内存与内核映射 尽管vmalloc函数族可用于从高端内存域向内核映射页帧(这些在内核空间中通常是无法直接看到的), 但这并不是这些函数的实际用途. 重要的是强调以下事实 : 内核提供了其他函数用于将ZONE_HIGHMEM页帧显式映射到内核空间, 这些函数与vmalloc机制无关. 因此, 这就造成了混乱. 而在高端内存的页不能永久地映射到内核地址空间. 因此, 通过alloc_pages()函数以__GFP_HIGHMEM标志获得的内存页就不可能有逻辑地址. 在x86_32体系结构总,

启动期间的内存管理之初始化过程概述----Linux内存管理(九)

日期 内核版本 架构 作者 GitHub CSDN 2016-06-14 Linux-4.7 X86 & arm gatieme LinuxDeviceDrivers Linux内存管理 在内存管理的上下文中, 初始化(initialization)可以有多种含义. 在许多CPU上, 必须显式设置适用于Linux内核的内存模型. 例如在x86_32上需要切换到保护模式, 然后内核才能检测到可用内存和寄存器. 而我们今天要讲的boot阶段就是系统初始化阶段使用的内存分配器. 1 前景回顾 1.1

linux内存操作----kernel 3.5.X copy_from_user()和copy_to_user()

亲爱的网友,我这里有套课程想和大家分享,如果对这个课程有兴趣的,可以加我的QQ2059055336和我联系. 课程内容简介 我们软件是基于移动设备的.所以我们必然的选择了安卓作为我们的开发工具.课程中,我们将简要的介绍Android的基本概念,然后进行我们的实战开发.在开发中,大家讲学习到基本的组件,适配UI,数据的存储,多线程下载,开机广播,闹钟提醒,短信发送等实际项目开发中碰到的有用的知识点.通过课程学习,让大家能够掌握Android软件开发的流程,注意点,及优化.帮助大家迅速的掌握Andr

linux调度器源码分析 - 初始化(二)

本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 引言 上期文章linux调度器源码分析 - 概述(一)已经把调度器相关的数据结构介绍了一遍,本篇着重通过代码说明调度器在系统启动初始化阶段是如何初始化和工作的.通过上期文章我们知道,在多核CPU和SMP系统中,每个CPU(多核COU中的每个核)都有自己的struct rq队列,而rq队列中又有着自己的struct cfs_rq和struct rt_rq.在初始化时就是对这三个结构进行初始化. init_tas

攻城狮在路上(叁)Linux(二十五)--- linux内存交换空间(swap)的构建

swap的功能是应付物理内存不足的状况,用硬盘来暂时放置内存中的信息. 对于一般主机,物理内存都差不多够用,所以也就不会用到swap,但是对于服务器而言,当遇到大量网络请求时或许就会用到. 当swap被使用的时候,主机的硬盘灯就会闪烁不停. 本篇介绍两种方式:1.设置一个swap分区   2.创建一个虚拟内存的文件. 一.使用物理分区构建swap: 1.首先是分区: A.fdisk /dev/sda; <== 根据后续提示创建一个分区. B.修改分区的ID,因为fdisk默认将分区的ID作为文件

Linux环境编程之共享内存区(二):Posix共享内存区

现在将共享内存区的概念扩展到将无亲缘关系进程间共享的内存区包括在内.Posix提供了两种在无亲缘关系进程间共享内存区的方法: 1.内存映射文件:由open函数打开,由mmap函数把得到的描述符映射到当前进程地址空间中的一个文件.(上一节就是这种技术) 2.共享内存区对象:由shm_open打开一个Posix名字(也许是在文件系统中的一个路径名),所返回的描述符由mmap函数映射到当前进程的地址空间.(本节内容) Posix共享内存区涉及以下两个步骤要求: 1.指定一个名字参数调用shm_open