1. 程式人生 > >linux核心mount原始碼剖析

linux核心mount原始碼剖析

mount命令是大家在平時使用linux的時候經常使用的一個命令,相信很多人都很熟悉這個命令,它的作用是把一個裝置掛載到根檔案系統的某一個目錄上邊去,但是有沒有人對他的內部實現有過一些瞭解的呢,我今天就像從linux的原始碼剖析,一層一層的剝開它實現的奧祕。
首先mount是一個系統呼叫,在使用者空間使用mount函式以後,會呼叫軟中斷,進入核心空間。然後根據傳入的引數,呼叫對應的中端門,隨後進入sys_mount函式,這個函式定義在fs/namespace.c裡,定義如下
/*傳入的dev_name是mount的裝置名,dir_name是掛載點,type是檔案系統型別,flags是標記,data是私有資料*/
asmlinkage long sys_mount(char __user * dev_name, char __user * dir_name,
			  char __user * type, unsigned long flags,
			  void __user * data)
{
	int retval;
	unsigned long data_page;
	unsigned long type_page;
	unsigned long dev_page;
	char *dir_page;
	/*type是字串的檔案系統名字,比如"ext4"這樣的字串,這個函式就是把掛載的選項字串複製到核心裡的一塊地方*/
	retval = copy_mount_options(type, &type_page);
	if (retval < 0)
		return retval;
	/*把使用者空間傳來的dir_name引數複製到核心的一塊記憶體裡邊*/
	dir_page = getname(dir_name);
	retval = PTR_ERR(dir_page);
	if (IS_ERR(dir_page))
		goto out1;
	/*把dev_name複製到核心的一個空閒頁上*/
	retval = copy_mount_options(dev_name, &dev_page);
	if (retval < 0)
		goto out2;
	/*把data資料複製到核心的一個空閒頁上*/
	retval = copy_mount_options(data, &data_page);
	if (retval < 0)
		goto out3;
	/*鎖住核心,防止其他程序搶佔*/
	lock_kernel();
	/*進入mount的主要工作*/
	retval = do_mount((char *)dev_page, dir_page, (char *)type_page,
			  flags, (void *)data_page);
	/*釋放鎖,並釋放記憶體*/
	unlock_kernel();
	free_page(data_page);


out3:
	free_page(dev_page);
out2:
	putname(dir_page);
out1:
	free_page(type_page);
	return retval;
}

然後進入do_mount函式,這個函式也定義在fs/namespace.c裡,定義如下
long do_mount(char *dev_name, char *dir_name, char *type_page,
		  unsigned long flags, void *data_page)
{
	struct nameidata nd;
	int retval = 0;
	int mnt_flags = 0;


	/* 不用的magic就拋棄 */
	if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
		flags &= ~MS_MGC_MSK;
	/*引數檢查*/
	if (!dir_name || !*dir_name || !memchr(dir_name, 0, PAGE_SIZE))
		return -EINVAL;
	if (dev_name && !memchr(dev_name, 0, PAGE_SIZE))
		return -EINVAL;
	/* 把頁的最後一個位元組置為零 */
	if (data_page)
		((char *)data_page)[PAGE_SIZE - 1] = 0;


	/* 把傳入的flags分解到mnt_flags上 */
	if (flags & MS_NOSUID)
		mnt_flags |= MNT_NOSUID;
	if (flags & MS_NODEV)
		mnt_flags |= MNT_NODEV;
	if (flags & MS_NOEXEC)
		mnt_flags |= MNT_NOEXEC;
	if (flags & MS_NOATIME)
		mnt_flags |= MNT_NOATIME;
	if (flags & MS_NODIRATIME)
		mnt_flags |= MNT_NODIRATIME;
	if (flags & MS_RELATIME)
		mnt_flags |= MNT_RELATIME;


	flags &= ~(MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_ACTIVE |
		   MS_NOATIME | MS_NODIRATIME | MS_RELATIME);


	/* 從引數中尋找到掛載點,並且檢查這個目錄是否存在合法 */
	retval = path_lookup(dir_name, LOOKUP_FOLLOW, &nd);
	if (retval)
		return retval;
	/* 安全操作 */
	retval = security_sb_mount(dev_name, &nd, type_page, flags, data_page);
	if (retval)
		goto dput_out;
	/* 如果不是第一次掛載的話,就執行do_remount */
	if (flags & MS_REMOUNT)
		retval = do_remount(&nd, flags & ~MS_REMOUNT, mnt_flags,
				    data_page);
	else if (flags & MS_BIND)/*  */
		retval = do_loopback(&nd, dev_name, flags & MS_REC);
	else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
		retval = do_change_type(&nd, flags);
	else if (flags & MS_MOVE)/* 移動目錄 */
		retval = do_move_mount(&nd, dev_name);
	else/* 第一次掛載 */
		retval = do_new_mount(&nd, type_page, flags, mnt_flags,
				      dev_name, data_page);
dput_out:
	path_release(&nd);
	return retval;
}

