xv6 lab10: mmap

Thaudmin發表於2024-12-09

實現兩個功能:分別是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)

相關文章