這篇文章寫的不錯,給大家進行分享
Linux系統是如何區分普通文件與設備驅動文件的研究一
文件的打開
一般來說對於文件或者是設備的操作都是從open開始的,我們首先要打開這個設備節點或者是普通文件,才可以對這個文件進行read、write、ioctl、mmap等操作。所以一切的起源於open。我們首先從open開始研究。
在linux系統進程當中,分為內核空間和用戶空間。當我們在用戶空間通過open之後,會產生一個軟中斷,然後通過系統調用陷入內核空間。通過系統調用號,我們可以跳轉到該中斷例程的入口地址,我們接著看內核源碼的實現。
1、在arch/x86/include/asm/unistd_32.h中定義了系統調用號
#define __NR_restart_syscall 0
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
#define __NR_close 6
#define __NR_waitpid 7
。。。。。。。。
2、當產生系統調用的時候,會進入到下面這個函數:
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode)
{
long ret;
if (force_o_largefile())
flags |= O_LARGEFILE;
ret = do_sys_open(AT_FDCWD, filename, flags, mode);
/* avoid REGPARM breakage on x86: */
asmlinkage_protect(3, ret, filename, flags, mode);
return ret;
}
3、最終會調用到do_sys_open(),這個函數首先分配一個可用的文件描述符。並且通過調用do_filp_open()通過傳進來的文件名查找到inode信息,並且根據這些信息創建一個file對象,並且將inode和file的關係關聯起來。
long do_sys_open(int dfd, const char __user *filename, int flags, int mode)
{
/*獲取文件名稱,由getname()函數完成,其內部首先創建存取文件名稱的空間,
然後*從用戶空間把文件名拷貝過來*/
char *tmp = getname(filename);
int fd = PTR_ERR(tmp);
if (!IS_ERR(tmp)) {
/*獲取一個可用的fd,此函數調用alloc_fd()函數從
fd_table中獲取一個可用fd,並做些簡單初始化,此函數內部實現比較簡單,
此次分析不細看,注意,對於文件描述符fd來講,它只對本進程有效,
也即它只在該進程中可見而在其它進程中代表著完全不同的文件。
*/
fd = get_unused_fd_flags(flags);
if (fd >= 0) {
/*fd獲取成功則開始打開文件,此函數是主要完成打開功能的函數,在此先放一放,下面詳細分析*/
//如果分配fd成功,則創建一個file對象
struct file *f = do_filp_open(dfd, tmp, flags, mode, 0);
if (IS_ERR(f)) {
/*打開失敗,釋放fd*/
put_unused_fd(fd);
fd = PTR_ERR(f);
} else {
/*文件如果已經被打開了,調用fsnotify_open()函數,根據inode所指定的信息進行打開
函數(參數為f)將該文件加入到文件監控的系統中。該系統是用來監控文件被打開,創建,
讀寫,關閉,修改等操作的*/
fsnotify_open(f->f_path.dentry);
/*將文件指針安裝在fd數組中
將struct file *f加入到fd索引位置處的數組中。如果後續過程中,有對該文件描述符的
操作的話,就會通過查找該數組得到對應的文件結構,而後在進行相關操作。*/
fd_install(fd, f);
}
}
/*釋放放置從用戶空間拷貝過來的文件名的存儲空間*/
putname(tmp);
}
return fd;
}
4、do_filp_open函數的一個重要作用就是根據傳遞近來的權限進行分析,並且分析傳遞近來的路徑名字,根據路徑名逐個解析成dentry,並且通過dentry找到inode,inode就是記錄著該文件相關的信息, 包括文件的創建時間和文件屬性所有者等等信息,根據這些信息就可以找到對應的文件操作方法。在這個過程當中有一個臨時的結構體用於保存在查找過程中的相關信息,就是
struct nameidata {
struct pathpath;//當前目錄的dentry數據結構
struct qstrlast;//這個結構體也是臨時性的,主要用來保存當前目錄的名稱,雜湊值。
unsigned intflags;
intlast_type;
unsigneddepth;//連接文件的深度(可能一個連接文件跟到最後還是一個了連接文件)
//用來保存連接文件的一些信息,下標表示連接文件的深度
char *saved_names[MAX_NESTED_LINKS + 1];
/* Intent data */
union {
struct open_intent open;
} intent;
};
struct file *do_filp_open(int dfd, const char *pathname,
int open_flag, int mode, int acc_mode)
{
/*當內核要訪問一個文件的時候,第一步要做的是找到這個文件,而查找文件的過程在vfs
裡面是由path_lookup或者path_lookup_open函數來完成的。這兩個函數將用戶傳進來的字符串表示的文件路徑轉換成一個dentry結構,並建立好相應的inode和file結構,將指向file
的描述符返回用戶。用戶隨後通過文件描述符,來訪問這些數據結構當函數正確返回的時候,已經將inode和dentry的結構體創建好,並且綁定好*/
error = do_path_lookup(dfd, pathname, LOOKUP_PARENT, &nd);
/*
這個函數根據 struct nameidata 結構返回一個 struct file。可以看到
struct file 是在使用了 __dentry_open() 函數後被填充的,且使用的第
一個參數是 nameidata->dentry,這也是為什麼我們要獲得 struct nameidata
的一個主要原因,其目的就是為了得到 struct dentry 結構。
在此時,已經將inode和dentry的結構體創建好,並且綁定好*/
filp = nameidata_to_filp(&nd, open_flag);
mnt_drop_write(nd.path.mnt);
return filp;
}
struct file *do_filp_open(int dfd, const char *pathname,
int open_flag, int mode, int acc_mode)
{
...................
/*當內核要訪問一個文件的時候,第一步要做的是找到這個文件,而查找文件的過程在vfs
裡面是由path_lookup或者path_lookup_open函數來完成的。這兩個函數將用戶傳進來的字符串
表示的文件路徑轉換成一個dentry結構,並建立好相應的inode和file結構,將指向file
的描述符返回用戶。用戶隨後通過文件描述符,來訪問這些數據結構
當函數正確返回的時候,已經將inode和dentry的結構體創建好,並且綁定好*/
error = do_path_lookup(dfd, pathname, LOOKUP_PARENT, &nd);
/*
這個函數根據 struct nameidata 結構返回一個 struct file。可以看到
struct file 是在使用了 __dentry_open() 函數後被填充的,且使用的第
一個參數是 nameidata->dentry,這也是為什麼我們要獲得 struct nameidata
的一個主要原因,其目的就是為了得到 struct dentry 結構。
在此時,已經將inode和dentry的結構體創建好,並且綁定好*/
filp = nameidata_to_filp(&nd, open_flag);
...................
}
static int do_path_lookup(int dfd, const char *name,
unsigned int flags, struct nameidata *nd)
{
...................
// 注意:這個函數才真正的分解路徑,調用實際文件系統的操作。
// 它本身也是個簡單封狀,實際是使用 __link_path_walk() 函數
// 完成操作。
retval = path_walk(name, nd);
...................
}
//分裝的一個路徑搜索路徑
static int path_walk(const char *name, struct nameidata *nd)
{
current->total_link_count = 0;
return link_path_walk(name, nd);
}
static __always_inline int link_path_walk(const char *name, struct nameidata *nd)
{
//對路徑名字進行解析
result = __link_path_walk(name, nd);
}
static int __link_path_walk(const char *name, struct nameidata *nd)
{
//對路徑的名name進行逐一分析出一個dentry項,分析完路徑名之後再進行查找inode
..............................
// 從緩存或調用實際文件系統函數獲取 inode 信息
到這裡才是查找對應 struct dentry 的具體操作,此函數首先從緩存中嘗試獲取
struct dentry 結構。如果獲取失敗,則調用 real_lookup() 函數使用實際文件
系統方法來讀取 inode 信息。這裡要明確 struct dentry 中包含了 struct inode
信息。*/
//真正的開始查找了,do_lookup(),首先現在內存中的雜湊表中查找,如果沒有找到
//那麼就要到磁盤或者相應的塊設備中去查找,並在內存建立相應的數據結構
//next是struct path類型的結構體,定義於/linux/fs/namei.c,用於保存從磁盤或者其他塊設備上查找的信息
err = do_lookup(nd, &this, &next);
.....................................
}
static int do_lookup(struct nameidata *nd, struct qstr *name,
struct path *path)
{
struct vfsmount *mnt = nd->path.mnt;
// 從 hlist 中獲取 struct dentry 結構,hlist 代表的是
// 一個 inode 的緩存即是一個 HASH 表
struct dentry *dentry = __d_lookup(nd->path.dentry, name);
//在緩衝區中沒有找到指定名字的dentry,調用real_lookup在磁盤中查找
/* 到這裡才是查找對應 struct dentry 的具體操作,此函數首先從緩存中嘗試獲取
struct dentry 結構。如果獲取失敗,則調用 real_lookup() 函數使用實際文件
系統方法來讀取 inode 信息。這裡要明確 struct dentry 中包含了 struct inode
信息。 */
// 如果沒有找到則會調用 real_lookup() 實際文件系統方法
// 從磁盤中獲取
if (!dentry)
goto need_lookup;
if (dentry->d_op && dentry->d_op->d_revalidate)
goto need_revalidate;
done:// 如果從緩存中找到,則設置 struct path 並返回
path->mnt = mnt;
path->dentry = dentry;
__follow_mount(path);
return 0;
need_lookup:
// 從磁盤中獲取// 使用實際文件系統方法,從磁盤中獲得 inode 信息
dentry = real_lookup(nd->path.dentry, name, nd);
.........................
}
//當所有查找dentry的方法都失效的時候,只有調用real_lookup()到磁盤中查找inode
static struct dentry * real_lookup(struct dentry * parent, struct qstr * name, struct nameidata *nd)
{
/*
在分析 real_lookup() 函數前,我們先來看一下 ext3 文件系統的 inode
結構。很明顯可以看出 lookup 指向了 ext3_lookup() 函數。
存放在 /fs/ext3/namei.c
struct inode_operations ext3_dir_inode_operations = {
......
.lookup = ext3_lookup,
......
};
file_system_type
register_filesystem
vfs_kern_mount
此函數先從緩存中查找對應的 inode,如果沒有則新分配一個 struct dentry
結構,然後調用 parent->d_inode->i_op->lookup 即調用了 ext3_lookup()
函數來查找 inode。
Lookup函數還會調用到一個重要的函數inode = ext3_iget(dir->i_sb, ino);
*/
result = dir->i_op->lookup(dir, dentry, nd);
}
//Lookup函數還會調用到一個重要的函數inode = ext3_iget(dir->i_sb, ino);該函數比較複雜,主要是完成inode的賦值,然後根據inode的信息來判斷該文件到底是什麼類型的文件,然後會調用到底層向對應的操作該文件的方法對其進行操作
struct inode *ext3_iget(struct super_block *sb, unsigned long ino)
{
// 注意:這裡的 __ext3_get_inode_loc 是產生
// 一個磁盤 I/O 從磁盤讀取真正的 struct inode
// 來填充 in core 類型的。注意這個函數使用的
// 第三個參數,為 0 的情況下產生 I/O 從磁盤
// 讀取,否則從 buffer_head 磁盤緩存中查找。
ret = __ext3_get_inode_loc(inode, &iloc, 0);
// 如果是普通文件的話,則使用 ext3_file_xxx 函數集
// 注意:在使用 ext3_file_operations 函數集時,它的
// open 函數對應的是 generic_file_open() 函數,而這個函數
// 除了判斷大文件是否合法外,幾乎就是一個空函數,也就是說
// 如果是在一個 ext3 文件系統上,open 操作其實沒有任何具體
// 動作,是無意義的。為什麼會這樣呢?在後面介紹文件系統時
// 會講到。
if (S_ISREG(inode->i_mode)) {//是普通文件,使用普通文件的操作方法
inode->i_op = &ext3_file_inode_operations;
inode->i_fop = &ext3_file_operations;
ext3_set_aops(inode);
} else if (S_ISDIR(inode->i_mode)) {// 如果是目錄的話,則要區別對待,使用 ext3_dir_xxx 函數集
inode->i_op = &ext3_dir_inode_operations;
inode->i_fop = &ext3_dir_operations;
} else if (S_ISLNK(inode->i_mode)) {
if (ext3_inode_is_fast_symlink(inode)) {// 如果是連接的話,也要區別對待,使用 ext3_symlink_xxx 函數集
inode->i_op = &ext3_fast_symlink_inode_operations;
nd_terminate_link(ei->i_data, inode->i_size,
sizeof(ei->i_data) - 1);
} else {
inode->i_op = &ext3_symlink_inode_operations;
ext3_set_aops(inode);
}
} else {
// 如果以上三種情況都排除了,那麼我們則認為他是一個設備驅動
// 注意:這裡的僅對 inode->i_op 函數集進行了直接賦值。對於
// inode->i_fop 函數集使用的是 init_special_inode() 函數
// 進行的賦值
inode->i_op = &ext3_special_inode_operations;//對inode的操作方法進行賦值
if (raw_inode->i_block[0])
init_special_inode(inode, inode->i_mode,//使用init_special_inode() 對文件操作方法進行進行賦值
old_decode_dev(le32_to_cpu(raw_inode->i_block[0])));
else
//例如是字符設備,在此處根據inode的信息,找到struct cdev對應的是我們自己實現的字符設備驅動
//在此處會將我們實現的字符設備驅動操作方法,賦值給文件結構體的操作方法。
init_special_inode(inode, inode->i_mode,
new_decode_dev(le32_to_cpu(raw_inode->i_block[1])));
}
}
函數void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)中又會對inode所對應的設備類型進行一個進一步的分析,然後使用到相對應不同的設備的操作方法進行操作。
void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
inode->i_mode = mode;
if (S_ISCHR(mode)) {//字符設備
inode->i_fop = &def_chr_fops;
inode->i_rdev = rdev;
} else if (S_ISBLK(mode)) {//塊設備
inode->i_fop = &def_blk_fops;
inode->i_rdev = rdev;
} else if (S_ISFIFO(mode))//FIFO設備
inode->i_fop = &def_fifo_fops;
else if (S_ISSOCK(mode))//網絡設備
inode->i_fop = &bad_sock_fops;
else
printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o)\\n",
mode);
}
/*
這個函數根據 struct nameidata 結構返回一個 struct file。可以看到
struct file 是在使用了 __dentry_open() 函數後被填充的,且使用的第
一個參數是 nameidata->dentry,這也是為什麼我們要獲得 struct nameidata
的一個主要原因,其目的就是為了得到 struct dentry 結構。
在此時,已經將inode和dentry的結構體創建好,並且綁定好,對於不同類型的設備
在上一步已經將其對應的默認文件操作方法賦值*/
struct file *nameidata_to_filp(struct nameidata *nd, int flags)
{
struct file *filp;
/* Pick up the filp from the open intent */
filp = nd->intent.open.file;
/* Has the filesystem initialised the file for us? */
if (filp->f_path.dentry == NULL)
// 這個函數主要就是填充一個 struct file 結構,通過這段
// 代碼也可以看到,一個 struct file 是動態分配的。
filp = __dentry_open(nd->path.dentry, nd->path.mnt, flags, filp,
NULL, cred);
return filp;
}
static struct file *__dentry_open(struct dentry *dentry, struct vfsmount *mnt,
int flags, struct file *f,
int (*open)(struct inode *, struct file *),
const struct cred *cred)
{
f->f_op = fops_get(inode->i_fop);
file_move(f, &inode->i_sb->s_files);
if (!open && f->f_op)
open = f->f_op->open;// 此處調用 def_chr_fops裡的open函數,即chrdev_open
if (open) {
error = open(inode, f);
if (error)
goto cleanup_all;
}
}
大家關注哦
閱讀更多 防火牆達人 的文章