1. 程式人生 > >ext2檔案系統原始碼之dir.c

ext2檔案系統原始碼之dir.c

今天我們來看ext2原始碼的dir.c檔案,這個檔案是做和目錄有關的操作的,也是很重要的一個檔案,大家認真看哦,有什麼和我不一致的大家一起探討~~
在講這個之前我覺得還是先說下ext2檔案系統的目錄方面的基礎比較好,這樣會更容易看明白。
ext2檔案系統分為inode和block,inode是索引,block是資料塊,那我們怎麼定義目錄呢?其實,目錄其實也是檔案的一種,只不過是目錄的內容是目錄內的檔案資訊,目錄也有inode,而目錄的inode索引導向的block內的內容就是一個一個的目錄內檔案資訊結構體,就是ext2_dir_entry_2,新版的ext2目錄結構體,定義如下
struct ext2_dir_entry_2 {
	__le32	inode;			/* Inode號*/
	__le16	rec_len;		/*本目錄項的長度*/
	__u8	name_len;		/*檔名長度 */
	__u8	file_type;		/*這個檔案的檔案型別*/
	char	name[EXT2_NAME_LEN];	/*檔名*/
};


rec_len代表當前目錄項的長度,這個欄位比較方便的一點是,當刪除這個目錄項的時候,只需要把上一個的目錄項的rec_len加上要刪除的目錄項的長度就可以了,而不需要把刪除的節點的後邊的目錄項一個一個的向前對齊,這樣就節省了IO開銷。
file_type欄位的值,也就是檔案型別,有以下幾種
enum {
         EXT2_FT_UNKNOWN,      /*未知*/
         EXT2_FT_REG_FILE,     /*常規檔案*/
         EXT2_FT_DIR,     /*目錄檔案*/
         EXT2_FT_CHRDEV,       /*字元裝置檔案*/
         EXT2_FT_BLKDEV,       /*塊裝置檔案*/
         EXT2_FT_FIFO,    /*命名管道檔案*/
         EXT2_FT_SOCK,    /*套接字檔案*/
         EXT2_FT_SYMLINK, /*符號連檔案*/
         EXT2_FT_MAX      /*檔案型別的最大個數*/
};


還有EXT2_DIR_REC_LEN巨集,ext2的目錄項規定大小必須是4的倍數,EXT2_DIR_REC_LEN巨集的引數是檔名的長度,我們看一下巨集的定義
/
*
 * EXT2_DIR_PAD defines the directory entries boundaries
 *
 * NOTE: It must be a multiple of 4
 */
#define EXT2_DIR_PAD		 	4
#define EXT2_DIR_ROUND 			(EXT2_DIR_PAD - 1)
#define EXT2_DIR_REC_LEN(name_len)	(((name_len) + 8 + EXT2_DIR_ROUND) & \
					 ~EXT2_DIR_ROUND)


前邊的8大家不明白的話,可以看ext2_dir_entry_2的實現,前邊的幾個變數加起來就是8個位元組,再加上名稱長度就得到了目錄項的理論長度,加上EXT2_DIR_ROUND,就是3,與3,得到邊界按照四對齊的大小
那麼開始吧!
/*
 *  linux/fs/ext2/dir.c
 *	作者版權資訊
 * Copyright (C) 1992, 1993, 1994, 1995
 * Remy Card ([email protected])
 * Laboratoire MASI - Institut Blaise Pascal
 * Universite Pierre et Marie Curie (Paris VI)
 * 法國最好的大學,法國巴黎第六大學的Remy Card大神,核心ext2的好多程式碼都是他寫的
 *  from
 *
 *  linux/fs/minix/dir.c
 *
 *  Copyright (C) 1991, 1992  Linus Torvalds
 *
 *  ext2 directory handling functions
 *
 *  Big-endian to little-endian byte-swapping/bitmaps by
 *        David S. Miller ([email protected]), 1995
 *
 * All code that works with directory layout had been switched to pagecache
 * and moved here. AV
 */

#include "ext2.h"
#include <linux/pagemap.h>

typedef struct ext2_dir_entry_2 ext2_dirent;

/*返回inode結構體所屬的檔案系統的塊大小位元組數*/
static inline unsigned ext2_chunk_size(struct inode *inode)
{
	return inode->i_sb->s_blocksize;
}
/*釋放申請的頁*/
static inline void ext2_put_page(struct page *page)
{
	kunmap(page);
	page_cache_release(page);
}
/*返回一個檔案佔用的頁的數目*/
static inline unsigned long dir_pages(struct inode *inode)
{
	/*inode->i_size是檔案大小的位元組數,PAGE_CACHE_SIZE是快取頁的大小,PAGE_CACHE_SHIFT是快取頁大小換算成二進位制位有多少位,這樣的演算法得到的就是inode對應的檔案的佔用快取頁的數目*/
	return (inode->i_size+PAGE_CACHE_SIZE-1)>>PAGE_CACHE_SHIFT;
}

