linux 内存管理 (四) 内存与IO的交互

2023-10-26

该文章参考宋宝华老师的内存管理课程,详细可以去听阅码场宋老师的课程。

●  内存与I/O的交换

●  page cache
●  free命令的详细解释
●  read、write和mmap
●  file-backed的页面和匿名页
●  swap以及zRAM
●  页面回收和LRU

1、page cache 的概念

CPU 读取 IO , MEM 在功能上其实是充当了一个 cache的角色,会很快。

 read write 一个文件的交互过程如下:

第一次读,内存中没有内容,内核会给其申请一页,然后从disk中,读取1页内容到内存中的这1页(伴随IO),然后再返回到用户态的程序。注意,不管你app读几个字节,都会从磁盘中读取至少1页。

当你第二次读,就会很快。因为没有IO的过程。注意:当你在用户层读写一个文件的时候,虽然你只读了10个字节,但是在Linux里面,它是按页来读的,读到page cache , 只不过在给 用户空间 返回的时候是按照你请求的数据来返回的。

第一次写,内存中没有内容,内核会给其申请一页,然后从disk中,读取1页内容到内存中的这1页(伴随IO),然后再将内容写到page cache中。注意,并没有直接写到disk 中(除非是direct io),至于什么时候写回,就是内核 dirty data 管理的机制了。

注意,第一次写,其实伴随着一次读的过程。

总之,page cache 不命中,就会从disk中读取内容到 page cache,这个IO过程是比较慢的

当我们拷贝一个文件 到 disk 中,其实并没有马上写到 disk ,所以我们才会进行  umount , sync(把 dirty data写回) 等等这些操作。

在 Linux 中读写文件,一般有两种方法,read write 这种方法伴随着有 user_buf  《------》 page cache 内存拷贝的过程。

还有一种 内存映射的方法来做,这样就没有内存拷贝的过程了。

 其实就是一段VMA。

 

#include <sys/mman.h>
#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>

int main(int argc, char **argv)
{
	char *m;
	int i;

	int fd = open("./hello.py", O_RDWR, S_IRUSR);
	if (fd == -1) {
		perror("hello.py failed\n");
		exit(-1);
	};

#define MAP_SIZE 50
	m = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE,
			MAP_SHARED, fd, 0);

	if (m == MAP_FAILED) {
		perror("mmap /dev/sda failed\n");
		exit(-1);
	}
	printf("%s\n", m);

	*m='a';
	*(m+1)='b';
	m[2]='c';
	printf("%s\n", m);

	munmap(m, MAP_SIZE);
	close(fd);
}

 

 这就是 Linux 中 修改文件的另一种方法,其实,对用户空间而言,文件就想一种内存,因为所有的操作都是一种内存中的操作,操作的是page cache ,  read / write /mmap 都是操作的page cache。

mmap的好处就是 少了1次内存拷贝。但是有很多很多的文件,都不支持 mmap, 比如串口设备文件 ,键盘等等,只有能够变成内存的东西才可以mmap。

这就能让我们理解了,我们在读写完U盘的时候,不能直接拔掉,而是要先执行 sync ,再umount拔掉,其实就是把 dirty data 写回的过程。

2、free 命令解析

 假设电脑有 2G 的内存,那这 2G 内存是怎么被瓜分的呢?

free 命令(早期的free命令)

第1行是从 buddy 级别来看的,因为buddy 不关心你的用户是谁,只要用了就是占用了。used 是系统中所有被使用的内存,free  是 系统中所有空闲的内存 , total = used + free 

第2行是从物理内存条的角度来看,因为 page cache是可以回收的,可以踢出去的。buffers 和 cached 都是 page cache ,但是由于 page cache 是可以回收的,所以第2行的内容 还会有一个 used  和 free 。 这里的 free 是把 buffers 和 cached 加上了。

buffers 和 cached 的区别是什么呢?

这里解释一下,EXT4 文件系统在实际访问 disk 中的文件时,会访问到 metadata , 就是指向具体数据位置的指针,这时候产生的page cache

也是计算到 buffers的。

 buffers 和 cached 在Linux 中是没有本质区别的,只是两种不同的统计方式。这两种都是 page cache ,访问裸分区也会有page cache的。

所以目前,新的free 命令 就不区分 buffers和cached了,注意这里的 available <  free + buff/cache , 这是因为什么的?

我们还是看看 meminfo吧 

 

 查看 Documentation/filesystems/proc.txt  可以看到 Memavailable 是一个估计值,并不是百分百等于的。Memavailable ≈ MemFree + buffers + cached ,当然也会和 SReclaimable 和  有关系

这里举一个例子,当访问了一段裸硬盘之后,free 命令的第一行 变化会比较明显,而第二行不明显,因为第二行是排除了 buffers 和 cache 的数据。

代码段的本质:其实就是page cache ,将代码段 mmap 到内存,然后 PC指针 运行。这和 read  mmap 一样的,都是从disk 中读取代码段到 page cache, 然后执行的。

