|
|
|
|
### 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_address<m1->vm_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对应的字符串相同
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<img src="C:\Users\admin\AppData\Local\Temp\WeChat Files\1e4436d61190e2b3451b91b14af78bd.png" alt="1e4436d61190e2b3451b91b14af78bd" style="zoom:150%;" />
|
|
|
|
|
|
|
|
|
|
#### munmap
|
|
|
|
|
|
|
|
|
|
#### clone
|
|
|
|
|
|
|
|
|
|
### 问题与建议
|
|
|
|
|
|
|
|
|
|
1.初始化问题
|
|
|
|
|
|
|
|
|
|
2.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|