/*返回inode對應的檔案的頁號為page_nr的最後一個合法的位元組的位置,再加一,提示:頁號從零開始*/
static unsigned
ext2_last_byte(struct inode *inode, unsigned long page_nr)
{
	/*先獲得檔案位元組大小*/
	unsigned last_byte = inode->i_size;
	/*減去前邊的頁的位元組數,page_nr << PAGE_CACHE_SHIFT就等於page_nr乘上頁大小*/
	last_byte -= page_nr << PAGE_CACHE_SHIFT;
	/*如果page_nr不是最後一頁,就返回當前頁的最後一個位元組位置加一*/
	if (last_byte > PAGE_CACHE_SIZE)
		last_byte = PAGE_CACHE_SIZE;
	return last_byte;
}
/*把page頁快取上的from到to的位元組修改提交上去*/
static int ext2_commit_chunk(struct page *page, unsigned from, unsigned to)
{
	/*找到這個緩衝區的擁有著*/
	struct inode *dir = page->mapping->host;
	int err = 0;
	dir->i_version++;
	/*呼叫頁緩衝區的函式把修改提交*/
	page->mapping->a_ops->commit_write(NULL, page, from, to);
	/*如果標誌上要求寫入立刻同步,就同步,否則釋放此頁*/
	if (IS_DIRSYNC(dir))
		err = write_one_page(page, 1);
	else
		unlock_page(page);
	return err;
}
/*檢驗頁有沒有錯誤*/
static void ext2_check_page(struct page *page)
{
	/*dir是頁的主人*/
	struct inode *dir = page->mapping->host;
	/*sb是dir的檔案系統vfs層的超級塊*/
	struct super_block *sb = dir->i_sb;
	/*chunk_size是檔案大小*/
	unsigned chunk_size = ext2_chunk_size(dir);
	/*返回頁的虛擬地址*/
	char *kaddr = page_address(page);
	/*檔案系統總的inode數目*/
	u32 max_inumber = le32_to_cpu(EXT2_SB(sb)->s_es->s_inodes_count);
	unsigned offs, rec_len;
	/*limit是頁快取的大小*/
	unsigned limit = PAGE_CACHE_SIZE;
	ext2_dirent *p;
	char *error;
	/*檔案大小右移PAGE_CACHE_SHIFT位得到的是檔案的最後一個快取頁的號碼,如果等於page的index,就是說page就是檔案的最後一部分對應的快取頁,並且檔案都在緩衝區裡*/
	if ((dir->i_size >> PAGE_CACHE_SHIFT) == page->index) {
		/*limit得到檔案大小最後一個頁的頁內偏移*/
		limit = dir->i_size & ~PAGE_CACHE_MASK;
		/*如果不為零,說明這個檔案目錄不是一個完整的塊大小的倍數,檔案可能不是塊大小的倍數,但是檔案目錄必定是塊大小的倍數,這裡就返回大小錯誤*/
		if (limit & (chunk_size - 1))
			goto Ebadsize;
		/*如果limit為0說明沒問題*/
		if (!limit)
			goto out;
	}
	/*EXT2_DIR_REC_LEN巨集我們前邊講過的,這裡就是遍歷目錄的block塊的內容,遍歷塊內的每一個ext2_dir_entry_2結構體*/
	for (offs = 0; offs <= limit - EXT2_DIR_REC_LEN(1); offs += rec_len) {
		/*p指標指向當前應該指向的ext2_dir_entry_2結構體*/
		p = (ext2_dirent *)(kaddr + offs);
		/*rec_len當前項的長度*/
		rec_len = le16_to_cpu(p->rec_len);
		/*當前項至少大於這個,如果小於,說明有問題,返回錯誤*/
		if (rec_len < EXT2_DIR_REC_LEN(1))
			goto Eshort;
		/*規定多有的目錄項邊界與4對齊,這說明沒有對齊,返回沒對齊的錯誤*/
		if (rec_len & 3)
			goto Ealign;
		/*rec_len和檔名大小不一致*/
		if (rec_len < EXT2_DIR_REC_LEN(p->name_len))
			goto Enamelen;
		/*大小超出當前塊了,說明rec_len有問題*/
		if (((offs + rec_len - 1) ^ offs) & ~(chunk_size-1))
			goto Espan;
		/*目錄項的inode編號大於inode的最大編號,編號錯誤*/
		if (le32_to_cpu(p->inode) > max_inumber)
			goto Einumber;
	}
	/*說明目錄項沒有和塊邊界對齊*/
	if (offs != limit)
		goto Eend;
out:
	/*page的flags有一個位標記這個page已經被檢查過了,這裡標記位為1*/
	SetPageChecked(page);
	return;

	/* 解決問題 */

Ebadsize:
	/*目錄的inode指向的檔案大小不合法,列印這個塊的大小不是塊大小的倍數*/
	ext2_error(sb, "ext2_check_page",
		"size of directory #%lu is not a multiple of chunk size",
		dir->i_ino
	);
	goto fail;
Eshort:
	/*rec_len比最小的值還要小*/
	error = "rec_len is smaller than minimal";
	goto bad_entry;
Ealign:
	/*目錄項未對齊*/
	error = "unaligned directory entry";
	goto bad_entry;
Enamelen:
	/*rec_len與名稱長度不匹配*/
	error = "rec_len is too small for name_len";
	goto bad_entry;
Espan:
	/*目錄項超出了塊的邊界*/
	error = "directory entry across blocks";
	goto bad_entry;
Einumber:
	/*inode號碼錯誤*/
	error = "inode out of bounds";
bad_entry:
	/*目錄項壞*/
	ext2_error (sb, "ext2_check_page", "bad entry in directory #%lu: %s - "
		"offset=%lu, inode=%lu, rec_len=%d, name_len=%d",
		dir->i_ino, error, (page->index<<PAGE_CACHE_SHIFT)+offs,
		(unsigned long) le32_to_cpu(p->inode),
		rec_len, p->name_len);
	goto fail;
Eend:
	p = (ext2_dirent *)(kaddr + offs);
	ext2_error (sb, "ext2_check_page",
		"entry in directory #%lu spans the page boundary"
		"offset=%lu, inode=%lu",
		dir->i_ino, (page->index<<PAGE_CACHE_SHIFT)+offs,
		(unsigned long) le32_to_cpu(p->inode));
fail:
	/*標記這個page標記過,但是有錯誤*/
	SetPageChecked(page);
	SetPageError(page);
}
/*從頁快取得到目錄的inode的第n頁資料*/
static struct page * ext2_get_page(struct inode *dir, unsigned long n)
{
	/*從目錄的inode獲得地址空間結構體*/
	struct address_space *mapping = dir->i_mapping;
	/*從地址空間讀取第n頁*/
	struct page *page = read_mapping_page(mapping, n, NULL);
	/*如果讀取成功了*/
	if (!IS_ERR(page)) {
		/*對映後檢查頁*/
		kmap(page);
		if (!PageChecked(page))
			ext2_check_page(page);
		/*如果這個頁有錯誤,就跳轉到fail*/
		if (PageError(page))
			goto fail;
	}
	return page;

fail:
	/*有錯誤的頁要釋放掉,返回IO錯誤號碼*/
	ext2_put_page(page);
	return ERR_PTR(-EIO);
}