我們主要分析下第一次掛載的過程,其他的都類似,第一次掛載做了很多的事情,do_new_mount函式也定義在fs/namespace.c裡,定義如下
static int do_new_mount(struct nameidata *nd, char *type, int flags,
			int mnt_flags, char *name, void *data)
{
	/* vfsmount結構體是掛載最重要的結構體 */
	struct vfsmount *mnt;
	/* 引數檢查 */
	if (!type || !memchr(type, 0, PAGE_SIZE))
		return -EINVAL;


	/* 如果沒有許可權,就結束函式 */
	if (!capable(CAP_SYS_ADMIN))
		return -EPERM;
	/* 為第一次掛載建立dentry,root的inode,如果已經存在的話,就返回已經存在的 */
	mnt = do_kern_mount(type, flags, name, data);
	if (IS_ERR(mnt))
		return PTR_ERR(mnt);
	/* 執行掛載到具體的掛載點 */
	return do_add_mount(mnt, nd, mnt_flags, NULL);
}

然後分析do_add_mount函式,這個函式也定義在fs/namespace.c裡,定義如下
int do_add_mount(struct vfsmount *newmnt, struct nameidata *nd,
		 int mnt_flags, struct list_head *fslist)
{/* 所有的引數都被放在了nameidata裡 */
	int err;
	/* 鎖住核心的訊號量,保持唯一性訪問 */
	down_write(&namespace_sem);
	/* 看看目錄掛載點是不是已經被掛載了,如果被掛載了,就再次尋找dentry和mount結構體 */
	while (d_mountpoint(nd->dentry) && follow_down(&nd->mnt, &nd->dentry))
		;
	err = -EINVAL;
	/* 如果真的已經被掛載,就返回錯誤 */
	if (!check_mnt(nd->mnt))
		goto unlock;


	/* 如果在一個掛載點是同一個檔案系統的話就返回錯誤 */
	err = -EBUSY;
	if (nd->mnt->mnt_sb == newmnt->mnt_sb &&
	    nd->mnt->mnt_root == nd->dentry)
		goto unlock;
	/* 如果根的inode是軟連線,就返回錯誤 */
	err = -EINVAL;
	if (S_ISLNK(newmnt->mnt_root->d_inode->i_mode))
		goto unlock;
	/*  把要掛載的dentry和掛載點的dentry結合,主要的工作函式*/
	newmnt->mnt_flags = mnt_flags;
	if ((err = graft_tree(newmnt, nd)))
		goto unlock;
	/* 記錄在檔案系統連結串列上 */
	if (fslist) {
		/* add to the specified expiration list */
		spin_lock(&vfsmount_lock);
		list_add_tail(&newmnt->mnt_expire, fslist);
		spin_unlock(&vfsmount_lock);
	}
	up_write(&namespace_sem);
	return 0;


unlock:
	up_write(&namespace_sem);
	mntput(newmnt);
	return err;
}

主要的工作是在graft_tree函式內部實現的,我們就進入graft_tree看一下,graft_tree函式定義在fs/namespace.c裡,定義如下
static int graft_tree(struct vfsmount *mnt, struct nameidata *nd)
{
	/*檢查超級塊可不可以掛載*/
	int err;
	if (mnt->mnt_sb->s_flags & MS_NOUSER)
		return -EINVAL;
	/*檢查兩個檔案系統是不是一致的*/
	if (S_ISDIR(nd->dentry->d_inode->i_mode) !=
	      S_ISDIR(mnt->mnt_root->d_inode->i_mode))
		return -ENOTDIR;


	err = -ENOENT;
	mutex_lock(&nd->dentry->d_inode->i_mutex);
	/*dead,死的也不行*/
	if (IS_DEADDIR(nd->dentry->d_inode))
		goto out_unlock;
	/*安全操作*/
	err = security_sb_check_sb(mnt, nd);
	if (err)
		goto out_unlock;
	/*如果掛載點是根目錄或者在快取裡邊*/
	err = -ENOENT;
	if (IS_ROOT(nd->dentry) || !d_unhashed(nd->dentry))
		/*掛載操作*/
		err = attach_recursive_mnt(mnt, nd, NULL);
out_unlock:
	mutex_unlock(&nd->dentry->d_inode->i_mutex);
	if (!err)
		security_sb_post_addmount(mnt, nd);
	return err;
}

