Linux核心原始碼情景分析-系統呼叫mknod
阿新 • • 發佈:2019-01-31
普通檔案可以用open或者create建立,FIFO檔案可以用pipe建立,mknod主要用於裝置檔案的建立。
在核心中,mknod是由sys_mknod實現的,程式碼如下:
asmlinkage long sys_mknod(const char * filename, int mode, dev_t dev) //比如filename為/tmp/server_socket,dev是裝置號 { int error = 0; char * tmp; struct dentry * dentry; struct nameidata nd; if (S_ISDIR(mode)) return -EPERM; tmp = getname(filename); if (IS_ERR(tmp)) return PTR_ERR(tmp); if (path_init(tmp, LOOKUP_PARENT, &nd))//尋找父節點,這裡就是/tmp節點 error = path_walk(tmp, &nd); if (error) goto out; dentry = lookup_create(&nd, 0);//尋找/tmp/server_socket節點,返回該節點的dentry結構,但是dentry->d_inode為NULL error = PTR_ERR(dentry); if (!IS_ERR(dentry)) { switch (mode & S_IFMT) { case 0: case S_IFREG://普通檔案 error = vfs_create(nd.dentry->d_inode,dentry,mode); break; case S_IFCHR: case S_IFBLK: case S_IFIFO: case S_IFSOCK://字元裝置,塊裝置,管道,socket檔案 error = vfs_mknod(nd.dentry->d_inode,mode,dev);//建立/tmp/server_socket節點的inode結構,並關聯到檔案系統中 break; case S_IFDIR: error = -EPERM; break; default: error = -EINVAL; } dput(dentry); } up(&nd.dentry->d_inode->i_sem); path_release(&nd); out: putname(tmp); return error; }
lookup_create,尋找/tmp/server_socket節點,程式碼如下:
static struct dentry *lookup_create(struct nameidata *nd, int is_dir) { struct dentry *dentry; down(&nd->dentry->d_inode->i_sem); dentry = ERR_PTR(-EEXIST); if (nd->last_type != LAST_NORM) goto fail; dentry = lookup_hash(&nd->last, nd->dentry);//nd->last是server_socket if (IS_ERR(dentry)) goto fail; if (!is_dir && nd->last.name[nd->last.len] && !dentry->d_inode) goto enoent; return dentry; enoent: dput(dentry); dentry = ERR_PTR(-ENOENT); fail: return dentry; }
vfs_mknod,建立/tmp/server_socket節點的inode結構,並關聯到檔案系統中,程式碼如下:struct dentry * lookup_hash(struct qstr *name, struct dentry * base)//name為server_socket,base為父節點/tmp的dentry結構 { struct dentry * dentry; struct inode *inode; int err; inode = base->d_inode;//父節點/tmp的i節點 err = permission(inode, MAY_EXEC); dentry = ERR_PTR(err); if (err) goto out; /* * See if the low-level filesystem might want * to use its own hash.. */ if (base->d_op && base->d_op->d_hash) { err = base->d_op->d_hash(base, name); dentry = ERR_PTR(err); if (err < 0) goto out; } dentry = cached_lookup(base, name, 0); if (!dentry) { struct dentry *new = d_alloc(base, name);//建立/tmp/server_socket節點的dentry結構 dentry = ERR_PTR(-ENOMEM); if (!new) goto out; lock_kernel(); dentry = inode->i_op->lookup(inode, new);//dentry為NULL unlock_kernel(); if (!dentry) dentry = new;//剛剛建立的new賦值給dentry,但是dentry->d_inode為NULL else dput(new); } out: return dentry; }
int vfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)//dir為/tmp父節點的inode結構,dentry為/tmp/server_socket節點的dentry結構
{
int error = -EPERM;
mode &= ~current->fs->umask;
down(&dir->i_zombie);
if ((S_ISCHR(mode) || S_ISBLK(mode)) && !capable(CAP_MKNOD))//檢驗當前程序是否允許建立裝置節點,此項檢驗僅用於待建立節點為裝置節點時
goto exit_lock;
error = may_create(dir, dentry);
if (error)
goto exit_lock;
error = -EPERM;
if (!dir->i_op || !dir->i_op->mknod)
goto exit_lock;
DQUOT_INIT(dir);
lock_kernel();
error = dir->i_op->mknod(dir, dentry, mode, dev);//對於Ext2,這個函式是ext2_mknod
unlock_kernel();
exit_lock:
up(&dir->i_zombie);
if (!error)
inode_dir_notify(dir, DN_CREATE);
return error;
}
may_create,檢查目標節點的inode結構是否存在。
static inline int may_create(struct inode *dir, struct dentry *child) {
if (child->d_inode)//也就是檢查d_inode是否為NULL
return -EEXIST;
if (IS_DEADDIR(dir))
return -ENOENT;
return permission(dir,MAY_WRITE | MAY_EXEC);
}
對於Ext2,dir->i_op->mknod是ext2_mknod,程式碼如下:
static int ext2_mknod (struct inode * dir, struct dentry *dentry, int mode, int rdev)
{
struct inode * inode = ext2_new_inode (dir, mode);//分配了一個inode結構
int err = PTR_ERR(inode);
if (IS_ERR(inode))
return err;
inode->i_uid = current->fsuid;
init_special_inode(inode, mode, rdev);
err = ext2_add_entry (dir, dentry->d_name.name, dentry->d_name.len,
inode);//inode關聯到檔案系統中,也就是通過父節點inode結構,能夠找到新建立的子節點的inode結構
if (err)
goto out_no_entry;
mark_inode_dirty(inode);//新建立的inode結構設定成"髒"
d_instantiate(dentry, inode);//將新建立的inode結構與dentry結構相關聯
return 0;
out_no_entry:
inode->i_nlink--;
mark_inode_dirty(inode);
iput(inode);
return err;
}
void init_special_inode(struct inode *inode, umode_t mode, int rdev)
{
inode->i_mode = mode;
if (S_ISCHR(mode)) {//字元裝置
inode->i_fop = &def_chr_fops;
inode->i_rdev = to_kdev_t(rdev);
} else if (S_ISBLK(mode)) {//塊裝置
inode->i_fop = &def_blk_fops;
inode->i_rdev = to_kdev_t(rdev);
inode->i_bdev = bdget(rdev);
} else if (S_ISFIFO(mode))//FIFO裝置
inode->i_fop = &def_fifo_fops;
else if (S_ISSOCK(mode))//socket裝置
inode->i_fop = &bad_sock_fops;
else
printk(KERN_DEBUG "init_special_inode: bogus imode (%o)\n", mode);
}
由於新建立的inode結構設定成了“髒”,核心在"同步"記憶體中的inode結構與磁碟上的索引節點的時候,就會將這個inode結構的內容寫到磁碟上分配給這個檔案的索引節點,即ext2_inode資料結構中。由於ext2_inode結構中並不存在i_rdev這麼個成分,而對於裝置檔案卻又不需要使用i_block[]陣列,所以就挪用其i_block[0]來儲存裝置號。要了解這一點,主要看ext2_update_inode程式碼中一個片段: if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode))//FIFO裝置和SOCKET裝置沒有裝置號
raw_inode->i_block[0] = cpu_to_le32(kdev_t_to_nr(inode->i_rdev));
else for (block = 0; block < EXT2_N_BLOCKS; block++)
raw_inode->i_block[block] = inode->u.ext2_i.i_data[block];
反過來,當通過ext2_read_inode從磁碟上讀入索引節點,併為之在記憶體中建立相應的inode結構時,則先將i_block[]陣列全部複製到i_data[]陣列中。如果是裝置檔案就呼叫init_special_inode將i_block[0]的內容填入inode結構的i_rdev。以下是片段:for (block = 0; block < EXT2_N_BLOCKS; block++)
inode->u.ext2_i.i_data[block] = raw_inode->i_block[block];
if (inode->i_ino == EXT2_ACL_IDX_INO ||
inode->i_ino == EXT2_ACL_DATA_INO)
/* Nothing to do */ ;
else if (S_ISREG(inode->i_mode)) {
} else if (S_ISDIR(inode->i_mode)) {
} else if (S_ISLNK(inode->i_mode)) {
} else
init_special_inode(inode, inode->i_mode,
le32_to_cpu(raw_inode->i_block[0]));
我們回過頭想一想,在Linux核心原始碼情景分析-檔案系統的安裝,/dev/sdb1,就是通過mknod建立的。