这个过程当然也是 demanding page , 执行到哪里,读到到哪里的过程。所以代码段在内存当中的副本,就是一个page_cache ,也就是可回收的 reclaimable

3、Linux 中 页面的两种类型

一种是有文件背景的,file-backed page , 这种是 可回收的。比如 代码段,比如mmap 一个文件。

另外一种是匿名页。映射的是进程的虚拟地址空间,并没有映射任何文件。比如 堆、栈、写实拷贝的页。这种是不可回收的。匿名页就要 常驻内存。

匿名页就要 常驻内存。但是当内存不足的时候,就会有一个矛盾。这个就是 匿名页 和 swap分区。

比如电脑就有 512M的内存,word 程序有400M匿名页, qq 有200M的匿名页,因为匿名页要常驻内存啊(堆、栈的数据不能丢啊),那512M内存 能同时跑起来 word  qq 吗?

Linux 就为 匿名页 伪造了一种文件背景 ,swap 分区。

swap 交换,作为动词,包括 文件背景页面的交换, 匿名页的交换。和 CONFIG_SWAP无关。

swap做名词的时候,通常将的是 匿名页的交换分区。只有使能 CONFIG_SWAP才会交换匿名页。

 Linux 也支持一个文件来做交换,而不是非要一个分区

4、LRU  最近最少使用算法

所有的这些页面替换算法,用的都是LRU算法。

  • CPU 内部的cache 和 内存直接的页面替换
  • 内存和硬盘之间的页面的替换
  • 交换分区,匿名页的替换

就是通过局部性原理决定的,你最近访问的东西,就是你马上要访问的东西 ,

你最近访问的东西周围的东西,就是你马上要访问的东西。

一个是时间局部性原理,一个是空间局部性原理。

你昨天再看某一部电视,看的破冰行动第1集,那你今天看破冰行动第2集的可能性就比较大。这是局部性原理

你的书架上有一本 Linux 内核的树,5年前就买了,但是从来都没看过,内心一直催促着今天一定要看,但是对不起,你直接把这本书扔掉,这不符合局部性原理。

局部性原理就是你最近最不常使用的,就是你未来最不常使用的。你过去活跃的,就是你将来活跃的。

当别人问道你,cache 替换算法,就是一个高速的东西和一个慢速的东西,替换数据的时候的算法,一定要在1秒之内回答他,LRU算法。

上面的这个图,访问页面的顺序 1、2、3、4 ,访问到4的时候,显然1是最不活跃的。接着继续访问 1、2 、5,当访问到5的时候,3显然是最不活跃的,

所以把3给替换了。这就是 最近最少使用算法LRU。

内存回收使用的门限。之后再讲。

# meld  file1   file2  可以在 Linux下 对比两个文件的改动,比diff好用。

 如果系统开启了 swap 匿名页的交换,系统就会没日没夜的去交换。

对于嵌入式系统而言,很少用到 swap 交换匿名页。主要因为两个原因

1、嵌入式系统的存储介质,读写速度比较慢。

2、flash 都是有读写次数,寿命的。

所以一般都不去使能swap 。

后来,有人在 Linux 里面加入了一个非常天才的设计,叫做 zRAM。它的存在,使得嵌入式系统也可以使用 swap 分区来交换匿名页了。

swap 就非常像虚拟内存,可以达到增大你的内存的效果。

5、天才想法 zRAM (CPU算力换取内存)

为什么说 zRAM 的想法很天才呢,这是一种 将CPU的算力 换取 内存 的一种想法,能不牛逼吗?

当你交换匿名页的时候,CPU会将匿名页的数据,透明的压缩,然后存到着预留的100M 空间中,交换回来的时候,再透明的解压。

虽然预留出了100M,但是并不等于系统的内存减少了100M,可能还会多于800M。

所在这种 zRAM在 嵌入式系统 已经非常常用了。配置起来也比较简单。

swapon 是使能交换分区,swapoff -a 是关掉所有的交换匿名页,是关不掉文件背景的交换的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

