mmap学习
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/mm.h>
#include <linux/gfp.h>
#include <linux/printk.h>
#define DEVICE_CNT 1
#define DEVICE_NAME "demo_dev "
struct chr_dev{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
int minor;
};
struct chr_dev demo_dev;
unsigned long virtual_addr_start;
unsigned long phy_addr_start;
struct page *page;
static int demo_open(struct inode *inode, struct file *file)
{
unsigned long pfn;
virtual_addr_start = 0;
phy_addr_start = 0;
page = NULL;
printk(DEVICE_NAME "%s, ===>\n", __func__);
virtual_addr_start = __get_free_page(GFP_KERNEL | __GFP_ZERO);
if (!virtual_addr_start) {
printk(DEVICE_NAME "%s, memory allocation failed\n", __func__);
virtual_addr_start = 0;
return -ENOMEM;
}
printk(DEVICE_NAME "%s, virtual_addr_start 0x%lx\n", __func__, virtual_addr_start);
page = virt_to_page((void *)virtual_addr_start); // 通过该接口直接获取 page
printk(DEVICE_NAME "%s, page %p\n", __func__, page);
// 虚拟地址和物理地址的映射
phy_addr_start = virt_to_phys((volatile void *)virtual_addr_start);
pfn = phy_addr_start >> PAGE_SHIFT;
printk(DEVICE_NAME "%s, phy_addr_start 0x%lx\n", __func__, phy_addr_start);
printk(DEVICE_NAME "%s, pfn of phy addr 0x%lx\n", __func__, pfn);
if (IS_ERR_OR_NULL(page)) {
printk(DEVICE_NAME "%s, page is invalid\n", __func__);
return -EFAULT;
}
memset((void *)virtual_addr_start, 0, PAGE_SIZE);
SetPageReserved(page);
printk(DEVICE_NAME "%s, <===\n", __func__);
return 0;
}
int demo_release(struct inode *inode, struct file *file)
{
char usr_data[20] = {0};
printk(DEVICE_NAME "%s, ===>\n", __func__);
memcpy((void *)&usr_data, (const void *)virtual_addr_start, 20);
printk(DEVICE_NAME "%s, user data %s\n", __func__, usr_data);
ClearPageReserved(page);
if (virtual_addr_start) {
free_page(virtual_addr_start);
virtual_addr_start= 0;
printk(DEVICE_NAME "%s, memory free success\n", __func__);
} else {
printk(DEVICE_NAME "%s, memory free failed\n", __func__);
}
virtual_addr_start = 0;
phy_addr_start = 0;
page = NULL;
printk(DEVICE_NAME "%s, <===\n", __func__);
return 0;
}
int demo_mmap(struct file *file, struct vm_area_struct *vma)
{
unsigned long vm_end, vm_start, vm_len;
int ret = 0;
printk(DEVICE_NAME "%s, ===>\n", __func__);
if (IS_ERR_OR_NULL(page)) {
printk(DEVICE_NAME "%s, page is invalid\n", __func__);
return -EFAULT;
}
vm_end = vma->vm_end;
vm_start = vma->vm_start;
vm_len = vm_end - vm_start;
printk(DEVICE_NAME "%s, vma size %lu\n", __func__, vm_len);
// 这里的 vma 代表的是 用户空间的地址
// 而 virtual_addr_start 和 phy_addr_start 都是内核空间的地址
// 因为这里搞混了,耽搁了一下午
ret = remap_pfn_range(vma,
vma->vm_start,
phy_addr_start>>PAGE_SHIFT,
vm_len,
vma->vm_page_prot);
if (ret == 0) {
printk(DEVICE_NAME "%s, remap success\n", __func__);
} else {
printk(DEVICE_NAME "%s, remap failed\n", __func__);
}
printk(DEVICE_NAME "%s, <===\n", __func__);
return 0;
}
static struct file_operations demo_fops = {
.owner = THIS_MODULE,
.open = demo_open,
.mmap = demo_mmap,
.release = demo_release,
};
static int demo_init(void) {
int result = 0;
printk(DEVICE_NAME "%s ===>\n", __func__);
if (demo_dev.major) {
demo_dev.devid = MKDEV(demo_dev.major, 0);
result = register_chrdev_region(demo_dev.devid, DEVICE_CNT, DEVICE_NAME);
if(result < 0){
goto out_err_1;
}
} else {
result = alloc_chrdev_region(&demo_dev.devid, 0, DEVICE_CNT, DEVICE_NAME);
if(result < 0){
goto out_err_1;
}
demo_dev.major = MAJOR(demo_dev.devid);
demo_dev.minor = MINOR(demo_dev.devid);
}
printk(DEVICE_NAME "%s, major=%d, minor=%d\r\n", __func__, demo_dev.major, demo_dev.minor);
demo_dev.cdev.owner = THIS_MODULE;
cdev_init(&demo_dev.cdev, &demo_fops);
cdev_add(&demo_dev.cdev, demo_dev.devid, DEVICE_CNT);
demo_dev.class = class_create(THIS_MODULE, DEVICE_NAME);
if (IS_ERR(demo_dev.class)) {
printk(KERN_ERR "%s, class_create failed\n", __func__);
result = PTR_ERR(demo_dev.class);
goto out_err_2;
}
demo_dev.device = device_create(demo_dev.class, NULL, demo_dev.devid, NULL, DEVICE_NAME);
if (IS_ERR(demo_dev.device)) {
printk(KERN_ERR "%s, device_create failed\n", __func__);
result = PTR_ERR(demo_dev.device);
goto out_err_3;
}
printk(DEVICE_NAME "%s PAGE_SHIFT 0x%lx\n", __func__, PAGE_SHIFT);
printk(DEVICE_NAME "%s PAGE_OFFSET 0x%lx\n", __func__, PAGE_OFFSET);
printk(DEVICE_NAME "%s <===\n", __func__);
return result;
out_err_3:
device_destroy(demo_dev.class, demo_dev.devid);
out_err_2:
class_destroy(demo_dev.class);
unregister_chrdev_region(demo_dev.devid, DEVICE_CNT);
cdev_del(&demo_dev.cdev);
out_err_1:
return result;
}
static void demo_exit(void) {
printk(DEVICE_NAME "%s ===>\n", __func__);
device_destroy(demo_dev.class, demo_dev.devid);
class_destroy(demo_dev.class);
unregister_chrdev_region(demo_dev.devid, DEVICE_CNT);
cdev_del(&demo_dev.cdev);
printk(DEVICE_NAME "%s <===\n", __func__);
return;
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
// %p 用于打印 void *
// %lx 用于打印unsigned long
//==============================================================
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main() {
char *filename = "/dev/demo_dev";
char *bufp;
size_t length = 4096;
int fd = open(filename,O_RDWR);
if(fd<0){
perror("open fail \n");
return -1;
}
printf("open file %s success!\n", filename);
sleep(3);
// void* mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
bufp = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (bufp == MAP_FAILED) {
printf("mmap kernel file page failed!\n");
} else {
printf("mmap kernel file page success!\n");
}
sleep(2);
memcpy(bufp, "hello world", 12);
munmap(bufp, length);
sleep(2);
close(fd);
return 0;
}
//==============================================================
[ 3047.622950] demo_dev demo_init ===>
[ 3047.623268] demo_dev demo_init, major=244, minor=0
[ 3047.623476] demo_dev demo_init <===
[ 3051.910723] demo_dev demo_open, ===>
[ 3051.910733] demo_dev demo_open, virtual_addr_start 0xffff90a2c26cf000
[ 3051.910737] demo_dev demo_open, page 00000000ab9feae0
[ 3051.910739] demo_dev demo_open, phy_addr_start 0x26cf000
[ 3051.910741] demo_dev demo_open, pfn of phy addr 0x26cf
[ 3051.910743] demo_dev demo_open, <===
[ 3054.912966] demo_dev demo_mmap, ===>
[ 3054.912975] demo_dev demo_mmap, vma size 4096
[ 3054.912989] demo_dev demo_mmap, remap success
[ 3054.912991] demo_dev demo_mmap, <===
[ 3058.914745] demo_dev demo_release, ===>
[ 3058.914751] demo_dev demo_release, user data hello world
[ 3058.914757] demo_dev demo_release, memory free success
[ 3058.914759] demo_dev demo_release, <===
[ 3179.697944] demo_devdemo_exit ===>
[ 3179.703738] demo_devdemo_exit <===
//=========================================================
【内核虚拟地址和内核物理地址的转换】
phy_addr_start >> PAGE_SHIFT 是 0x26cf
打印 PAGE_SHIFT 和 PAGE_OFFSET 如下:
[ 5365.521986] demo_dev demo_init PAGE_SHIFT 0xc
[ 5365.521992] demo_dev demo_init PAGE_OFFSET 0xffff90a2c0000000
// 内核虚拟地址转内核物理地址
#define __pa(x) ((unsigned long) (x) - PAGE_OFFSET)
// 内核物理地址转内核虚拟地址
#define __va(x) ((void *)((unsigned long) (x) + PAGE_OFFSET))
//内核虚拟地址转page
#define virt_to_page(kaddr) pfn_to_page(__pa(kaddr) >> PAGE_SHIFT)
// 这里的 __pa(kaddr) 是将 内核虚拟地址转换为 内核物理地址
// __pa(kaddr) >> PAGE_SHIFT 是通过 内核物理地址 计算 内核页帧号
// pfn_to_page 是通过 内核页帧号 得到 page*
//=========================================================
【获取一个物理页】
#define __get_free_page(gfp_mask) \
__get_free_pages((gfp_mask), 0)
/*
* Common helper functions. Never use with __GFP_HIGHMEM because the returned
* address cannot represent highmem pages. Use alloc_pages and then kmap if
* you need to access high mem.
*/
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
{
struct page *page;
page = alloc_pages(gfp_mask & ~__GFP_HIGHMEM, order);
if (!page)
return 0;
// void *转换为 unsigned long
// 虚拟地址接口,位于mm.h,这里返回的是 page->virtual
return (unsigned long) page_address(page);
}
EXPORT_SYMBOL(__get_free_pages);
//=========================================================
【获取一个物理零页】
unsigned long get_zeroed_page(gfp_t gfp_mask)
{
return __get_free_pages(gfp_mask | __GFP_ZERO, 0);
}
EXPORT_SYMBOL(get_zeroed_page);
//=========================================================
【释放page】
#define __free_page(page) __free_pages((page), 0)
/**
* __free_pages - Free pages allocated with alloc_pages().
* @page: The page pointer returned from alloc_pages().
* @order: The order of the allocation.
*
* This function can free multi-page allocations that are not compound
* pages. It does not check that the @order passed in matches that of
* the allocation, so it is easy to leak memory. Freeing more memory
* than was allocated will probably emit a warning.
*
* If the last reference to this page is speculative, it will be released
* by put_page() which only frees the first page of a non-compound
* allocation. To prevent the remaining pages from being leaked, we free
* the subsequent pages here. If you want to use the page's reference
* count to decide when to free the allocation, you should allocate a
* compound page, and use put_page() instead of __free_pages().
*
* Context: May be called in interrupt context or while holding a normal
* spinlock, but not in NMI context or while holding a raw spinlock.
*/
void __free_pages(struct page *page, unsigned int order)
{
if (put_page_testzero(page))
free_the_page(page, order);
else if (!PageHead(page))
while (order-- > 0)
free_the_page(page + (1 << order), order);
}
EXPORT_SYMBOL(__free_pages);
//=========================================================
【释放page】
#define free_page(addr) free_pages((addr), 0)
void free_pages(unsigned long addr, unsigned int order)
{
if (addr != 0) {
VM_BUG_ON(!virt_addr_valid((void *)addr));
__free_pages(virt_to_page((void *)addr), order);
}
}
EXPORT_SYMBOL(free_pages);
//=========================================================
【映射内核内存到user空间】
/**
* remap_pfn_range - remap kernel memory to userspace
* @vma: user vma to map to
* @addr: target page aligned user address to start at
* @pfn: page frame number of kernel physical memory address
* @size: size of mapping area
* @prot: page protection flags for this mapping
*
* Note: this is only safe if the mm semaphore is held when called.
*
* Return: %0 on success, negative error code otherwise.
*/
int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
unsigned long pfn, unsigned long size, pgprot_t prot)
{
int err;
err = track_pfn_remap(vma, &prot, pfn, addr, PAGE_ALIGN(size));
if (err)
return -EINVAL;
err = remap_pfn_range_notrack(vma, addr, pfn, size, prot);
if (err)
untrack_pfn(vma, pfn, PAGE_ALIGN(size));
return err;
}
EXPORT_SYMBOL(remap_pfn_range);
//=========================================================
【SetPageReserved 和 ClearPageReserved 的封装】
/* Free the reserved page into the buddy system, so it gets managed. */
static inline void free_reserved_page(struct page *page)
{
ClearPageReserved(page);
init_page_count(page);
__free_page(page);
adjust_managed_page_count(page, 1);
}
#define free_highmem_page(page) free_reserved_page(page)
static inline void mark_page_reserved(struct page *page)
{
SetPageReserved(page);
adjust_managed_page_count(page, -1);
}
//=========================================================
扩展做法
void* mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset) 的最后一个标记
可以作为和kernel的约定。
如下所示:
user空间传递 offset ,kernel空间决定将它作为页的偏移
这样用户空间可以决定用哪一页
static int demo_mmap(struct file *file, struct vm_area_struct *vma)
{
int ret = 0;
unsigned long pfn_start = (phy_addr_start >> PAGE_SHIFT) + vma->vm_pgoff; //页帧号
unsigned long size = vma->vm_end - vma->vm_start;
ret = remap_pfn_range(vma, vma->vm_start, pfn_start, size, vma->vm_page_prot);
if (ret) {
printk(DEVICE_NAME "%s remap_pfn_range failed [0x%lx 0x%lx)\n",
__func__, vma->vm_start, vma->vm_end);
}
return ret;
}
还有另一个方式:
void* mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset) 的最后一个
直接指定物理地址,前提是用户知道板子的物理地址
//=========================================================
熟悉如下概念
内核物理地址
内核虚拟地址
page(页)
页帧
vma
前四个之间的转换关系见上文代码
推荐博客:https://www.cnblogs.com/pengdonglin137/p/8149859.html
//=========================================================
(验证)
映射前(main中加sleep)
root@ubuntu:~# cat /proc/2741/maps
00400000-00401000 r-xp 00000000 08:01 269767 /home/kiss1994/study/test35/main
00600000-00601000 r--p 00000000 08:01 269767 /home/kiss1994/study/test35/main
00601000-00602000 rw-p 00001000 08:01 269767 /home/kiss1994/study/test35/main
0113f000-01160000 rw-p 00000000 00:00 0 [heap]
7f41c2186000-7f41c2346000 r-xp 00000000 08:01 396057 /lib/x86_64-linux-gnu/libc-2.23.so
7f41c2346000-7f41c2546000 ---p 001c0000 08:01 396057 /lib/x86_64-linux-gnu/libc-2.23.so
7f41c2546000-7f41c254a000 r--p 001c0000 08:01 396057 /lib/x86_64-linux-gnu/libc-2.23.so
7f41c254a000-7f41c254c000 rw-p 001c4000 08:01 396057 /lib/x86_64-linux-gnu/libc-2.23.so
7f41c254c000-7f41c2550000 rw-p 00000000 00:00 0
7f41c2550000-7f41c2576000 r-xp 00000000 08:01 396068 /lib/x86_64-linux-gnu/ld-2.23.so
7f41c2759000-7f41c275c000 rw-p 00000000 00:00 0
7f41c2775000-7f41c2776000 r--p 00025000 08:01 396068 /lib/x86_64-linux-gnu/ld-2.23.so
7f41c2776000-7f41c2777000 rw-p 00026000 08:01 396068 /lib/x86_64-linux-gnu/ld-2.23.so
7f41c2777000-7f41c2778000 rw-p 00000000 00:00 0
7ffc07773000-7ffc07794000 rw-p 00000000 00:00 0 [stack]
7ffc077a0000-7ffc077a3000 r--p 00000000 00:00 0 [vvar]
7ffc077a3000-7ffc077a5000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
映射后(main中加sleep)
root@ubuntu:~# cat /proc/2741/maps
00400000-00401000 r-xp 00000000 08:01 269767 /home/kiss1994/study/test35/main
00600000-00601000 r--p 00000000 08:01 269767 /home/kiss1994/study/test35/main
00601000-00602000 rw-p 00001000 08:01 269767 /home/kiss1994/study/test35/main
0113f000-01160000 rw-p 00000000 00:00 0 [heap]
7f41c2186000-7f41c2346000 r-xp 00000000 08:01 396057 /lib/x86_64-linux-gnu/libc-2.23.so
7f41c2346000-7f41c2546000 ---p 001c0000 08:01 396057 /lib/x86_64-linux-gnu/libc-2.23.so
7f41c2546000-7f41c254a000 r--p 001c0000 08:01 396057 /lib/x86_64-linux-gnu/libc-2.23.so
7f41c254a000-7f41c254c000 rw-p 001c4000 08:01 396057 /lib/x86_64-linux-gnu/libc-2.23.so
7f41c254c000-7f41c2550000 rw-p 00000000 00:00 0
7f41c2550000-7f41c2576000 r-xp 00000000 08:01 396068 /lib/x86_64-linux-gnu/ld-2.23.so
7f41c2759000-7f41c275c000 rw-p 00000000 00:00 0
7f41c2774000-7f41c2775000 rw-s 00000000 00:06 406 /dev/demo_dev
7f41c2775000-7f41c2776000 r--p 00025000 08:01 396068 /lib/x86_64-linux-gnu/ld-2.23.so
7f41c2776000-7f41c2777000 rw-p 00026000 08:01 396068 /lib/x86_64-linux-gnu/ld-2.23.so
7f41c2777000-7f41c2778000 rw-p 00000000 00:00 0
7ffc07773000-7ffc07794000 rw-p 00000000 00:00 0 [stack]
7ffc077a0000-7ffc077a3000 r--p 00000000 00:00 0 [vvar]
7ffc077a3000-7ffc077a5000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
通过在map前后加sleep打印maps的信息,得出用户空间在如下位置映射了一个页
7f41c2774000-7f41c2775000 rw-s 00000000 00:06 406 /dev/demo_dev
7f41c2775000 - 7f41c2774000 = 0x1000
"7f41c2774000"是vma->vm_start的值,"7f41c2775000"是vma->vm_end的值
vma表示的user空间虚拟内存区域大小
"rw-s"表示的是vma->vm_flags,其中’s’表示share,'p’表示private
"00000000"表示偏移量,也就是vma->vm_pgoff的值
"00:06"表示该设备节点的主次设备号
"406"表示该设备节点的inode值
"/dev/demo_dev"表示设备节点的名字
//==============================================================================
该进程的进程栈如下:
7ffc07773000-7ffc07794000 rw-p 00000000 00:00 0 [stack]
7ffc07794000 - 7ffc07773000 = 0x21000B = 132KB
mmap区恰好位于栈区和堆区之之间,它和栈区都是向下增长,其他区都是向上增长
进程的栈太大,会导致mmap区太小
mmap区除了mmap映射用,还会给动态库的映射和 匿名映射用
参考博客:https://www.cnblogs.com/pengdonglin137/p/8150462.html
vmalloc也可以和用户空间进行映射,使用它分配的内存虚拟地址是连续的,但是不保证物理页帧也连续。
vmalloc在分配内存时是调用alloc_page一页一页的分配,就是每次从伙伴系统只分配一页,
然后将分配得到的单页物理页帧映射到内核的vmalloc区连续的虚拟地址上。
比如:
用vmalloc分配128KB的内存,vmalloc计算发现需要分配32个page,
然后会调用32次alloc_page(),每次从伙伴系统分配1个page,
每分配一个page就将该page映射到准备好的连续的虚拟地址上,
当然也就无法保证这些page之间对应的物理页帧的连续性。
在调用remap_pfn_range的时候就需要注意,必须一页一页地映射。
通过vmalloc分配的页,要用vmalloc_to_pfn 进行页帧的映射。
static void *kbuff = vmalloc(BUF_SIZE);
static int remap_pfn_mmap(struct file *file, struct vm_area_struct *vma)
{
unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
unsigned long virt_start = (unsigned long)kbuff + (unsigned long)(vma->vm_pgoff << PAGE_SHIFT);
unsigned long pfn_start = (unsigned long)vmalloc_to_pfn((void *)virt_start);
unsigned long size = vma->vm_end - vma->vm_start;
int ret = 0;
unsigned long vmstart = vma->vm_start;
int i = 0;
printk("phy: 0x%lx, offset: 0x%lx, size: 0x%lx\n", pfn_start << PAGE_SHIFT, offset, size);
while (size > 0) {
// 一页一页映射
ret = remap_pfn_range(vma, vmstart, pfn_start, PAGE_SIZE, vma->vm_page_prot);
if (ret) {
printk("%s: remap_pfn_range failed at [0x%lx 0x%lx]\n",
__func__, vmstart, vmstart + PAGE_SIZE);
ret = -ENOMEM;
goto err;
} else
printk("%s: map 0x%lx (0x%lx) to 0x%lx , size: 0x%lx, number: %d\n", __func__, virt_start,
pfn_start << PAGE_SHIFT, vmstart, PAGE_SIZE, ++i);
if (size <= PAGE_SIZE)
size = 0;
else {
size -= PAGE_SIZE;
vmstart += PAGE_SIZE;
virt_start += PAGE_SIZE;
pfn_start = vmalloc_to_pfn((void *)virt_start);
}
}
return 0;
err:
return ret;
}
remap_pfn_range映射内存中的保留页和设备IO内存
附件1
kmalloc 和 __get_free_page 的区别
kmalloc() 分配连续的内核虚拟地址,用于小内存分配。
__get_free_page() 分配连续的内核虚拟地址,用于整页分配。
前者基于slab(slab基于buddy),后者基于buddy,两者最终都使用 __alloc_page() 来返回物理页的page结构。
都是在“物理内存映射区” lowmem 进行内存分配,其内核虚拟地址与物理地址仅相差一个常量。
附件2
匿名映射是否可以作为父子进程通信的手段?
学习博客:https://blog.csdn.net/liuyuchen282828/article/details/99537988
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/wait.h>
int var=100;
int main(void) {
int *p;
pid_t pid;
//不使用文件参数传-1
p=(int *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);//对映射区>各自独占
if(p==MAP_FAILED){ //不是p==NULL时出错
perror("mmap,error");
exit(1);
}
//完成数据传递
pid=fork();
if(pid==0){
*p=2000;
var=1000;
printf("child,*p=%d,var = %d\n",*p,var);
}else{
sleep(2);
printf("parent,*p = %d,var = =%d\n",*p,var);
wait(NULL);
int ret= munmap(p,4);
if(ret==-1){
perror("munmap error");
exit(1);
}
}
return 0;
}
是可以通信的,前提是要设置好 MAP_ANONYMOUS 和 MAP_SHARED
一个用于匿名(懒得映射某个文件),一个强调共享
借助文件系统,mmap可以实现血缘进程的映射(支持命名和匿名),非血缘进程的映射(只支持命名)
如果 open 时 O_RDONLY ,mmap 时 PROT 参数指定 PROT_READ|PROT_WRITE ,会映射出错
创建映射区的权限小于等于打开文件的权限,映射区创建的过程中存在一次读文件操作权限不足。
映射区的释放与文件关闭无关。只要映射建立成功,文件可以立即关闭
附件3
验证如下语句:
当调用malloc分配内存的时候,如果传给malloc的参数小于128KB时,系统会在heap区分配内存,
分配的方式是向高地址调整brk指针的位置。
当传给malloc的参数大于128KB时,系统会在mmap区分配,即分配一块新的vma