Ⅰ 写内存算是访问内存吗
当用户访问用户空间的这段地址范围时,实际是访问设备内存。
在linux上电时,并不会为外设地址空间建立页表。
但我们知道,linux访问内存使用的都是虚拟地址,因此如果想访问外设的寄存器(一般包括数据寄存器、控制寄存器与状态寄存器),需要在驱动初始化中将外设所处的物理地址映射为虚拟地址,使用ioremap接口可以实现该功能。
ioremap & ioremap_nocache
ioremap和ioremap_nocache实现相同,使用场景为映射device memory类型内存。同时不使用cache(device memory本身就没有cacheable这个属性),即CPU的读写操作直接操作设备内存。
ioremap_cached
ioremap_cached用来映射memory type为normal memory的设备,同时使用cache,这会提高内存的访问速度,提高系统的性能。
ioremap_wc & ioremap_wt
ioremap_wc用来映射memory type为normal memory的设备,同时不使用cache。
I/O内存访问流程
request_mem_region
ioremap
rw
iounmap
release_mem_region
二、设备地址映射到用户空间
一般情况下,用户空间是不能够直接访高肢问设备的。mmap可实现这个功能。
mmap通过将设备内存映射到用户空间的一段内存上,这样,当用户访问用户空间的这段地址范围时,实际是访问设备内存。这样在每次访问时,节省了用户空间和内核空间的复制过程。
无论是普通文件还是设备文件,读写都是基于系统的虚拟文件系统接口,普通文件为了保护磁盘,避免频繁读写,还引入带缓冲页机制,通过read/write/ioctl访问文件时,都需经历“用户到内核”的内存拷贝过程,然后才将文件内容写入磁盘。
通过mmap方法,将文件(包括设备文件)映射到用户进程虚拟内存空间,代替read/write/ioctl的访问方式,此时内存拷贝过程只有“用户空间到虚拟内存空间”,省去了“用户到内核”的拷贝过程,在数据量大的情况下能显着提升读写效率。因此,mmap也称为“零拷贝”(zero )技术。
caddr_t *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
fd为文件描述符,一般由open返回。fd也可指定为-1,并指定flags参数中的MAP_ANON,表示匿名映射。
length指映射的字节数,从offset开始计算;
prot指定访问权限;
start指定文件被映射到用户空间的起始地址,一般设为NULL,由内核指定改地址;
函数返回值为映射到用户空间的地址。
1
2
3
4
5
1
2
3
4
5
mmap过程
1、在虚拟内存中查找一块VMA
2、将这块VMA进行映射
3、如果设备驱动程序或文件系统的file_operation定义了mmap接口,则调用它;
4、将VMA插入进程的VMA链表中
1
2
3
4
1
2
3
4
进程在映射空间的对共享内容的修改不会实时同步写回到磁盘文件中,只有调用munmap()函数释放映射后才会执行同步操作。mmap机制提供msync()函数,用于手动同步修改内容到磁盘源文件。
linux内核使用vm_area_struct结构来表示一个独立的虚拟内存区域,由于每个不同质的虚拟内存区域功能和内部机制都不同,因此一个进程使用多个vm_area_struct结构来分别表示不同滚老类型的虚拟内存区域。各个vm_area_struct结构使用链表或者树形结构链接,方便进程快速访问,如下图所示:
在这里插入图片描述
vm_area_struct结构中包含区域起始和终止地址以及其他相关信息,同时也包含一个vm_ops指针,其内部可引出所有针对这个区域可以使用的系统调用函数。这样,进程对某一虚拟内存区域的任何操作需要用要的信息,都可以从vm_area_struct中获得。mmap函数就是要创建一个新的vm_area_struct结构,并将其与文件的物理磁盘地址相连。
三、devmem原理
“/dev/mem”设备
“/大念升dev/mem”是linux系统的一个虚拟字符设备,无论是标准linux系统还是嵌入式linux系统,都支持该设备。
“/dev/mem”设备是内核所有物理地址空间的全映像,这些地址包括:
物理内存(RAM)空间
物理存储(ROM)空间
cpu总线地址
cpu寄存器地址
外设寄存器地址,GPIO、定时器、ADC
1
2
3
4
5
1
2
3
4
5
“/dev/mem”设备通常与“mmap”结合使用,可将指定内存映射到用户空间。
类似的还有/dev/kmem设备,kernel看到的虚拟内存的全镜像。可以用来访问kernel的内容。
devmem命令原理
应用程序通过mmap函数实现对/dev/mem驱动中mmap方法的使用,映射了设备的内存到用户空间,实现对这些物理地址的读写操作。
类似的有devkmem命令,通过mmap函数实现对/dev/kmem驱动中mmap方法的使用,映射了设备的内核空间到用户空间,实现对这些物理地址的读写操作。
四、malloc原理
malloc的工作原理
可执行文件加载到内存中的时候,就给栈和堆划分了固定大小的空间。使用vm_area_struct结构体指明了一个连续区域的头地址和尾地址。
malloc函数分配内存主要是使用brk和mmap系统调用
brk(): 小于128k
在堆段分配malloc的内存,将堆顶的指针brk往上推;
mmap(): 大于128k
是在堆和栈之间(文件映射区域)找分配一块空闲的虚拟内存,
1
2
3
4
1
2
3
4
malloc系统调用后,并没有实际分配物理内存。
这时候读虚拟内存地址,返回值是0;
第一次写的时候,发生缺页中断,才会实际分配物理内存,建立虚拟内存与物理内存的映射关系。
缺页中断
malloc的空间没有实际分配的情况下,在写的时候会报缺页中断。实际上:
进程线性地址空间里的页面不必常驻内存,在执行一条指令时,如果发现他要访问的页没有在内存中(即存在位为0),那么停止该指令的执行,并产生一个页不存在的异常,对应的故障处理程序可通过从外存加载该页的方法来排除故障,之后,原先引起的异常的指令就可以继续执行,而不再产生异常。
当一个进程发生缺页中断的时候,进程会陷入内核态,执行以下操作:
1、检查要访问的虚拟地址是否合法
2、查找/分配一个物理页
3、填充物理页内容(读取磁盘,或者直接置0,或者啥也不干)
4、建立映射关系(虚拟地址到物理地址)
1
2
3
4
1
2
3
4
重新执行发生缺页中断的那条指令
如果第3步,需要读取磁盘,那么这次缺页中断就是majflt,否则就是minflt。
如何查看进程发生缺页中断的次数:
ps -o majflt,minflt -C program
majflt代表major fault,中文名叫大错误,minflt代表minor fault,中文名叫小错误。
这两个数值表示一个进程自启动以来所发生的缺页中断的次数。
malloc的free
前面知道,通过移动brk申请的内存,存放在进程的堆区域中。
free是由运行库实现,它只是在已分配的堆块前面加一个可用标志,并不实际释放内存,不论是物理内存还是进程的堆空间。
在下次的malloc时,这块空间可能被重用。
如果进程的堆空间出现较多的碎片(这是逻辑地址中的碎片),运行库的堆管理例程会移动/合并碎片,此时可能会出现物理内存的释放/重新分配。
而对于brk指针,只有它指向的那片内存被free的时候才会下移。比如先malloc了一个A,然后malloc了一个B。free掉A之后,brk是不会下移的;free掉B的时候brk才会下移。