/*ext2的字串對比函式,和strncmp不一樣,ext2_match成功返回1,失敗返回0,在呼叫之前呼叫者需要保證len <= EXT2_NAME_LEN 並且de != NULL*/
static inline int ext2_match (int len, const char * const name,
					struct ext2_dir_entry_2 * de)
{
	/*如果長度都不一樣,就不可能一樣,直接返回錯誤*/
	if (len != de->name_len)
		return 0;
	/*如果目錄項的inode為0,說明這個目錄項被刪除了,返回0*/
	if (!de->inode)
		return 0;
	/*對比name和de->name是否一致,返回和memcmp相反的返回值*/
	return !memcmp(name, de->name, len);
}

/*呼叫者需要保證p至少是頁邊界的前六個位元組之前,這個函式返回p指向的目錄項的下一個目錄項*/
static inline ext2_dirent *ext2_next_entry(ext2_dirent *p)
{
	/*rec_len是當前的目錄項的長度,當前指標加上rec_len個位元組長度就得到了下一項的開頭,但是rec_len是結構體的第5,6個位元組,所以必須保證p至少是頁邊界的前六個位元組之前*/
	return (ext2_dirent *)((char*)p + le16_to_cpu(p->rec_len));
}
/*驗證目錄項,base是頁的起始地址,offset是要檢查的目錄項偏移,mask是塊大小減一得到的掩碼*/
static inline unsigned 
ext2_validate_entry(char *base, unsigned offset, unsigned mask)
{
	/*指向要檢查的目錄項*/
	ext2_dirent *de = (ext2_dirent*)(base + offset);
	/*指向要檢驗的目錄項所在頁的第一個目錄項位置*/
	ext2_dirent *p = (ext2_dirent*)(base + (offset&mask));
	/*遍歷從第一個到我們要檢驗的這個*/
	while ((char*)p < (char*)de) {
		/*如果檢驗到rec_len=0,就是有錯的,跳出迴圈*/
		if (p->rec_len == 0)
			break;
		p = ext2_next_entry(p);
	}
	/*返回有錯誤的目錄項的頁內偏移*/
	return (char *)p - base;
}
/*ext2檔案系統的檔案型別表,上邊我們說過的*/
static unsigned char ext2_filetype_table[EXT2_FT_MAX] = {
	[EXT2_FT_UNKNOWN]	= DT_UNKNOWN, /*未知*/
	[EXT2_FT_REG_FILE]	= DT_REG,     /*常規檔案*/
	[EXT2_FT_DIR]		= DT_DIR,     /*目錄檔案*/
	[EXT2_FT_CHRDEV]	= DT_CHR,     /*字元裝置檔案*/
	[EXT2_FT_BLKDEV]	= DT_BLK,     /*塊裝置檔案*/
	[EXT2_FT_FIFO]		= DT_FIFO,    /*命名管道檔案*/
	[EXT2_FT_SOCK]		= DT_SOCK,    /*套接字檔案*/
	[EXT2_FT_SYMLINK]	= DT_LNK,     /*符號連檔案*/
};
/*S_SHIFT巨集是位的偏移,S_IFREG等巨集的位都在12位以後,這個結構體方便通過檔案的模式,mode欄位獲得檔案型別*/
#define S_SHIFT 12
static unsigned char ext2_type_by_mode[S_IFMT >> S_SHIFT] = {
	[S_IFREG >> S_SHIFT]	= EXT2_FT_REG_FILE,
	[S_IFDIR >> S_SHIFT]	= EXT2_FT_DIR,
	[S_IFCHR >> S_SHIFT]	= EXT2_FT_CHRDEV,
	[S_IFBLK >> S_SHIFT]	= EXT2_FT_BLKDEV,
	[S_IFIFO >> S_SHIFT]	= EXT2_FT_FIFO,
	[S_IFSOCK >> S_SHIFT]	= EXT2_FT_SOCK,
	[S_IFLNK >> S_SHIFT]	= EXT2_FT_SYMLINK,
};
/*設定目錄項的型別*/
static inline void ext2_set_de_type(ext2_dirent *de, struct inode *inode)
{
	/*獲得目錄項的模式*/
	mode_t mode = inode->i_mode;
	/*檢查EXT2_FEATURE_INCOMPAT_FILETYPE位,如果為1,就根據mode賦值檔案型別,否則置為0,就是未知檔案型別*/
	if (EXT2_HAS_INCOMPAT_FEATURE(inode->i_sb, EXT2_FEATURE_INCOMPAT_FILETYPE))
		de->file_type = ext2_type_by_mode[(mode & S_IFMT)>>S_SHIFT];
	else
		de->file_type = 0;
}
/*讀取檔案的目錄內容,filp是要讀取得檔案指標,dirent是讀取出來存放的緩衝區,filldir是把讀取出來的資料按照不同的格式存放在dirent緩衝區裡的方法*/
static int
ext2_readdir (struct file * filp, void * dirent, filldir_t filldir)
{
	/*先得到檔案的偏移*/
	loff_t pos = filp->f_pos;
	/*獲得目錄的inode*/
	struct inode *inode = filp->f_path.dentry->d_inode;
	struct super_block *sb = inode->i_sb;
	/*pos與上快取塊的掩碼得到了offset,得到的是在塊內的偏移*/
	unsigned int offset = pos & ~PAGE_CACHE_MASK;
	/*n得到的是當前讀取到的頁編號*/
	unsigned long n = pos >> PAGE_CACHE_SHIFT;
	/*npages是檔案佔用的頁數目*/
	unsigned long npages = dir_pages(inode);
	unsigned chunk_mask = ~(ext2_chunk_size(inode)-1);
	unsigned char *types = NULL;
	/*如果filp->f_version和inode->i_version不一致,就需要檢驗,這version記錄檔案的版本號,每次使用後都會加一,不一致就說明有可能內容不一樣*/
	int need_revalidate = filp->f_version != inode->i_version;
	/*檢驗pos值是不是超出限制了*/
	if (pos > inode->i_size - EXT2_DIR_REC_LEN(1))
		return 0;
	/*如果ext2檔案系統有incompt_feature欄位,先把types指標指向檔案型別表*/
	if (EXT2_HAS_INCOMPAT_FEATURE(sb, EXT2_FEATURE_INCOMPAT_FILETYPE))
		types = ext2_filetype_table;
	/*遍歷從當前讀到的頁到最後的頁*/
	for ( ; n < npages; n++, offset = 0) {
		char *kaddr, *limit;
		ext2_dirent *de;
		/*前邊講過的函式,根據inode和頁編號得到這個inode的第n頁*/
		struct page *page = ext2_get_page(inode, n);
		/*如果值有錯,報錯,並把f_pos指向下一頁*/
		if (IS_ERR(page)) {
			ext2_error(sb, __FUNCTION__,
				   "bad page in #%lu",
				   inode->i_ino);
			filp->f_pos += PAGE_CACHE_SIZE - offset;
			return -EIO;
		}
		/*獲得page的虛擬地址*/
		kaddr = page_address(page);
		/*如果需要檢驗的話*/
		if (unlikely(need_revalidate)) {
			/*如果塊內偏移不是0,就檢驗偏移的值,並且新的合法的偏移值賦給f_pos*/
			if (offset) {
				offset = ext2_validate_entry(kaddr, offset, chunk_mask);
				filp->f_pos = (n<<PAGE_CACHE_SHIFT) + offset;
			}
			/*保持版本號一致,不需要檢驗*/
			filp->f_version = inode->i_version;
			need_revalidate = 0;
		}
		/*根據得到的緩衝區,指向ext2的目錄項結構體*/
		de = (ext2_dirent *)(kaddr+offset);
		/*ext2_last_byte函式我們上邊講過,就是頁內最後一個合法的位元組,得到的limit解釋頁內的合法的邊界*/
		limit = kaddr + ext2_last_byte(inode, n) - EXT2_DIR_REC_LEN(1);
		/*遍歷頁內的每一個目錄項,ext2_next_entry函式我們之前也講過,下一個目錄項*/
		for ( ;(char*)de <= limit; de = ext2_next_entry(de)) {
			/*0長度的目錄項是不合法的,返回IO錯誤,釋放當前頁*/
			if (de->rec_len == 0) {
				ext2_error(sb, __FUNCTION__,
					"zero-length directory entry");
				ext2_put_page(page);
				return -EIO;
			}
			/*如果inode號不為0,(為0說明這個項已經被刪除了,直接跳過)*/
			if (de->inode) {
				int over;
				unsigned char d_type = DT_UNKNOWN;
				/*d_type從目錄項裡得到檔案型別*/
				if (types && de->file_type < EXT2_FT_MAX)
					d_type = types[de->file_type];
				/*使用傳進來的filldir函式來填充dirent緩衝區*/
				offset = (char *)de - kaddr;
				over = filldir(dirent, de->name, de->name_len,
						(n<<PAGE_CACHE_SHIFT) | offset,
						le32_to_cpu(de->inode), d_type);
				/*如果寫超出記憶體邊界,釋放頁並返回*/
				if (over) {
					ext2_put_page(page);
					return 0;
				}
			}
			/*檔案指標加上目錄項的長度*/
			filp->f_pos += le16_to_cpu(de->rec_len);
		}
		/*讀完以後要釋放*/
		ext2_put_page(page);
	}
	return 0;
}

