實現兩個功能:分別是mmap與munmap,將檔案對映到記憶體當中,併為一個執行緒記錄他管理的檔案所在的頁表目錄。
函式原型如下:
char* mmap( char* addr, int len, int prot, int flags,int fd, int off);
int munmap( char* addr , int len );
其中mmap引數含義分別是對映地址(為0時由核心程式碼決定),對映記憶體長度,對映檔案許可權(read/write),對映型別(SHARED/PRIVATE,為SHARED則將檔案寫入到物存中),檔案描述符,偏移位置。
為了實現對映,對於一個程序我們當然需要分配一塊記憶體區域出來供mmap使用。這裡我的思路是以檔案為單元對實體記憶體區域進行記錄,因為一個程序可以開啟多個檔案,所以要建立一個檔案結構體陣列用來表示已經對映的記憶體區域片:
#define VMA_MAX 16
struct VMA{
int valid;
uint64 addr;
int len;
int prot;
int flags;
int off;
struct file* f;
uint64 mapcnt;
};
struct proc {
//...
struct VMA vma[VMA_MAX]; // virtual memory address field arr
//...
};
```cpp
至於記憶體分配策略,需要考慮是從堆區向下開始分還是棧區向上。
![](https://img2024.cnblogs.com/blog/3331448/202412/3331448-20241209193847783-1782044609.png)
如果是從棧區開始,那麼就是和sbrk繫結在一起,但這一部分有懶空間分配,所以從堆區,heap處向下分配。
對於sys_mmap,直接找到一個當前程序未使用的位置進行程序賦值。對於分配空間,直接從maxaddr向下分配。
uint64
sys_mmap(void){
uint64 addr;
int len , prot , flags , fd , off;
if( argaddr( 0 , &addr ) < 0 || argint( 1 , &len ) < 0 || argint( 2 , &prot ) < 0 || argint( 3 , &flags ) < 0 || argint( 4 , &fd ) < 0 || argint( 5 , &off ) < 0 )
return -1;
struct proc* p = myproc();
struct file* f = p->ofile[fd];
// to ensure the prot
if( ( flags == MAP_SHARED && f->writable == 0 && (prot&PROT_WRITE)) )
return -1;
// to find a empty vma and init it
int idx;
for( idx = 0 ; idx < VMA_MAX ; idx++ )
if( p->vma[idx].valid == 0 )
break;
if( idx == VMA_MAX )
panic("no empty vma field");
struct VMA* vp = &p->vma[idx];
vp->valid = 1;
vp->len = len;
vp->flags = flags;
vp->off = off;
vp->prot = prot;
vp->f = f;
filedup( f ); //increase the ref of the file
vp->addr = (p->maxaddr-=len); // asign a useable virtual address to the vma field , and maintain the maxaddr
return vp->addr;
}
對於sys_unmap,如果頁表發生了修改,就進行寫回操作。
uint64
sys_munmap(void)
{
uint64 addr;
int length;
if(argaddr(0, &addr) || argint(1, &length))
return -1;
struct proc *p = myproc();
for(int i = 0; i < MAXMMAP; ++i)
{
if((p->map_addr[i].addr == addr) || (p->map_addr[i].addr + p->map_addr[i].length == addr + length))
{
if(p->map_addr[i].addr == addr) p->map_addr[i].addr += length;
p->map_addr[i].length -= length;
if((p->map_addr[i].flags & MAP_SHARED) && (p->map_addr[i].prot & PROT_WRITE))
filewrite(p->map_addr[i].mfile, addr, length);
uvmunmap(p->pagetable, addr, length/PGSIZE, 1);
if(p->map_addr[i].length == 0)
{
fileclose(p->map_addr[i].mfile);
p->map_addr[i].used = 0;
}
break;
}
}
return 0;
}
最後在usertrap中實現缺頁,這裡採用逐頁分配,也即讀到一處mmap也只分配相應那一頁,不分配檔案的所有頁。
else if( r_scause() == 0xd )
{
uint64 addr = r_stval();
struct VMA* vp = 0;
//to fina the target vma
for( int i=0 ; i<VMA_MAX ; i++ )
if( p->vma[i].addr <= addr && addr < p->vma[i].addr + p->vma[i].len && p->vma[i].valid == 1 )
{
vp = &p->vma[i];
break;
}
if( vp != 0 )
{
uint64 mem = (uint64)kalloc();
memset( (void*)mem , 0 , PGSIZE );
if( -1 == mappages( p->pagetable , addr, PGSIZE , mem , PTE_U | PTE_V | ( vp->prot << 1 ) ) )
panic("pagefault map error");
vp->mapcnt += PGSIZE; //maintain the mapcnt
ilock( vp->f->ip );
readi( vp->f->ip , 0 , mem , addr-vp->addr , PGSIZE); //copy a page of the file from the disk
iunlock( vp->f->ip );
}
else
{
printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
printf(" sepc=%p stval=%p\n", r_sepc(), r_stval());
p->killed = 1;
}
}
根據提示修改exit與fork兩處系統呼叫:
![](https://img2024.cnblogs.com/blog/3331448/202412/3331448-20241209195936363-674978211.png)
![](https://img2024.cnblogs.com/blog/3331448/202412/3331448-20241209195855781-1473869528.png)
修改完畢,測試透過。
![](https://img2024.cnblogs.com/blog/3331448/202412/3331448-20241209200332076-457099824.png)