OS - MMAP初探

简介: OS - MMAP初探

20210205010254918.png

生猛干货

计算机专业学生、非科班程序员必备! 90%程序员忽略的大厂招聘考点、技能进阶秘诀尽在这里!


What’s mmap

https://man7.org/linux/man-pages/man2/mmap.2.html



20210205010854190.png


简单来说: mmap() 系统调用能够将文件映射到内存空间,然后可以通过读写内存来读写文件


mmap 函数



20210205010920881.png


 #include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);


start:指定要映射的内存地址,一般设置为 NULL 让操作系统自动选择合适的内存地址。

length:映射地址空间的字节数,它从被映射文件开头 offset 个字节开始算起。

prot:指定共享内存的访问权限。可取如下几个值的可选:PROT_READ(可读), PROT_WRITE(可写), PROT_EXEC(可执行), PROT_NONE(不可访问)。

flags:由以下几个常值指定:MAP_SHARED(共享的) MAP_PRIVATE(私有的), - MAP_FIXED(表示必须使用 start 参数作为开始地址,如果失败不进行修正),其中,MAP_SHARED , MAP_PRIVATE必选其一,而 MAP_FIXED 则不推荐使用。

fd:表示要映射的文件句柄。

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);
}


20210205010942403.png



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


mmap() 的底层原理

虚拟内存空间与物理内存空间

我们先来看下 操作系统的虚拟内存空间物理内存空间的概念 。


2021020501110412.png


在 32位的 Linux 内核中,每个进程都独有 4GB 的虚拟内存空间,但所有进程却共用相同的物理内存空间。


物理内存空间就是安装在电脑上的内存条,如果内存条只有 1GB,那么物理内存空间就只有 1GB。但虚拟内存空间是逻辑上的内存空间,虚拟内存空间必须映射到物理内存空间才能使用。


虚拟内存空间与物理内存空间映射关系如下:



20210205011218497.png


映射是按内存页进行的,一个内存页为 4KB 大小。


在上图中,P1 是进程1,P2 是进程2。


进程1的虚拟内存页A映射到物理内存页A

进程2的虚拟内存页A映射到物理内存页B

进程1的虚拟内存页B和进程2的虚拟内存页B同时映射到物理内存页C,也就是说进程1和进程2共享了物理内存页C


vm_area_struct 结构


在Linux内核中,虚拟内存是用过结构体 vm_area_struct 来管理的,通过 vm_area_struct 结构体可以把虚拟内存划分为多个用途不相同的内存区,比如可以划分为数据段区、代码段区等等.

vm_area_struct

struct vm_area_struct {
struct mm_struct * vm_mm;   /* The address space we belong to. */
unsigned long vm_start;     /* Our start address within vm_mm. */
unsigned long vm_end;       /* The first byte after our end address
                       within vm_mm. */
/* linked list of VM areas per task, sorted by address */
struct vm_area_struct *vm_next;
pgprot_t vm_page_prot;      /* Access permissions of this VMA. */
unsigned long vm_flags;     /* Flags, listed below. */
rb_node_t vm_rb;
struct vm_area_struct *vm_next_share;
struct vm_area_struct **vm_pprev_share;
/* Function pointers to deal with this struct. */
struct vm_operations_struct * vm_ops;
...
struct file * vm_file;       /* File we map to (can be NULL). */
...
};


vm_area_struct 结构各个字段作用:


vm_mm:指向进程内存空间管理对象。

vm_start:内存区的开始地址。

vm_end:内存区的结束地址。

vm_next:用于连接进程的所有内存区。

vm_page_prot:指定内存区的访问权限。

vm_flags:内存区的一些标志。

vm_file:指向映射的文件对象。

vm_ops:内存区的一些操作函数。


vm_area_struct 结构与虚拟内存地址的关系如下图:


2021020501141927.png



每个进程都由 task_struct 结构进行管理,task_struct 结构中的 mm 成员指向了每个进程的内存管理结构 mm_struct,而 mm_struct 结构的 mmap 成员记录了进程虚拟内存空间各个内存区的 vm_area_struct 结构链表。


