网站3d展示怎么做的,精品成品源码网站,广告接单有什么平台,网页广告过滤文章目录1 kmap2 映射内核内存到用户空间使用remap_pfn_range使用io_remap_pfn_rangemmap文件操作建立VMA和实际物理地址的映射mmap 之前分配 一次性映射mmap 之前分配 Page FaultPage Fault 中分配 映射内核内存有时需要重新映射#xff0c;无论是从内核到用户空间还是从内…
文章目录1 kmap2 映射内核内存到用户空间使用remap_pfn_range使用io_remap_pfn_rangemmap文件操作建立VMA和实际物理地址的映射mmap 之前分配 一次性映射mmap 之前分配 Page FaultPage Fault 中分配 映射内核内存有时需要重新映射无论是从内核到用户空间还是从内核空间到内核。常见的情况是将内核内存重新映射到用户空间但还有其他一些情况例如需要访问高内存的情况。
1 kmap
kmap()用于将指定的页面映射到内核地址空间。
Linux内核将其896MB地址空间永久映射到物理内存较低的896MB低端内存。在4GB系统上内核仅剩下128MB用来映射剩余3.2GB物理内存高端内存。由于低端内存采用永久一对一映射因此内核可以直接寻址。而对于高端内存高于896MB的内存内核必须将所请求的高端内存区域映射到其地址空间前面提到的128MB就是专门为此保留。用于执行此操作的函数是kmap()。kmap()用于将指定的页面映射到内核地址空间。
void *kmap(struct page *page);当分配到高端内存页时它不能直接寻址必须调用调用kmap()函数将高端内存映射到内存地址空间。该映射将持续到kunmap()位置
void *kunmap(struct page *page);所谓暂时指的是映射应该在不需要的时候立即撤销。请记住128MB不足以映射3.2GB。最好的编程习惯是在不需要时取消高端内存映射。这就是必须在每次访问高端内存页面时输入kmap()-kunmap序列的原因了
该函数适用于高端内存和低端内存也就是说如果页面结构驻留在低端内存中那么返回的是页面的虚拟地址因为低端内存页面已经有永久映射。如果页面属于高端内存则在内核页表中创建永久映射并返回地址。
2 映射内核内存到用户空间
映射物理地址是其中一个有用的功能特别是在嵌入式系统中。有时可能想要与用户空间共享部分内核内存。如前所述CPU在用户空间时以非特权模式运行。要让进程访问内核内存区域需要将该区域映射到进程地址空间。
使用remap_pfn_range
remap_pfn_range()将物理内存通过内核逻辑地址映射到用户空间进程。它对于实现mmap()特别有用。 在文件上调用mmap()系统调用后CPU切换到特权模式运行相应的file_operations.mmap()内核函数它反过来调用remap_pfn_range()。这将产生映射区域的PTE将其赋给进程当然还有不同的保护标志进程的VMA列表更新为新的VMA项这将使用PTE访问相同的内存。
这样内核不是通过复制来浪费内存而只是复制PTE但是内核和用户空间PTE具有不同的属性。remap_pfn_range()原型如下
int remap_pfn_range ( struct vm_area_struct * vma,unsigned long virt_addr,unsigned long pfn,unsigned long size,pgprot_t prot);
vma 这个我们不用担心因为在调用file_operations.mmap函数时mmap调用do_mmap()会创建一个新的VMA并初始化此vma就是创建的新的VMA加入进程的虚拟地址空间里这个已经确定了。virt_addrVMA开始位置的用户虚拟地址vma-vm_start,这将导致映射的虚拟地址范围位于virt_addr~virt_addrsizepfn所映射内核内存区域的页面帧码它对应于通过PAGE_SHIFT位右移得到的物理地址。产生pfn时应应该考虑vma偏移量。由于vma结构的vm_pgoff字段在页码中包含偏移值因此需要以字节形式精确提取偏移量offset vma-vm_pgoffPAGE_SHIFT.最后pnfvirt_to_phys(bufferoffset)PAGE_SHIFT.size需要建立映射的VMA的大小以字节为单位prot代表新VMA所要求的保护。驱动程序可以修改默认值但应该使用OR运算符将vma-vm_page_port中的值作基础因为它的某些位已经由用户空间设置其中一些标志如下
VM_IO指定设备内存映射I/OVM_DONTCOPY告诉内核不要在分叉上复制该vmaVM_DONTEXPAND防止vma通过mremap扩展VM_DONTDUMP禁止在核心转储内包含vma
使用io_remap_pfn_range
前面讨论的remap_pfn_range()不适用于将I/O内存映射到用户空间。在这种情况下相应的函数是io_remap_pfn_range()它们的参数相同唯一改变的是PFN的来源其原型如下 int io_remap_page_range(struct vm_area_struct *vma, unsigned long virt_addr,unsigned long phys_addr, unsigned long size, pgprot_t prot);当试图将I/O内存映射到用户空间时不需要使用ioremap()ioremap()用于内核映射将I/O内存映射到内核地址空间。
只需要将真实的物理I/O地址通过PAGE_SHIFT向下移位生成PFN直接传递给io_remap_pfn_range()。即使有一些体系将io_remap_pfn_range()定义为remap_pfn_range()但在其他体系结构中并非如此考虑到移植能力只有在PFN参数指向RAM的情况下才使用remap_pfn_range()在pys_addr指向I/O聂村的情况下才使用io_remap_pfn_range()。
mmap文件操作
内核mmap函数是struct file_operations结构的一部分当用户执行系统调用mmap(2)把物理内存映射到用户虚拟地址时才执行它。出于安全考虑用户空间进程不能直接访问设备内存因此用于空间进程使用mmap()系统调用将该设备映射到调用进程的虚拟地址空间。在映射之后用户空间进程可以通过返回的地址直接写入设备内存。 #include sys/mman.hvoid *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);int munmap(void *addr, size_t length);
用户空间的mmap()会通过系统调用调用内核的do_mmap()函数。 do_mmap()函数会
首先创建一个新的VMA并初始化然后加入进程的虚拟地址空间里调用底层的mmap函数建立VMA和实际物理地址的映射建立页表
什么是底层的mmap函数呢在不同的设备是不一样的。比如说我们映射的是一个普通文件底层文件系统已经帮我们实现了mmap我们可以直接使用但是如果我们新写了一个驱动我们想为驱动提供mmap的接口那么就需要我们实现mmap的接口设备驱动的mmap实现主要是将这个物理设备的可操作区域映射到一个进程的虚拟地址空间这样用户空间就可以直接采用指针的方式访问设备的可操作区域。在驱动中的mmap实现主要完成一件事就是建立设备的可操作区域到进程虚拟空间地址的映射过程。
addr映射开始的用户空间虚拟地址如果指定NULL则自动确定正确的地址length指定映射长度prot指定VMA的权限flags决定映射类型私有还是共享fd设备文件描述符offset指定映射区的偏移量在物理内存里面
建立VMA和实际物理地址的映射
在 linux 驱动中建立映射关系的方法主要有如下两种
一次性映射 —— 在 mmap 回调函数中一次性建立好整块内存的映射关系通常以 remap_pfn_range() 为代表 。Page Fault —— mmap 先不建立映射关系等上层触发缺页异常时在 fault 中断处理函数中建立映射关系缺哪块补哪块通常以 vm_insert_page() 为代表。
而内存分配的时机也会影响驱动程序的设计大致分为如下三种
在 mmap 系统调用之前分配在 mmap 系统调用过程中分配在 fault 中断处理函数中分配
因此不同的分配时机 不同的映射机制就会得到不同的 mmap 的实现策略。
下面就以示例代码的形式为大家展示几种典型的 mmap 驱动实现方式。
mmap 之前分配 一次性映射 描述
驱动初始化时先分配好 3 个 PAGE。上层执行 mmap 系统调用时在底层 mmap 回调函数中通过 remap_pfn_range() 一次性建立好所有的映射关系并将映射后的起始虚拟地址返回给应用程序。应用程序使用返回的虚拟地址进行内存读写操作。
驱动代码
#include linux/module.h
#include linux/miscdevice.h
#include linux/mm.h
#include linux/uaccess.h
#include linux/fs.h
#include linux/slab.hstatic void *kaddr;static int my_mmap(struct file *file, struct vm_area_struct *vma)
{return remap_pfn_range(vma, vma-vm_start,(virt_to_phys(kaddr) PAGE_SHIFT) vma-vm_pgoff,vma-vm_end - vma-vm_start, vma-vm_page_prot);
}static struct file_operations my_fops {.owner THIS_MODULE,.mmap my_mmap,
};static struct miscdevice mdev {.minor MISC_DYNAMIC_MINOR,.name my_dev,.fops my_fops,
};static int __init my_init(void)
{kaddr kzalloc(PAGE_SIZE * 3, GFP_KERNEL);return misc_register(mdev);
}
module_init(my_init);
mmap 之前分配 Page Fault 描述
驱动初始化时预先分配好 3 个 PAGE。上层执行 mmap 系统调用底层驱动在 mmap 回调函数中不建立映射关系而是将本地实现的 vm_ops 挂接到进程的 vma-vm_ops 指针上然后函数返回。上层获取到一个未经映射的进程地址空间并对其进行内存读写操作导致触发缺页异常。缺页异常最终会调用前面挂接的 vm_ops-fault() 回调接口在该接回调中通过 vm_insert_page() 建立物理内存与用户地址空间的映射关系。异常返回后应用程序就可以继续之前被中断的读写操作了。
注意这种情况每次 Page Fault 中断只能映射一个 Page
驱动代码
#include linux/module.h
#include linux/miscdevice.h
#include linux/mm.h
#include linux/uaccess.h
#include linux/fs.h
#include linux/slab.hstatic void *kaddr;static int my_fault(struct vm_fault *vmf)
{struct vm_area_struct *vma vmf-vma;int offset, ret;offset vmf-pgoff * PAGE_SIZE;ret vm_insert_page(vma, vmf-address, virt_to_page(kaddr offset));if (ret)return VM_FAULT_SIGBUS;return VM_FAULT_NOPAGE;
}static const struct vm_operations_struct vm_ops {.fault my_fault,
};static int my_mmap(struct file *file, struct vm_area_struct *vma)
{vma-vm_flags | VM_MIXEDMAP;vma-vm_ops vm_ops;return 0;
}static struct file_operations my_fops {.owner THIS_MODULE,.mmap my_mmap,
};static struct miscdevice mdev {.minor MISC_DYNAMIC_MINOR,.name my_dev,.fops my_fops,
};static int __init my_init(void)
{kaddr kzalloc(PAGE_SIZE * 3, GFP_KERNEL);return misc_register(mdev);
}
module_init(my_init);
Page Fault 中分配 映射 映射的过程和示例二完全一样只是内存分配的时机是在 page fault 中断处理函数中进行的。
这里为了简化代码总共只分配一个 page多个 page 可通过 vmf-pgoff 来进行区分。
#include linux/module.h
#include linux/miscdevice.h
#include linux/mm.h
#include linux/uaccess.h
#include linux/fs.h
#include linux/slab.hstatic struct page *page;static int my_fault(struct vm_fault *vmf)
{struct vm_area_struct *vma vmf-vma;int ret;if (!page)page alloc_page(GFP_KERNEL);ret vm_insert_page(vma, vmf-address, page);if (ret)return VM_FAULT_SIGBUS;return VM_FAULT_NOPAGE;
}static const struct vm_operations_struct vm_ops {.fault my_fault,
};static int my_mmap(struct file *file, struct vm_area_struct *vma)
{vma-vm_flags | VM_MIXEDMAP;vma-vm_ops vm_ops;return 0;
}static struct file_operations my_fops {.owner THIS_MODULE,.mmap my_mmap,
};static struct miscdevice mdev {.minor MISC_DYNAMIC_MINOR,.name my_dev,.fops my_fops,
};static int __init my_init(void)
{return misc_register(mdev);
}
module_init(my_init);