### mmap函数实验 #### 函数的参数介绍: ~~~ void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); long mmap() prot 页面的权限标志 flags 页面私有/共享的标志 off :文件的偏移量,从文件的哪一部分开始映射到页面中 ~~~ #### vmarea_struct 结构 因为涉及虚拟地址的管理,所以定义了如下结构: ~~~c struct vmarea_struct{ unsigned long vm_start;//虚拟地址开始的地址 unsigned long vm_end;//虚拟地址结束的地址 int mode;//页面权限 int flag;//页面是否共享 int size;//大小 需要与PAGE_SIZE对齐 off_t off;//映射文件的偏移量 struct vmarea_struct *next;//下一个vmarea_struct,链表结构比较简便,会牺牲一些速度 //struct file *dir;//要映射文件的结构 int fd;//映射文件的文件描述符 }; ~~~ 在task_struct加入该结构 ~~~c struct task_struct { .............. struct vmarea_struct *mmap;//映射虚拟地址的链表 }; ~~~ 在INIT_TASK需要进行进行初始化 ~~~c #define INIT_TASK \ ...... /*mmap*/ NULL,\ } ~~~ mmap是将文件的一部分内容映射到进程地址空间之中,原理如下图: ![img](https://img-blog.csdnimg.cn/20200423194816652.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM4Mjg5ODE1,size_16,color_FFFFFF,t_70) ​ step1:在当前进程的虚拟地址空间中,寻找一段空闲的连续的地址空间(寻址方式) ​ step2:寻找并将空闲页映射到对应的虚拟地址空间之中(get_free_page和put_page),并根据flags判断是否将该页面映射到其他进程对应的虚拟内存中(参考了try_to_page函数的实现) ​ step3:将文件内容写到空闲页之中(参考了sys_read和find_entry的实现) mmap内存映射的实现过程,总的来说可以分为三个阶段: ***\*(一)进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域\**** 1. 进程在用户空间调用库函数mmap,原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); 2. 在当前进程的虚拟地址空间中,寻找一段空闲的满足要求的连续的虚拟地址。 3. 为此虚拟区分配一个vm_area_struct结构,接着对这个结构的各个域进行了初始化。 4. 将新建的虚拟区结构(vm_area_struct)插入进程的虚拟地址区域链表。这里采用的链表是最简单的单向链表,会牺牲一些速度,但胜在简便 注意:寻址方式 为简便期间并防止进程调用malloc导致的堆生长与 映射的虚拟地址产生冲突,所以从0x2000000开始映射一直到0x4000000 分配及初始化的代码如下: ~~~c if(start==NULL){ //需要由程序分配合适的地址空间 //由于是借助链表实现 所以需要搜索来找到合适的地址进行使用 if(current->mmap==NULL) buf = 0x2000000;//如果初始化为NULL if(!buf){ struct vmarea_struct *m2 = current->mmap; struct vmarea_struct *m3 = current->mmap->next; for(m3;m3->next;m3 = m3->next){ if(m3->vm_start-m2->vm_end>=size){ buf = m2->vm_end; break; } m2 = m3; } if(!buf) buf = m3->vm_end; } } //初始化 struct vmarea_struct *m1 = (struct vmarea_struct *)malloc(sizeof(struct vmarea_struct)); m1->mode = prot; m1->flag = flags; m1->next = NULL; m1->size = size; m1->vm_start = buf; m1->vm_end = (unsigned long)buf+size; m1->fd = fd; m1->off = off; if(current->mmap==NULL) current->mmap = m1; else{//插到最后一个位置 struct vmarea_struct *m2=current->mmap; for(m2;m2->next;m2=m2->next) ; m2->next = m1; } ~~~ ***\*(二)实现文件物理地址和进程虚拟地址的一一映射关系\**** 5. 用get_free_page获得空闲页 6. 修改页目录项和页表项的内容使其能够指向空闲页,这片虚拟地址并没有任何数据关联到主存中 7. ~~~c //接下来进行映射等操作 //参考了try_to_share的实现 unsigned long from; unsigned long to; unsigned long from_page; unsigned long to_page; unsigned long phys_addr; unsigned long page; unsigned long tmp; unsigned long vm_address=m1->vm_start; while(vm_addressvm_end){ from_page = to_page = ((vm_address>>20)&0xffc);//逻辑的页目录项偏移 from_page +=((current->start_code>>20)&0xffc);//该进程目录项地址 //from处是否存在页目录项 否则进行申请 from = *(unsigned long *) from_page; from &=0xfffff000; from_page = from +((vm_address>>10)&0xffc);//页表项指针 phys_addr = *(unsigned long *) from_page;//页表项内容 if(!(page = get_free_page())){ printk("no free page\n"); return -1; } /* phys_addr = page |0x7; mem_map[(page-LOW_MEM)>>12]++;*/ /*if(!(*(unsigned long *) from_page & 0x1)){ if(page = get_free_page()) phys_addr = page |0x7,mem_map[(page-LOW_MEM)>>12]++; else oom(); }*/ put_page(page,vm_address); //对页面分配权限 *(unsigned long *)from_page &= (prot&PROT_WRITE)?0xffffffff:0xfffffffd; //对flags进行处理,重点处理的是MAP_SHARE,私有的话不需要进行多余的处理 //需要将所有进程共享同一页面 if(flags&MAP_SHARED){ struct task_struct ** task; for (task = &LAST_TASK ; task>&FIRST_TASK;--task){ if (!*task) continue; else if (current == *task) continue; else if(!try_to_share(vm_address,*task)) printk("current pid %d cann't share memory\n",(*task)->pid); } } vm_address += PAGE_SIZE; } ~~~ ***\*(三)进程实现文件内容到物理内存的拷贝\**** 这里采用的实现是在函数内就将文件内容拷贝到物理页面上 为映射分配了新的虚拟地址区域后,通过待映射的文件指针,在文件描述符表中找到对应的文件描述符,通过文件描述符,链接到内核“已打开文件集”中该文件的文件结构体(struct file),每个文件结构体维护着和这个已打开文件相关各项信息。 9.参考find_entry和sys_read函数,读取文件内容,并写到内存空间中 ~~~c if (!(block = dir->i_zone[0])) return NULL; if (!(bh = bread(dir->i_dev,block))) return NULL; char *s = (char *) bh->b_data; char *p1=(char *)s; char *p2=(char *)buf; while(len--) put_fs_byte(*(p1++),p2++); ~~~ ### munmap实验 munmap函数的功能是解除内存映射 #### 实现原理及步骤 ***\*(一)在当前进程的链表中能够寻找到对应的vmarea_struct,并将之从链表中删除\**** 1.从当前进程链表开头寻找,找到为止 ***\*(二)根据寻找到的vmarea_struct结构判断页面的权限,如果是有写权限的,需要将页面内容写到对应的文件之中\**** 将buf内容写到fd之中的函数,参照了sys_write的实现 ~~~c int mmap_wirte(int fd,off_t pos,char *buf,int count){ struct file * file; struct m_inode * inode; if (fd>=NR_OPEN || count <0 || !(file=current->filp[fd])) return -EINVAL; if (!count) return 0; inode=file->f_inode; file->f_pos = pos; if (inode->i_pipe) return (file->f_mode&2)?write_pipe(inode,buf,count):-EIO; if (S_ISCHR(inode->i_mode)) return rw_char(WRITE,inode->i_zone[0],buf,count,&file->f_pos); if (S_ISBLK(inode->i_mode)) return block_write(inode->i_zone[0],&file->f_pos,buf,count); if (S_ISREG(inode->i_mode)) return file_write(inode,file,buf,count); printk("(Write)inode->i_mode=%06o\n\r",inode->i_mode); return -EINVAL; } ~~~ ***\*(三)解除虚拟地址对物理页面的映射,需要判断该页面是否是共享的,如果是共享的需要进行改动\**** 与mmap的映射相似,只是对页表项的最后一位进行了改动 ~~~c //取消进程p的共享 与try_share很类似 只是少了几步 static int cancle_share(unsigned long address, struct task_struct * p) { unsigned long from; unsigned long from_page; unsigned long phys_addr; from_page = ((address>>20) & 0xffc); from_page += ((p->start_code>>20) & 0xffc); /* is there a page-directory at from? */ from = *(unsigned long *) from_page; from &= 0xfffff000; from_page = from + ((address>>10) & 0xffc); phys_addr = *(unsigned long *) from_page; /* is the page clean and present? */ phys_addr &= 0xfffff000; if (phys_addr >= HIGH_MEMORY || phys_addr < LOW_MEM) return 0; /* share them: write-protect */ *(unsigned long *) from_page &= ~1; phys_addr -= LOW_MEM; phys_addr >>= 12; mem_map[phys_addr]--; return 1; } ~~~ ### 函数验证 #### mmap 测试文件的函数 测试的思路是,打开一个文件并将该文件映射到内存中,输出对应位置的字符串 ~~~c void test_mmap(void){ char *array; const char *str = " Hello, mmap success."; int fd; /*打开文件并将文件内容写入到test_mmap.txt中*/ fd = open("test_mmap.txt", O_RDWR | O_CREAT, S_IRUSR|S_IWUSR); write(fd, str, strlen(str)); fstat(fd, &kst); printf("file len: %d\n", (int)kst.st_size); //将test_mmap.txt文件映射到内存中,虚拟地址是array array = mmap(NULL, kst.st_size, PROT_WRITE | PROT_READ, MAP_FILE | MAP_SHARED, fd, 0); printf("mmap addr: %x\n", (unsigned int)array); if (array == MAP_FAILED) { printf("mmap error.\n"); }else{ printf("mmap content: %s\n", array); munmap(array, kst.st_size); } close(fd); } ~~~ 测试结果如下:可以看到与str对应的字符串相同 1e4436d61190e2b3451b91b14af78bd #### munmap #### clone ### 问题与建议 1.初始化问题 2.