1. 程式人生 > >Linux 系統呼叫之 mknod

Linux 系統呼叫之 mknod

繼續上一篇的分析,我們在使用命令: mknod /dev/my_chr_dev0 c $major 0 建立裝置檔案的時候,核心做了哪些事呢?可以肯定的一點是,核心肯定建立了一個 inode 結構體,並加入到系統裡面,要不然在開啟裝置檔案的時候,會因為lookup_fast()函式找不到相應的 inode,從而使得開啟失敗。也許你會說,lookup_fast()函式失敗了,還有lookup_slow()函式呢?這裡因為是特殊檔案,情況有所不同,如果lookup_fast()函式失敗了,那麼就會導致開啟失敗,不會在lookup_slow()函式裡面動態建立 inode,而建立inode的工作其實是在 mknod 系統呼叫裡面完成的。下面來簡單分析其具體過程。

首先通過 strace 來檢視下系統呼叫的傳入引數:

 strace -o syscall mknod /dev/test c 243 0

結果如下:

...
mknod("/dev/test", S_IFCHR|0666, makedev(243, 0)) = 0
...

好了,現在來看下核心裡面關於 mknod 系統呼叫的定義,在 source/fs/namei.c 檔案中(Linux所有系統呼叫都是通過巨集 SYSCALL_DEFINEn 定義的,關於這個巨集的詳細說明,參考這裡):

SYSCALL_DEFINE3(mknod, const char __user *, filename, umode_t, mode, unsigned, dev)
{
	return sys_mknodat(AT_FDCWD, filename, mode, dev);
}

好了,來看 sys_mknodat 的定義

SYSCALL_DEFINE4(mknodat, int, dfd, const char __user *, filename, umode_t, mode,
		unsigned, dev)
{
	...
        /* 這裡進行路徑解析並建立新的 dentry */
        dentry = user_path_create(dfd, filename, &path, lookup_flags);
	...
	switch (mode & S_IFMT) {
		...
                /* 在這裡建立 inode */                        
                case S_IFCHR: case S_IFBLK:
                        error = vfs_mknod(path.dentry->d_inode,dentry,mode,
                                new_decode_dev(dev));
                        break;
             ...
}

可見,其實就兩步:1,建立 dentry;2,建立 inode。先看dentry的建立,user_path_create()函式的定義:

inline struct dentry *user_path_create(int dfd, const char __user *pathname,
				struct path *path, unsigned int lookup_flags)
{
	return filename_create(dfd, getname(pathname), path, lookup_flags);
}
static struct dentry *filename_create(int dfd, struct filename *name,
				struct path *path, unsigned int lookup_flags)
{
	...
	name = filename_parentat(dfd, name, lookup_flags, path, &last, &type);
	...
	dentry = __lookup_hash(&last, path->dentry, lookup_flags);
	...
        return dentry;
}

其中 filename_parentat()函式主要完成的是路徑解析的工作,其中呼叫了 path_parentat()->link_path_walk()函式來完成路徑解析工作,前面文章以及介紹過,這裡不再詳細分析。而__lookup_hash()函式(先在系統快取中查詢dentry,如果找不到)則主要通過呼叫d_alloc()函式建立新的 dentry 並加入到系統中。主要所使用的函式在前面也介紹過,這裡不再分析。下面重點分析 inode 的建立過程: vfs_mknod()函式:

int vfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev)
{
	...
	error = dir->i_op->mknod(dir, dentry, mode, dev);
	...

	return error;
}

這裡呼叫了檔案系統相關的函式:dir->i_op->mknod()。這是父目錄 /dev 的i_op->mknod 函式,這個函式指標指向的是shmem_mknod()函式:

static int
shmem_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev)
{
	struct inode *inode;
	int error = -ENOSPC;

	inode = shmem_get_inode(dir->i_sb, dir, mode, dev, VM_NORESERVE);
	if (inode) {
		...
		d_instantiate(dentry, inode); /* 可簡單理解成:  dentry->d_inode = inode; */
		dget(dentry); /* Extra count - pin the dentry in core */
	}
	return error;
 	...
}
static struct inode *shmem_get_inode(struct super_block *sb, const struct inode *dir,
				     umode_t mode, dev_t dev, unsigned long flags)
{
	struct inode *inode;
	struct shmem_inode_info *info;
	struct shmem_sb_info *sbinfo = SHMEM_SB(sb);

	if (shmem_reserve_inode(sb))
		return NULL;
	
	/* 在核心空間建立 inode 結構體(分配記憶體) */
	inode = new_inode(sb);
	if (inode) {
		/* 下面是各種成員變數的初始化 */
		inode->i_ino = get_next_ino();
		inode_init_owner(inode, dir, mode);
		inode->i_blocks = 0;
		inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode);
		inode->i_generation = get_seconds();
		info = SHMEM_I(inode);
		memset(info, 0, (char *)inode - (char *)info);
		spin_lock_init(&info->lock);
		info->seals = F_SEAL_SEAL;
		info->flags = flags & VM_NORESERVE;
		INIT_LIST_HEAD(&info->shrinklist);
		INIT_LIST_HEAD(&info->swaplist);
		simple_xattrs_init(&info->xattrs);
		cache_no_acl(inode);
		/***********************************************/

		switch (mode & S_IFMT) {
		default:
			inode->i_op = &shmem_special_inode_operations;
			init_special_inode(inode, mode, dev);  /* 我們最感興趣的在這裡 */
			break;
		...
		}
	} else
		shmem_free_inode(sb);
	return inode;
}

可見在這個函式裡面,首先通過new_inode()函式在核心空間分配記憶體,這裡不再詳細展開。然後對各個成員變數進行初始化,這裡我們也不感興趣,最感興趣的地方在 init_special_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;
	} 
	...
}

可見這裡儲存了兩個重要的成員變數:檔案操作函式集和裝置號。而這個檔案操作函式集是一個通用的操作集,所有字元驅動檔案開啟時都會呼叫,在這個函式裡面,通過裝置號來找到真正的該裝置的檔案操作函式集。先看這個 def_chr_fops 的定義:

/*
 * Dummy default file-operations: the only thing this does
 * is contain the open that then fills in the correct operations
 * depending on the special file...
 */
const struct file_operations def_chr_fops = {
	.open = chrdev_open,
	.llseek = noop_llseek,
};

而這個 chrdev_open()函式就是我們上一篇裡面分析的函式。可見這個 mknod 系統呼叫無非就是把檔案的裝置號儲存到新建立的 inode 裡面,而真正的驅動相關的檔案操作函式集並沒有儲存在這裡面,而是儲存在 cdev_map->probes 陣列中(上一篇分析過),但巧妙之處在於我們可以通過檔案的裝置號輕鬆的找到驅動相關的檔案操作函式集。最後一點需要說明的是我們回到 shmem_mknod()函式,這裡顯式的呼叫了 dget() 函式。其實在通過呼叫d_alloc()函式建立新的 dentry 時,已經將將其引用計數設定為1:

dentry->d_lockref.count = 1
這裡再次呼叫 dget() 函式就是要保證通過 mknod 函式建立的 inode 永遠不會被釋放掉(除非 rm /dev/my_chr_dev0)。這樣就保證了 lookup_fast()函式總能成功返回。