You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

9.7 KiB

mmap函数实验

函数的参数介绍:

void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

long mmap()

prot 页面的权限标志

flags 页面私有/共享的标志

off :文件的偏移量,从文件的哪一部分开始映射到页面中

vmarea_struct 结构

因为涉及虚拟地址的管理,所以定义了如下结构:

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加入该结构

struct task_struct {
..............
	struct vmarea_struct *mmap;//映射虚拟地址的链表
};

在INIT_TASK需要进行进行初始化

#define INIT_TASK \
......
/*mmap*/	NULL,\
}

mmap是将文件的一部分内容映射到进程地址空间之中原理如下图

img

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

    分配及初始化的代码如下:

    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;
    	}
    

*(二)实现文件物理地址和进程虚拟地址的一一映射关系*

  1. 用get_free_page获得空闲页

  2. 修改页目录项和页表项的内容使其能够指向空闲页,这片虚拟地址并没有任何数据关联到主存中

//接下来进行映射等操作
	//参考了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函数读取文件内容并写到内存空间中

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的实现

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的映射相似只是对页表项的最后一位进行了改动

//取消进程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

测试文件的函数

测试的思路是,打开一个文件并将该文件映射到内存中,输出对应位置的字符串

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.初始化问题