【动画图文深度详解】内存映射文件 mmap 原理深度剖析

认识文件

 

 

 

 

 

 

 

The following figure illustrates how a File System works:

 

 

 

Every modern Operating System (OS) has a component called a File System. That component is part of the OS kernel and it implements things like "files" and "file directories".

 

  • The File System maintains special files called "file directories" and stores the information about other files in these directories.

  • The File System maintains the "file cache." When new information is written to a file, it stores it in the Storage System (on disks) and it also copies this information into the File System "cache buffers". 
    When file information is read from storage, it passes it to the application program and also copies it into the "cache buffers" 
    When the same (or some other) application needs to read the same portion of the cached file, the File System simply retrieves that information from its cache buffers instead of re-reading it from the Storage System.

 

用户空间与内核空间


 

 

 

Inode-Dentry Cache

 

 

 

 

 

 

 

 

计算机科学中的所有问题都可以通过添加一层中间层来解决。

 

 

inode_operations, super_operations, address_space_operations

 

 

 

 

 

什么是 mmap?

mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。

mmap 必须以PAGESIZE为单位进行映射,而内存也只能以页为单位进行映射,若要映射非PAGESIZE整数倍的地址范围,要先进行内存对齐,强行以PAGE_SIZE的倍数大小进行映射。如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。

mmap操作提供了一种机制,让用户程序直接访问设备内存,这种机制,相比较在用户空间和内核空间互相拷贝数据,效率更高。在要求高性能的应用中比较常用。mmap映射内存必须是页面大小的整数倍,面向流的设备不能进行mmap,mmap的实现和硬件有关。

 

mmap 函数详解

用户空间调用库函数 mmap 原型

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

参数说明

  • start:映射区的开始地址,设置为0时表示由系统决定映射区的起始地址。

  • length:映射区的长度。//长度单位是 以字节为单位,不足一内存页按一内存页处理

  • prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起:

PROT_EXEC //页内容可以被执行
PROT_READ //页内容可以被读取
PROT_WRITE //页可以被写入
PROT_NONE //页不可访问
flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体
MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。
MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。
MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。
MAP_DENYWRITE //这个标志被忽略。
MAP_EXECUTABLE //同上
MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。
MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。
MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展。
MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。
MAP_ANON //MAP_ANONYMOUS的别称,不再被使用。
MAP_FILE //兼容标志,被忽略。
MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。
MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。
MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。
  • fd:有效的文件描述词。一般是由open()函数返回,其值也可以设置为-1,此时需要指定flags参数中的MAP_ANON,表明进行的是匿名映射。

  • off_toffset:被映射对象内容的起点。

返回说明

成功执行时,mmap()返回被映射区的指针,munmap()返回0。失败时,mmap()返回MAP_FAILED[其值为(void *)-1],munmap返回-1。errno被设为以下的某个值

EACCES:访问出错
EAGAIN:文件已被锁定,或者太多的内存已被锁定
EBADF:fd不是有效的文件描述词
EINVAL:一个或者多个参数无效
ENFILE:已达到系统对打开文件的限制
ENODEV:指定文件所在的文件系统不支持内存映射
ENOMEM:内存不足,或者进程已超出最大内存映射数量
EPERM:权能不足,操作不允许
ETXTBSY:已写的方式打开文件,同时指定MAP_DENYWRITE标志
SIGSEGV:试着向只读区写入
SIGBUS:试着访问不属于进程的内存区

内核函数mmap

内核 mmap 函数原型(不同于用户空间库函数):int mmap(struct file *filp, struct vm_area_struct *vma) 

内核mmap函数通过虚拟文件系统inode模块定位到文件磁盘物理地址。通过remappfnrange函数建立页表,即实现了文件地址和虚拟地址区域的映射关系。此时,这片虚拟地址并没有任何数据关联到主存中。

mmap 内存映射原理详解

mmap内存映射的实现过程,总的来说可以分为三个阶段:

进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域

1、进程在用户空间调用库函数mmap :

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

2、在当前进程的虚拟地址空间中,寻找一段空闲的满足要求的连续的虚拟地址

3、为此虚拟区分配一个vmareastruct结构,接着对这个结构的各个域进行了初始化

4、将新建的虚拟区结构(vmareastruct)插入进程的虚拟地址区域链表或树中

调用内核空间的系统调用函数 mmap(不同于用户空间函数),实现文件物理地址和进程虚拟地址的一一映射关系

