天天看點

uCore作業系統程式設計實驗手記(八)對uCore檔案系統的認識Lab8

uCore作業系統程式設計實驗手記(八)

  • 對uCore檔案系統的認識
    • 檔案系統的層次結構
    • 通用檔案系統通路接口
    • 虛拟檔案系統VFS
    • Simple FS檔案系統
      • 記憶體中的索引節點
    • 裝置接口層
    • 打開檔案的處理流程
  • Lab8
    • 完成讀檔案操作的實作
    • 完成基于檔案系統的執行程式機制的實作

對uCore檔案系統的認識

本實驗對象是檔案系統。

檔案系統是作業系統4大子產品之一,其負責組織外存上的資料,保證其可讀性、可靠性和高效性等。在作業系統的4大子產品中,記憶體管理和裝置管理是最為底層的部分,而程序管理和檔案管理則相對屬于上層功能,因為和硬體直接交流比較少。這四個子產品中,每個部分又分為若幹子層,以實作不同程度的抽象和功能定義。

事實上,雖然檔案系統隻占有八個實驗中的一部分,但并不是說檔案系統的技術細節僅僅用這一個實驗的練習就能夠說的非常清楚。下面就會看到,檔案系統的複雜性不亞于另外三個中的任何一個。

檔案系統的層次結構

檔案系統仍然是按照層級化的結構來設計。

在傳統UNIX系統中,檔案系統的要素被抽象化為4個内容:即檔案(file)、目錄項(dentry)、索引節點(inode)和安裝點(mount point)。換句話說,任何一個檔案系統都離不開這些方面的定義。例如,Windows系統中應用的FAT12/16系統:在磁盤的引導扇區中,存放着整個檔案系統的全局資訊。這個就相當于UNIX檔案系統中的超級塊。其功能同安裝點類似。而後緊跟的是FAT表,由于FAT檔案系統的檔案以鍊式方式組織,是以這個FAT表起到了一個索引的作用。而後緊跟的是根目錄區,這個區中存放的是若幹結構數組,每個項描述了一個根目錄中的檔案/檔案夾的具體資訊。這個就是前面說到的目錄項,也兼有部分索引inode的功能。再往後就是資料區了,存放各個檔案中的資料,FAT檔案系統視檔案為連續的位元組流,不細化檔案的内部結構。

本次實驗的uCore系統的檔案系統是模仿Linux系統的。其采用的具體檔案系統Simple File System(SFS)也是類UNIX的,是以和上述Windows中的檔案系統不太一樣。首先認識uCore作業系統的檔案子系統的層次結構。

主要區分4個層次:

通用檔案系統通路接口層、檔案系統抽象層、Simple FS檔案系統層、外設接口層。這裡面我首先貼一張實驗指導書中的圖:

uCore作業系統程式設計實驗手記(八)對uCore檔案系統的認識Lab8

最頂層是通用檔案系統通路接口,這一層是檔案系統功能的高度抽象。包括相關的庫、使用者态接口和核心态系統調用實作。如:read()、sys_read()函數等。

下一層是虛拟檔案系統VFS。虛拟檔案系統是UNIX系統為向多種不同的檔案系統提供支援而采取的一種手段。即,高度抽象檔案系統的共性特征及其相關的功能、操作,定義一系列的資料結構和調用接口。當使用某一種具體檔案系統時,直接将有關的函數和描述結構與VFS對接即可。

下一層是具體檔案系統SFS。這是檔案系統特征、特性、功能的具體化。例如檔案系統以何種方式組織,檔案的結構是什麼樣的,對檔案、目錄的各種函數如何定義等等。

最下面一層是外設通路接口,顯然這一層要和裝置管理對接(這又是一個複雜的層次結構),是以說,檔案子系統是工作在裝置管理子系統之上的。這一層将封裝裝置通路的具體接口,但不涉及具體的過程。例如,io_out()函數這個接口屬于這一層。但函數的具體實作細節不屬于檔案系統考慮的範疇。

通用檔案系統通路接口

之前提到過,這部分主要是一些和檔案通路有關的庫函數、使用者态接口和核心态系統調用接口。open()函數、close()函數、read()函數、write()函數都是能在程式設計時使用的使用者态接口函數。調用這類函數,将可以層層到達相關的系統調用接口,如sys_read()函數等。舉個例子:

該read()函數将通過系統調用接口,調用sys_read()核心函數。參數filehandle是通過open函數獲得的檔案句柄,因為使用檔案前必須要打開檔案以獲得檔案句柄。參數buffer是存放讀取内容的緩沖區指針。參數nbytes是要讀取的位元組數,實際上能夠讀取的位元組數不一定能到這個數字,比如檔案結束。傳回值count是實際讀取的檔案位元組數,如果讀取發生錯誤的話,傳回-1。

與檔案相關的open()、close()、read()、write()使用者庫函數對應的是sys_open()、sys_close()、sys_read()、sys_write()四個系統調用接口。與目錄相關的readdir使用者庫函數對應的是sys_getdirentry()系統調用。這些函數接口将通過syscall函數來獲得ucore的核心服務。當到了ucore核心後,再調用檔案系統抽象層VFS的file接口和dir接口。

虛拟檔案系統VFS

虛拟檔案系統(virtual file system)是對檔案系統的高度抽象。這樣,通用檔案系統通路接口層隻需通路檔案系統抽象層,而不需關心具體檔案系統的實作細節和接口。VFS的實際結構比較複雜,本實驗忽略了很多細節。

file&dir接口層定義了程序在核心中直接通路的檔案相關資訊,這定義在file資料結構中:

struct file {
    enum {
        FD_NONE, FD_INIT, FD_OPENED, FD_CLOSED,
    } status;                         //通路檔案的執行狀态
    bool readable;                    //檔案是否可讀
    bool writable;                    //檔案是否可寫
    int fd;                           //檔案在filemap中的索引值
    off_t pos;                        //通路檔案的目前位置
    struct inode *node;               //該檔案對應的記憶體inode指針
    int open_count;                   //打開此檔案的次數
};
           

這個結構在程序檔案描述符中有何應用?在kern/process/proc.h中的proc_struct結構中描述了程序通路檔案的資料接口files_struct:

struct files_struct {
    struct inode *pwd;                //程序目前執行目錄的記憶體inode指針
    struct file *fd_array;            //程序打開檔案的數組,指向上述file結構體
    atomic_t files_count;             //通路此檔案的線程個數
    semaphore_t files_sem;            //確定對程序控制塊中fs_struct的互斥通路
};
           

當使用者程序打開一個檔案時,将從fd_array數組中取得一個空閑file項,然後會把此file的成員變量node指針指向一個代表此檔案的inode的記憶體起始位址。

inode是位于記憶體的索引節點,它是VFS結構中的重要資料結構,因為它實際負責把不同檔案系統的特定索引節點資訊統一封裝起來,避免了程序直接通路具體檔案系統。其定義如下:

struct inode {
	//聯合體,在不同情況下(裝置檔案系統?SFS?)有不同的形式
	//包含不同檔案系統特定inode資訊的union成員變量
    union {
        struct device __device_info;          //裝置檔案系統記憶體inode資訊
        struct sfs_inode __sfs_inode_info;    //SFS檔案系統記憶體inode資訊
    } in_info;   
    enum {
        inode_type_device_info = 0x1234,
        inode_type_sfs_inode_info,
    } in_type;                          //此inode所屬檔案系統類型
    atomic_t ref_count;                 //此inode的引用計數
    atomic_t open_count;                //打開此inode對應檔案的個數
    struct fs *in_fs;                   //※抽象的檔案系統,包含通路檔案系統的函數指針
    const struct inode_ops *in_ops;   //※抽象的inode操作,包含通路inode的函數指針     
};
           

成員變量in_ops,是對此inode的操作函數指針清單,其資料結構定義如下:

struct inode_ops {
    unsigned long vop_magic;
    int (*vop_open)(struct inode *node, uint32_t open_flags);
    int (*vop_close)(struct inode *node);
    int (*vop_read)(struct inode *node, struct iobuf *iob);
    int (*vop_write)(struct inode *node, struct iobuf *iob);
    int (*vop_getdirentry)(struct inode *node, struct iobuf *iob);
    int (*vop_create)(struct inode *node, const char *name, bool excl, struct inode **node_store);
int (*vop_lookup)(struct inode *node, char *path, struct inode **node_store);
……
 };
           

把具體檔案系統的函數與這套結構對接,即可實作通過VFS操作具體檔案系統。

Simple FS檔案系統

這裡僅簡要了解SFS的結構。