/*ext2檔案系統在一個給定的目錄內尋找一個目錄項,返回的目錄項保證是合法的,引數page得到的是目錄項被找到的緩衝區,*/
struct ext2_dir_entry_2 * ext2_find_entry (struct inode * dir,
			struct dentry *dentry, struct page ** res_page)
{
	/*name就是目錄名,namelen名字長度*/
	const char *name = dentry->d_name.name;
	int namelen = dentry->d_name.len;
	/*EXT2_DIR_REC_LEN巨集前邊講過,rec_len就是這個名字對應的目錄項的大小,按位元組算*/
	unsigned reclen = EXT2_DIR_REC_LEN(namelen);
	unsigned long start, n;
	/*這個inode有多少個頁*/
	unsigned long npages = dir_pages(dir);
	struct page *page = NULL;
	/*從inode得到ext2_inode_info結構體,記憶體裡的ext2_inode_info儲存ext2的一些資訊*/
	struct ext2_inode_info *ei = EXT2_I(dir);
	ext2_dirent * de;
	/*空目錄,直接返回*/
	if (npages == 0)
		goto out;

	/* 先把它賦值為NULL */
	*res_page = NULL;
	/*i_dir_start_lookup就是目錄項在記憶體的開始尋找塊編號*/
	start = ei->i_dir_start_lookup;
	if (start >= npages)
		start = 0;
	n = start;
	do {
		/*ext2_get_page函式,之前講過,獲得第n頁*/
		char *kaddr;
		page = ext2_get_page(dir, n);
		/*如果成功了的話*/
		if (!IS_ERR(page)) {
			/*轉化成虛擬地址,然後轉成ext2目錄項指標型別*/
			kaddr = page_address(page);
			de = (ext2_dirent *) kaddr;
			/*kaddr是邊界地址*/
			kaddr += ext2_last_byte(dir, n) - reclen;
			/*遍歷頁內的所有合法目錄項*/
			while ((char *) de <= kaddr) {
				/*rec_len為0肯定是不可以的,釋放頁,返回*/
				if (de->rec_len == 0) {
					ext2_error(dir->i_sb, __FUNCTION__,
						"zero-length directory entry");
					ext2_put_page(page);
					goto out;
				}
				/*ext2_match之前講過,判斷名字是不是一致,如果一致,跳轉到found*/
				if (ext2_match (namelen, name, de))
					goto found;
				/*沒找到就去找下一個*/
				de = ext2_next_entry(de);
			}
			ext2_put_page(page);
		}
		/*如果已經找到最後頁,再從第一頁開始找*/
		if (++n >= npages)
			n = 0;
		/* 下一頁超過了檔案的總塊數目,不過這基本不可能 */
		if (unlikely(n > (dir->i_blocks >> (PAGE_CACHE_SHIFT - 9)))) {
			ext2_error(dir->i_sb, __FUNCTION__,
				"dir %lu size %lld exceeds block count %llu",
				dir->i_ino, dir->i_size,
				(unsigned long long)dir->i_blocks);
			goto out;
		}
	} while (n != start);
out:
	return NULL;

found:
	/*找到了,res_page就是得到目錄項所在的緩衝區*/
	*res_page = page;
	/*ei->i_dir_start_lookup標記上次尋找到的頁編號*/
	ei->i_dir_start_lookup = n;
	return de;
}
/*獲得dir目錄所在的../目錄項,p獲得../目錄項所在的頁*/
struct ext2_dir_entry_2 * ext2_dotdot (struct inode *dir, struct page **p)
{
	/*之前講過,這個函式獲得dir目錄的第0頁*/
	struct page *page = ext2_get_page(dir, 0);
	ext2_dirent *de = NULL;
	/*頁正確的話*/
	if (!IS_ERR(page)) {
		/*目錄項列表中,第一個是./,而下一個就是../*/
		de = ext2_next_entry((ext2_dirent *) page_address(page));
		*p = page;
	}
	return de;
}
/*通過目錄的檔名獲得這個檔案的inode編號,dir是目錄的inode,dentry是檔案的dentry結構體*/
ino_t ext2_inode_by_name(struct inode * dir, struct dentry *dentry)
{
	ino_t res = 0;
	struct ext2_dir_entry_2 * de;
	struct page *page;
	/*上邊剛講過的函式,獲得目錄項結構體*/
	de = ext2_find_entry (dir, dentry, &page);
	if (de) {
		/*res得到inode編號*/
		res = le32_to_cpu(de->inode);
		ext2_put_page(page);
	}
	return res;
}