5、为映射分配了新的虚拟地址区域后,通过待映射的文件指针,在文件描述符表中找到对应的文件描述符,通过文件描述符,链接到内核“已打开文件集”中该文件的文件结构体(struct file),每个文件结构体维护着和这个已打开文件相关各项信息。

6、通过该文件的文件结构体,链接到 file_operations 模块,调用内核函数mmap :int mmap(struct file *filp, struct vm_area_struct *vma) , 不同于用户空间库函数。

7、内核mmap函数通过虚拟文件系统inode模块定位到文件磁盘物理地址。

8、通过remappfnrange函数建立页表,即实现了文件地址和虚拟地址区域的映射关系。此时,这片虚拟地址并没有任何数据关联到主存中。

进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝

9、进程的读或写操作访问虚拟地址空间这一段映射地址,通过查询页表,发现这一段地址并不在物理页面上。因为目前只建立了地址映射,真正的硬盘数据还没有拷贝到内存中,因此引发缺页异常。

10、缺页异常进行一系列判断,确定无非法操作后,内核发起请求调页过程。

11、调页过程先在交换缓存空间(swap cache)中寻找需要访问的内存页,如果没有则调用nopage函数把所缺的页从磁盘装入到主存中。

12、之后进程即可对这片主存进行读或者写的操作,如果写操作改变了其内容,一定时间后系统会自动回写脏页面到对应磁盘地址,也即完成了写入到文件的过程。

注:前两个阶段仅在于创建虚拟区间并完成地址映射,但是并没有将任何文件数据的拷贝至主存。真正的文件读取是当进程发起读或写操作时。修改过的脏页面并不会立即更新回文件中,而是有一段时间的延迟,可以调用msync()来强制同步, 这样所写的内容就能立即保存到文件里了。

参考文章:https://blog.csdn.net/qq_36675830/article/details/79283113

mmap 与 read 对比

常规文件操作为了提高读写效率和保护磁盘,使用了页缓存机制。这样造成读文件时需要先将文件页从磁盘拷贝到页缓存中,由于页缓存处在内核空间,不能被用户进程直接寻址,所以还需要将页缓存中数据页再次拷贝到内存对应的用户空间中。这样,通过了两次数据拷贝过程,才能完成进程对文件内容的获取任务。写操作也是一样,待写入的buffer在内核空间不能直接访问,必须要先拷贝至内核空间对应的主存,再写回磁盘中(延迟写回),也是需要两次数据拷贝。

而使用mmap操作文件中,创建新的虚拟内存区域和建立文件磁盘地址和虚拟内存区域映射这两步,没有任何文件拷贝操作。而之后访问数据时发现内存中并无数据而发起的缺页异常过程,可以通过已经建立好的映射关系,只使用一次数据拷贝,就从磁盘中将数据传入内存的用户空间中,供进程使用。

总而言之,常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝。而mmap操控文件,只需要从磁盘到用户主存的一次数据拷贝过程。说白了,mmap的关键点是实现了用户空间和内核空间的数据直接交互而省去了空间不同数据不通的繁琐过程。

Page Cache

页缓存是面向文件,面向内存的。通俗来说,它位于内存和文件之间缓冲区,文件IO操作实际上只和page cache交互,不直接和内存交互。page cache可以用在所有以文件为单元的场景下,比如网络文件系统等等。page cache通过一系列的数据结构,比如inode, address_space, struct page,实现将一个文件映射到页的级别:

1、struct page结构标志一个物理内存页,通过page + offset就可以将此页帧定位到一个文件中的具体位置。同时struct page还有以下重要参数:

(1)标志位flags来记录该页是否是脏页,是否正在被写回等等;

(2)mapping指向了地址空间address_space,表示这个页是一个页缓存中页,和一个文件的地址空间对应;

(3)index记录这个页在文件中的页偏移量;

2、文件系统的inode实际维护了这个文件所有的块block的块号,通过对文件偏移量offset取模可以很快定位到这个偏移量所在的文件系统的块号,磁盘的扇区号。同样,通过对文件偏移量offset进行取模可以计算出偏移量所在的页的偏移量。

3、page cache缓存组件抽象了地址空间addressspace这个概念来作为文件系统和页缓存的中间桥梁。地址空间addressspace通过指针可以方便的获取文件inode和struct page的信息,所以可以很方便地定位到一个文件的offset在各个组件中的位置,即通过:文件字节偏移量 --> 页偏移量 --> 文件系统块号 block --> 磁盘扇区号