SFS以block(4K,與記憶體page大小相等)為基本機關。類似于Windows中提到的簇,這裡以8個扇區為機關,任何小于8個扇區的單元都要以8個扇區,也就是4K大小來存儲。

uCore作業系統程式設計實驗手記(八)對uCore檔案系統的認識Lab8

在上圖中可以看出,SFS在磁盤上以超級塊、根目錄索引節點、磁盤塊配置設定位圖、檔案/檔案夾實際使用區為基本結構。

第0個塊是超級塊(superblock),它包含了關于檔案系統的所有關鍵參數,當計算機被啟動或檔案系統被首次接觸時,超級塊的内容就會被裝入記憶體。其定義如下:

struct sfs_super {
    uint32_t magic;              /* 用于識别SFS檔案系統的魔數0x2f8dbe2a */
    uint32_t blocks;                  /* 此檔案系統中磁盤塊的總數 */
    uint32_t unused_blocks;                /* 此檔案系統中未使用的磁盤塊的總數 */
    char info[SFS_MAX_INFO_LEN + 1];                /* 檔案系統描述 */
};
           

第1個塊是root-dir的inode,用來記錄根目錄的相關資訊。root-dir是SFS檔案系統的根結點,通過這個root-dir的inode資訊就可以定位并查找到根目錄下的所有檔案資訊。

從第2個塊開始,根據SFS中所有塊的數量,用1個bit來表示一個塊的占用和未被占用的情況。這個區域稱為SFS的freemap區域,這将占用若幹個塊空間,取決于實體存儲媒體的大小。為了更好地記錄和管理freemap區域,專門提供了兩個檔案kern/fs/sfs/bitmap.[ch]來完成根據一個塊号查找或設定對應的bit位的值。

在sfs_fs.c檔案中的sfs_do_mount()函數中,完成了加載位于硬碟上的SFS檔案系統的超級塊superblock和freemap的工作。這樣,在記憶體中就有了SFS檔案系統的全局資訊。

結構sfs_disk_inode記錄了檔案或目錄的内容存儲的索引資訊,即索引節點,該資料結構在硬碟裡儲存,需要時讀入記憶體。而結構sfs_disk_entry表示一個目錄中的一個檔案或目錄,即目錄項,包含該項所對應inode的位置和檔案名,同樣也在硬碟裡儲存,需要時讀入記憶體。

SFS中的磁盤索引節點代表了一個實際位于磁盤上的檔案。結構sfs_disk_inode的定義如下:

struct sfs_disk_inode {//占一個block大小
    uint32_t size;               //如果inode表示正常檔案,則size是檔案大小
    uint16_t type;               //※inode的檔案類型,從這裡區分不同檔案
    uint16_t nlinks;                  //此inode的硬連結數
    uint32_t blocks;                  //此inode的資料塊數的個數
    uint32_t direct[SFS_NDIRECT];        //此inode的直接資料塊索引值(有SFS_NDIRECT個)
    uint32_t indirect;         //此inode的一級間接資料塊索引值,為0表示不使用間接索引
};
           

預設的,ucore裡SFS_NDIRECT是12,即直接索引的資料頁大小為124KB = 48KB;當使用一級間接資料塊索引時,ucore支援最大的檔案大小為124KB + 1024*4KB = 48KB+ 4MB(回憶這個1024怎麼來的?一個塊4KB,單個索引4B,是以一個塊可以放1024個索引項)。資料索引表内,0 表示一個無效的索引。

對于目錄,索引值指向的資料儲存的是目錄下所有的檔案名以及對應的索引節點所在的索引塊(磁盤塊)所形成的數組。相當于FAT裡的目錄項:

struct sfs_disk_entry {//占一個block大小
    uint32_t ino;                                   //索引節點所占資料塊索引值
    char name[SFS_MAX_FNAME_LEN + 1];               //檔案名
};
           

這裡要明确一下,每個檔案系統下的inode都應該配置設定唯一的inode編号(FAT檔案系統有簇号)。SFS 下,每個inode直接用它所在的磁盤block的編号作為inode編号。

inode的檔案操作函數在SFS下這樣定義:

static const struct inode_ops sfs_node_fileops = {
    .vop_magic                      = VOP_MAGIC,
    .vop_open                       = sfs_openfile,
    .vop_close                      = sfs_close,
    .vop_read                       = sfs_read,
    .vop_write                      = sfs_write,
    ……
};
           

inode的目錄操作函數在SFS下這樣定義:

static const struct inode_ops sfs_node_dirops = {
    .vop_magic                      = VOP_MAGIC,
    .vop_open                       = sfs_opendir,
    .vop_close                      = sfs_close,
    .vop_getdirentry                = sfs_getdirentry,		//目錄内資料的特殊性
    .vop_lookup                     = sfs_lookup,                           
    ……
};
           

記憶體中的索引節點

上述提及的資料結構都是SFS檔案系統在實體磁盤上的組織形式。當檔案被打開,其索引節點被載入記憶體,變為如下的形式。可見其包含了SFS的硬碟inode資訊,而且還增加了其他一些資訊,有便于判斷是否被改寫、互斥操作、回收和快速定位等作用:

struct sfs_inode {
    struct sfs_disk_inode *din;               /* 指向磁盤inode在記憶體中的副本 */
    uint32_t ino;                             /* inode所在的實體塊号 */
    uint32_t flags;                           /* inode的有關标志 */
    bool dirty;                               /* 該檔案是否被修改過 */
    int reclaim_count;                        /* 程序打開此檔案的次數 */
    semaphore_t sem;                          /* 通路此檔案的信号量 */
    list_entry_t inode_link;                /* entry for linked-list in sfs_fs */
    list_entry_t hash_link;                 /* entry for hash linked-list in sfs_fs */
};
           

裝置接口層

為了統一地通路裝置,可以把一個裝置看成一個檔案,通過通路檔案的接口來通路裝置。uCore目前實作了stdin裝置檔案檔案、stdout裝置檔案、disk0裝置。

ucore定義了struct device,其描述如下:

struct device {
    size_t d_blocks;    //裝置占用的資料塊個數            
    size_t d_blocksize;  //資料塊的大小
    int (*d_open)(struct device *dev, uint32_t open_flags);  //打開裝置的函數指針
    int (*d_close)(struct device *dev); 					 //關閉裝置的函數指針
    int (*d_io)(struct device *dev, struct iobuf *iob, bool write);//讀寫裝置的函數指針
    int (*d_ioctl)(struct device *dev,int op,void *data);//用ioctl方式控制裝置的函數指針
};
           

這個資料結構能夠支援對塊裝置(比如磁盤)、字元裝置(比如鍵盤、序列槽)的表示,完成對裝置的基本操作。ucore虛拟檔案系統為了把這些裝置連結在一起,還定義了一個裝置連結清單,即雙向連結清單vdev_list,這樣通過通路此連結清單,可以找到ucore能夠通路的所有裝置檔案。

但這個裝置描述沒有與檔案系統以及表示一個檔案的inode資料結建構立關系,為此,還需要另外一個資料結構把device和inode聯通起來,這就是vfs_dev_t資料結構:

typedef struct {
    const char *devname;		//裝置名
    struct inode *devnode;			//指向在記憶體中VFS的inode,裝置類型
    struct fs *fs;				//指向檔案系統
    bool mountable;				//是否可挂載
    list_entry_t vdev_link;			//用于連結構造雙向連結清單vdev_list
} vfs_dev_t;
           

利用vfs_dev_t資料結構,就可以讓檔案系統通過一個連結vfs_dev_t結構的雙向連結清單找到device對應的inode資料結構。一個inode節點的成員變量in_type的值是0x1234,則此 inode的成員變量in_info将成為一個device結構。這個inode對應一個裝置檔案。

打開檔案的處理流程

當打開一個檔案時,首先是調用open()函數。通過這個函數陷入核心,調用sys_open()函數、sys_fileopen()函數,而後進入VFS的處理流程。

VFS怎樣處理?

1.配置設定一個file資料結構:首先調用的是file_open()函數,它要給這個即将打開的檔案配置設定一個file資料結構的變量,這個變量其實是目前程序的打開檔案數組current->fs_struct->filemap[]中的一個空閑元素。

2.調用vfs_open()函數找到path指出的檔案所對應的基于inode資料結構的VFS索引節點:通過vfs_lookup找到path對應檔案的inode、調用vop_open函數打開檔案。

SFS檔案系統做了什麼?

vfs_lookup()函數會調用vop_lookup()函數。在sfs_inode.c中的sfs_node_dirops變量定義了“.vop_lookup = sfs_lookup”,是以這個函數是SFS中具體地檔案系統操作函數。sfs_lookup函數以“/”為分割符,從左至右逐一分解path獲得各個子目錄和最終檔案對應的inode節點。當無法分解path後,就意味着找到了sfs_filetest1對應的inode節點,就可順利傳回了。根據目錄項中記錄的inode所處的資料塊索引值找到路徑名對應的SFS磁盤inode,讀入磁盤inode的内容,建立SFS記憶體inode。