/* ext2裡吧目錄項列表的一個項變成inode檔案指向的檔案 */
void ext2_set_link(struct inode *dir, struct ext2_dir_entry_2 *de,
			struct page *page, struct inode *inode)
{
	/*首先找到de指向的目錄項開始結束偏移地址*/
	unsigned from = (char *) de - (char *) page_address(page);
	unsigned to = from + le16_to_cpu(de->rec_len);
	int err;
	/*對頁緩衝區寫入要先鎖住,然後呼叫prepare_write準備都下*/
	lock_page(page);
	err = page->mapping->a_ops->prepare_write(NULL, page, from, to);
	BUG_ON(err);
	/*賦值inode編號*/
	de->inode = cpu_to_le32(inode->i_ino);
	/*之前講過的函式,寫完編號寫檔案型別*/
	ext2_set_de_type (de, inode);
	/*把修改提交*/
	err = ext2_commit_chunk(page, from, to);
	/*減少頁引用計數*/
	ext2_put_page(page);
	/*目錄的inode修改時間記錄*/
	dir->i_mtime = dir->i_ctime = CURRENT_TIME_SEC;
	EXT2_I(dir)->i_flags &= ~EXT2_BTREE_FL;
	/*這個inode已經髒了*/
	mark_inode_dirty(dir);
}
/*ext2增加目錄對於檔案的連線,就是把一個檔案放到目錄裡,dentry是要放入的檔案的dentry結構體,inode是要放的檔案*/
int ext2_add_link (struct dentry *dentry, struct inode *inode)
{
	/*要放入的目錄的inode*/
	struct inode *dir = dentry->d_parent->d_inode;
	/*檔名和名字長度*/
	const char *name = dentry->d_name.name;
	int namelen = dentry->d_name.len;
	/*塊大小*/
	unsigned chunk_size = ext2_chunk_size(dir);
	/*目錄項結構體大小*/
	unsigned reclen = EXT2_DIR_REC_LEN(namelen);
	unsigned short rec_len, name_len;
	struct page *page = NULL;
	ext2_dirent * de;
	/*這個檔案的頁數目*/
	unsigned long npages = dir_pages(dir);
	unsigned long n;
	char *kaddr;
	unsigned from, to;
	int err;

	/*遍歷目錄的目錄項列表*/
	for (n = 0; n <= npages; n++) {
		char *dir_end;
		/*前邊講過這個函式,獲得dir目錄的第n頁*/
		page = ext2_get_page(dir, n);
		/*檢查返回結果*/
		err = PTR_ERR(page);
		if (IS_ERR(page))
			goto out;
		/*頁轉化成為虛擬地址*/
		lock_page(page);
		kaddr = page_address(page);
		/*dir_end得到的值是頁內的最後一盒合法位元組在的偏移位置*/
		dir_end = kaddr + ext2_last_byte(dir, n);
		/*de指向開始的地址*/
		de = (ext2_dirent *)kaddr;
		/*使kaddr指向最後,並且預留下足夠存放要加入的目錄項的空間*/
		kaddr += PAGE_CACHE_SIZE - reclen;
		/*遍歷每一個項*/
		while ((char *)de <= kaddr) {
			/*已經到本頁內的最後一項了,說明還有空間存放目錄項,跳轉到找到了*/
			if ((char *)de == dir_end) {
				/* We hit i_size */
				name_len = 0;
				rec_len = chunk_size;
				de->rec_len = cpu_to_le16(chunk_size);
				de->inode = 0;
				goto got_it;
			}
			/*發現了rec_len為0的目錄項,說明在IO讀寫的時候出現錯誤,釋放鎖,跳出*/
			if (de->rec_len == 0) {
				ext2_error(dir->i_sb, __FUNCTION__,
					"zero-length directory entry");
				err = -EIO;
				goto out_unlock;
			}
			err = -EEXIST;
			/*發現目錄內已經有和要加入的目錄項名字一樣的,退出*/
			if (ext2_match (namelen, name, de))
				goto out_unlock;
			/*name_len是當前到的目錄項應該的rec_len,rec_len是當前項的記錄的rec_len,因為可能後邊的目錄項被刪除了,使得這兩個欄位不一樣*/
			name_len = EXT2_DIR_REC_LEN(de->name_len);
			rec_len = le16_to_cpu(de->rec_len);
			/*如果當前的目錄項inode號是0說明已經被刪除了,並且rec_len大於reclen,說明空間也足夠,跳轉到找到了*/
			if (!de->inode && rec_len >= reclen)
				goto got_it;
			/*如果rec_len比本目錄項的空間加上要新增的空間還大,說明後邊的空間足夠插入一個我們想要插入的目錄項,跳轉到找到了*/
			if (rec_len >= name_len + reclen)
				goto got_it;
			/*加上rec_len就是找下一項*/
			de = (ext2_dirent *) ((char *) de + rec_len);
		}
		/*遍歷完這一頁,仍然沒有找到*/
		unlock_page(page);
		ext2_put_page(page);
	}
	/*沒找到就報BUG*/
	BUG();
	return -EINVAL;

got_it:
	/*from和to是表示要加入的目錄項在頁內的偏移位置開始和結束*/
	from = (char*)de - (char*)page_address(page);
	to = from + rec_len;
	/*要向頁緩衝寫入的時候都要先呼叫prepare_write*/
	err = page->mapping->a_ops->prepare_write(NULL, page, from, to);
	if (err)
		goto out_unlock;
	/*如果inode不為0,說明當前目錄項不是空的,但是這個目錄項的後邊有空間*/
	if (de->inode) {
		/*de1指向後邊的空間*/
		ext2_dirent *de1 = (ext2_dirent *) ((char *) de + name_len);
		/*賦值新的目錄項rec_len*/
		de1->rec_len = cpu_to_le16(rec_len - name_len);
		/*原來的rec_len值變更*/
		de->rec_len = cpu_to_le16(name_len);
		/*de指向要寫入的目錄項地址*/
		de = de1;
	}
	/*開始逐個賦值,名字長度,檔名,inode編號*/
	de->name_len = namelen;
	memcpy (de->name, name, namelen);
	de->inode = cpu_to_le32(inode->i_ino);
	ext2_set_de_type (de, inode);
	/*寫完以後把修改提交*/
	err = ext2_commit_chunk(page, from, to);
	/*目錄的inode修改時間更正*/
	dir->i_mtime = dir->i_ctime = CURRENT_TIME_SEC;
	EXT2_I(dir)->i_flags &= ~EXT2_BTREE_FL;
	/*寫後都要標記髒*/
	mark_inode_dirty(dir);
	/* OFFSET_CACHE */
out_put:
	ext2_put_page(page);
out:
	return err;
out_unlock:
	unlock_page(page);
	goto out_put;
}

