标签:程序代码 结束 静态 使用场景 碎片 swa cpu 运行时 一级缓存
为了让每个进程认为独占地使用内存,并且让每个进程看到的内存是一致的,操作系统对物理内存、磁盘进行了抽象,抽象出虚拟内存。并且把虚拟内存、物理内存以相同固定大小的页进行切分管理(分页),虚拟内存中叫页,物理内存中的叫页帧。
每个进程虚拟地址空间是独立的。用户访问的是虚拟内存的地址,即虚拟地址。需要通过 CPU 芯片上的内存管理单元 MMU 硬件根据页表翻译成物理地址,才能真正访问内存。
页表:每个进程都有它的独立的页表(放在内存里),用来存对虚拟页、物理页的映射。页表可以有多级页表,以时间换取空间(实际上,多级页表的地址翻译,并不比单级页表慢很多)。
如果直接按一个个程序加载到内存,会出现内存碎片。
后来出现分段机制,按程序的各段来存储,从而减少碎片,但是还是有很多。
所以引出分页,把程序分成更小的页(一般大小为 4KB)来管理内存。分得更小,会增加负荷,但实际上利大于弊。
通过虚拟地址访问数据:
MMU 先通过它里面的 TLB 缓存查询,如果没有,则去内存中的页表进行查询。成功翻译成物理地址后,访问一级缓存获取数据。如果没有则访问二级缓存(可能还有三级缓存)。还是没有就访问内存。
物理内存不够时:
将不用的页面换出到磁盘中的 swap 分区里。
包含:
Linux 中进程的虚拟地址空间,有内核内存。内核内存有个区域是每个进程都不相同的,如页表、内核栈等。
内核为每个进程维护一个单独的 task_struct 任务结构(进程控制块 PCB),包含着内核态运行所需要的信息,如 PID、指向用户栈的指针、可执行目标文件的名字等。
task_struct 中有一个条目指向着 mm_struct,它描述虚拟内存的状态。mm_struct 里有个 mmap 变量,是一个 vm_area_struct(区域结构)链表,这个链表的每个 vm_area_struct 都描述着虚拟内存中的一个区域,如区域的起始处、结束处、区域内所有页的读写权限、页面是否与其他进程共享等。(mm_struct 里还有 pgd,指向进程的页表)
c 语言的 malloc 等方法。
用户可通过 malloc 在堆上分配内存,malloc 内部是用 brk 系统调用实现的。brk 会给进程创建 vm_area_struct 来分配小块虚拟内存。
malloc 如果是申请大块内存,就会用 mmap 。
内存里没有时,即出现缺页时会触发缺页异常/中断,让缺页异常处理程序去处理并分配内存。
虚拟内存分配,不代表真正分配了物理内存,只有需要写的时候发生缺页中断/异常,才会去分配物理内存,即按需分配。并把虚拟地址和物理地址映射起来。
处理程序返回时,CPU 会重新返回到引起缺页的那条指令,该指令再次被送到 MMU 进行翻译。因为此时 MMU 能正常翻译地址,所以就不会产生缺页中断,程序会继续执行下去。
按需进行页面调度:目前现代系统都是在缺页异常发生时才会去分配 / 缓存到内存。
rocketMQ:
一些对实时性要求很高的中间件,例如 rocketmq,把消息存储在一个大小为 1G 的文件中,为了加快读写速度,会将这个文件映射到内存后,对每个页写一比特数据,这样就可以把整个 1G 文件都加载进内存,在实际读写的时候就不会发生缺页了,这个在 rocketmq 内部叫做文件预热。
对于分配比较大的内存,如分配页(32 位里是 4 KB)级别或以上的,可以使用伙伴系统(页面分配器)。
比一个页小的内存,可以用 slab 机制。
将不用的页面换出到磁盘中的 swap 分区里。
从物理内存中找出牺牲页进行分配。如果这个牺牲页面有被修改过,则需要先换出到磁盘,然后再把新的页面换入,并更新页表。
https://www.cnblogs.com/beifei/archive/2011/06/12/2078840.html
在内核中为每个文件单独维护一个 page cache。page cache 保存用户进程访问过的该文件的内容。
进程对文件的读写时会直接操作 page cache,内核会在适当的时候将 page cache 中的内容写到磁盘上(也可以手动控制回写),这样可以大大减少磁盘的访问次数,从而提高性能。
page cache 中的内容以页为单位保存在内存中。
当用户要访问文件中的某个偏移量上的数据时,内核会以偏移量为索引,找到相应的内存页,如果该页没有读入内存,则需要访问磁盘读取数据。
为了提高页的查询速度并且节省 page cache 占用的内存,linux 内核使用树来保存 page cache 中的页。
普通的 write 调用只是将数据写到 page cache 中,并将其标记为 dirty 就返回了,磁盘 I/O 通常不会立即执行。这样做的好处是减少磁盘的回写次数,提供吞吐率,不足就是机器一旦意外挂掉,page cache中的数据就会丢失。一般安全性比较高的程序会在每次 write 之后,调用 fsync 立即将 page cache 中的内容回写到磁盘中。
内存映射包含虚拟内存与物理内存、磁盘之间的映射。
而 mmap 系统调用是实现内存映射文件的一种方法。
read 系统调用的过程:
即 read 为了提高效率使用了页缓存机制,但这需要经历两次拷贝,以及用户态、内核态的转换,所以性能并不高。
mmap 系统调用与 read 的区别:
mmap 系统调用是将硬盘文件映射到用户空间中,从而让进程可以直接访问自身地址空间的虚拟地址来访问文件内容。
所以,
所以频繁对一个文件进行读取操作时,mmap 会比 read 更高效一些。
首先,从进程虚拟空间中找出一块空闲区域,并为此区域分配一个 vm_area_struct。(创建虚拟映射区域)
把待映射的文件描述符放到文件表,且找出磁盘地址。并建立页表,把磁盘地址、虚拟地址进行映射。(地址映射)
当进程要访问该空间时,会引起缺页异常,此时把文件内容拷贝到物理内存。(分配内存)
所以 mmap 只发生一次数据拷贝。
所以 mmap 在效率上不一定会比带缓冲区的快。
https://stackoverflow.com/questions/45972/mmap-vs-reading-blocks
http://www.leviathan.vip/2019/01/13/mmap%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/
读写大文件。链接动态库文件。
在 malloc 内部,如果是要求分配大内存,会去使用 mmap 进行内存映射。
进程之间共享内存。
rocketMQ 等。
都是系统调用。
brk 用来分配小块内存,mmap 用来分配大块内存。
C 语言的 malloc 函数内部,如果是大块内存,会去调用 mmap,反之调用 brk。
brk 是通过移动堆顶的位置来分配内存。这些内存释放后不会立刻归还,而是被缓存起来,这样就可以重复使用,进而减少缺页异常而去分配内存的次数。
mmap 是在文件映射段里找一块空闲内存进行分配。mmap 分配的物理内存,会在释放时直接归还系统。
两个方法都是按需分配物理内存,不会在调用时直接分配。
从系统开机到用户能使用内存,经过哪些过程
系统调用模块
设备管理模块
文件系统模块
进程管理模块
标签:程序代码 结束 静态 使用场景 碎片 swa cpu 运行时 一级缓存
原文地址:https://www.cnblogs.com/dmsdus/p/11456894.html