Linux 内存管理二

本文深入探讨了Linux内存管理,包括32位系统中每个进程4GB的虚拟内存与共享物理内存的映射关系,以及ARM处理器的MMU如何将虚拟地址转换为物理地址。讲解了内存映射、页表的工作原理,特别是页表在内存管理和MMU中的角色。还介绍了cache和buffer在提高数据访问效率中的作用,并详细阐述了mmap系统调用在文件映射到内存中的应用,对比了mmap与普通读写操作的区别和优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

物理内存和虚拟内存

  在 32位的 Linux 内核中,每个进程都独有 4GB 的虚拟内存空间,但所有进程却共用相同的物理内存空间。物理内存空间就是安装在电脑上的内存条,如果内存条只有 1GB ,那么物理内存空间就只有 1GB 。但虚拟内存空间是逻辑上的内存空间,虚拟内存空间必须映射到物理内存空间才能使用。
  虚拟内存空间与物理内存空间映射关系如下:
在这里插入图片描述
  P1 是进程1, P2 是进程2。进程1的虚拟内存页A映射到物理内存页A,进程2的虚拟内存页A映射到物理内存页B。进程1的虚拟内存页B和进程2的虚拟内存页B同时映射到物理内存页C,也就是说进程1和进程2共享了物理内存页C。

内存管理硬件结构

在这里插入图片描述
  ARM处理器访问的不是存储器的物理地址,而是经过MMU转换后的虚拟地址,系统在启动时MMU会自动将DDR或者片内的SRAM的起始地址处转化为CPU的虚拟地址0处。即CPU按照自己的想法进行虚拟地址的访问,MMU会自动将其转化为物理地址。

MMU和内存映射

  在ARM Linux中,内存相关属性的描述在DTS文件中,例如:

memory@0000{
	device_type = "memory";
	reg = <0x6000000 0x4000000>;
}

  在内核启动过程中会解析DTB文件的内存节点,调用过程为:

start_kernel()
	-->setup_arch(&command_line);
		-->setup_machine_fdt(__atags_pointer);
			-->setup_machine_fdt()
				-->early_init_dt_scan_nodes()
					-->early_init_dt_scan_memory()

编不下去了,这代码我根本没看过
  对于用于MMU的CPU而言,CPU在任何时候发出的都是虚拟地址,MMU通过查询页表将虚拟地址转化为物理地址,从而访问DDR。MMU里面存放了页表寄存器,用来记录页表本身存放的地方。
  linux内核的内存空间分为了内核空间和用户空间,页表也分为内核页表和进程页表,内核页表在开启分页前就被linux内核创立了,因为开启分页后所有的内存访问都必须经过MMU。进程页表则是在进程被创建时创里,每一个进程都有独立的页表,进程描述符保存进程页表的物理地址。内核在进程切换的时候会从进程描述符中获得找到对应的页表地址。如果通过系统调用、异常和中的进入内核态,则会切换到内核页表

页表

页表是存放在物理地址的
  MMU会根据CPU发来的虚拟地址去内部的TLB(快表,可以理解为和内存的cache类似)中寻找页表,如果找不到则通过页表寄存器存放的页表基地址去寻找页表项,找到后加载到TLB中。ARM架构支持一级和二级页表。
在这里插入图片描述
  页表项就是一个32位的数据,页表项的bit0和bit1表示他是一级还是二级页表。一级页表可以映射1M的内存空间,二级页表可以映射4K的内存空间。对于对于32位CPU发出的虚拟地址,MMU会根据bit20~bit31找到页表项,例如0x12345678,MMU会先找到页表的0x123项,如果bit1和bit0标识他为一级页表项,则他会根据页表项取出物理基地址(section base address),例如改基地址炜0x8000000,则能计算得出访问的物理地址炜基地址加上偏移地址:0x8000000+0x45678 = 0x80045678。同样的二级页表会根据bit12-bit19找到二级页表项。页表项中还有一些标识位,例如是否使用cache、buffer等。