/*ext2刪除一個目錄項,dir是刪除的目錄項,page是目錄項緩衝區的頁,其實刪除很簡單,就是找到那個目錄項,把inode號碼變成0,然後把前邊那一項的rec_len加上刪除項的長度*/
int ext2_delete_entry (struct ext2_dir_entry_2 * dir, struct page * page )
{
	struct address_space *mapping = page->mapping;
	struct inode *inode = mapping->host;
	/*kaddr是頁開始虛擬地址*/
	char *kaddr = page_address(page);
	/*from是dir目錄項在的頁對應的塊的開始,檔案系統的塊大小和page大小不一樣*/
	unsigned from = ((char*)dir - kaddr) & ~(ext2_chunk_size(inode)-1);
	/*to是要刪除的目錄項的結尾*/
	unsigned to = ((char*)dir - kaddr) + le16_to_cpu(dir->rec_len);
	ext2_dirent * pde = NULL;
	/*de指向要刪除的目錄項所在塊的開始*/
	ext2_dirent * de = (ext2_dirent *) (kaddr + from);
	int err;
	/*遍歷檢查所在的塊*/
	while ((char*)de < (char*)dir) {
		/*0長度的目錄不合法,不能忍,直接返回錯誤*/
		if (de->rec_len == 0) {
			ext2_error(inode->i_sb, __FUNCTION__,
				"zero-length directory entry");
			err = -EIO;
			goto out;
		}
		pde = de;
		/*下一項*/
		de = ext2_next_entry(de);
	}
	/*pde是要刪除的目錄項前邊的一個,from就是它的頁內偏移*/
	if (pde)
		from = (char*)pde - (char*)page_address(page);
	/*向頁內寫入之前要鎖住的*/
	lock_page(page);
	/*預寫入*/
	err = mapping->a_ops->prepare_write(NULL, page, from, to);
	BUG_ON(err);
	/*依次寫入rec_len和inode號碼,rec_len變成原來的前邊的項的rec_len加上後邊要刪除的那個rec_len*/
	if (pde)
		/*前邊項的rec_len增加,就代表後邊的項刪除*/
		pde->rec_len = cpu_to_le16(to-from);
	/*要刪除的目錄項inode號變成0*/
	dir->inode = 0;
	/*提交修改*/
	err = ext2_commit_chunk(page, from, to);
	/*修改時間*/
	inode->i_ctime = inode->i_mtime = CURRENT_TIME_SEC;
	EXT2_I(inode)->i_flags &= ~EXT2_BTREE_FL;
	/*標記髒了*/
	mark_inode_dirty(inode);
out:
	/*釋放對這個頁的引用*/
	ext2_put_page(page);
	return err;
}