主要的工作是在attach_recursive_mnt函式內部實現的,attach_recursive_mnt函式定義在fs/namespace.c裡,定義如下
static int attach_recursive_mnt(struct vfsmount *source_mnt,
			struct nameidata *nd, struct nameidata *parent_nd)
{
	LIST_HEAD(tree_list);
	struct vfsmount *dest_mnt = nd->mnt;
	struct dentry *dest_dentry = nd->dentry;
	struct vfsmount *child, *p;
	/*引數檢查,是不是合理的,比如要掛載點在目的點的下邊*/
	if (propagate_mnt(dest_mnt, dest_dentry, source_mnt, &tree_list))
		return -EINVAL;
	/*是否可分享*/
	if (IS_MNT_SHARED(dest_mnt)) {
		for (p = source_mnt; p; p = next_mnt(p, source_mnt))
			set_mnt_shared(p);
	}
	/*鎖住vfsmount結構體,我們穿入的parent_nd是NULL所以執行else的*/
	spin_lock(&vfsmount_lock);
	if (parent_nd) {
		detach_mnt(source_mnt, parent_nd);
		attach_mnt(source_mnt, nd);
		touch_mnt_namespace(current->nsproxy->mnt_ns);
	} else {
		/*dest_dentry的d_mounted++,標記已經掛載,source_mnt結構體填充*/
		mnt_set_mountpoint(dest_mnt, dest_dentry, source_mnt);
		/*把新的vfsmount提交到全域性hash表*/
		commit_tree(source_mnt);
	}


	list_for_each_entry_safe(child, p, &tree_list, mnt_hash) {
		list_del_init(&child->mnt_hash);
		commit_tree(child);
	}
	spin_unlock(&vfsmount_lock);
	return 0;
}

相關推薦

linux核心mount原始碼剖析

mount命令是大家在平時使用linux的時候經常使用的一個命令,相信很多人都很熟悉這個命令,它的作用是把一個裝置掛載到根檔案系統的某一個目錄上邊去,但是有沒有人對他的內部實現有過一些瞭解的呢,我今天

資料結構 筆記:Linux核心連結串列剖析

Linux核心連結串列的位置及依賴 -位置 ·{linux-2.6.39}\\include\linux\list.h -依賴 #include <linux/types.h> #include <linux/stddef.h> #includ

linux下poll和epoll核心原始碼剖析

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Linux 1.0核心原始碼剖析】執行程式——exec.c

父程序 fork的子程序的目的自然不是建立一個幾乎與自己一模一樣的程序。而是通過子程序呼叫 exec 函式簇去執行另外一個程式。exec() 系統呼叫必須定位該執行檔案的二進位制映像,載入並執行它。 exec() 的Linux實現支援不同的二進位制格式,這是通過 linux

LINUX核心研究----IO複用函式epoll核心原始碼深度剖析

select和poll的效率瓶頸有兩個    1、每次呼叫這些函式的時候都需要將監控的fd和需要監控的事件從使用者空間拷貝到核心空間,非常影響效率。而epoll就是自己儲存使用者空間拷入的fd和需要監控

Linux 核心網路協議棧原始碼剖析】socket.c——BSD Socket層(1)

寫在前面:本系列文章先把各個層對應的檔案原始碼剖析一遍,最後再穿插起來,理清整個協議棧網路資料包的上下傳送通道,從整體實現上進行把握。         圖片來源於《Linux 核心網路棧原始碼情景分析》 更上層函式:tcp socket函式介紹。本篇則是介紹BSD Sock

Linux 核心網路協議棧原始碼剖析】bind 函式剖析

socket 函式並沒有為套接字繫結本地地址和埠號,對於伺服器端則必須顯性繫結地址和埠號。bind 函式主要是伺服器端使用,把一個本地協議地址賦予套接字。 1、應用層——bind 函式 #include <sys/socket.h> int bind(int

Linux 系統呼叫 —— fork 核心原始碼剖析

系統呼叫流程簡述 fork() 函式是系統呼叫對應的 API,這個系統呼叫會觸發一個int 0x80 的中斷; 當用戶態程序呼叫 fork() 時,先將 eax(暫存器) 的值置為 2(即 __NR_fork 系統呼叫號);   執行 int $0x80,cpu 進入核心態;   執行

讀書筆記:LINUX核心完全剖析:基於0.12核心