linux 内存管理 (四) 内存与IO的交互 的相关文章

  • 虚拟地址,虚拟地址空间, 交换分区

    1 虚拟内存是内存管理的一种方式 它在磁盘上划分出一块空间由操作系统管理 当物理内存耗尽是充当物理内存来使用 它将多个物理内存碎片和部分磁盘空间重定义为连续的地址空间 以此让程序认为自己拥有连续可用的内存 当物理内存不足时 操作系统会将处于
  • 一张图深度解析Linux共享内存的内核实现

    一张图深度解析Linux共享内存的内核实现 Sailor forever sailing 9806 163 com http blog csdn net sailor 8318 article details 39484747 PDF版本下
  • 将内存映射到文件描述符的系统调用(逆映射)?

    我希望能够将内存映射到文件描述符 以便我可以使用一些需要文件描述符的现有函数 这基本上就是我正在寻找的 void do operation1 int fd char data DATA MAX embedded binary data in
  • 通过使用 mmap() 在进程之间共享内存

    我使用的是Linux 2 6 我有一个环境 其中两个进程通过消息传递模式的简单实现来模拟 使用共享内存 数据交换 我有一个客户端进程 从父进程 即服务器 分叉 它将结构 消息 写入创建的内存映射区域 在分叉之后 message m mmap
  • 如何配置 Mongodb 的缓存大小?

    我需要在 Mongodb 中处理缓存 这样做需要大量 RAM 吗 在这种情况下使用存储引擎有什么特点和优势 方法 重启后无效 bin mongo 管理员 db adminCommand setParameter 1 wiredTigerEn
  • 使用 MALLOC_MMAP_THRESHOLD_ 和 MALLOC_MMAP_MAX_ 减少内存碎片

    我一直在尝试使用 MALLOC MMAP THRESHOLD 和 MALLOC MMAP MAX env 变量来影响长时间运行的 Python 2 进程中的内存管理 看http man7 org linux man pages man3 m
  • 如何获取大页面大小的值?

    我希望直接从我的 C 代码中获取大页面大小的值 而无需运行 bash 命令 从 bash 我可以做到这一点 grep pse proc cpuinfo gt dev null echo 2M huge page size are suppo
  • 为什么 Python 的 mmap 不能处理大文件?

    编辑 此问题仅适用于 32 位系统 如果您的计算机 操作系统和 python 实现都是 64 位的 那么 mmap ing 大文件可以可靠地工作并且非常高效 我正在编写一个模块 除其他外 它允许对文件进行按位读取访问 这些文件可能很大 数百
  • Linux:如何检查进程可用的最大连续地址范围

    我想在命令行输入pid 取回未被保留的最大连续地址空间 有任何想法吗 我们的 32 位应用程序在 64 位 RHEL 5 4 上运行 运行一段时间后 比如 24 小时 就会崩溃 当时仅使用了 2 5 GB 的内存 但我们遇到了内存不足的错误
  • Java map / nio / NFS 问题导致虚拟机故障:“编译的 Java 代码中最近的不安全内存访问操作发生故障”

    我已经为特定的二进制格式编写了一个解析器类 nfdump http nfdump sf net 如果有人感兴趣 它使用 java nio映射字节缓冲区 http java sun com j2se 1 4 2 docs api java n
  • 如何在Java中内存映射(mmap)Linux块设备(例如/dev/sdb)?

    我可以使用 Java 读取 写入 Linux 块设备java nio 以下代码有效 Path fp FileSystems getDefault getPath dev sdb FileChannel fc null try fc File
  • 通过 nfs 共享 mmap 文件?

    场景A 为了在同一主机上运行的两个进程之间共享读 写内存块 Joe 从两个进程映射同一个本地文件 场景B 为了在两个不同主机上运行的两个进程之间共享读 写内存块 Joe 在主机之间通过 nfs 共享一个文件 然后从两个进程映射共享文件 有人
  • mmap 标志 MAP_UNINITIALIZED 未定义

    mmap 文档提到了标志 MAP UNINITIALIZED 但该标志似乎没有定义 在 Centos7 和 Xenial 上尝试过 两个发行版都没有定义该标志sys mman h正如所指控的那样 令人惊讶的是 互联网似乎并没有意识到这一点
  • 我可以要求内核填充(故障)一系列匿名页面吗?

    在Linux中 使用C 如果我通过以下方式请求大量内存malloc或类似的动态分配机制 很可能支持返回区域的大多数页面实际上不会映射到我的进程的地址空间 相反 每次我第一次访问其中一个分配的页面时都会发生页面错误 然后内核将映射到 匿名 页
  • 内存映射文件和单个块的原子写入

    如果我使用普通 IO API 读取和写入单个文件 则保证每个块的写入都是原子的 也就是说 如果我的写入仅修改单个块 则操作系统保证要么写入整个块 要么什么也不写入 如何在内存映射文件上达到相同的效果 内存映射文件只是字节数组 因此如果我修改
  • 为什么 POSIX mmap 不返回 volatile void*?

    mmap 返回 void 但不是volatile void 如果我使用 mmap 来映射共享内存 那么另一个进程可能正在写入该内存 这意味着从同一内存位置进行的两次后续读取可能会产生不同的值 这正是 volatile 所针对的情况 那么为什
  • mmap() 返回 EINVAL

    我无法获取mmap功能来工作 它返回EINVAL错误代码 void mapped mmap void map addr slide map size PROT WRITE PROT READ MAP PRIVATE MAP ANON bpr
  • Linux进程间共享内存

    我有使用多个进程的服务器 fork 有大量数据可以由一个进程创建 并且应该在其他进程之间共享 因此 我使用 shm open mmap 创建共享内存并将其映射到虚拟内存 struct SharedData const char name i
  • mmap() 整个大文件

    我正在尝试使用以下代码 test c mmap 二进制文件 8Gb include
  • mmap() 和锁定文件

    考虑以下代码片段 故意缺少错误处理 void foo const char path off t size int fd void ret fd open path O RDWR lockf fd F LOCK 0 ret mmap NUL

随机推荐