/*把當前目錄項列表設定為空,就是說列表裡只有./和../兩個項,inode是要修改的目錄項的所在inode */
int ext2_make_empty(struct inode *inode, struct inode *parent)
{
	struct address_space *mapping = inode->i_mapping;
	struct page *page = grab_cache_page(mapping, 0);
	/*塊大小*/
	unsigned chunk_size = ext2_chunk_size(inode);
	struct ext2_dir_entry_2 * de;
	int err;
	void *kaddr;
	/*看看得到的頁是不是有問題*/
	if (!page)
		return -ENOMEM;
	/*預寫入操作*/
	err = mapping->a_ops->prepare_write(NULL, page, 0, chunk_size);
	if (err) {
		unlock_page(page);
		goto fail;
	}
	/*先修改第一個./目錄,kaddr是頁開始虛擬地址*/
	kaddr = kmap_atomic(page, KM_USER0);
	/*吧頁內全部變成0*/
	memset(kaddr, 0, chunk_size);
	/*de指向第一個項*/
	de = (struct ext2_dir_entry_2 *)kaddr;
	/*設定./目錄的值,依次是名稱長度,rec_len,名稱,inode號碼,檔案型別*/
	de->name_len = 1;
	de->rec_len = cpu_to_le16(EXT2_DIR_REC_LEN(1));
	memcpy (de->name, ".\0\0", 4);
	de->inode = cpu_to_le32(inode->i_ino);
	ext2_set_de_type (de, inode);
	/*設定../的值,先把de指向第二個要寫入的項*/
	de = (struct ext2_dir_entry_2 *)(kaddr + EXT2_DIR_REC_LEN(1));
	/*設定../目錄的值,依次是名稱長度,rec_len,名稱,inode號碼,檔案型別*/
	de->name_len = 2;
	de->rec_len = cpu_to_le16(chunk_size - EXT2_DIR_REC_LEN(1));
	de->inode = cpu_to_le32(parent->i_ino);
	memcpy (de->name, "..\0", 4);
	ext2_set_de_type (de, inode);
	/*釋放頁緩衝區*/
	kunmap_atomic(kaddr, KM_USER0);
	/*提交修改*/
	err = ext2_commit_chunk(page, 0, chunk_size);
fail:
	/*失敗了就釋放頁快取*/
	page_cache_release(page);
	return err;
}

/*檢查一個目錄是不是空的,inode是要檢查的目錄的inode結構體 */
int ext2_empty_dir (struct inode * inode)
{
	struct page *page = NULL;
	/*npages是inode的頁數目*/
	unsigned long i, npages = dir_pages(inode);
	/*遍歷目錄的每一個頁*/
	for (i = 0; i < npages; i++) {
		char *kaddr;
		ext2_dirent * de;
		/*獲得遍歷到的當前頁*/
		page = ext2_get_page(inode, i);
		/*檢查獲得的是不是壞的*/
		if (IS_ERR(page))
			continue;
		/*獲得頁的虛擬地址,轉化成目錄項指標型別*/
		kaddr = page_address(page);
		de = (ext2_dirent *)kaddr;
		/*kaddr指向結尾邊界*/
		kaddr += ext2_last_byte(inode, i) - EXT2_DIR_REC_LEN(1);
		/*遍歷當前頁*/
		while ((char *)de <= kaddr) {
			/*如果有rec_len為0的項直接返回不為空*/
			if (de->rec_len == 0) {
				ext2_error(inode->i_sb, __FUNCTION__,
					"zero-length directory entry");
				printk("kaddr=%p, de=%p\n", kaddr, de);
				goto not_empty;
			}
			/*如果inode編號不是0並且還不是./和../目錄說明不為空*/
			if (de->inode != 0) {
				/* 檢查 . and .. */
				if (de->name[0] != '.')
					goto not_empty;
				if (de->name_len > 2)
					goto not_empty;
				if (de->name_len < 2) {
					if (de->inode !=
					    cpu_to_le32(inode->i_ino))
						goto not_empty;
				} else if (de->name[1] != '.')
					goto not_empty;
			}
			/*下一個目錄項*/
			de = ext2_next_entry(de);
		}
		/*檢查完這個頁檢查下一個*/
		ext2_put_page(page);
	}
	return 1;

not_empty:
	/*結束時釋放當前頁*/
	ext2_put_page(page);
	return 0;
}
/*ext2的目錄節點的操作函式結構體*/
const struct file_operations ext2_dir_operations = {
	.llseek		= generic_file_llseek,
	.read		= generic_read_dir,
	.readdir	= ext2_readdir,
	.ioctl		= ext2_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl	= ext2_compat_ioctl,
#endif
	.fsync		= ext2_sync_file,
};



相關推薦

ext2檔案系統原始碼dir.c

今天我們來看ext2原始碼的dir.c檔案,這個檔案是做和目錄有關的操作的,也是很重要的一個檔案,大家認真看哦,有什麼和我不一致的大家一起探討~~ 在講這個之前我覺得還是先說下ext2檔案系統的目錄方面的基礎比較好,這樣會更容易看明白。 ext2檔案系統分為inode和bl

Linux高階程式設計基礎——檔案系統程式設計遞迴遍歷/home目錄

檔案系統程式設計之遞迴遍歷/home目錄 /編寫程式完成以下功能: 1.遞迴遍歷/home目錄,打印出所有檔案和子目錄名稱及節點號。 2.判斷檔案型別,如果是子目錄,繼續進行遞迴遍歷,直到遍歷完所有子目錄為止。/ #include <stdio.h> #include &

Linux高階程式設計基礎——檔案系統程式設計目錄檔案

檔案系統程式設計之目錄檔案 //1.新建/home/user目錄; //2.把當前工作路徑移至/home/user目錄; //3.列印當前工作路徑; #include <stdio.h> #include <stdlib.h> #include <sys

Linux高階程式設計基礎——檔案系統程式設計操作檔案屬性

檔案系統程式設計之操作檔案屬性 /編寫程式實現以下功能: 1.新建檔案,設定檔案許可權遮蔽字為0; 2.建立該檔案的硬連結檔案,列印硬連結檔案的inode節點號和檔案大小; 3.建立該檔案的軟連結檔案,列印軟連結檔案的inode節點號和檔案大小; 列印軟連結檔案中的內容; 4.列印原始檔

Linux高階程式設計基礎——檔案系統程式設計檔案型別資訊