讀書筆記:LINUX核心完全剖析   IBM PC及其相容機主要使用 獨立編址方式,採用獨立的I/O地址空間對控制裝置中的暫存器進行定址和訪問,IBM PC也部分地使用統一編址。對於使用EISA、PCI等匯流排結構的PC,有64KB的I/O地址空間可供使用。在普通Li

Linux 核心 IPC 通訊原始碼分析-訊息佇列

簡介 目的 本文對最新的 Linux-4.19.4 核心原始碼進行分析,並詳細指出核心 IPC 機制中的訊息佇列的原理。 程序間通訊 IPC(程序間通訊,InterProcess Communication)是核心提供的系統中程序間進行通訊的一種機制。系統中每個程序的使用者地

編譯linux核心原始碼,安裝、刪除核心

Linux核心編譯、安裝流程 本部落格屬於原創,轉載請註明來源 此處只講linux核心編譯步驟至於安裝虛擬機器,安裝ubuntu作業系統請自行百度 環境資訊: Linux作業系統:ubuntu16.04 核心版本:4.15.0-29-generic 需要編譯和安裝的核心原始碼

linux核心建立flash上的各分割槽原始碼進行分析

1.注意:核心原始碼版本為4.9 2.首先注意關鍵字串"partitions found on MTD device 這句話在drivers/mtd/mtdpart.c的parse_mtd_partitions()中出現 3.mtd_device_parse_register()呼叫了parse_mtd

linux核心原始碼分析-夥伴系統

之前的文章已經介紹了夥伴系統,這篇我們主要看看原始碼中是如何初始化夥伴系統、從夥伴系統中分配頁框,返回頁框於夥伴系統中的。   我們知道,每個管理區都有自己的夥伴系統管理屬於這個管理區的頁框,這也說明了,在夥伴系統初始化時,管理區必須要已經存在(初始化完成)

[原始碼和文件分享]Linux核心編譯及新增系統呼叫

1 總體設計思路 系統呼叫的本質是呼叫核心函式,以核心態執行程式。為了在核心態下執行,本實驗針對Linux的核心進行修改,增加自定義系統呼叫函式實現使用者態程式對任意程序的nice值進行修改或者讀取來進行測試。 2 主要函式的介面設計 核心態程式 SYSCALL_DEFINE3

紅黑樹原理淺談(附Linux核心原始碼註釋)

引言:紅黑樹(英語:Red–black tree)是一種自平衡二叉查詢樹,是在電腦科學中用到的一種資料結構,典型的用途是實現關聯陣列。它是在1972年由魯道夫·貝爾發明的,他稱之為"對稱二叉B樹",它現代的名字是在Leo J. Guibas和Robert Sedgewick於19

《深入分析Linux核心原始碼》筆記:Linux 核心結構

一、Linux 核心在整個作業系統中的位置                      圖1 Linux核心在整個作業系統中的位置 1)使用者程序 使用者程序位於作業系統的最上層,它執行在作業系統上,成為一個作業系統中的一個程序。 2)系統呼叫介面 應用程式中,可以

《深入分析linux核心原始碼》筆記:linux 核心原始碼

一、Linux核心原始碼的結構 Linux 核心原始碼位於/usr/src/linux 目錄下。 include/目錄包含了建立核心程式碼時所需的大部分包含檔案,這個模組利用其他模組重建核心。 i

Linux核心原始碼分析--zImage出生實錄(Linux-3.0 ARMv7)

此文為兩年前為好友劉慶敏的書《嵌入式Linux開發詳解--基於AT91RM9200和Linux 2.6》中幫忙寫的章節的重新整理。如有雷同,純屬必然。經作者同意,將我寫的部分重新整理後放入blog中。 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

四、Kafka 核心原始碼剖析

一、Kafka消費者原始碼介紹 1.分割槽消費模式原始碼介紹 分割槽消費模式直接由客戶端(任何高階語言編寫)使用Kafka提供的協議向伺服器傳送RPC請求獲取資料,伺服器接受到客戶端的RPC請求後,將資料構造成RPC響應,返回給客戶端,客戶端解析相應的RPC響應獲取資料。Kafka支援的協議眾多,使用

I/O複用 poll的核心原始碼剖析

一:I/O 複用技術   I/O複用技術是:把我們關注的描述符組成一個描述符表(通常不止一個描述符),呼叫I/O複用函式(select/poll/epoll),當描述符表中有可進行非阻塞I/O操作的描述符時,複用函式返回;否則阻塞複用函式,直到描述符表中有可進行非阻塞I/O操作的描述符