歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

Linux中文件名解析處理源碼分析

前言

Linux中對一個文件進行操作的時候,一件很重要的事情是對文件名進行解析處理,並且找到對應文件的inode對象,然後創建表示文件的file對象。在此,對文件名解析過程,並且如何找到對應inode的過程進行源碼分析。分析代碼基於Linux-3.2版本。

關鍵函數分析

不管是通過應用層的API函數還是在內核中打開一個文件,最終都需要調用filp_open函數,該函數的主要職責就是解析文件名,找到文件對應的inode對象,然後分配內存創建file對象,最後執行該文件對應的file->open函數。

filp_open的核心處理函數是path_openat,該函數分析如下:

static struct file *path_openat(int dfd, const char *pathname,
struct nameidata *nd, const struct open_flags *op, int flags)
{
struct file *base = NULL;
struct file *filp;
struct path path;
int error;
/* 創建一個file對象 */
filp = get_empty_filp();
if (!filp)
return ERR_PTR(-ENFILE);

.filp->f_flags = op->open_flag;
nd->intent.open.file = filp;
nd->intent.open.flags = open_to_namei_flags(op->open_flag);
nd->intent.open.create_mode = op->mode;
/* 初始化檢索的起始目錄,判斷起始目錄是根目錄還是當前目錄,並且初始化nd->inode對象,為link_path_walk函數的解析處理做准備。 */
error = path_init(dfd, pathname, flags | LOOKUP_PARENT, nd, &base);
if (unlikely(error))
goto out_filp;

.current->total_link_count = 0;
/* 關鍵的字符串解析處理函數,其核心思想是分級解析字符串,通過字符串對應的目錄項找到下一級目錄的inode節點。該函數的具體分析如下。 */
error = link_path_walk(pathname, nd);
if (unlikely(error))
goto out_filp;
/* do_last函數創建或者獲取文件對應的inode對象,並且初始化file對象,至此一個表示打開文件的內存對象filp誕生 */
filp = do_last(nd, &path, op, pathname);
while (unlikely(!filp)) { /* trailing symlink */
struct path link = path;
void *cookie;
if (!(nd->flags & LOOKUP_FOLLOW)) {
path_put_conditional(&path, nd);
path_put(&nd->path);
filp = ERR_PTR(-ELOOP);
break;
}
nd->flags |= LOOKUP_PARENT;
nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);
error = follow_link(&link, nd, &cookie);
if (unlikely(error))
filp = ERR_PTR(error);
else
filp = do_last(nd, &path, op, pathname);
put_link(nd, &link, cookie);
}
out:
if (nd->root.mnt && !(nd->flags & LOOKUP_ROOT))
path_put(&nd->root);
if (base)
fput(base);
release_open_intent(nd);
return filp;

.out_filp:
filp = ERR_PTR(error);
goto out;
}

link_path_walk函數完成了基本的名字解析功能,是名字字符串解析處理實現的核心。該函數的實現基於分級解析處理的思想。例如,當需要解析“/dev/mapper/map0”字符串時,其首先需要判斷從何處開始解析?根目錄還是當前目錄?案例是從根目錄開始解析,那麼獲取根目錄的dentry對象並開始分析後繼字符串。以’/’字符為界按序提取字符串,首先我們可以提取”dev”字符串,並且計算該字符串的hash值,通過該hash值查找detry下的inode hash表,就可以得到/dev/目錄的inode對象。依次類推,最後解析得到”/dev/mapper/”目錄的inode對象以及文件名”map0”。至此,link_path_walk函數的使命完成,最後可以通過do_last函數獲取或者創建文件inode。link_path_walk函數分析如下:

static int link_path_walk(const char *name, struct nameidata *nd)
{
struct path next;
int err;
/* 移除’/’字符 */
while (*name=='/')
name++;
/* 如果解析已經完成,直接返回 */
if (!*name)
return 0;

./* At this point we know we have a real path component. */
for(;;) {
unsigned long hash;
struct qstr this;
unsigned int c;
int type;
/* inode訪問的permission檢查 */
err = may_lookup(nd);
if (err)
break;

.this.name = name;
c = *(const unsigned char *)name;
/* 初始化hash值 */
hash = init_name_hash();
do {
name++;
/* 累計計算名字字符串的hash值 */
hash = partial_name_hash(c, hash);
c = *(const unsigned char *)name;
/* 如果遇到’/’字符,結束一次hash計算統計 */
} while (c && (c != '/'));
/* 得到字符串長度和hash結果 */
this.len = name - (const char *) this.name;
this.hash = end_name_hash(hash);

.type = LAST_NORM;
/* LAST_DOT和LAST_DOTDOT情形判斷 */
if (this.name[0] == '.') switch (this.len) {
case 2: /* LAST_DOTDOT是上級目錄 */
if (this.name[1] == '.') {
type = LAST_DOTDOT;
nd->flags |= LOOKUP_JUMPED;
}
break;
case 1: /* LAST_DOT是當前目錄 */
type = LAST_DOT;
}
if (likely(type == LAST_NORM)) {
/* LAST_NORM標記說明是需要通過本地目錄進行字符串解析 */
struct dentry *parent = nd->path.dentry;
nd->flags &= ~LOOKUP_JUMPED;
if (unlikely(parent->d_flags & DCACHE_OP_HASH)) {
/* 如果該標記有效,需要重新計算hash值 */
err = parent->d_op->d_hash(parent, nd->inode,
&this);
if (err <0)
break;
}
}
/* 如果字符串已經解析完畢,直接跳轉到last_component */
/* remove trailing slashes? */
if (!c)
goto last_component;
while (*++name == '/');
if (!*name)
goto last_component;
/* 通過walk_component函數找到解析字符串對應的inode,並且將nd->inode改稱最新inode,准備繼續解析後面的字符串信息。因為目錄項所管理的inode在系統中通過hash表進行維護,因此,通過hash值可以很容易的找到inode。如果內存中還不存在inode對象,對於ext3文件系統會通過ext3_lookup函數從磁盤上獲取inode的元數據信息,並且構造目錄項中所有的inode對象。 */
err = walk_component(nd, &next, &this, type, LOOKUP_FOLLOW);
if (err <0)
return err;

.if (err) {
err = nested_symlink(&next, nd);
if (err)
return err;
}
if (can_lookup(nd->inode))
continue;
/* 字符串還沒有解析完畢,但是當前的inode已經繼續不允許解析處理了,所以,返回錯誤碼 */
err = -ENOTDIR;
break;
/* here ends the main loop */

.last_component:
/* 最後一個字符串不需要解析處理,需要由do_last函數來處理,此處結束解析,正確返回 */
nd->last = this;
nd->last_type = type;
return 0;
}
terminate_walk(nd);
return err;
}

小結

文件名解析處理是文件系統的必備功能,通過文件名的解析索引到表示文件的inode內存對象,並且創建文件對象file。在文件名解析的過程中,首先需要確定的是檢索起始點,然後通過hash table查找目錄項以及檢索文件。在查找的過程中,需要考慮文件訪問的權限以及符號連接等問題。總體來說這些代碼難度不是很大,但是需要有一個整體的思路,就可以更好的理解分析代碼了,這裡只是對名字解析過程中的幾個關鍵函數進行拋磚引玉式的分析。不正之處,敬請指出。

 

Copyright © Linux教程網 All Rights Reserved