一、背景
在之前的博客?缺頁異常導致的iowait打印出相關文件的絕對路徑-CSDN博客 里 2.2.3 一節里,我們講到了file,fd,inode,dentry,super_block這幾個概念,在這篇博客里,我們針對inode和dentry做一些實驗,針對的是軟鏈接和硬鏈接的場景。
二、實驗源碼及最普通常見的場景
2.1 實驗源碼
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/dcache.h>
#include <linux/namei.h>// 模塊參數
static char *filepath = "/tmp/testfile"; // 默認文件路徑
module_param(filepath, charp, S_IRUGO);
MODULE_PARM_DESC(filepath, "Path of the file to open");char buffer[4096];int getfullpath(struct inode *inode)
{struct dentry *dentry;hlist_for_each_entry(dentry, &inode->i_dentry, d_u.d_alias) {char *path;path = dentry_path_raw(dentry, buffer, PAGE_SIZE);if (IS_ERR(path)){continue; }printk("dentry name = %s , path = %s\n", dentry->d_name.name, path);}return 0;
}static int __init my_module_init(void) {struct file *file;printk(KERN_INFO "Opening file: %s\n", filepath);// 打開文件file = filp_open(filepath, O_RDONLY, 0);if (IS_ERR(file)) {printk(KERN_ERR "Error opening file: %ld\n", PTR_ERR(file));return PTR_ERR(file);}getfullpath(file->f_inode);// 關閉文件filp_close(file, NULL);return -EINVAL;
}static void __exit my_module_exit(void) {printk(KERN_INFO "Module exiting\n");
}module_init(my_module_init);
module_exit(my_module_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Zhaoxin");
MODULE_DESCRIPTION("A simple module to read dentry from a file's inode");
2.2 源碼分析
上面的源碼還是比較簡單的,在內核模塊里打開一個文件,使用傳入的filepath參數,要注意,如果用file_path這個名字會造成和內核里的file_path的符號相沖突導致編譯不過的情況。
內核模塊根據傳入的filepath的路徑(可以是相對路徑)打開文件,然后使用打開的文件里的file->f_inode作為參數,傳給getfullpath函數,這個getfullpath函數改造自之前的博客?缺頁異常導致的iowait打印出相關文件的絕對路徑-CSDN博客 里的 2.2.3 一節里的getfullpath函數,改得更簡單,只是打印到dmesg里:
insmod ko一定會返回失敗,為了方便調試,不用rmmod,可直接再運行insmod:
2.3?最普通場景的場景的運行情況
我們創建一個aa.txt文件,然后執行insmod testinode.ko filepath=aa.txt
dmesg的內容如下:
這時候,這個aa.txt就一個文件,沒有創建過相關的硬鏈接或軟鏈接。
三、增加一個硬鏈接后的運行情況
我們通過如下命令創建一個硬鏈接:
ln aa.txt bb.txt
然后執行一樣的命令:
insmod testinode.ko filepath=aa.txt
dmesg里的內容如下:
如果用:
insmod testinode.ko filepath=bb.txt
dmesg里的內容是一樣的:
可以看到,如果創建一個硬鏈接后,用其中任意一個符號來open文件,通過其對應的inode遍歷得到的dentry全路徑是一模一樣的。
為了進一步做實驗,我們把inode的指針也打出來:
可以從上圖里看到,無論是打開aa.txt還是打開bb.txt,其inode的地址是一樣的。
四、增加一個軟鏈接后的運行情況
通過ln -s bb.txt slinkbb.txt之后,再運行抓到的dmesg情況:
可以看到用軟鏈接的名字來open和用硬鏈接的名字來open,得到的file的f_inode地址是一樣的,自然通過f_inode指針遍歷得到的dentry也是一樣的。
我們用file里的f_path.dentry來打印出其name:
do {char *path;path = dentry_path_raw(file->f_path.dentry, buffer, PAGE_SIZE);if (IS_ERR(path)){break;}printk("[2] dentry name = %s , path = %s\n", file->f_path.dentry->d_name.name, path);} while(0);
得到的dmesg如下:
說明file里的f_path.dentry可以得到它的軟鏈接的源頭文件路徑。
4.1 軟鏈接不同于硬鏈接,會新分配一個inode
通過ls -li如下圖就可以看到新增一個硬鏈接并不會新增一個inode,而新增一個軟鏈接就會新增一個inode(另外,之所以用open出來的file看似關聯不上這個新的inode,因為它已經在filp_open期間根據軟鏈接的inode找到了實體文件)
另外,軟鏈接可以鏈接一個文件夾或一個不存在的文件,而硬鏈接不行。
4.2 軟鏈接相關的內核實現
我們看一下軟鏈接相關的內核實現,看看是哪里分配的一個inode。
創建軟鏈接的系統調用是symlink,如下圖實現:
看一下它調用的do_symlinkat的實現:
上圖里創建軟鏈接的核心函數是紅色框出的vfs_symlink函數,如下圖調用的是對應文件系統的symlink實現:
比如ext4的symlink實現,如下圖里的ext4_new_inode_start_handle函數來創建新的inode:
然后調用下圖里的ext4_add_nondir函數來把創建出來的inode放到所在的目錄的數據塊里(也建立了inode和dentry的鏈接):