/*檔案系統程式設計之檔案型別資訊——實驗題/ //編寫程式實現以下功能: //1.輸入檔名稱,能夠判斷檔案型別,判斷實際使用者對該檔案具有哪些存取許可權; //2.要求打印出檔案型別資訊,inode節點編號,連結數目,使用者id,組id,檔案大小資訊; //3.修改檔案的許可權為當前使

Linux高階程式設計基礎——檔案系統程式設計檔案寫入操作

檔案系統程式設計之檔案寫入操作——實驗題 //編寫程式碼,完成以下功能: //1.建立檔案file1,寫入字串“abcdefghijklmn”; //2.建立檔案file2,寫入字串“ABCDEFGHIJKLMN”; //3.讀取file1中的內容,寫入file2,使file2中的字串內容為“a

Linux高階程式設計基礎——檔案系統程式設計檔案描述符

檔案系統程式設計之檔案描述符——實驗題 /*編寫程式碼,完成以下功能: 1.建立新檔案,該檔案具有使用者讀寫許可權。 2.採用dup/dup2/fcntl複製一個新的檔案描述符,通過新檔案描述符向檔案寫入“class_name”字串; 3.通過原有的檔案描述符讀取檔案中的內容,並且列印顯示;*/

ext2檔案系統結構分析

轉載自:https://blog.csdn.net/yuzhihui_no1/article/details/50256713 ext2檔案系統 總體儲存佈局 我們知道,一個磁碟可以劃分成多個分割槽,每個分割槽必須先用格式化工具(例如某種mkfs命令)格式化成某種格式的檔案系統,然後才能

淺談ext2檔案系統

我們知道檔案系統是組織和管理磁碟上的檔案,並向用戶提供操作介面(open、read、write等),Unix中的每個物件幾乎都可以當做檔案來看待。核心在沒有結構的硬體上構造結構化的檔案系統,而檔案抽象在整個系統中廣泛使用。Linux支援多種檔案系統,如ext2,ext3,vfat等,ex

Linux磁碟管理——Ext2檔案系統

前言 通常而言,對於一塊新磁碟我們不是直接使用,而是先分割槽,分割槽完畢後格式化,格式化後OS才能使用這個檔案系統。分割槽可能會涉及到MBR和GPT問題。至於格式化和檔案系統又有什麼關係? 這裡的格式化指的是高階格式化,由於每種OS所設定的檔案屬性/許可權並不相同, 為了能夠操作這些檔案,就需要對parti

Linux磁碟管理——日誌檔案系統與資料一致性 Linux磁碟管理——Ext2檔案系統

參考:Linux磁碟管理——Ext2檔案系統 資料不一致 上圖是Ext2結構圖,其他FS結構類似。 一般來說,我們將 inode table 與 data block 稱為資料區;至於其他例如 superblock、 block bitmap 與 inode bitmap 等稱為 metadata

區塊鏈研究-星際檔案系統IPFSWindows環境安裝及使用入門

      add指令會將剛才的根目錄下的所有檔案加入到網路中,並為訪問三個目錄生成了不同的多重雜湊節點ID addedQmXnA3jtjcthBbgrkx8eeJX9YrvFT7BRJVxvhvkpKGEUPk fileTest/test/README.md addedQmZiBRkXJVvunKhxaUD

Linux檔案系統分析二(超級塊,i節點點陣圖和邏輯塊點陣圖)

第二個扇區和第一個扇區一樣屬於引導塊,這裡就不列舉出其內容了,這裡的一塊是兩個扇區即1024B。接下來的一塊就是大名鼎鼎的超級塊了。其內容如下:00000400h: E0 01 A0 05 01 00 01 00 13 00 00 00 00 1C 08 10 ; ??..

EXT2檔案系統實現原理

目錄 二    塊快取    6 EXT2檔案系統結構概覽 1.1 EXT2檔案系統結構框圖 每一個檔案或者目錄在磁碟上都有一個inode用於管理檔案本身屬性資訊,還有資料塊用於存放檔案內容。其inode'和資料塊關係如下圖:   如果檔案比較小,其資料

Linux核心及檔案系統移植jffs2燒錄後無法啟動

近一週的時候都在玩linux 核心及檔案系統移植,使用的版本如下: Bootloader: u-boot-2010.06.tgz Kernel:   linux-3.0.y.tgz

EXT2檔案系統簡介

一、EXT2檔案系統檔案組織形式 EXT2檔案系統是Linux系統中廣泛使用的檔案系統,該檔案系統是一種索引式檔案系統,它將分割槽分為inode和block,它會給每個檔案分配一個inode,inod

(十三)linux檔案系統詳解(基於ext2檔案系統

  我們知道,一個磁碟可以劃分成多個分割槽,每個分割槽必須先用格式化工具(例如某種mkfs命令)格式化成某種格式的檔案系統,然後才能儲存檔案,格式化的過程會在磁碟上寫一些管理儲存佈局的資訊。下圖是一個磁碟分割槽格式化成ext2檔案系統後的儲存佈局:

(轉載)Ext2 檔案系統的硬碟佈局

轉自http://www.ibm.com/developerworks/cn/linux/filesystem/ext2/#icomments 前言 本文的資料來源是 Linux 核心中 ext3 檔案系統的原始碼。為了便於讀者查閱原始碼,本文中一些關鍵的技術詞彙都使用了核

EXT2檔案系統

檔案系統特性         舉例來說,windows 98 以前的微軟作業系統主要利用的檔案系統是 FAT (或 FAT16),windows 2000 以後的版本有所謂的 NTFS 檔案系統,至於 Linux 的正統檔案系統則為 Ext2 (Linux second e

linux下的ext2檔案系統

當我們剛拿到一塊磁碟的時候,我們需要將磁碟格式化成某種格式的檔案系統,這樣才能讓磁碟儲存資料。在windows下,使用的是一種叫做NTFS的檔案系統,而在Linux下,使用的是ext系列的檔案系統,現在最新的版本是ext4,不過原理都差不多,這裡就ext2檔案系統做個簡要的