4、页缓存实际上就是采用了一个基数树结构将一个文件的内容组织起来存放在物理内存struct page中。一个文件inode对应一个地址空间addressspace。而一个addressspace对应一个页缓存基数树。

在真正的把用户数据读写到磁盘或者是存储设备前,内核还会在page cache中管理这些数据。这些page的存在有效的管理了用户数据和读写的效率。用户数据不是直接来自于应用层,读(read)或者是写入(write)磁盘和存储介质,而是被一层一层的应用所划分,在每一层次中都会有不同的功能对应。最后发生交互时,在最恰当的时机触发磁盘的操作。通过IO驱动写入磁盘和存储介质。

比较 Buffer Cache 和 Page Cache

buffer和cache是两个不同的概念:

  • cache是高速缓存,用于CPU和内存之间的缓冲;

  • buffer是I/O缓存,用于内存和硬盘的缓冲;

简单的说,cache是加速“读”,而buffer是缓冲“写”,前者解决读的问题,保存从磁盘上读出的数据,后者是解决写的问题,保存即将要写入到磁盘上的数据。

buffer cache 和 page cache都是为了处理设备和内存交互时高速访问的问题。buffer cache可称为块缓冲器,page cache可称为页缓冲器。在linux不支持虚拟内存机制之前,还没有页的概念,因此缓冲区以块为单位对设备进行。在linux采用虚拟内存的机制来管理内存后,页是虚拟内存管理的最小单位,开始采用页缓冲的机制来缓冲内存。Linux2.6之后内核将这两个缓存整合,页和块可以相互映射,同时,页缓存page cache面向的是虚拟内存,块I/O缓存Buffer cache是面向块设备。需要强调的是,页缓存和块缓存对进程来说就是一个存储系统,进程不需要关注底层的设备的读写。

buffer cache和page cache两者最大的区别是缓存的粒度。buffer cache面向的是文件系统的块。而内核的内存管理组件采用了比文件系统的块更高级别的抽象:页page,其处理的性能更高。因此和内存管理交互的缓存组件,都使用页缓存。

page cache是VFS的一部分,buffer cache是块设备驱动的一部分,或者说page cache是面向用户IO的cache,buffer cache是面向块设备IO的cache,page cache按照文件的逻辑页进行缓冲,buffer cache按照文件的物理块进行缓冲。page cache与buffer cache并不相互独立而是相互融合的,同一文件的cache页即可存在于page cache中,又可存在于buffer cache中,它们在物理内存中只有一份拷贝。文件系统接口就处于page cache和buffer cache之间,它完成page cache的逻辑页与buffer cache的物理块之间的相互转换,再交给统一的块设备IO进行调度处理,文件的逻辑块与物理块的关系就表现为page cache与buffer cache的关系。

Page cache实际上是针对文件系统的,是文件的缓存,在文件层面上的数据会缓存到page cache。文件的逻辑层需要映射到实际的物理磁盘,这种映射关系由文件系统来完成。当page cache的数据需要刷新时,page cache中的数据交给buffer cache,但是这种处理在2.6版本的内核之后就变的很简单了,没有真正意义上的cache操作。

Buffer cache是针对磁盘块的缓存,也就是在没有文件系统的情况下,直接对磁盘进行操作的数据会缓存到buffer cache中,例如,文件系统的元数据都会缓存到buffer cache中。

简单说来,page cache用来缓存文件数据,buffer cache用来缓存磁盘数据。在有文件系统的情况下,对文件操作,那么数据会缓存到page cache,如果直接采用dd等工具对磁盘进行读写,那么数据会缓存到buffer cache。

Address Space

下面我们总结已经讨论过的addressspace所有功能。addressspace是Linux内核中的一个关键抽象,它被作为文件系统和页缓存的中间适配器,用来指示一个文件在页缓存中已经缓存了的物理页。因此,它是页缓存和外部设备中文件系统的桥梁。如果将文件系统可以理解成数据源,那么address_space可以说关联了内存系统和文件系统。

地址空间addressspace链接到页缓存基数树和inode,因此addressspace通过指针可以方便的获取文件inode和page的信息。

在内存映射的过程中,并没有实际的数据拷贝,文件没有被载入内存,只是逻辑上被放入了内存,具体到代码,就是建立并初始化了相关的数据结构(struct address_space),这个过程有系统调用mmap()实现,所以建立内存映射的效率很高。