cache及buffer(写缓冲器)

在这里插入图片描述
  cache相当于高速内存,用来解决高速CPU和低俗内存之间的问题,chche主要用来写。例如程序中创建了一个局部变量a,则相同的时间内可能会多次访问,使用cache可以提高访问效率。
写缓冲区主要用来都,可以将多个写操作集合起来一次写入内存。
读内存数据:先看看cache是否有,如果则直接从cache返回数据,如果没有则从内存读入cache。
写数据:可以直接写入内存,但是这样太慢了,也可以写入cache,cache在合适的时候回写到内存。数据写入到cache后有两种策略

  1. 写通:使用写缓冲器(buffer),cache将写数据给buffer,buffer在合适的时候写回内存,buffer会将多个写的操作合并为一个,所以实际数据写回的顺序可能和程序不一致。对于内存来说差别不大。但是如果是硬件寄存器就会有问题,所以硬件的寄存器不会使用buffer功能
  2. 写回:cpu将数据写回cache,此时没有立即写回内存,所以内存和cache的数据是不一致的。当数据写回到cache时,cache的这一行数据被标记为”脏“,cache满时再将脏数据写回到内存,这样大批量的写回数据能提高效率,不过cache和内存数据不一致,也可能会有问题。例如比如CPU产生了新数据,DMA把数据从内存搬到网卡,这时候就要CPU执行命令先把新数据从cache刷到内存。反过来也是一样的,DMA从网卡得过了新数据存在内存里,CPU读数据之前先把cache中的数据丢弃。

  所以在建立页表项时,如果基地址是硬件寄存器的话,则不要使用cache和buffer。显存很少有读操作,基本上都是写,用cache偶尔读的话会占cache空间,使用buffer能加速写的效率,普通内存则既能使用cache也能使用buffer。

mmap系统调用介绍

  mmap() 系统调用能够将文件映射到内存空间,然后可以通过读写内存来读写文件。我们先来看看 mmap()系统调用的用法吧, mmap() 函数的原型如下:

void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

参数说明:

  1. start :指定要映射的内存地址,一般设置为 NULL 让操作系统自动选择合适的内存地址。
  2. length :映射地址空间的字节数,它从被映射文件开头 offset 个字节开始算起。
  3. prot :指定共享内存的访问权限。可取如下几个值的或: PROT_READ (可读), PROT_WRITE (可写), PROT_EXEC (可执行), PROT_NONE (不可访问)。
  4. flags :由以下几个常值指定: MAP_SHARED (共享的) MAP_PRIVATE (私有的),MAP_FIXED (表示必须使用 start 参数作为开始地址,如果失败不进行修正),其中,MAP_SHARED , MAP_PRIVATE 必选其一,而 MAP_FIXED 则不推荐使用。
  5. fd :表示要映射的文件句柄。
  6. offset :表示映射文件的偏移量,一般设置为 0 表示从文件头部开始映射。函数的返回值为最后文件映射到进程空间的地址,进程可直接操作起始地址为该值的有效地址。

调用示例:

int main() {
	...
	fd = open(name, flag, mode);
	if (fd < 0) {
		// error process...
		exit(1);
	}
	addr = mmap(NULL, len, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED, fd, 0);
	if (addr < 0) {
		// error process...
		exit(1);
	}
	memset(addr, 0, len);
	...
	exit(0);
}

  上面的例子首先通过 open() 系统调用打开一个文件,然后通过调用 mmap() 把文件映射到内存空间,映射成功后就可以通过操作函数返回的内存地址来对文件进行读写操作。

mmap和普通读写

  普通读写需要先将磁盘数据读入到页缓存,页缓存在内核空间,然后用户空间在和内核空间交换数据。mmap是建立页缓存内存的页表项,相当于,建立页缓存的实际物理地址和虚拟地址的关系,直接将数据写入到页缓存,页缓存自动回写脏页到磁盘。减少了一次用户空间到内核空间的数据拷贝。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值