以上是簡化的大概的處理流程。

Lab8

實驗八的要求是,實作讀檔案操作函數和基于檔案系統的執行程式機制。

完成讀檔案操作的實作

編寫在sfs_inode.c中sfs_io_nolock()讀檔案資料的實作代碼:

//首先解釋參數
static int sfs_io_nolock(struct sfs_fs *sfs, struct sfs_inode *sin, void *buf, off_t offset, size_t *alenp, bool write);
//buf是讀入緩沖區指針,off_t是檔案指針距檔案頭的偏移,alenp是讀的大小,write是讀寫标志
//LAB8:EXERCISE1 YOUR CODE HINT: call sfs_bmap_load_nolock, sfs_rbuf, sfs_rblock,etc. read different kind of blocks in file
	/*
	 * (1) If offset isn't aligned with the first block, Rd/Wr some content from offset to the end of the first block
	 *       NOTICE: useful function: sfs_bmap_load_nolock, sfs_buf_op
	 *               Rd/Wr size = (nblks != 0) ? (SFS_BLKSIZE - blkoff) : (endpos - offset)
	 * (2) Rd/Wr aligned blocks 
	 *       NOTICE: useful function: sfs_bmap_load_nolock, sfs_block_op
     * (3) If end position isn't aligned with the last block, Rd/Wr some content from begin to the (endpos % SFS_BLKSIZE) of the last block
	 *       NOTICE: useful function: sfs_bmap_load_nolock, sfs_buf_op	
	*/
	//源碼,按照提示分析。因為資料不一定與block對其,是以分成三段讀取
	//讀取頭部的資料,即起始處距離下一個block邊界的大小,不足一個block
    if ((blkoff = offset % SFS_BLKSIZE) != 0) {
    	//找到第一個資料塊的大小,看這個“頭部”有沒有越過下一個block邊界
        size = (nblks != 0) ? (SFS_BLKSIZE - blkoff) : (endpos - offset);
        //先找到記憶體檔案索引對應的block的編号ino
        if ((ret = sfs_bmap_load_nolock(sfs, sin, blkno, &ino)) != 0) {
            goto out;
        }
        if ((ret = sfs_buf_op(sfs, buf, size, ino, blkoff)) != 0) {//完成實際的讀寫操作
            goto out;
        }
        alen += size;//alen記錄已讀取的資料量
        if (nblks == 0) {//此條件成立代表要讀的資料尾沒有超過block邊界,已經讀完了
            goto out;
        }
        buf += size, blkno ++, nblks --;//buf指針重新整理,讀入的檔案塊數+1……
    }
	//讀取中間部分的資料,一塊一塊地讀
    size = SFS_BLKSIZE;
    while (nblks != 0) {//隻要nblks沒有減少到0,就一直讀取完整的資料塊
        if ((ret = sfs_bmap_load_nolock(sfs, sin, blkno, &ino)) != 0) {
            goto out;
        }
        if ((ret = sfs_block_op(sfs, buf, ino, 1)) != 0) {
            goto out;
        }
        //alen指針重新整理,buf指針重新整理,讀入的檔案塊數+1,剩餘完整檔案塊數-1
        alen += size, buf += size, blkno ++, nblks --;
    }
	//讀取末尾的資料,首先求取剩餘的最後的要讀的資料量
    if ((size = endpos % SFS_BLKSIZE) != 0) {
        if ((ret = sfs_bmap_load_nolock(sfs, sin, blkno, &ino)) != 0) {
            goto out;
        }
        if ((ret = sfs_buf_op(sfs, buf, size, ino, 0)) != 0) {
            goto out;
        }
        alen += size;//alen指針重新整理,至此讀取完畢
    }
out:
    *alenp = alen;//這裡判斷已經讀取的資料量是否和想要讀取的資料量相同
    if (offset + alen > sin->din->size) {//如果讀取的尾位置已經超過檔案的大小
        sin->din->size = offset + alen;//更新檔案大小
        sin->dirty = 1;//髒位置1
    }
    return ret;
}
           

給出設計實作”UNIX的PIPE機制“的概要設計方案:

PIPE即管道機制。

管道可以看作是由核心管理的一個緩沖區,一端連接配接程序A的輸出,另一端連接配接程序B的輸入。程序A會向管道中放入資訊,而程序B會取出被放入管道的資訊。當管道中沒有資訊,程序B會等待,直到程序A放入資訊。當管道被放滿資訊的時候,程序A會等待,直到程序B取出資訊。當兩個程序都結束的時候,管道也自動消失。

我沒有見過管道機制的實作代碼。但是現在暫時做個小猜想:通過将兩個 file 結構指向同一個臨時的VFS索引節點,而這個VFS索引節點又指向一個實體頁面。實作管道機制類似于實作一個用于程序通信的信箱,相同點是都需要發送、接受原語;不同點是管道通過檔案實作(read()\write()函數)。

完成基于檔案系統的執行程式機制的實作

改寫proc.c中的load_icode函數和其他相關函數,實作基于檔案系統的執行程式機制。

初始化有關fs的程序控制結構:

// alloc_proc - alloc a proc_struct and init all fields of proc_struct
static struct proc_struct * alloc_proc(void) {
    struct proc_struct *proc = kmalloc(sizeof(struct proc_struct));
    if (proc != NULL) {
    //LAB4:EXERCISE1 YOUR CODE
    /*
     * below fields in proc_struct need to be initialized
     *       enum proc_state state;                      // Process state
	···
     */
    //LAB5 YOUR CODE : (update LAB4 steps)
    /*
     * below fields(add in LAB5) in proc_struct need to be initialized	
     *       uint32_t wait_state;                        // waiting state
     *       struct proc_struct *cptr, *yptr, *optr;     // relations between processes
	 */
    //LAB8:EXERCISE2 YOUR CODE HINT:need add some code to init fs in proc_struct, ...
        proc->state = PROC_UNINIT;
        proc->pid = -1;
        proc->runs = 0;
        proc->kstack = 0;
        proc->need_resched = 0;
        proc->parent = NULL;
        proc->mm = NULL;
        memset(&(proc->context), 0, sizeof(struct context));
        proc->tf = NULL;
        proc->cr3 = boot_cr3;
        proc->flags = 0;
        memset(proc->name, 0, PROC_NAME_LEN);
        proc->wait_state = 0;
        proc->cptr = proc->optr = proc->yptr = NULL;
        proc->rq = NULL;
        proc->run_link.prev = proc->run_link.next = NULL;
        proc->time_slice = 0;
        proc->lab6_run_pool.left = proc->lab6_run_pool.right = 		
        		proc->lab6_run_pool.parent = NULL;
        proc->lab6_stride = 0;
        proc->lab6_priority = 0;
        proc->filesp = NULL;
    }
    return proc;
}
           

實作load_icode()函數中從檔案系統加載程式的部分:

static int load_icode(int fd, int argc, char **kargv) {
    /* LAB8:EXERCISE2 YOUR CODE  HINT:how to load the file with handler fd  in to process's memory? how to setup argc/argv?
     * MACROs or Functions:
     *  mm_create        - create a mm
     *  setup_pgdir      - setup pgdir in mm
     *  load_icode_read  - read raw data content of program file
     *  mm_map           - build new vma
     *  pgdir_alloc_page - allocate new memory for  TEXT/DATA/BSS/stack parts
     *  lcr3             - update Page Directory Addr Register -- CR3
     */
	/* (1) create a new mm for current process
     * (2) create a new PDT, and mm->pgdir= kernel virtual addr of PDT
     * (3) copy TEXT/DATA/BSS parts in binary to memory space of process
     *    (3.1) read raw data content in file and resolve elfhdr
     *    (3.2) read raw data content in file and resolve proghdr based on info in elfhdr
     *    (3.3) call mm_map to build vma related to TEXT/DATA
     *    (3.4) callpgdir_alloc_page to allocate page for TEXT/DATA, read contents in file
     *          and copy them into the new allocated pages
     *    (3.5) callpgdir_alloc_page to allocate pages for BSS, memset zero in these pages
     * (4) call mm_map to setup user stack, and put parameters into user stack
     * (5) setup current process's mm, cr3, reset pgidr (using lcr3 MARCO)
     * (6) setup uargc and uargv in user stacks
     * (7) setup trapframe for user environment
     * (8) if up steps failed, you should cleanup the env.
     */
    assert(argc >= 0 && argc <= EXEC_MAX_ARG_NUM);
	//(1)建立記憶體管理器 
    ···
    struct Page *page;
	//(3)從檔案加載程式到記憶體
    struct elfhdr __elf, *elf = &__elf;
	//(3.1)讀取elf檔案頭,這部分内容和lab1類似
    if ((ret = load_icode_read(fd, elf, sizeof(struct elfhdr), 0)) != 0) {
        goto bad_elf_cleanup_pgdir;
    } 
    if (elf->e_magic != ELF_MAGIC) {
        ret = -E_INVAL_ELF;
        goto bad_elf_cleanup_pgdir;
    } 
    struct proghdr __ph, *ph = &__ph;
    uint32_t vm_flags, perm, phnum;
    for (phnum = 0; phnum < elf->e_phnum; phnum ++) {//有多少個段就循環多少次
        off_t phoff = elf->e_phoff + sizeof(struct proghdr) * phnum;
	 	//(3.2)循環讀取程式的每個段的頭部 
        if ((ret = load_icode_read(fd, ph, sizeof(struct proghdr), phoff)) != 0) {
            goto bad_cleanup_mmap;
        }
        if (ph->p_type != ELF_PT_LOAD) {
            continue ;
        }
        if (ph->p_filesz > ph->p_memsz) {
            ret = -E_INVAL_ELF;
            goto bad_cleanup_mmap;
        }
        if (ph->p_filesz == 0) {
            continue ;
        }
		//(3.3)設定好虛拟位址與實體位址之間的映射
        vm_flags = 0, perm = PTE_U;
        if (ph->p_flags & ELF_PF_X) vm_flags |= VM_EXEC;
        if (ph->p_flags & ELF_PF_W) vm_flags |= VM_WRITE;
        if (ph->p_flags & ELF_PF_R) vm_flags |= VM_READ;
        if (vm_flags & VM_WRITE) perm |= PTE_W;
        if ((ret = mm_map(mm, ph->p_va, ph->p_memsz, vm_flags, NULL)) != 0) {
            goto bad_cleanup_mmap;
        }
        off_t offset = ph->p_offset;
        size_t off, size;
        uintptr_t start = ph->p_va, end, la = ROUNDDOWN(start, PGSIZE);
 
        ret = -E_NO_MEM;
		//(3.4)複制資料段和代碼段 
        end = ph->p_va + ph->p_filesz;
        while (start < end) {
            if ((page = pgdir_alloc_page(mm->pgdir, la, perm)) == NULL) {
                ret = -E_NO_MEM;
                goto bad_cleanup_mmap;
            }
            off = start - la, size = PGSIZE - off, la += PGSIZE;
            if (end < la) {
                size -= la - end;
            }
            if ((ret = load_icode_read(fd, page2kva(page) + off, size, offset)) != 0) {
                goto bad_cleanup_mmap;
            }
            start += size, offset += size;
        }
		//(3.5)建立BSS段
        end = ph->p_va + ph->p_memsz;
 
        if (start < la) {
            /* ph->p_memsz == ph->p_filesz */
            if (start == end) {
                continue ;
            }
            off = start + PGSIZE - la, size = PGSIZE - off;
            if (end < la) {
                size -= la - end;
            }
            memset(page2kva(page) + off, 0, size);
            start += size;
            assert((end < la && start == end) || (end >= la && start == la));
        }
        while (start < end) {
            if ((page = pgdir_alloc_page(mm->pgdir, la, perm)) == NULL) {
                ret = -E_NO_MEM;
                goto bad_cleanup_mmap;
            }
            off = start - la, size = PGSIZE - off, la += PGSIZE;
            if (end < la) {
                size -= la - end;
            }
            memset(page2kva(page) + off, 0, size);
            start += size;
        }
    }
	//關閉檔案,加載程式結束
    sysfile_close(fd);
	//(4)建立相應的虛拟記憶體映射表
	···
           

給出設計實作基于”UNIX的硬連結和軟連結機制“的概要設計方案:

vfs中預留了硬連結的實作接口:

int vfs_link(char *old_path, char *new_path);

在實作硬連結機制,建立硬連結link時,為new_path建立對應的file項,并把其inode指向old_path所對應的inode,inode的引用計數加1。在unlink時将引用計數減去1即可(這個方案從“簡書”中獲得)。

2019-08-07 23:02

繼續閱讀