先看下address_space结构定义:

struct address_space {

       struct inode           *host;            /* owner: inode, block_device拥有它的节点 */

       struct radix_tree_root    page_tree;       /* radix tree of all pages包含全部页面的radix树 */

       rwlock_t        tree_lock;       /* and rwlock protecting it保护page_tree的自旋锁  */

       unsigned int           i_mmap_writable;/* count VM_SHARED mappings共享映射数 VM_SHARED记数*/

       struct prio_tree_root      i_mmap;         /* tree of private and shared mappings 优先搜索树的树根*/

       struct list_head       i_mmap_nonlinear;/*list VM_NONLINEAR mappings 非线性映射的链表头*/

       spinlock_t              i_mmap_lock; /* protect tree, count, list 保护i_mmap的自旋锁*/

       unsigned int           truncate_count;      /* Cover race condition with truncate 将文件截断的记数*/

       unsigned long         nrpages;  /* number of total pages 页总数*/

       pgoff_t                  writeback_index;/* writeback starts here 回写的起始偏移*/

       struct address_space_operations *a_ops;     /* methods  操作函数表*/

       unsigned long         flags;             /* error bits/gfp mask ,gfp_mask掩码与错误标识 */

       struct backing_dev_info *backing_dev_info; /* device readahead, etc预读信息 */

       spinlock_t              private_lock;   /* for use by the address_space  私有address_space锁*/

       struct list_head       private_list;     /* ditto 私有address_space链表*/

       struct address_space     *assoc_mapping;    /* ditto 相关的缓冲*/

} __attribute__((aligned(sizeof(long))));

了解了struct addressspace这个东东,我们就知道“一个 addressspace与一个偏移量能够确定一个page cache 或swap cache中的一个页面”。

一般情况下用户进程调用mmap()时,只是在进程空间内新增了一块相应大小的缓冲区,并设置了相应的访问标识,但并没有建立进程空间到物理页面的映射。因此,第一次访问该空间时,会引发一个缺页异常。、对于共享内存映射情况,缺页异常处理程序首先在swap cache中寻找目标页(符合addressspace以及偏移量的物理页),如果找到,则直接返回地址;如果没有找到,则判断该页是否在交换区 (swap area),如果在,则执行一个换入操作;如果上述两种情况都不满足,处理程序将分配新的物理页面,并把它插入到page cache中。进程最终将更新进程页表。注:对于映射普通文件情况(非共享映射),缺页异常处理程序首先会在page cache中根据addressspace以及数据偏移量寻找相应的页面。如果没有找到,则说明文件数据还没有读入内存,处理程序会从磁盘读入相应的页面,并返回相应地址,同时,进程页表也会更新。

我们再来看完整的文件读写流程。

文件读写基本流程

读文件

1、进程调用库函数向内核发起读文件请求;

2、内核通过检查进程的文件描述符定位到虚拟文件系统的已打开文件列表表项;

3、调用该文件可用的系统调用函数read()

3、read()函数通过文件表项链接到目录项模块,根据传入的文件路径,在目录项模块中检索,找到该文件的inode;

4、在inode中,通过文件内容偏移量计算出要读取的页;

5、通过inode找到文件对应的address_space;

6、在address_space中访问该文件的页缓存树,查找对应的页缓存结点:

(1)如果页缓存命中,那么直接返回文件内容;

(2)如果页缓存缺失,那么产生一个页缺失异常,创建一个页缓存页,同时通过inode找到文件该页的磁盘地址,读取相应的页填充该缓存页;重新进行第6步查找页缓存;

7、文件内容读取成功。

写文件

1-5、前5步和读文件一致,在address_space中查询对应页的页缓存是否存在:

6、如果页缓存命中,直接把文件内容修改更新在页缓存的页中。写文件就结束了。这时候文件修改位于页缓存,并没有写回到磁盘文件中去。

7、如果页缓存缺失,那么产生一个页缺失异常,创建一个页缓存页,同时通过inode找到文件该页的磁盘地址,读取相应的页填充该缓存页。此时缓存页命中,进行第6步。

8、一个页缓存中的页如果被修改,那么会被标记成脏页。脏页需要写回到磁盘中的文件块。有两种方式可以把脏页写回磁盘:

(1)手动调用sync()或者fsync()系统调用把脏页写回

(2)pdflush进程会定时把脏页写回到磁盘

