本篇文章以mknod创建字符设备文件进行讲解
字符设备驱动的Demo例子可参考该篇文章
Linux 编写简单驱动并测试
1. mknod 命令
mknod /dev/hello c 520 0
该命令主要通过制定要创建的设备文件名称/dev/hello
,以及设备类型c
字符设备,最后的520 0
表示为主设备号和次设备号。
当我们使用该命令创建好了/dev/hello
设备文件,当我们在用户态对该文件进行open()
、write()
、read()
时,就会调用到/dev/hello
设备文件对应的设备驱动的file_operations
对应的.open
、.write
、.read
回调函数。
那么这一切是怎么做到的呢?这就跟我们的mknod
命令的实现有关。
2. mknod 原理
当我们输入mknod
命令时,实际上会创建设备文件/dev/hello
和所对应的inode
,以及将主设备号和次设备号形成的设备号保存在inode
的i_rdev
中。
3. mknod 源码分析
已经知道mknode
主要是为设备文件创建inode
的原理后,我们来看一下代码是如何实现的。
3.1 mknod
SYSCALL_DEFINE3(mknod, const char __user *, filename, umode_t, mode, unsigned, dev)
{
return do_mknodat(AT_FDCWD, getname(filename), mode, dev);
}
这个是mknod
命令的系统调用,我们键入的命令mknod /dev/hello c 520 0
,将会为这个函数进行赋值,赋值的内容如下:
filename = "/dev/hello";
mode = mode | S_IFCHR;
dev = MKDEV(520, 0);
3.2 do_mknodat
static int do_mknodat(int dfd, struct filename *name, umode_t mode,
unsigned int dev)
{
struct user_namespace *mnt_userns;
struct dentry *dentry;
struct path path;
int error;
unsigned int lookup_flags = 0;
error = may_mknod(mode);
if (error)
goto out1;
retry:
dentry = filename_create(dfd, name, &path, lookup_flags);
error = PTR_ERR(dentry);
if (IS_ERR(dentry))
goto out1;
if (!IS_POSIXACL(path.dentry->d_inode))
mode &= ~current_umask();
error = security_path_mknod(&path, dentry, mode, dev);
if (error)
goto out2;
mnt_userns = mnt_user_ns(path.mnt);
switch (mode & S_IFMT) {
case 0: case S_IFREG:
error = vfs_create(mnt_userns, path.dentry->d_inode,
dentry, mode, true);
if (!error)
ima_post_path_mknod(mnt_userns, dentry);
break;
case S_IFCHR: case S_IFBLK:
error = vfs_mknod(mnt_userns, path.dentry->d_inode,
dentry, mode, new_decode_dev(dev));
break;
case S_IFIFO: case S_IFSOCK:
error = vfs_mknod(mnt_userns, path.dentry->d_inode,
dentry, mode, 0);
break;
}
out2:
done_path_create(&path, dentry);
if (retry_estale(error, lookup_flags)) {
lookup_flags |= LOOKUP_REVAL;
goto retry;
}
out1:
putname(name);
return error;
}
由mknod()
函数调用到do_mknodat()
函数进行下一步的处理,在这个函数中我们可以看到几个主要流程步骤:
filename_create()
函数初始化了一个struct dentry
还有一个struct path
,其中dentry
表示当前/dev/hello
的信息,而path
则表示/dev
的路径信息。vfs_mknod()
该函数是最主要的处理部分。由于我们输入的mknod /dev/hello c 520 0
命令会让mode & S_IFCHR
情况成立,所以会进入到vfs_mknod()
创建inode
流程。
3.3 vfs_mknod
int vfs_mknod(struct user_namespace *mnt_userns, struct inode *dir,
struct dentry *dentry, umode_t mode, dev_t dev)
{
bool is_whiteout = S_ISCHR(mode) && dev == WHITEOUT_DEV;
int error = may_create(mnt_userns, dir, dentry);
if (error)
return error;
if ((S_ISCHR(mode) || S_ISBLK(mode)) && !is_whiteout &&
!capable(CAP_MKNOD))
return -EPERM;
if (!dir->i_op->mknod)
return -EPERM;
error = devcgroup_inode_mknod(mode, dev);
if (error)
return error;
error = security_inode_mknod(dir, dentry, mode, dev);
if (error)
return error;
error = dir->i_op->mknod(mnt_userns, dir, dentry, mode, dev);
if (!error)
fsnotify_create(dir, dentry);
return error;
}
EXPORT_SYMBOL(vfs_mknod);
vfs_mknod()
函数是创建inode
的公共流程函数,现在可以分析一下传入的主要参数信息:
struct inode *dir
:/dev
目录所指向的inode
信息struct dentry *dentry
:/dev/hello
我们要创建的设备文件的dentry
umode_t mode
:该mode
是指的我们要创建的设备文件类型,如我们键入的mknod /dev/hello c 520 0
命令,所以我们要创建的是字符设备,也就是说S_ISCHR(mode)等于truedev_t dev
:和开始进入mknod系统调用一样都是,dev = MKDEV(520, 0)
从vfs_mknod()
函数来看,最终会由/dev
所指向的inode
中的i_op
中的mknod
回调函数进行处理,因此到这里算是分析一部分了。
而我们的/dev
所指向的inode
中的i_op
中的mknod
回调函数到底是什么呢?
答案:shmem_mknod()
函数,具体为什么?是因为/dev
是一个目录,这个目录对应的文件系统是devtmpfs
,而这个devtmpfs
文件系统所使用的struct inode_operations
操作是shmem_dir_inode_operations
,因此会调用到shmem_mknod()
函数。
3.4 shmem_mknod
static int
shmem_mknod(struct user_namespace *mnt_userns, 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) {
error = simple_acl_create(dir, inode);
if (error)
goto out_iput;
error = security_inode_init_security(inode, dir,
&dentry->d_name,
shmem_initxattrs, NULL);
if (error && error != -EOPNOTSUPP)
goto out_iput;
error = 0;
dir->i_size += BOGO_DIRENT_SIZE;
dir->i_ctime = dir->i_mtime = current_time(dir);
d_instantiate(dentry, inode);
dget(dentry); /* Extra count - pin the dentry in core */
}
return error;
out_iput:
iput(inode);
return error;
}
由shmem_mknod()
调用到shmem_get_inode()
函数完成最终的/dev/hello
设备文件所对应的inode
的创建。
3.4 shmem_get_inode
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);
ino_t ino;
if (shmem_reserve_inode(sb, &ino))
return NULL;
inode = new_inode(sb);
if (inode) {
inode->i_ino = ino;
inode_init_owner(&init_user_ns, inode, dir, mode);
inode->i_blocks = 0;
inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode);
inode->i_generation = prandom_u32();
info = SHMEM_I(inode);
memset(info, 0, (char *)inode - (char *)info);
spin_lock_init(&info->lock);
atomic_set(&info->stop_eviction, 0);
info->seals = F_SEAL_SEAL;
info->flags = flags & VM_NORESERVE;
info->i_crtime = inode->i_mtime;
INIT_LIST_HEAD(&info->shrinklist);
INIT_LIST_HEAD(&info->swaplist);
simple_xattrs_init(&info->xattrs);
cache_no_acl(inode);
mapping_set_large_folios(inode->i_mapping);
switch (mode & S_IFMT) {
default:
inode->i_op = &shmem_special_inode_operations;
init_special_inode(inode, mode, dev);
break;
case S_IFREG:
inode->i_mapping->a_ops = &shmem_aops;
inode->i_op = &shmem_inode_operations;
inode->i_fop = &shmem_file_operations;
mpol_shared_policy_init(&info->policy,
shmem_get_sbmpol(sbinfo));
break;
case S_IFDIR:
inc_nlink(inode);
/* Some things misbehave if size == 0 on a directory */
inode->i_size = 2 * BOGO_DIRENT_SIZE;
inode->i_op = &shmem_dir_inode_operations;
inode->i_fop = &simple_dir_operations;
break;
case S_IFLNK:
/*
* Must not load anything in the rbtree,
* mpol_free_shared_policy will not be called.
*/
mpol_shared_policy_init(&info->policy, NULL);
break;
}
lockdep_annotate_inode_mutex_key(inode);
} else
shmem_free_inode(sb);
return inode;
}
从shmem_get_inode()
函数可以得到几个关键步骤:
inode = new_inode(sb);
这里会分配一个inode
- 如果分配到
inode
,则会对inode
进行一系列的初始化设置 - 最后由
init_special_inode
完成对inode
最后的设置
3.5 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;
} else if (S_ISBLK(mode)) {
inode->i_fop = &def_blk_fops;
inode->i_rdev = rdev;
} else if (S_ISFIFO(mode))
inode->i_fop = &pipefifo_fops;
else if (S_ISSOCK(mode))
; /* leave it no_open_fops */
else
printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o) for"
" inode %s:%lu\n", mode, inode->i_sb->s_id,
inode->i_ino);
}
EXPORT_SYMBOL(init_special_inode);
从init_special_inode()
函数可以看出来,当执行mknod /dev/hello c 520 0
命令为/dev/hello
设备文件生成的inode
时,由于指定的设备文件类型为字符类型,所以会为inode
进行如下赋值操作:
if (S_ISCHR(mode)) {
inode->i_fop = &def_chr_fops;
inode->i_rdev = rdev;
}
4. 总结
到这里对于Linux中mknod命令实现原理以及源码分析已经结束。
我们执行mknod命令最终只是生成一个/dev/hello
设备文件,而且为该设备文件生成对应的inode
信息而已。
至于这个inode
的信息如何使用,请看下一章节:
Linux中open命令实现原理以及源码分析
谢谢大家浏览,本博客为博主原创,未经允许不可转载。