diff --git a/README.md b/README.md index 15f9d06..793d925 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,335 @@ -# mmap_munmap_clone -## mmap实现 -参考了1.0源码,定义了新的结构体vmarea_struct,在task_struct添加了对应的变量mmap,并进行了初始化 -在memmory.c中实现了这个函数 -由于测试文件只传了两个参数,剩余的自己进行了定义 +### 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. + +