内存学习笔记

    技术2023-10-21  81

    目录

    内存条/总线/DMAOS内存管理和内存分类分页分页的时间和空间优化分段内存的分类 内存相关的系统调用brk()mmap

    内存条/总线/DMA

    内存和CPU之间有数据总线和地址总线直接相连,交互。

    PCIE总线也是和CPU直接相连,比如可以插显卡。

    南桥:接一些USB,硬盘,网卡,声卡

    DMA控制器:(DMAC, 直接内存访问控制器)

    CPU会把一些机械的重复工作,比如读文件交给DMAC。

    CPU交给DMAC,DMAC将磁盘内容读取到内存, 以中断的形式通知CPU文件读取完毕。

    DMAC为什么可以直接访问内存?

    因为在CPU将IO任务交给控制器时,会把总线控制权完全交给DMAC, CPU这时候不能控制总线。

    在读取过程中,并不是连续掌握总线的控制权,而是和CPU有个交接,可能按照时间片轮换掌握控制权。

    OS内存管理和内存分类

    视频地址:

    为什么要有逻辑地址?

    逻辑地址就是程序看到的地址,对应物理地址。

    程序无法知道可用的物理地址,所以需要作出映射。

    逻辑地址和物理地址如何映射

    ①一种简单的思路:使用固定偏移量映射。

    存在缺陷:程序使用的内存并不固定,可能是动态变化的,使用的内存大小在变化,不能确定一个最大值分配,即使可以确定一个最大值假设为200,也会有很多内存长时间不被使用,这叫做内碎片。

    假设程序释放内存,大小为200的内存区域被释放,如果下一个程序需求是201,那么不能使用这块内存,如果长时间得不到使用,这块被闲置的内存就叫做外碎片。

    ②分页:下一小节

    分页

    逻辑内存和物理内存都进行切分,分成固定大小,每一个都叫做页。

    为了区分,逻辑内存每一片叫做页,物理内存叫做帧。

    从页到帧需要page table页表维护映射关系。例如页编号,帧编号。

    小知识:

    内存的一个地址里面住的是一个字节的数据32位OS的物理地址有2^32个=4G,每个地址一个字节,所以内存大小4GB。任何一个32位程序可以操作的逻辑地址为2^32个上面会导致多个程序使用的内存和大于物理内存,此时会借助磁盘,将并不着急使用的内存放到磁盘中,所以一些页直接映射到磁盘中,而不是帧中。

    一次内存映射的过程:

    条件: 机器32位系统,256MB内存,页大小4KB,程序32位程序

    4kb=12位

    逻辑地址:32位= 20bit 页号+12bit偏移物理地址(256MB):28bit = 16bit帧号+12bit偏移

    如果帧号是磁盘,会发生什么?

    如果帧号这一栏没有对应帧号,会发生一个缺页中断,触发程序进入内核态,内核会找到磁盘数据,加载到物理内存的帧中,将加载成功的帧号填写到页表中。重新进行寻址过程。

    如果所有的帧都满了,应该加载到哪里呢

    会有很多页的替换算法,可以进行选择。

    分页小结:

    分页让每个程序都有很大的逻辑地址空间,通过映射和置换算法,使内存“无限大。 不同进程的内存各自维系一个页表,只要帧号不相同,让不同进程的内存互相隔离,保证安全。 分页降低了内存碎片问题。 页表是保存在内存中,我们需要读取两次内存,先读取页表,在进行查找帧。 而且页表占用内存空间。

    分页的时间和空间优化

    时间优化:

    快表TLB,将最长访问的页表项存储到一个更快的硬件中,一般是MMU(集成在CPU中,空间很小,一般是8-128个页表项),寻址先查TLB,再查PT。快表命中率很高,因为程序最常访问的页没几个。

    空间优化:

    多级页表

    分段

    程序内部的内存管理. 最早的分段,就是将程序分成几个大的内存区域,每个段在分成几个页,每个段都有页表。需要使用段号+页号得到帧。

    这种方式已经被淘汰了。

    但C语言中经常还有段错误,堆栈等。

    段保留了逻辑意义,并不在内存管理中起到作用,对虚拟内存可以分成多个段,TEXT, Data, Heap, Libraries, Stack. 这是虚拟内存空间的,程序的虚拟内存空间 对逻辑内存分段。常见的win的DLL动态链接库等都是放在堆和栈中间的一个区域中。

    malloc 如果申请大于128K的内存会调用MMAP,在堆和栈之间的区域申请内存,和这里的lib是相同位置,因为他们都是页映射磁盘。

    进程是可以共享函数库的,这也是进程通信中共享内存的通信方式。

    分段和分页的结合方式:

    每个段都有很多页,页表中存储段号和页号唯一映射物理地址帧号。 但是段页结合的方式旨在C86 intel CPU等支持,更新的X86-64架构都不支持短夜结合,虽然保留了段的概念,只是程序层面便于运算,不会影响分页式内存管理。

    CPU的三级缓存如何作用

    内存的分类

    LINUX系统

    total

    used

    free :完全没有使用,因为系统会缓存部分文件,如果程序需要使用内存,这不分的缓存文件的内存是aviliable的

    shared:used里面包含部分shared

    buff/cache:文件缓存

    aviliable:可用,我们一般关心可用,不关心free

    win系统:

    使用中34GB

    可用:29.4G

    已提交:58G/69.2G 后一个数字就是物理内存大小加程序映射到磁盘的内存之和。

    前一个数字就是确切的提交的内存,所有程序向操作系统申请的内存。 但是大于使用中的量,因为操作系统并不会申请就给,在需要的使用在分配。甚至已提交的内存可以大于实际的物理内存,比如申请1T,可以的因为操作系统并不会直接给这么大的内存。

    已缓存:29.G 对文件的缓冲,对应buffer/cache

    分页缓冲池:内核和驱动设备的一些使用空间,可以分页,可以映射到磁盘。

    非分页缓冲池:必须保存在物理内存,不可以分页,不可以映射到磁盘。

    内存相关的系统调用

    系统调用是用户态转到内核态的方法之一, 还有中断和异常。

    用户是无法直接操作硬件,需要交给内核,处理之后结果交给用户。以malloc为例,这是封装了BRK和MMAP的系统调用,小于128K,使用BRK,大于128K调用MMAP.

    brk()

    C语言提供了sbrk函数

    #include<stdio> #include<unistd.j> int main(){ void * first = sbrk(0);//申请0字节大小内存,返回内存头部地址 void * second = sbrk(1); void * third = sbrk(0); printf("%p\n",first); printf("%p\n",second); printf("%p\n",third); return 0; }

    结果输出:

    ox1dab000

    0x1dab000

    0x1dab001

    发现brk申请内存时,内存是连续的,提高了heap部分的上届。虚拟内存,借助上面的图,就是heap段上面的箭头。

    int main(){ int * first = (int *)sbrk(1);//申请一个字节,强制转换为4个字节的int*类型 *(first+1) = 123;//给第5-8个字节进行赋值 return 0; }

    上面程序没有报错,正常运行,因为操作系统使用分页,电脑页大小比如4k, 其实sbrk申请了1页4096字节大小,所以5-8个字节也在这些字节里面,是当前进程可以支配的内存中。

    int main(){ int * first = (int *)sbrk(1); *(first+1024) = 123;//int类型后移1024相当于后移4096字节,所以程序会报段错误 return 0; }

    mmap

    void* mmap(void*addr,size_t length, int prot, int flags, int fd, off_t offset); //起始地址,长度,权限,标志位,文件描述符,FD中的偏移量 int munmap(void* addr, size_t length);

    mmap作用将文件映射到内存,如果fd传-1,直接申请内存。

    void main(){ int * a = (int*)mmap(NULL, 100*4096, PORT_READ|PORT-wWRITE,MAP_PROVATE|HAP_ANONYMOUS,-1,0); //申请了100页 int* b = a; // 每一页都赋一个值,因为OS是惰性的,申请不用就不给你。 for(int i = 0; i < 100; i++){ b = (void *)a+(i*4096); *b = 1; } while(1){ sleep(1); } }

    之后进行监控内存。

    出现100次minflts(OS发现缺页)

    mmap和普通读文件的区别?

    调用read(),进入内核态,文件加载到kernel space, 内核将文件拷贝到用户空间,切换为用户态。

    mmap直接将文件进行一个映射,一开始页表中直接对应磁盘,需要读取触发缺页中断,将文件加载到内存中,页表中的映射其实是既有用户态内存也有内核态内存。也就是用户空间的key对应的value, 和内核空间的key对应的value是同一个值。 (个人理解,程序中的虚拟地址,kernel space 和用户空间的两个地址对应同一个value???程序中具体的kernal地址和用户地址想不明白了。。。)

    所以也有人说,实现了mmap达到了共享内存的效果。

    总之,省去了从内核空间到用户空间的拷贝。

    但是read和mmap各有利弊,首先是mmap无法使用buffer cache, 而且mmap第一次加载触发的缺页异常和read谁时间更长也不一定。

    Processed: 0.010, SQL: 9