同时注意,脏页不能被置换出内存,如果脏页正在被写回,那么会被设置写回标记,这时候该页就被上锁,其他写请求被阻塞直到锁释放。

mmap 性能分析

既然建立内存映射没有进行实际的数据拷贝,那么进程又怎么能最终直接通过内存操作访问到硬盘上的文件呢?那就要看内存映射之后的几个相关的过程了。

mmap()会返回一个指针ptr,它指向进程逻辑地址空间中的一个地址,这样以后,进程无需再调用read或write对文件进行读写,而只需要通过ptr就能够操作文件。但是ptr所指向的是一个逻辑地址,要操作其中的数据,必须通过MMU (关于 MMU 我们会在文末“内存碎片与虚拟内存” 部分单独介绍) 将逻辑地址转换成物理地址。

这时,如果MMU在地址映射表中无法找到与ptr相对应的物理地址,也就是MMU失败,将会产生一个缺页中断(page fault),缺页中断的中断响应函数会在swap中寻找相对应的页面,如果找不到(也就是该文件从来没有被读入内存的情况),则会通过mmap()建立的映射关系,从硬盘上将文件读取到物理内存中。

如果在拷贝数据时,发现物理内存不够用,则会通过虚拟内存机制(swap)将暂时不用的物理页面交换到硬盘上。

通过内存映射的方法访问硬盘上的文件,效率要比read和write系统调用高,这是为什么呢?

原因是read() 进行了两次数据拷贝

1.它首先将文件内容从硬盘拷贝到内核空间的一个缓冲区。

2.然后再将这些数据从内核空间的缓冲区拷贝到用户空间。

而 mmap() 只进行了 一次数据拷贝

mmap() 的数据拷贝是在缺页中断处理时进行的。由于mmap()将文件直接映射到用户空间,所以中断函数根据这个映射关系,直接将文件从硬盘拷贝到用户空间。 

就看缺页中断性能的拷贝跟read() 中的 2 次拷贝哪个性能好了。

通常情况下,内存映射的效率要比read/write效率高。

mmap 与直接IO(read、write)的效率比较

不能简单的说哪个效率高,要看具体实现与具体应用。

无论是通过mmap方式或read/write方式访问文件在内核中都必须经过两个缓存:一个是用addressspace来组织的以页为基础的缓存;一个是以buffer来组织的缓存,但实际上这两个缓存只是同一个缓冲池里内容的不同组织方式。当需要从文件读写内容时,都经过 addressspace_operation中提供的函数也就是说路径是一致的。如果是用read/write方式,用户须向内核指定要读多少,内核再把得到的内容从内核缓冲池拷向用户空间;写也须要有一个大致如此的过程。mmap的优势在于通过把文件的某一块内容映射到用户空间上,用户可以直接向内核缓冲池读写这一块内容,这样一来就少了内核与用户空间的来回拷贝所以通常更快。但 mmap方式只适用于更新、读写一块固定大小的文件区域而不能做像诸如不断的写内容进入文件导到文件增长这类的事。

二者的主要区别在于,与mmap和memcpy相比,read和write执行了更多的系统调用,并做了更多的复制。read和write将数据从内核缓冲区中复制到应用缓冲区,然后再把数据从应用缓冲区复制到内核缓冲区。而mmap和memcpy则直接把数据从映射到地址空间的一个内核缓冲区复制到另一个内核缓冲区。当引用尚不存在的内存页时,这样的复制过程就会作为处理页错误的结果而出现(每次错页读发生一次错误,每次错页写发生一次错误)。

所以他们两者的效率的比较就是系统调用和额外的复制操作的开销和页错误的开销之间的比较,哪一个开销少就是哪一个表现更好。

用mmap可以避免与读写打交道,这样可以简化程序逻辑,有利于编程实现。

参考文章:https://blog.csdn.net/qq100440110/article/details/52104311

大家关于“mmap()”更快的认识来自于 read() 是需要内存拷贝的;当今硬件技术的发展,使得内存拷贝消耗的时间已经极大降低了;但“mmap()”的开销在于一次 pagefault,这个开销相比而言已经更高了,而且 pagefault 的处理任务现在比以前还更多了;而且,mmap之后,再有读操作不会经过系统调用,在 LRU 比较最近使用的页的时候不占优势。

另外,如果若干个进程访问同一个文件,每个进程都要在自己的地址空间维护一个副本,浪费了内存空间.而如果能够通过一定的机制将页面映射到进程的地址空间中,也就是说首先通过简单的产生某些内存管理数据结构完成映射的创建.当进程访问页面时产生一个缺页中断,内核将页面读入内存并且更新页表指向该页面.而且这种方式非常方便于同一副本的共享。

 

 

 


 

番外篇

 

 

LINUX IO STACK DIAGRAM

 

 i'm programmer

 

A high-level overview of the Linux IO stack diagram relating its various layers. This diagram shows a relationship between applications, the VFS layer, block layer, I/O schedulers, SCSI layers, various device drivers, and physical devices.

 

Linux IO stack diagram

 

The two main areas of the Linux storage subsystem are

  • Filesystems layer (include virtual file system) – Each file system type defines their own structures and logic rules used to manage these groups of information and their names.

  • Storage devices support layer – Linux storage is based on block devices. Block devices provide buffered access to the hardware, always allowing to read or write any sized block. They are commonly used to represent hardware like hard disks.

Both layers are used to implement how the kernel retrieves data from and stores data to secondary memory devices such as hard disks, magnetic tapes, optical discs or Flash memories.

Linux supports many different file systems, but common choices for the system disk on a block device include the ext* family, XFS, JFS, ReiserFS and btrfs.SquashFS is a common compressed read-only file system.

参考链接:https://www.improgrammer.net/linux-io-stack-diagram/

 

关于内存碎片与虚拟内存的介绍

MMU:Memory Management Unit

Memory fragmentation

Programs existed inside the computer on the hard drive (rather than loaded using punch cards or tape). As the storage space of RAM got bigger, multiple programs could be loaded into it at the same time. The Operating system would give a second program a different section of RAM.

This scheduling led to a problem called memory fragmentation.

 

 

Memory fragmentation occurs as programs load to RAM at different times, when space becomes available. Here’s an example of how it could occur:

1. Program A loads into address 0 in the RAM and given 5MB of RAM to run. Program B loads into address 5242880 and given 3MB of RAM to run.

2. Program A finishes. Program C (3MB) loads into address 0. As Program C doesn’t need 5MB, there’s now a gap in RAM.

3. Program D needs 5MB of memory. The gap of 2MB is too small, so Program D is placed somewhere else in RAM. The gap remains.

As more programs load and finish, more gaps occur. Eventually you will run out of memory, even though you have plenty of memory in these gaps.

The operating system solves this problem by using virtual memory to map physical memory.

Virtual memory

Virtual memory is a memory management technique. A piece of hardware called a Memory Management Unit maps a virtual address () to a physical address.

When loaded, programs are given a virtual address of 0 to however much they need. The MMU puts the program wherever there is space on the RAM.

It can also split the program around the RAM, filling in any gaps.

Let’s look at the scenario above using the MMU and virtual memory.

 

 

 

 

1. Program A loads into address 0 in the RAM, and given 5MB of RAM to run. It’s given a virtual address of 0 to 5242880 Program B loads into address 5242880 and given 3MB of RAM to run. It’s given a virtual address of 0 to 3145728

2. Program A finishes. Program C receives a virtual address of 0 to 3145728. We place C in 0 in RAM. There’s now a gap in RAM.

3. Program D needs 5MB of memory. The gap is too small. Program D is split into two: 2MB are placed in 3145728, 3MB are placed in 8388608. Program D is given the virtual memory address of 0 to 5242880.

Using this technique of virtual memory, there are no gaps in RAM and you won’t run out of memory as easily as before.

Each program is self contained. It doesn’t write over any other program. It doesn’t matter where the program is in memory, the MMU does all the mapping. Programs no longer need to be in one continued block.

Other roles of an OS

As well as scheduling programs into memory, Operating Systems schedule how programs use the CPU (we’ll look at this in more detail in the next step). Operating systems also have a role in managing access to different levels of resources. This protects the integrity of the system - which we’ll also be looking at later.

Other roles of an operating system include:

  • Interacting with hardware through drivers written for a specific piece of hardware and OS.

  • Providing utility software, such as disk management or security software.

  • Dealing with input/output events, like a key press.

Also, operating systems also need to provide user interfaces, allowing users to interact with them and start programs. The OS also manages the file structure on storage devices, as well as controlling which users have access to these files and programs.

 

 

 

 

Kotlin 开发者社区

国内第一Kotlin 开发者社区公众号,主要分享、交流 Kotlin 编程语言、Spring Boot、Android、React.js/Node.js、函数式编程、编程思想等相关主题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

光剑书架上的书

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值