linux核心mount原始碼剖析
首先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操作的描述符