open系統調用源碼剖析

1.簡介

需要前備知識,對教程的task_struct結構體有一定的瞭解,尤其是要對其中的struct file有一定了解,明白物理內存與虛擬內存的映射關係,明白文件系統的分層,最最重要的一點是會使用工具追償源碼,現在就來看一看open系統調用。

#define AT_FDCWD		-100    /* Special value used to indicate
                                    openat should use the current working directory. */
//linux-4.13.16\fs\open.c
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
{   ...
	return do_sys_open(AT_FDCWD, filename, flags, mode);
}

略去一些細節性的東西(話說看內核源碼,最忌諱深入追求細節),AT_FDCWD根據註釋應該是指示在當前文件夾下查找吧,接下來調用do_sys_open函數:

long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
{
    .../*相關數據結構定義與參數檢查*/
	fd = get_unused_fd_flags(flags);//不論我文件存在與否,都要一個fd綁定struct file
	if (fd >= 0) {
		struct file *f = do_filp_open(dfd, tmp, &op);//依據tmp結構獲取文件的struct file
		if (IS_ERR(f)) {//失敗了走這兒。。。
			put_unused_fd(fd);
			fd = PTR_ERR(f);
		} else {//找到了走這兒。。。
			fsnotify_open(f);
			fd_install(fd, f);
		}
	}
	putname(tmp);
	return fd;//爲open函數返回的fd
}

關於get_unused_fd_flags獲取空閒fd,其實不算太複雜,先找到進程對應的struct file結構體數組,找到還沒有被使用的即可,而且剖析源碼會發現,使用了好多自旋鎖,原因無他,當前進程不想被阻塞。所以主要看do_filp_open看它怎樣找到一個struct file結構。

2.敢問我的file結構體在何方

首先認識一個關於保存文件路徑等相關信息用於起輔助作用的結構體:

//linux-4.13.16\fs\namei.c
struct nameidata {
	struct path	path; //該結構體標識了文件路徑相關的信息
	struct qstr	last;
	struct path	root;
	struct inode	*inode; /* path.dentry.d_inode */
	unsigned int	flags;
	unsigned	seq, m_seq;
	int		last_type;
	unsigned	depth;
	int		total_link_count;
	struct saved {
		struct path link;
		struct delayed_call done;
		const char *name;
		unsigned seq;
	} *stack, internal[EMBEDDED_LEVELS];
	struct filename	*name;
	struct nameidata *saved;
	struct inode	*link_inode;
	unsigned	root_seq;
	int		dfd;
};
//其初始化方式
static void set_nameidata(struct nameidata *p, int dfd, struct filename *name)
{
	struct nameidata *old = current->nameidata;
	p->stack = p->internal;
	p->dfd = dfd;
	p->name = name;
	p->total_link_count = old ? old->total_link_count : 0;
	p->saved = old;
	current->nameidata = p;//爲何要掛在進程上?
}

do_filp_open:

struct file *do_filp_open(int dfd, struct filename *pathname,
		const struct open_flags *op)
{
	struct nameidata nd;
	int flags = op->lookup_flags;
	struct file *filp;

	set_nameidata(&nd, dfd, pathname);//初始化了nd變量,將路徑名等信息均保存在該結構
	filp = path_openat(&nd, op, flags | LOOKUP_RCU);//加載文件至內存,返回struct file
	...//失敗了怎麼處理
	restore_nameidata();
	return filp;
}

其實到此處就應該明白一點,任何東西都不是大白菜,平時文件在磁盤上,想要操作它顯然直接從磁盤讀取是不合適的,內核設計的文件系統,內存關聯設計原理告訴我們它應該會將文件先映射在內存裏面,然後初始化我們分配好的struct file 結構體,這上面會有豐富的信息包括文件的inode節點等。故現在我們就來看一看它是如何串聯的?

static struct file *path_openat(struct nameidata *nd,
			const struct open_flags *op, unsigned flags)
{
	const char *s;
	struct file *file;
	int opened = 0;
	int error;

	file = get_empty_filp();
    ...
	file->f_flags = op->open_flag;//初始化f_flags
    ...
	s = path_init(nd, flags);//繼續初始化struct nameidata結構,返回輸入的路徑名
    ...//下面link_path_walk進行路徑名的劃分
	while (!(error = link_path_walk(s, nd)) &&
		(error = do_last(nd, file, op, &opened)) > 0) {...}
	//先解析路徑前面的部分,然後調用do_last解析路徑最後面的部分
	terminate_walk(nd);
    。。。
	return file;
}

do_last分兩種情況解析處理,若在文件路徑上找到了要查找的文件的對應的dentry項則返回,否則就調用相關函數在kmem_cache裏面分配一個dentry對象:

static int do_last(struct nameidata *nd,
		   struct file *file, const struct open_flags *op,
		   int *opened){
    ...
    error = lookup_fast(nd, &path, &inode, &seq);//在緩存中找dentry
    ...
        error = lookup_open(nd, &path, file, op, got_write, opened);
        //此函數處理dentry沒有找到的情況
        //先分配dentry,然後調用inode所在文件系統的lookip_open函數在文件系統中查找dentry
        //查找到就好,若是仍舊沒有查找到,在通過看flag是否有O_CREAT標記確定是否是創建文件操作
        //是則創建之,否則返回錯誤
    error = vfs_open(&nd->path, file, current_cred());
    //這處最後調用vfs_open真正的打開文件		       
}

3.總結

總的來說,這個open系統調用是比較複雜的。

  • 通過task_struct 找到一個空閒文件描述符
  • 根據輸入的路徑名在dentry對應的高速緩存中找到要打開文件對應的dentry數據結構
  • 若沒有找到則調用相關函數在dentry對應的kmem_cache中創建新的dentry對象,然後去fs查找
  • 根據dentry結構找到對應的inode對象用來初始化file結構體
  • 最後調用vfs_open函數真正打開文件

附註

  • 硬盤文件系統由一個個的inode塊和普通數據塊組成
  • inode結構我們又將它叫做元數據,保存文件好多類型的信息以及具體的數據塊
  • ext2與ext3文件系統定義一個大小爲15的數組,前面12項直接保存具體的塊,後面3項依次爲1,2,3級索引。
  • ext4引入了一種叫做extens的數據結構組織形式,它可以放置一個extent_header與4個普通的索引或者葉子節點exten,這樣就組成了一個多叉樹,擴展性大大提高,文件的容量也大大提高
  • 我們使用inode位圖與塊位圖來組織和管理磁盤上的空閒塊。
  • 這樣我們就可以串起來了,調用open函數,若確實有這個文件就直接打開就好,若沒有且設置了O_CREAT標記,就調用表示這個文件的dentry的inode的create函數創建之,在創建的時候其實就是調用文件系統的相關函數,從位圖中找到空閒的inode塊
  • 受限於塊位圖與大量的塊以及inode位圖與inode塊索引的磁盤容量太小的緣故,我們將這樣一個組合叫做一個塊組,這樣一個個塊組就構成了我們的文件系統。
  • 對於文件系統中的每一個塊組我們有一個超級快,記錄了一些相關信息,包括此塊組有多少塊多少inode等。。。
  • 軟硬鏈接的根本區別就是對inode的使用方式不同,硬鏈接與文件共享inode而軟連接有自己的inode只不過其數據塊裏面指向的是真正的文件而已。最後,軟鏈接可以跨文件系統,而硬鏈接不可以。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章