这系列主要是我对WASM研究的笔记,可能内容比较简略。总共包括:git
现代的内存寻址机制,都引入了名为「分段」的概念:不一样级别的程序、程序的不一样数据类型,存放在不一样的「段」上面,而后再定义在「段」上的偏移量。也就是说,现代程序看到的地址都不是线性的,而是分段过的地址,形如:segmentSelector:offset
。这些段和偏移量组成的空间,就是逻辑内存空间;这些二元组,就是逻辑地址。程序员
段标识符是由一个16位长的字段组成,又称为段选择符(Selector),由处理器提供段寄存器来存放段标识符,段寄存器有6种:github
cs
代码段寄存器,指向包含程序指令的段;ss
栈寄存器,指向包含当前程序的段;ds
数据段寄存器,指向包含静态数据或者全局数据段;es
, fs
, gs
称为附加段寄存器,做通常用途,能够指向任意的数据段段寄存器存放的并非段基地址也不是段描述符。段的详细信息须要经过选择器从描述符表(Descriptor Table,段表)中获得。 这样作能够加快查询速度,也能够进行权限控制,只有访问级别够的程序才能成功拿到段基地址和长度web
16位选择器具体组成:编程
ldtr
,DT相似于一个数组,每项占8个字节段表描述符的结构比较复杂,不过最重要的是段的段基(BASE
, 32bit)和段界(LIMIT
, 20bit)数组
经过段描述符获得BASE
以后,再与逻辑地址偏移量offset
相加,就获得了线性地址:缓存
当段描述符中的G = 1时, 分页机制启用。markdown
分页就是人为地在逻辑上将连续的内存空间,按照固定大小切分红一段一段。对于线性内存来讲,这样切分出来的固定大小叫作页();对于物理内存来讲,这样切分出来的固定大小叫作页帧(
)。 分页机制将线性内存分为若干页,将物理内存分为若干帧,并创建从页到帧的映射关系。这个映射关系,是一个「多对一」的映射。 线性地址的转换分两步完成,每一步都基于一种都基于一种转换表,第一种转换表称为页目录表转换,第二种转换称为页表转换。使用这种二级模式的目的在于减小每一个进程页表所需的RAM的数量。就像咱们看书有个书目录同样,方便快捷。具体转换以下图所示:架构
内存分段与分页功能重合,所以不少新架构或OS倾向于使用Flat Segmentation,如x86-64和Linuxide
在Linux中细分了四种段:
全部的用户进程都是使用同一个用户代码段描述符和用户数据段描述符,它们是__USER_CS
和__USER_DS
,也就是每一个进程处于用户态时,它们的CS寄存器和DS寄存器中的值是相同的。当任何进程或者中断异常进入内核后,都是使用相同的内核代码段描述符和内核数据段描述符,它们是__KERNEL_CS
和__KERNEL_DS
。这里要明确记得,内核数据段实际上就是内核态堆栈段。 逻辑地址是由段选择符(16位) + 段内偏移量offset(32位)得来。以前也说到,只有处于用户态,CS和DS寄存器中的值都是__USER_CS
和__USER_DS
。只要处于内核态,CS和DS寄存器中的值都是__KERNEL_CS
和__KERNEL_DS
。在咱们编程过程当中,实际上提供的地址都是一个偏移量,系统会自动将这个偏移量与CS中的段选择符进行结合。也就是咱们使用的逻辑地址实际上只使用了offset这一段,段选择符都为空。以前也说了这四个段描述符的BASE都为0x00000000,也得出当逻辑地址经过这样的分段机制转为线性地址后,实际上并无变化,也就是逻辑地址=线性地址(其实这两个地址都是offset的值)。
除了内存分段管理以外,应用程序也有段的概念,主要是描述的程序对数据的组织。通常Linux程序拥有下面几个段:
wasm的栈能够简化为:
从 往低位增加而堆从
往高位增加,由于栈先放置因此须要在编译的时候给一个最大值。
栈空间能够经过下面方式设置:
clang \
--target=wasm32 \
-O3 \
-flto \
-nostdlib \
-Wl,--no-entry \
-Wl,--export-all \
-Wl,--lto-O3 \
-Wl,-z,stack-size=$[8 * 1024 * 1024] \ # Set maximum stack size to 8MiB
-o add.wasm \
add.c
复制代码
// llvm 源码: // <https://github.com/llvm-mirror/lld/blob/master/wasm/Driver.cpp#L355> Config->InitialMemory = args::getInteger(Args, OPT_initial_memory, 0); Config->GlobalBase = args::getInteger(Args, OPT_global_base, 1024); Config->MaxMemory = args::getInteger(Args, OPT_max_memory, 0); Config->ZStackSize = args::getZOptionValue(Args, OPT_z, "stack-size", WasmPageSize); //... 复制代码
refer: dassur.ma/things/c-to…
If the effective address of a memory access is a multiple of the alignment attribute value of the memory access, the memory access is considered aligned, otherwise it is considered misaligned. Aligned and misaligned accesses have the same behavior
若是一个内存的访问有效地址(Effective address)是储存器访问的对齐属性的倍数,那么此次储存器访问就被称为是对齐的,不然是不对齐。对齐与不对齐的访问具备相同的行为,可是对齐会提升CPU的处理速度。
wasm32 的对齐属性是32,wasm64的native 对齐属性就是64
也便是当前访问的真实地址(相对于offset来讲)
i32.const 3 ;; address_operand = 3
i64.const 1234 ;; value
i64.store16 1 3 ;; alignment=1, offset=3, effective_address = 3 + 3 = 6
复制代码
上述是对齐的,可是若是是:
i32.const 3 ;; address_operand = 3
i64.const 1234 ;; value
i64.store16 2 3 ;; alignment=2
复制代码
那么将会对不齐: