加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

linux内存映射(Memorymapping)

(2020-02-23 10:18:44)
标签:

it

kernel

linux

杂谈

Memory Management in Linux

Address Types

Linux 是个虚拟内存操作系统,用户进程看到的内存没有直接对应硬件使用的物理地址,虚拟地址产生了一个转换层,这导致了一些很好的特性,使用虚拟地址,系统上的程序可以分配远多于实际物理内存的内存空间。

Linux的地址类型包括:
     用户空间程序所见的常规地址,32位或者64位长,依赖于底层硬件架构,每个进程有它自己的虚拟地址空间。
    处理器和系统内存的地址,32位或者64位,有些情况下即使32位系统也可以使用64位地址。
    周边总线和内存建使用的地址,通常是和处理器使用的物理地址相同,但并不总是这样,Bus addresses 和架构是高度相关的。





mmap Overview

在Linux内核,映射内核地址空间到用户地址空间是可能的,这消除了复制用户空间信息到到内核空间的开销,反之也一样,这是通过设备驱动和用户空间设备接口(/dev)来实现。这个功能可以用来在设备驱动struct file_operations结构中实现mmap()操作,以及在用户空间的系统调用的mmap()实现。


虚拟内存的基本管理单元是页(page),通常大小是4k,但在某些平台可以达到64k,在任何时候处理虚拟内存时,要处理2类地址:虚拟地址和物理地址,所有的CPU访问(including from kernel space)使用虚拟地址,会在页表的帮助下被MMu转换为物理地址,内存的物理页面由页帧号标识(PFN),PFN很容易由物理地址除以页面大小来得到 (or by shifting the physical address with PAGE_SHIFT bits to the right).

基于效率的原因,虚拟地址空间被分为用户空间和内核空间,同样的理由,内核空间包含内存映射区域,称作lowmem,这是一个连续的映射的物理内存,从最低的物理地址 (usually 0)开始,lowmem被映射的虚拟地址由PAGE_OFFSET定义。

在32位系统中,不是所有的物理内存都能被映射到lowmem,因为在内核空间有个独立的区域,称为highmem,这可以用来任意地映射物理内存。
kmalloc()分配的内存驻留在lowmem,它是持续的内存区域,vmalloc()分配的内存是不来连续的,并且不驻留在lowmem, (it has a dedicated zone in highmem).


Structures used for memory mapping

这里将讨论Linux内核内存管理子系统相关的基本数据结构,包括,struct page, struct vm_area_struct, struct mm_struct.

struct page

struct page用于嵌入有关系统中所有物理页面的信息,在系统中对所有的物理页面内核有一个struct page结构。和这个结构相关的函数包括:
===========================

  • virt_to_page() returns the page associated with a virtual address
  • pfn_to_page() returns the page associated with a page frame number
  • page_to_pfn() return the page frame number associated with a struct page
  • page_address() returns the virtual address of a struc page; this functions can be called only for pages from lowmem
  • kmap() creates a mapping in kernel for an arbitrary physical page (can be from highmem) and returns a virtual address that can be used to directly reference the page
==========================

struct vm_area_struct

struct vm_area_struct保持一片连续内存区域的信息,进程的内存区域可以通过检查在procfs的进程的maps属性来查看:
====================
root@qemux86:~# cat /proc/1/maps
#address                   perms offset    device inode     pathname
08048000-08050000 r-xp 00000000 fe:00 761        /sbin/init.sysvinit
08050000-08051000 r--p 00007000 fe:00 761        /sbin/init.sysvinit
08051000-08052000 rw-p 00008000 fe:00 761        /sbin/init.sysvinit
092e1000-09302000 rw-p 00000000 00:00 0          [heap]
4480c000-4482e000 r-xp 00000000 fe:00 576        /lib/ld-2.25.so
4482e000-4482f000 r--p 00021000 fe:00 576        /lib/ld-2.25.so
4482f000-44830000 rw-p 00022000 fe:00 576        /lib/ld-2.25.so
44832000-449a9000 r-xp 00000000 fe:00 581        /lib/libc-2.25.so
449a9000-449ab000 r--p 00176000 fe:00 581        /lib/libc-2.25.so
449ab000-449ac000 rw-p 00178000 fe:00 581        /lib/libc-2.25.so
449ac000-449af000 rw-p 00000000 00:00 0
b7761000-b7763000 rw-p 00000000 00:00 0
b7763000-b7766000 r--p 00000000 00:00 0          [vvar]
b7766000-b7767000 r-xp 00000000 00:00 0          [vdso]
bfa15000-bfa36000 rw-p 00000000 00:00 0          [stack]
=====================
内存区域由start address, stop address, length, permissions确定。

struct vm_area_struct在每一次从用户空间的mmap()调用时创建,支持mmap()操作的驱动必须完成和初始化
struct vm_area_struct结构,此结构最终要的域包括:
===================
  • vm_start, vm_end - 内存区域的起始和终止地址(these fields also appear in /proc//maps);
  • vm_file - the pointer to the associated file structure (if any);
  • vm_pgoff - the offset of the area within the file;
  • vm_flags - a set of flags;
  • vm_ops - a set of working functions for this area
  • vm_next, vm_prev - the areas of the same process are chained by a list structure
=======================

struct mm_struct

struct mm_struct结构包含了一个进程的所有内存区域,struct task_struct结构的mm域是指向当前进程的struct mm_struct结构的指针。

Device driver memory mapping

内存映射是unix系统最令人感兴趣的特色之一,从设备驱动的哦观点看,内存映射工具允许对用户空间设备的直接内存访问。
为了分配 mmap()操作给设备驱动,设备驱动的struct file_operations结构的mmap 域必须实现,在这种情况下,用户空间进程可以使用与设备关联的文件描述符上的mmap这个系统调用。

mmap()系统调用带有下列参数:
=================
void *mmap(caddr_t addr, size_t len, int prot,
           int flags, int fd, off_t offset);
=================
为了在用户空间和设备间映射内存,用户进程必须打开设备并以该文件描述符为参数发起mmap()系统调用:
===================================
int (*mmap)(struct file *filp, struct vm_area_struct *vma);
=========================

filp是指向在用户空间打开的设备的struct file的指针,vma参数是内存必须被设备映射的内存的虚拟地址空间。
设备必须分配内存(using kmalloc(), vmalloc(), alloc_pages())并映射它到由vma参数指示的用户地址空间,在函数remap_pfn_range()的帮助下。

remap_pfn_range()函数将映射一段连续的物理地址空间到vm_area_struct结构表述的虚拟地址空间。
===================
int remap_pfn_range (structure vm_area_struct *vma, unsigned long addr,
                     unsigned long pfn, unsigned long size, pgprot_t prot);
==========================
remap_pfn_range()的相关参数解析:
------------------------------

  • vma - the virtual memory space in which mapping is made;
  • addr - the virtual address space from where remapping begins; page tables for the virtual address space between addr and addr + size will be formed as needed
  • pfn the page frame number to which the virtual address should be mapped
  • size - the size (in bytes) of the memory to be mapped
  • prot - protection flags for this mapping
-----------------------------
下面的例子使用该函数映射连续内存地址为页帧号pfn (memory that was previously allocated)的物理地址到vma->vm_start虚拟地址。
=========================
struct vm_area_struct *vma;
unsigned long len = vma->vm_end - vma->vm_start;
int ret ;

ret = remap_pfn_range(vma, vma->vm_start, pfn, len, vma->vm_page_prot);
if (ret < 0) {
    pr_err("could not map the address area\n");
    return -EIO;
}
===========================
为了获得物理内存的页面帧号,必须考虑内存你是如何分配的,对每一个kmalloc(), vmalloc(), alloc_pages(), 必须考虑不同的方法,对kmalloc(),可以考虑如下
--------------------------------
static char *kmalloc_area;

unsigned long pfn = virt_to_phys((void *)kmalloc_area)>>PAGE_SHIFT;
--------------------------------
vmalloc()
=====================
static char *vmalloc_area;

unsigned long pfn = vmalloc_to_pfn(vmalloc_area);
====================
alloc_pages():
=====================
struct page *page;

unsigned long pfn = page_to_pfn(page);
====================
因为页面被映射到用户空间,可能被交换到缓冲区,为了避免此现象,必须对此物理页面设置PG_reserved位,调用函数SetPageReserved()设置,调用函数ClearPageReserved()清除此位。
=====================
void alloc_mmap_pages(int npages)
{
    int i;
    char *mem = kmalloc(PAGE_SIZE * npages);

    if (!mem)
        return mem;

    for(i = 0; i < npages * PAGE_SIZE; i += PAGE_SIZE) {
        SetPageReserved(virt_to_page(((unsigned long)mem) + i));

    return mem;
}

void free_mmap_pages(void *mem, int npages)
{
    int i;

    for(i = 0; i < npages * PAGE_SIZE; i += PAGE_SIZE) {
        ClearPageReserved(virt_to_page(((unsigned long)mem) + i));

    kfree(mem);
}
========================

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有