当调用 mmap() 时,内核会创建一个 vm_area_struct 结构,并且把 vm_start 和 vm_end 指向虚拟内存空间的某个内存区,并且把 vm_file 字段指向映射的文件对象。然后调用文件对象的 mmap 接口来对 vm_area_struct 结构的 vm_ops 成员进行初始化,如 ext2 文件系统的文件对象会调用 generic_file_mmap() 函数进行初始化,代码如下:

static struct vm_operations_struct generic_file_vm_ops = {
nopage:     filemap_nopage,
};
int generic_file_mmap(struct file * file, struct vm_area_struct * vma)
{
struct address_space *mapping = file->f_dentry->d_inode->i_mapping;
struct inode *inode = mapping->host;
if ((vma->vm_flags & VM_SHARED) && (vma->vm_flags & VM_MAYWRITE)) {
if (!mapping->a_ops->writepage)
return -EINVAL;
}
if (!mapping->a_ops->readpage)
return -ENOEXEC;
UPDATE_ATIME(inode);
vma->vm_ops = &generic_file_vm_ops;
return 0;
}


vm_operations_struct 结构的 nopage 接口会在访问内存发生异常时被调用,上面指向的是 filemap_nopage() 函数,filemap_nopage() 函数的主要工作是:


  1. 把映射到虚拟内存区的文件内容读入到物理内存页中。
  2. 对访问发生异常的虚拟内存地址与物理内存地址进行映射。


处理过程如下

20210205011536942.png


如上图所示,虚拟内存页m 映射到 物理内存页x,并且把映射的文件的内容读入到物理内存中,这样就把内存与文件的映射关系建立起来,对映射的内存区进行读写操作实际上就是对文件的读写操作。


一般来说,对映射的内存空间进行读写并不会实时写入到文件中,所以要对内存与文件进行同步时需要调用 msync() 函数来实现。


对文件的读写


像 read()/write() 这些系统调用,首先需要进行内核空间,然后把文件内容读入到缓存中,然后再对缓存进行读写操作,最后由内核定时同步到文件中。过程如下图:


20210205011625648.png


而调用 mmap() 系统调用对文件进行映射后,用户对映射后的内存进行读写实际上是对文件缓存的读写,所以减少了一次系统调用,从而加速了对文件读写的效率。如下图:




20210205011649190.png


搞定计算机基础内功


https://artisan.blog.csdn.net/article/details/113667911?spm=1001.2014.3001.5502

相关文章
|
6月前
|
存储 缓存 Linux
浅谈OS虚拟内存
浅谈OS虚拟内存
72 1
|
2月前
|
关系型数据库 Unix Shell
File - os.tcsetpgrp(fd, pg)函数
`os.tcsetpgrp(fd, pg)` 函数在进行进程控制和信号管理时非常有用,但它涉及Unix底层的工作原理,因此使用时需具备相应知识,以确保正确和适用,并注意相关的权限和错误处理。
146 61
|
6月前
|
Linux
内核态的文件操作函数:filp_open、filp_close、vfs_read、vfs_write、set_fs、get_fs
内核态的文件操作函数:filp_open、filp_close、vfs_read、vfs_write、set_fs、get_fs
616 0
系统编程之文件IO(六)——获取文件属性(stat、fstat、lstat、struct stat)
系统编程之文件IO(六)——获取文件属性(stat、fstat、lstat、struct stat)
153 0
系统编程之文件IO(六)——获取文件属性(stat、fstat、lstat、struct stat)
|
API C语言 开发者
OS
BIOS注意点 1. BIOS是通过汇编或者C语言写的, 要想调动BIOS程序提供的函数, 需要CPU运行在16位模式下, 而我们的操作系统一般是在32位或者64位运行, 所以在操作系统的启动盘中, 我们需要在16位模式下调用BIOS程序的函数通过BIOS获取一些硬件的参数信息, 接着让CPU进入到32位或者64位模式 再谈C语言中的链接 1.
1087 0
|
PHP Linux 消息中间件