[《[arm驅動]linux設備地址映射到用戶空間》涉及內核驅動函數二個,內核結構體二個,分析了內核驅動函數二個;可參考的相關應用程序模板或內核驅動模板二個,可參考的相關應用程序模板或內核驅動四個
一、問題描述:一般情況下,用戶空間是不可能也不應該直接訪問設備的,但是,設備驅動程序中可實現mmap()函數,這個函數可使用戶空間直接訪問設備的物理地址。
1、mmap()函數工作原理:mmap()實現了這樣的一個映射過程,它將用戶的內存空間的一般內存(準確來說是執行mmap進程的映射區域內存)與設備內存關聯,當用戶訪問用戶空間的這段地址範圍時,實際上會轉化爲對設備的訪問(linux上一切皆文件)。
2、mmap優點:1、對於設備文件,最大的有點就是用戶空間可以直接訪問設備內存;2、普通文件被映射到進程地址空間後,進程進程訪問文件的速度也變塊,不必再調read(),write(),可以用memcpy,strcpy等操作寫文件,寫完後用msync()同步一下。(感覺還是很抽象,看了後面的實例一就明白了)
3、應用場景:mmap()的這種能力用於顯示適配器一類的設備,屏幕幀的像素不再需要從一個用戶空間到內核空間的複製過程。
二、應用程序相關函數
1、建立映射:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
a) 參數含義:
addr: 指定映射的起始地址, 通常設爲NULL, 由系統指定。
length: 映射到內存的文件長度。
prot: 映射區的保護方式, 可以是:
PROT_EXEC: 映射區可被執行
PROT_READ: 映射區可被讀取
PROT_WRITE: 映射區可被寫入
PROT_NONE 映射區不可訪問.
flags: 映射區的特性, 可以是:
MAP_SHARED:對此區域所做的修改內容獎寫入文件內;允許其他映射該文件的進程共享,意思是:n個mmap.out程序在運行,這n個進程的“虛擬內存區域”的物理空間空間都相同。詳看《虛擬內存共享原理圖b》
虛擬內存共享原理圖b
MAP_PRIVATE:對此區域所做的修改不會更改原來的文件內容,對映射區的寫入操作會產生一個映射區的複製(copy-on-write);意思是:n個mmap.out程序在運行,但是虛擬內存區域的物理地址會被內核另外分配。詳看《虛擬內存共享原理圖c》
虛擬內存共享原理圖c
fd: 由open返回的文件描述符, 代表要映射的文件。
offset: 以文件開始處的偏移量, 必須是分頁大小的整數倍, 通常爲0, 表示從文件頭開始映射。
b)返回值:返回成功----函數的返回值爲最後文件映射到進程空間的地址(參照文件內存映射原理圖示 a),進程可直接操作起始地址爲該值的有效地址。返回失敗返回MAP_FAILED(-1),錯誤原因存於errno 中。
2、解除映射:
int munmap(void *addr, size_t length);
3、 同步回寫函數:
int msync(const void *start, size_t length, int flags);
如果您希望立即將數據寫入文件中,可使用msync。
a)參數
start爲記憶體開始位置(mmap函數返回的值---地址),length爲長度。
flags則有三個:
MS_ASYNC : 請Kernel快將資料寫入,發出回寫請求後立即返回
MS_SYNC : 在msync結束返回前,將資料寫入。
MS_INVALIDATE使用回寫的內容更新該文件的其它映射
實例一)mmap普通文件被映射到進程地址空間實例
mmapfile.c
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/mman.h> #include <string.h> #include <stdlib.h> void printfMapChar(char *nameChar, char *mapChar){//打印mapChar的內容 printf("%s = %s\n\n", nameChar,mapChar); } void printfMmapAddr(char *nameChar, char *mmapChar){//打印mmapChar的地址 printf("%s'address = %p\n",nameChar, mmapChar); } void printfDivLine(char *desc){ printf("********%s*******\n", desc); } int main(){ int fd; char *mapChar;//mapchar存放虛擬內存地址 char *checkChar;//驗證是否mapChar是映射地址 printf("mypid is %d\n",getpid());//輸出本pid /*獲得映射區域地址,賦值mapChar*/ fd = open("/tmp/test.txt",O_RDWR); mapChar = mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//獲得映射區域地址MAP_SHARED更改mapchar後改變fd文件內容 /*****************/
Tip:此時mapchar就是虛擬內存區域的物理地址部分的首地址;也就是《文件內存映射原理圖示 a》中的fd文件存儲映射部分對應的的首地址,當進車訪問mapchar這段地址範圍時,實際上會轉化爲對文件fd的訪問
/********打印映射區域內容;和mapChar*********/ printfDivLine("打印映射區域內容;和mapChar"); printfMapChar("mapChar", mapChar); /**************/ /*******通過mapChar將數據寫入映射區域*******/ strcpy(mapChar, "writeSrc,writeSrc,writeSrc,writeSrc,writeSrc,writeSrc,");//寫入映射區域 printfDivLine("通過mapChar將數據寫入映射區域"); printfMapChar("mapChar", mapChar); /**********checkChar驗證*********/ checkChar = mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//獲得映射區域地址 close(fd);//不使用fd時就可以close printfDivLine("checkChar驗證"); printfMmapAddr("mapChar", mapChar); printfMmapAddr("checkChar", checkChar); printfMapChar("checkChar", checkChar); munmap(mapChar, 100);//釋放mapchar的映射,此時文件的映射在內存內然存在 munmap(checkChar, 100); return 0; }
運行結果:
mypid is 28529 ********打印映射區域內容;和mapChar******* mapChar = this is a just test temp file ********通過mapChar將數據寫入映射區域******* mapChar = writeSrc,writeSrc,writeSrc,writeSrc,writeSrc,writeSrc, ********checkChar驗證******* mapChar'address = 0x7f356eaaa000 checkChar'address = 0x7f356eaa9000 checkChar = writeSrc,writeSrc,writeSrc,writeSrc,writeSrc,writeSrc,
Tip:一個進程的內存映象由下面幾部分組成:程序代碼、數據、BSS和棧區域,以及內存映射的區域。一個進程的內存區域可以通過查看/proc/pid/maps
三、給驅動設備添加mmap虛擬內存映射
內核函數一)1、 驅動中的mmap(內核空間):
int(*mmap)(struct file *,struct vm_area_struct *);
2、在struct file_operations中與mmap接口函數關聯
static struct file_operations module_drv_fops = { //............ .mmap = memdev_mmap, //............... }
結構體一)3、struct vm_area_struct(VMA)結構體如下
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. */ struct rb_node vm_rb; struct vm_operations_struct * vm_ops; /* Information about our backing store: */ unsigned long vm_pgoff; /* Offset (within vm_file) in PAGE_SIZE units, *not* PAGE_CACHE_SIZE */ struct file * vm_file; /* File we map to (can be NULL). */ void * vm_private_data; /* was vm_pte (shared mem) */ unsigned long vm_truncate_count;/* truncate_count or restart_addr */ //.................. };
4、struct vm_area_struct(VMA)結構體flag參數
VM_IO將該VMA標記爲內存映射的IO區域,VM_IO會阻止系統將該區域包含在進程的存放轉存(core dump )中
VM_RESERVED標誌內存區域不能被換出
內核函數二) 5、內核mmap中創建頁表函數:remap_pfn_range();
作用用“addr ~ addr + size之間的虛擬地址”構造頁表,參考《虛擬內存共享原理圖b》和《虛擬內存共享原理圖c》
int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr, unsigned long pfn, unsigned long size, pgprot_t prot)
a)參數
1) vma: 虛擬內存區域指針(默認使用vma)
2)addr: 虛擬地址的起始值(默認使用vma->vm_start)
3)pfn:總的來說(pfn = virt_to_phys(void *mem))>>PAGE_SHIFT(常用);或使用默認方式vma->vm_pgoff)
推理:pfn是虛擬地址應該映射到的物理地址所在的物理頁幀號就是物理地址右移PAGE_SHIFT位,若PAGE_SIZE爲4k則PAGE_SHIFT爲12(2的12次方爲4k),因此PAGE_SIZE爲1<<PAGE_SHIFT;虛擬地址可將"物理地址>>PAGE_SHIFT"得到(虛擬地址 = 物理地址>>PAGE_SHIFT)。如何得到物理地址:將驅動設備中某個內存變量用函數virt_to_phys(void *mem)轉換成物理地址;(物理地址 = virt_to_phys(void *mem));
4)size: 要映射的區域的大小。(默認使用vma->vm_end - vma->vm_start)
5)prot: VMA的保護屬性。(默認使用vma->vm_page_prot)
模板一)6、mmap驅動模板
static int memdev_mmap(struct file*file, struct vm_area_struct *vma){ struct VirtualDisk *devp = file->private_data; /*鑾峯緱璁懼緇撴瀯浣撴寚閽?/ vma->vm_flags |= VM_IO; vma->vm_flags |= VM_RESERVED; if (remap_pfn_range(vma,vma->vm_start,virt_to_phys(devp->mem)>>PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot)) return -EAGAIN; return 0; }
file_oprations中添加或修改.mmap
static struct file_operations module_drv_fops = { //............ .mmap = memdev_mmap, //............... }
實例二)7、完整實例
a)驅動部分
//“module_drv”,"module_","module" #include <linux/module.h>//模塊所需的大量符號和函數定義 #include <linux/kernel.h> #include <linux/fs.h>//文件系統相關的函數和頭文件 #include <linux/init.h> //指定初始化和清除函數 #include <linux/delay.h> #include <linux/cdev.h> //cdev結構的頭文件包含<linux/kdev_t.h> #include <linux/device.h> #include <linux/mm.h> //#include <linux/sched.h>//包含驅動程序使用的大部分內核API的定義,包括睡眠函數以及各種變量聲明 #include <asm/uaccess.h>//在內核和用戶空間中移動數據的函數 #include <asm/irq.h> #include <asm/io.h> #include <asm/arch/regs-gpio.h> #include <asm/hardware.h> #define VIRTUALDISK_SIZE 0x1000//4k #define MEM_CLEAR 0x1 #define VIRTUALDISK_MAJOR 250 int VirtualDisk_major = VIRTUALDISK_MAJOR; struct VirtualDisk{ struct cdev cdev;//詳細看cdev機制 unsigned char mem[VIRTUALDISK_SIZE ]; long count; /*記錄設備目前被多少設備打開*/ }; static struct class *module_class; static struct class_device *module_class_dev; struct VirtualDisk *VirtualDiskp; static int module_drv_open(struct inode *inode, struct file *file) { printk("module_dev read\n"); file->private_data = VirtualDiskp; VirtualDiskp->count++; /*增加設備打開次數*/ return 0; } static int module_drv_release(struct inode *inode, struct file *file) { printk("module_dev release\n"); VirtualDiskp->count--; /*減少設備打開次數*/ return 0; } /*seek文件定位函數:seek()函數對文件定位的起始地址可以是文件開頭(SEEK_SET,0)、當前位置(SEEK_CUR,1)、文件尾(SEEK_END,2)*/ static loff_t module_drv_llseek(struct file *file, loff_t offset, int origin){ loff_t ret = 0;/*返回的位置偏移*/ switch (origin) { case SEEK_SET: /*相對文件開始位置偏移*/ if (offset < 0)/*offset不合法*/ { ret = - EINVAL; /*無效的指針*/ break; } if ((unsigned int)offset > VIRTUALDISK_SIZE)/*偏移大於設備內存*/ { ret = - EINVAL; /*無效的指針*/ break; } file->f_pos = (unsigned int)offset; /*更新文件指針位置*/ ret = file->f_pos;/*返回的位置偏移*/ break; case SEEK_CUR: /*相對文件當前位置偏移*/ if ((file->f_pos + offset) > VIRTUALDISK_SIZE)/*偏移大於設備內存*/ { ret = - EINVAL;/*無效的指針*/ break; } if ((file->f_pos + offset) < 0)/*指針不合法*/ { ret = - EINVAL;/*無效的指針*/ break; } file->f_pos += offset;/*更新文件指針位置*/ ret = file->f_pos;/*返回的位置偏移*/ break; default: ret = - EINVAL;/*無效的指針*/ break; } return ret; } /*設備控制函數:ioctl()函數接受的MEM_CLEAR命令,這個命令將全局內存的有效數據長度清零,對於設備不支持的命令,ioctl()函數應該返回-EINVAL*/ static int module_drv_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg){ struct VirtualDisk *devp = file->private_data;/*獲得設備結構體指針*/ switch (cmd) { case MEM_CLEAR:/*設備內存清零*/ memset(devp->mem, 0, VIRTUALDISK_SIZE); printk(KERN_INFO "VirtualDisk is set to zero\n"); break; default: return - EINVAL; } return 0; } /*讀函數:讀寫函數主要是讓設備結構體的mem[]數組與用戶空間交互數據,並隨着訪問字節數變更返回用戶的文件讀寫偏移位置*/ static ssize_t module_drv_read(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { unsigned long p = *ppos; /*記錄文件指針偏移位置*/ unsigned int countt = count;/*記錄需要讀取的字節數*/ int ret = 0; /*返回值*/ struct VirtualDisk *devp = file->private_data; /*獲得設備結構體指針*/ printk("module_dev read\n"); /*分析和獲取有效的讀長度*/ if (p >= VIRTUALDISK_SIZE ) /*要讀取的偏移大於設備的內存空間*/ return countt ? - ENXIO: 0;/*讀取地址錯誤*/ if (countt > VIRTUALDISK_SIZE - p)/*要讀取的字節大於設備的內存空間*/ countt = VIRTUALDISK_SIZE - p;/*將要讀取的字節數設爲剩餘的字節數*/ /*內核空間->用戶空間交換數據*/ if (copy_to_user(buf, (void*)(devp->mem + p), countt)) { ret = - EFAULT; } else { *ppos += countt; ret = countt; printk("read %d bytes(s) is %ld\n", countt, p); } printk("bytes(s) is %s\n", buf); return ret; } /* file 是文件指針,count 是請求的傳輸數據長度,buff 參數是指向用戶空間的緩衝區,這個緩衝區或者保存要寫入的數據,或者是一個存放新讀入數據的空緩衝區,該地址在內核空間不能直接讀寫,ppos 是一個指針指向一個"long offset type"對象, 它指出用戶正在存取的文件位置. 返回值是一個"signed size type。寫的位置相對於文件開頭的偏移。 */ static ssize_t module_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { unsigned long p = *ppos; /*記錄文件指針偏移位置*/ int ret = 0; /*返回值*/ unsigned int countt = count;/*記錄需要寫入的字節數*/ struct VirtualDisk *devp = file->private_data; /*獲得設備結構體指針*/ printk("module_dev write\n"); /*分析和獲取有效的寫長度*/ if (p >= VIRTUALDISK_SIZE )/*要寫入的偏移大於設備的內存空間*/ return countt ? - ENXIO: 0;/*寫入地址錯誤*/ if (countt > VIRTUALDISK_SIZE - p)/*要寫入的字節大於設備的內存空間*/ countt = VIRTUALDISK_SIZE - p;/*將要寫入的字節數設爲剩餘的字節數*/ /*用戶空間->內核空間*/ if (copy_from_user(devp->mem + p, buf, countt)) ret = - EFAULT; else { *ppos += countt;/*增加偏移位置*/ ret = countt;/*返回實際的寫入字節數*/ printk("written %d bytes(s) from%ld\n", countt, p); } return ret; return 0; } static int memdev_mmap(struct file*file, struct vm_area_struct *vma){ struct VirtualDisk *devp = file->private_data; /*獲得設備結構體指針*/ vma->vm_flags |= VM_IO; vma->vm_flags |= VM_RESERVED; if (remap_pfn_range(vma,vma->vm_start,virt_to_phys(devp->mem)>>PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot)) return -EAGAIN; return 0; } static struct file_operations module_drv_fops = { .owner = THIS_MODULE, /* 這是一個宏,推向編譯模塊時自動創建的__this_module變量 */ .open = module_drv_open, .read = module_drv_read, .write = module_drv_write, .release = module_drv_release, .llseek = module_drv_llseek, .ioctl = module_drv_ioctl, .mmap = memdev_mmap, }; /*將 cdev 結構嵌入一個你自己的設備特定的結構,你應當初始化你已經分配的結構使用以上函數,有一個其他的 struct cdev 成員你需要初始化. 象 file_operations 結構,struct cdev 有一個擁有者成員,應當設置爲 THIS_MODULE,一旦 cdev 結構建立, 最後的步驟是把它告訴內核, 調用: cdev_add(&dev->cdev, devno, 1);*/ static void VirtualDisk_setup_cdev(struct VirtualDisk *dev, int minorIndex){ int err; int devno = MKDEV(VirtualDisk_major, minorIndex); cdev_init(&dev->cdev, &module_drv_fops); dev->cdev.owner = THIS_MODULE; err = cdev_add(&dev->cdev, devno, 1); if(err){ printk("error %d cdev file added\n", err); } } static int module_drv_init(void) { int result; dev_t devno = MKDEV(VirtualDisk_major, 0); if(VirtualDisk_major){ result = register_chrdev_region(devno, 1, "module"); }else{ result = alloc_chrdev_region(&devno, 0, 1, "module"); VirtualDisk_major = MAJOR(devno); } if(result < 0 ){ return result; } VirtualDiskp = kmalloc(sizeof(struct VirtualDisk), GFP_KERNEL); if(!VirtualDiskp){ result = -ENOMEM; goto fail_malloc; } memset(VirtualDiskp, 0, sizeof(struct VirtualDisk)); VirtualDisk_setup_cdev(VirtualDiskp, 0); module_class = class_create(THIS_MODULE, "module_drv"); if (IS_ERR(module_class)) return PTR_ERR(module_class); module_class_dev = class_device_create(module_class, NULL, MKDEV(VirtualDisk_major, 0), NULL, "module"); /* /dev/xyz */ if (IS_ERR(module_class_dev)) return PTR_ERR(module_class_dev); return 0; fail_malloc: unregister_chrdev_region(devno, 1); return result; } static void module_drv_exit(void) { cdev_del(&VirtualDiskp->cdev); kfree(VirtualDiskp); unregister_chrdev_region(MKDEV(VirtualDisk_major, 0), 1); class_device_unregister(module_class_dev); class_destroy(module_class); } module_init(module_drv_init); module_exit(module_drv_exit); MODULE_LICENSE("GPL");
實例三)b)與驅動對應的應用程序部分
#include <stdio.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<unistd.h> #include<sys/mman.h> int main(){ int fd; char *start; //char buf[100]; char *buf; char *end; char key_vals[20]; /*打開文件*/ printf("mypid is %d",getpid()); fd = open("/dev/module",O_RDWR); buf = (char *)malloc(100); memset(buf, 0, 100); start=mmap(NULL,10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); end = mmap(NULL, 20,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//addr爲NULL,由系統分配 /* 讀出數據 */ strcpy(buf,start); sleep (1); printf("buf 1 = %s\n",buf); /* 寫入數據 */ strcpy(start,"Buf Is Not Null!rgrgrfgfgfdg"); memset(buf, 0, 100); strcpy(buf,start); sleep (1); printf("buf 2 = %s\n",buf); /* 寫入數據 */ strcpy(end,"is it reality! is not sure,are you ok, make sure ,you"); memset(buf, 0, 100); strcpy(buf,start); sleep (1); printf("buf 3 = %s\n",buf); printf("buf 3 = %s\n",end); read(fd, key_vals, sizeof(key_vals)); printf("key_vals 3 = %s\n",key_vals); // while(1); munmap(start,10); /*解除映射*/ munmap(end,20); /*解除映射*/ free(buf); close(fd); return 0; }
第三部分:struct stat 作用
1、stat,lstat,fstat1 函數都是獲取文件(普通文件,目錄,管道,socket,字符,塊()的屬性。函數原型#include <sys/stat.h>
2、向stat,fstat1、lstat傳入文件名字path、fd、path,獲取文件對應屬性buf。
int stat(const char *path, struct stat *buf); //文件路徑或文件名 int fstat(int fd, struct stat *buf);//文件描述符 int lstat(const char *path, struct stat *buf);//連接文件
結構體二)struct stat結構
struct stat { mode_t st_mode; //文件對應的模式,文件,目錄等 ino_t st_ino; //inode節點號 dev_t st_dev; //設備號碼 dev_t st_rdev; //特殊設備號碼 nlink_t st_nlink; //文件的連接數 uid_t st_uid; //文件所有者 gid_t st_gid; //文件所有者對應的組 off_t st_size; //普通文件,對應的文件字節數(常用) time_t st_atime; //文件最後被訪問的時間 time_t st_mtime; //文件內容最後被修改的時間 time_t st_ctime; //文件狀態改變時間 blksize_t st_blksize; //文件內容對應的塊大小 blkcnt_t st_blocks; //文件內容對應的塊數量 };
四、與mmap應用程序中“普通文件虛擬內存映射模板和實例
模板二)1、 mmap()應用程序模板
int fd; /*獲得映射區域地址,賦值mapChar*/ fd = open("/tmp/test.txt",O_RDWR); struct stat fileStat; /* 獲取文件的屬性 */ if ((fstat(fd, &fileStat)) == -1) { perror("fstat"); } unsigned int fileBufferSize; fileBufferSize = fileStat.st_size;/*mmap回寫時,字節最大大小 爲fileStat.st_size,所以定義字節大fileStat.st_size*/ mmap(NULL,fileBufferSize,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//獲得映射區域地址 munmap(checkChar, fileBufferSize);
實例四)2、完整實例
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/mman.h> #include <string.h> #include <stdlib.h> #include <unistd.h> void printfMapChar(char *nameChar, char *mapChar){ printf("%s = %s\n\n", nameChar,mapChar); } void printfDivLine(char *desc){ printf("********%s*******\n", desc); } int main(){ int fd; char *mapChar; char *checkChar;//驗證是否mapChar是映射地址 struct stat fileStat; printf("mypid is %d\n",getpid());//輸出本pid /*獲得映射區域地址,賦值mapChar*/ fd = open("/tmp/test.txt",O_RDWR); /* 獲取文件的屬性 */ if ((fstat(fd, &fileStat)) == -1) { perror("fstat"); } unsigned int fileBufferSize; fileBufferSize = fileStat.st_size;/*mmap回寫時,字節最大大小 爲fileStat.st_size,所以定義字節大fileStat.st_size*/
Tip:mmap回寫時,回寫字節最大大小爲fileStat.st_size,所以定義字節大fileStat.st_size。(這個我沒有根據,只是實驗結果是這樣)
mapChar = mmap(NULL,fileBufferSize,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//獲得映射區域地址MAP_SHARED更改mapchar後改變fd文件內容 /*****************/ /********打印映射區域內容;和mapChar*********/ printfDivLine("打印映射區域內容;和mapChar"); printfMapChar("mapChar", mapChar); /**************/ /*******通過mapChar將數據寫入映射區域*******/ strcpy(mapChar, "writeSrc,writeSrc,writeSrc,writeSrc,writeSrc,writeSrc,");//寫入映射區域 printfDivLine("通過mapChar將數據寫入映射區域"); printfMapChar("mapChar", mapChar); /**********checkChar驗證*********/ checkChar = mmap(NULL,fileBufferSize,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//獲得映射區域地址 close(fd);//不使用fd時就可以close printfDivLine("checkChar驗證"); printfMapChar("checkChar", checkChar); munmap(mapChar, fileBufferSize);//釋放mapchar的映射,此時文件的映射在內存內然存在 munmap(checkChar, fileBufferSize); return 0; }