2025-01-25 备注:后来在博文 http://iyenn.com/rec/1709550.html 中,完整实现了一次函数mmap,包含其在驱动程序中的操作函数,所以看这篇博文前务必看下博文 http://iyenn.com/rec/1709550.html,看完之后你再来看这篇博文就很清晰了。
2025-01-25 备注:后来在博文 http://iyenn.com/rec/1709550.html 中,完整实现了一次函数mmap,包含其在驱动程序中的操作函数,所以看这篇博文前务必看下博文 http://iyenn.com/rec/1709550.html,看完之后你再来看这篇博文就很清晰了。
2025-01-25 备注:后来在博文 http://iyenn.com/rec/1709550.html 中,完整实现了一次函数mmap,包含其在驱动程序中的操作函数,所以看这篇博文前务必看下博文 http://iyenn.com/rec/1709550.html,看完之后你再来看这篇博文就很清晰了。
在博文“IMX6ULL开发板基础实验:Framebuffer驱动程序的简单应用实例”中,有一句与内存映射有关的代码,是整个实验中比较关键的代码,代码如下:
fb_base = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
- 1
这篇博文此这句代码为切入口,了解Linux开发中存映射函数mmap()的使用。
内存映射代码详解
这行代码:
fb_base = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
- 1
是用于映射Framebuffer的内存到用户空间的一行关键代码。以下是它的详细解释:
mmap
函数:
mmap
是一个系统调用,用于将一个文件或设备的内容映射到进程的虚拟内存空间。通过这个映射,程序可以像访问内存一样访问文件内容,而不需要进行显式的读写操作。
参数详解:
-
第一个参数
NULL
:
这个参数表示我们不关心映射区域的起始地址,因此传入NULL
,系统会自动为我们选择一个合适的地址。 -
第二个参数
screen_size
:
这是映射的字节数,也就是你想要映射的区域的大小。它等于屏幕的总字节数(宽度 * 高度 * 每个像素的字节数)。在代码中,这个值由var.xres * var.yres * var.bits_per_pixel / 8
计算得到。 -
第三个参数
PROT_READ | PROT_WRITE
:
这指定了映射区域的权限,PROT_READ
表示可以读取映射区域,PROT_WRITE
表示可以写入映射区域。此时,我们需要同时读和写Framebuffer内容。 -
第四个参数
MAP_SHARED
:
这个参数表示我们希望将映射区域标记为共享模式。共享模式意味着如果映射区域的内容发生了变化,这些变化会反映到原始的文件或设备(这里是/dev/fb0
,即Framebuffer设备)上。它允许用户程序直接修改Framebuffer的内容,同时显示在屏幕上。 -
第五个参数
fd_fb
:
这是要映射的文件或设备的文件描述符,表示要映射的目标文件是/dev/fb0
(Framebuffer设备)。在程序中,fd_fb
是通过open("/dev/fb0", O_RDWR)
打开的文件描述符。 -
第六个参数
0
:
这是偏移量,表示从文件的哪个位置开始映射。通常在设备文件映射时,偏移量为0
。
函数返回值:
mmap
返回的是映射区域的起始地址。它将返回一个指向该区域的指针(此处是 fb_base
)。如果映射失败,它会返回 (void *)-1
,在这种情况下你需要检查错误并做出处理。
代码的作用:
这行代码的作用是将 /dev/fb0
设备中的物理内存区域映射到用户空间,当然光有函数mmap还不行,驱动程序中还得有相应的操作函数去具体实现这种映射,详见 http://iyenn.com/rec/1709550.html。
通过这种方式,你可以直接访问Framebuffer的内存,并对其进行操作(例如清屏、绘制像素等),而不需要显式的读写操作。接下来,你可以通过 fb_base
指针来访问和修改Framebuffer内容。
通过 mmap
映射,程序可以直接读取和修改屏幕的像素数据,这在嵌入式图形编程中非常常见。
什么叫对设备的显式和隐式读写操作?
显式的读写操作指的是通过标准的读写函数(如 read()
和 write()
)来显式地进行数据的读取和写入操作。
与隐式操作的对比:
-
显式读写操作:程序明确地使用
read()
或write()
函数从文件或设备中读取数据,或者将数据写入文件或设备。例如,使用read(fd, buffer, size)
从文件描述符fd
中读取size
字节的数据到buffer
中,或使用write(fd, buffer, size)
将buffer
中的数据写入到文件描述符fd
对应的设备或文件。 -
隐式操作:隐式操作通常指的是不需要显式调用读取和写入函数的操作。通过内存映射(如
mmap
)实现内存区域和文件或设备的直接关联后,程序可以通过直接操作内存来访问文件或设备的内容。你在内存中修改数据时,实际上这些操作会直接影响映射到该内存区域的文件或设备。因此,数据读写的过程变得"隐式"——你不需要明确调用read()
或write()
,而是直接在内存中修改数据。
显式读写操作的例子:
int fd = open("/dev/fb0", O_RDWR); // 打开文件描述符
char buffer[100];
int n = read(fd, buffer, sizeof(buffer)); // 显式读取数据到 buffer
write(fd, buffer, n); // 显式写入数据到设备
- 1
- 2
- 3
- 4
隐式读写操作的例子:
当你使用 mmap
映射文件或设备到内存时,可以直接通过指针访问内存。例如:
unsigned char *fb_base = (unsigned char *)mmap(NULL, screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
// 直接在内存中修改数据
fb_base[0] = 0xFF; // 修改内存的第一个字节
- 1
- 2
- 3
在上述 mmap
示例中,虽然我们没有使用 read()
或 write()
,但是通过修改 fb_base
内存指针中的数据,实际上也在修改映射的文件或设备的内容。这种方式使得程序与底层设备的交互变得更加高效,因为直接操作内存比使用传统的 read()
和 write()
函数更为快速。
总结:
- 显式读写操作 是通过专门的系统调用(如
read()
和write()
)进行的,需要手动指定读取和写入的目标位置和数据。 - 隐式操作 是通过内存映射(如
mmap
)实现的,程序直接通过内存指针访问和修改文件或设备的内容,底层操作会自动同步。
映射之后是不是首先内存中的数据得到修改,然后这样修改后的数据再由系统同步到设备中?
是的,你的理解是正确的。通过内存映射(如 mmap
)后,程序对内存的修改会首先发生在程序的虚拟内存中,而这些修改会在适当的时候同步到映射的设备或文件中。
具体过程:
-
内存映射:
通过mmap
,你将一个文件或设备(比如/dev/fb0
,即 framebuffer)映射到程序的虚拟内存中。在这时,操作系统创建了一个内存区域,这个区域直接映射到文件或设备的内容。你可以像操作普通内存一样读取和修改这个内存区域的数据。 -
内存中的数据修改:
当你通过指针(例如fb_base
)修改内存中的数据时,这些修改是直接作用于映射到内存的虚拟地址的。这些修改是程序内部的操作,尚未直接写入到设备或文件。 -
数据同步:
- 延迟同步: 操作系统会在适当的时候自动将内存中的修改同步回原始文件或设备(即 framebuffer)。这种同步通常是延迟的,意味着操作系统可能不会立刻把内存中的改动写入到设备中。具体的同步时机取决于操作系统的策略。
- 同步机制: 操作系统使用的同步机制可能是懒惰写入(lazy writing)或者在进程退出时自动同步。你也可以使用
msync()
系统调用手动请求将内存中的修改立即同步到设备或文件。
比如:
msync(fb_base, screen_size, MS_SYNC); // 将内存中的修改同步到设备
- 1
-
设备驱动层的处理:
当内存被修改后,设备驱动(如 framebuffer 驱动)会定期或按需检查内存区域,并将内存中的变化反映到设备屏幕上。这是由操作系统的内存管理和设备驱动共同完成的。
关键点:
- 内存修改优先: 程序首先会在内存中修改数据。
- 自动同步或手动同步: 修改会在系统管理下同步到设备中。你可以依赖操作系统自动同步,也可以使用
msync
来手动触发同步。 - 屏幕更新: 对于像 framebuffer 这样的设备,修改通常会在内存中进行,而屏幕显示更新则由设备驱动负责,通过周期性检查或其他机制将内存的变化呈现出来。
总结:
内存映射后,程序可以直接修改内存中的数据,这些修改通常会在合适的时机由操作系统同步回设备(如屏幕)。因此,内存中的数据在被修改后,并不立即直接反映到设备上,而是通过同步机制在适当的时候进行更新。
什么叫程序的虚拟内存
虚拟内存 是现代操作系统用来管理内存的一种技术,它允许程序拥有一个独立的、连续的内存地址空间,甚至比实际的物理内存要大。简单来说,虚拟内存是操作系统提供给程序的“假象内存”,它通过映射和管理物理内存(RAM)和硬盘存储设备的存储空间,让每个程序都感觉自己有一个单独的、大容量的内存区域。
虚拟内存的关键概念:
-
虚拟地址空间:
每个程序在运行时,操作系统为其分配一个独立的虚拟地址空间。虚拟地址空间是程序能够访问的内存地址的集合,程序只能看到和操作这些虚拟地址。操作系统会将虚拟地址映射到实际的物理内存地址。 -
物理内存与虚拟内存的映射:
虚拟内存的地址与实际物理内存的地址并不直接对应。操作系统通过一个叫做**页表(Page Table)**的数据结构,管理虚拟地址和物理地址之间的映射关系。这样,程序在访问虚拟内存时,操作系统会将虚拟地址转换为物理内存的地址。即使物理内存不连续,虚拟内存对程序来说看起来仍然是连续的。 -
内存分页:
操作系统通常使用**分页(Paging)**机制将虚拟内存分割成大小固定的块(通常为4KB或8KB),称为“页面(Page)”。这些虚拟页面与物理内存中的物理页面进行映射。操作系统会管理这些映射,使得程序访问虚拟内存时,可以从物理内存中获取相应的内容。 -
内存交换(Swapping):
如果物理内存不足,操作系统可以将部分虚拟内存内容交换到硬盘上的交换空间(Swap Space)中,以释放物理内存。当程序需要这些数据时,操作系统会将它们从硬盘交换回内存。这种方式允许程序使用的虚拟内存超过系统的物理内存。
虚拟内存的作用:
-
独立的地址空间: 每个程序拥有自己的虚拟内存空间,这样就避免了多个程序之间的内存干扰或冲突。例如,程序A无法访问程序B的内存区域,增加了系统的稳定性和安全性。
-
内存保护: 操作系统可以通过虚拟内存的机制来隔离不同程序的内存,避免程序对其他程序的内存进行非法访问。这是一种基本的安全措施,防止程序相互影响。
-
简化编程: 程序开发人员只需要关注虚拟内存的地址,而不需要关心实际的物理内存布局和分配。操作系统会负责管理和调度物理内存,程序像操作连续的内存一样使用虚拟内存。
-
内存共享与映射: 通过内存映射(如
mmap
),操作系统可以让多个进程共享一块虚拟内存区域(例如映射文件到内存)。多个程序可以同时访问同一块数据,同时操作系统确保数据的一致性和同步。
程序的虚拟内存
当我们说“程序的虚拟内存”,指的就是程序运行时所看到的内存空间。这个内存空间是虚拟的,它并不等同于物理内存,而是通过操作系统的虚拟内存管理机制进行管理的。虚拟内存为每个程序提供了一个独立的、隔离的内存空间,程序通过该虚拟内存地址进行操作,但在物理层面,这些虚拟地址会映射到实际的物理内存或硬盘存储上。
例子:
当一个程序使用 malloc
(或者其他内存分配函数)请求内存时,它实际上是从虚拟内存中请求内存地址。这个地址并不是实际的物理内存地址,而是由操作系统和硬件通过分页机制和页表映射到物理内存中的。当程序访问这些虚拟地址时,操作系统会将它们转换为物理内存中的真实位置。
总结:
程序的虚拟内存 是操作系统提供的一个地址空间,程序通过这个地址空间进行内存访问。它看起来是连续的和独立的,但实际上,操作系统通过虚拟内存管理机制将这些虚拟地址映射到实际的物理内存中。虚拟内存的使用让程序能够更方便、安全地运行,而不必直接与物理内存打交道。



评论记录:
回复评论: