内存和CPU之间有数据总线和地址总线直接相连,交互。
PCIE总线也是和CPU直接相连,比如可以插显卡。
南桥:接一些USB,硬盘,网卡,声卡
DMA控制器:(DMAC, 直接内存访问控制器)
CPU会把一些机械的重复工作,比如读文件交给DMAC。
CPU交给DMAC,DMAC将磁盘内容读取到内存, 以中断的形式通知CPU文件读取完毕。
DMAC为什么可以直接访问内存?
因为在CPU将IO任务交给控制器时,会把总线控制权完全交给DMAC, CPU这时候不能控制总线。
在读取过程中,并不是连续掌握总线的控制权,而是和CPU有个交接,可能按照时间片轮换掌握控制权。
视频地址:
为什么要有逻辑地址?逻辑地址就是程序看到的地址,对应物理地址。
程序无法知道可用的物理地址,所以需要作出映射。
逻辑地址和物理地址如何映射①一种简单的思路:使用固定偏移量映射。
存在缺陷:程序使用的内存并不固定,可能是动态变化的,使用的内存大小在变化,不能确定一个最大值分配,即使可以确定一个最大值假设为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.
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作用将文件映射到内存,如果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谁时间更长也不一定。