LINUX devtmpfs设备文件系统分析 (设备文件创建、删除、访问等)
在之前几篇文章中,我们介绍了文件系统的注册、超级块的创建、dentry、inode创建、文件描述符,以及这些结构体之间的关联,文件系统模块与进程模块之间的关联,本文介绍dev文件系统,该文件系统涉及设备文件的创建、访问以及对设备文件的访问操作等。
本篇文章主要包括如下小节(代码基于linux3.10版本)
1.设备文件系统的注册
2.设备文件系统的挂载
3.设备文件的创建
4.设备文件的删除
5.devtmpfsd线程事务处理
6.设备访问与VFS文件系统之间的关联
7.设备注册时inode节点创建的方式。
对于devtmpfs模块,使用了完成量,而针对完成量的介绍,会单独介绍,本文则不再细述。
一、设备文件系统的注册
在之前几篇文章的说明中,已经介绍了文件系统相关的数据结构,此处即不再赘述,针对设备文件系统而言,其文件系统变量定义如下:
static struct file_system_type dev_fs_type = {
.name = "devtmpfs",
.mount = dev_mount,/*(用于超级块、根dentry、根inode)*/
.kill_sb = kill_litter_super,
};
文件系统的名称为“devtmpfs”,该文件系统的mount接口函数为dev_mount,而其kill_sb接口则为kill_litter_super.
在设备文件系统初始化接口devtmpfs_init中,通过调用register_filesystem,完成文件系统的注册(关于文件系统注册函数的分析,已在之前分析过,可在该链接中查看https://www.toutiao.com/i6731356430449771016/)
二、设备文件系统的挂载
在应用层挂载文件系统,通过mount命令即调用内核的系统调用函数sys_mount(通过调用do_mount实现挂载),实现挂载操作;
而在内核中,子模块同样可调用sys_mount接口实现文件系统的挂载操作。
sys_mount接口分析
sys_mount接口的流程图如下,其主要功能如下:
对于新挂载操作,则调用do_new_mount接口进行挂载操作,进行超级块、根目录的dentry、根目录的inode变量的创建(针对根目录的dentry、inode创建,则是通过调各文件系统注册的mount接口实现,对devtmpfs而言,即为dev_mount,而dev_mount则最终调用shmem_fill_super进行根目录的dentry、inode的创建)。
对于devtmpfs,会挂载两次,在devtmpfs模块初始化时,在其处理线程中,通过调用sys_mount将其挂载至"/"目录下,其代码如下:
*err = sys_mount("devtmpfs", "/", "devtmpfs", MS_SILENT, options);
而在prepare_namespace接口中,通过调用devtmpfs_mount接口,将其挂载至/dev目录下,其代码如下:
/*
挂载devtmpfs_mount函数。
该接口主要调用sys_mount接口实现sys_mount接口的挂载。
目前该接口被prepare_namespace接口调用,进行devtmpfs接口的二次挂载操作,且挂载点为/dev目录。
*/
int devtmpfs_mount(const char *mntdir)
{
int err;
if (!mount_dev)
return 0;
if (!thread)
return 0;
err = sys_mount("devtmpfs", (char *)mntdir, "devtmpfs", MS_SILENT, NULL);
if (err)
printk(KERN_INFO "devtmpfs: error mounting %i\n", err);
else
printk(KERN_INFO "devtmpfs: mounted\n");
return err;
}
关于第一次挂载的具体意义,我目前还没有搞清楚。
根目录的inode的inode_ops接口如下所示,对于devtmpfs而言,且目录的inode节点的inode_ops均使用下面定义的接口函数。
static const struct inode_operations shmem_dir_inode_operations = {
#ifdef CONFIG_TMPFS
.create = shmem_create,
.lookup = simple_lookup,
.link = shmem_link,
.unlink = shmem_unlink,
.symlink = shmem_symlink,
.mkdir = shmem_mkdir,
.rmdir = shmem_rmdir,
.mknod = shmem_mknod,
.rename = shmem_rename,
#endif
#ifdef CONFIG_TMPFS_XATTR
.setxattr = shmem_setxattr,
.getxattr = shmem_getxattr,
.listxattr = shmem_listxattr,
.removexattr = shmem_removexattr,
#endif
#ifdef CONFIG_TMPFS_POSIX_ACL
.setattr = shmem_setattr,
#endif
};
三、设备文件及目录创建对外接口
devtmpfs模块对外提供了devtmpfs_create_node接口,用于linux内核其他子模块调用,创建设备文件及目录
该接口的主要功能如下:
1.根据传递的struct device*类型的变量dev,获取要创建的文件的路径名;
2.创建struct req 类型的变量req,用于存储本次创建设备文件所需的参数;
3.将struct req 类型的变量req与全局变量requests链接;
4.调用wake_up_process,唤醒devtmpfsd线程,由devtmpfsd线程实现设备文件的创建操作
int devtmpfs_create_node(struct device *dev)
{
const char *tmp = NULL;
struct req req;
if (!thread)
return 0;
req.mode = 0;
req.uid = GLOBAL_ROOT_UID;/*该文件所属的user*/
req.gid = GLOBAL_ROOT_GID;/*该文件所属的group*/
/*设置要创建文件的名称*/
req.name = device_get_devnode(dev, &req.mode, &req.uid, &req.gid, &tmp);
if (!req.name)
return -ENOMEM;
if (req.mode == 0)
req.mode = 0600;
/*设置设备文件的类型(字符设备/块设备)*/
if (is_blockdev(dev))
req.mode |= S_IFBLK;
else
req.mode |= S_IFCHR;
req.dev = dev;
/*初始化req的完成量变量*/
init_completion(&req.done);
spin_lock(&req_lock);
req.next = requests;
requests = &req;
spin_unlock(&req_lock);
/*唤醒devtmpfsd线程,用于处理文件的创建*/
wake_up_process(thread);
/*等待文件创建完成*/
wait_for_completion(&req.done);
kfree(tmp);
return req.err;
}
四、设备文件及目录删除对外接口
devtmpfs模块对外提供了devtmpfs_delete_node接口,用于linux内核其他子模块调用,删除设备文件及目录
该接口与devtmpfs_create_node口类似,其主要功能如下:
1.根据传递的struct device*类型的变量dev,获取要创建的文件的路径名;
2.创建struct req 类型的变量req,用于存储本次删除设备文件所需的参数;
3.将struct req 类型的变量req与全局变量requests链接;
4.调用wake_up_process,唤醒devtmpfsd线程,由devtmpfsd线程实现设备文件的删除操作
int devtmpfs_delete_node(struct device *dev)
{
const char *tmp = NULL;
struct req req;
if (!thread)
return 0;
req.name = device_get_devnode(dev, NULL, NULL, NULL, &tmp);
if (!req.name)
return -ENOMEM;
req.mode = 0;
req.dev = dev;
init_completion(&req.done);
spin_lock(&req_lock);
req.next = requests;
requests = &req;
spin_unlock(&req_lock);
wake_up_process(thread);
wait_for_completion(&req.done);
kfree(tmp);
return req.err;
}
五、devtmpfsd线程事务处理
该线程主要用于devtmpfs的挂载,以及设备文件或目录的创建与删除。其主要的功能如下(流程图如下所示):
1.调用sys_unshare,主要是不共享ns以及fs信息;
2.调用sys_mount挂载devtmpfs文件系统,且挂载点为根目录
3.将当前进程的当前目录设置为'/';
4.将当前进程的根目录设置为当前进程的当前目录
5.调用complete唤醒devtmpfs驱动的初始化线程,让初始化接口退出
6.处理节点的创建与删除节点的请求(req)
该线程在处理事务时涉及handle接口、handle_create、handle_remove、dev_rmdir、create_path、dev_mkdir、kern_path_create、do_path_lookup、path_lookupat接口的调用,本次会对这几个接口进行简要介绍说明
static int devtmpfsd(void *p)
{
char options[] = "mode=0755";
int *err = p;
*err = sys_unshare(CLONE_NEWNS);
if (*err)
goto out;
*err = sys_mount("devtmpfs", "/", "devtmpfs", MS_SILENT, options);
if (*err)
goto out;
/*设置进程的当前目录为根目录*/
sys_chdir("/.."); /* will traverse into overmounted root */
/*设置进程当前的目录为进程的根目录*/
sys_chroot(".");
/*唤醒setup_done完成量上所有等待的线程*/
complete(&setup_done);
/*该代码片段的作用为
1.处理requests上所有的req,并对每一个req,则调用handle进行处理;
2.处理完成后,调用complete唤醒相应等待的线程;
3.当所有的req均处理完成后,则让出本线程的调度权。
*/
while (1) {
spin_lock(&req_lock);
while (requests) {
struct req *req = requests;
requests = NULL;
spin_unlock(&req_lock);
while (req) {
struct req *next = req->next;
req->err = handle(req->name, req->mode,
req->uid, req->gid, req->dev);
complete(&req->done);
req = next;
}
spin_lock(&req_lock);
}
__set_current_state(TASK_INTERRUPTIBLE);
spin_unlock(&req_lock);
schedule();
}
return 0;
out:
complete(&setup_done);
return *err;
}
设备文件及目录的创建接口
devtmpfsd线程调用handle_create进行设备文件及目录的创建。
针对handle_create而言,其处理流程如下,其功能如下:
1.调用kern_path_create用于创建设备文件的dentry接口;
2.若文件目录,则调用create_path(通过调用dev_mkdir),实现目录的dentry与inode节点的创建。
设备目录的创建
设备目录通过调用dev_mkdir接口实现目录的dentry与inode的创建,该接口实现的功能如下:
1.首先调用kern_path_create进行dentry的创建;
2.若dentry创建成功,则vfs_mkdir创建目录的inode,该接口最终调用dir->i_op->mkdir进行目录inode进行的创建(shmem_mkdir->shmem_mknod进行inode创建的操作,传递的创建模式包含为S_IFDIR,即创建目录,
则设置inode->i_op=shmem_dir_inode_operations;inode->i_fop = &simple_dir_operations)
static int dev_mkdir(const char *name, umode_t mode)
{
struct dentry *dentry;
struct path path;
int err;
/*调用kern_path_create对*/
dentry = kern_path_create(AT_FDCWD, name, &path, LOOKUP_DIRECTORY);
if (IS_ERR(dentry))
return PTR_ERR(dentry);
err = vfs_mkdir(path.dentry->d_inode, dentry, mode);
if (!err)
/* mark as kernel-created inode */
dentry->d_inode->i_private = &thread;
done_path_create(&path, dentry);
return err;
}
设备文件及目录的删除接口
devtmpfsd线程调用handle_remove进行设备文件及目录的创建。
该接口通过调用vfs_unlink,实现节点的删除操作;
通过调用dput,实现dentry的删除操作。
*/
static int handle_remove(const char *nodename, struct device *dev)
{
struct path parent;
struct dentry *dentry;
int deleted = 1;
int err;
/*获取要删除路径对应dentry*/
dentry = kern_path_locked(nodename, &parent);
if (IS_ERR(dentry))
return PTR_ERR(dentry);
/*若该dentry存在inode节点,则调用vfs_unlink进行删除操作(即调用shmem_rmdir进行删除操作)。*/
if (dentry->d_inode) {
struct kstat stat;
struct path p = {.mnt = parent.mnt, .dentry = dentry};
err = vfs_getattr(&p, &stat);
if (!err && dev_mynode(dev, dentry->d_inode, &stat)) {
struct iattr newattrs;
/*
* before unlinking this node, reset permissions
* of possible references like hardlinks
*/
newattrs.ia_uid = GLOBAL_ROOT_UID;
newattrs.ia_gid = GLOBAL_ROOT_GID;
newattrs.ia_mode = stat.mode & ~0777;
newattrs.ia_valid =
ATTR_UID|ATTR_GID|ATTR_MODE;
mutex_lock(&dentry->d_inode->i_mutex);
notify_change(dentry, &newattrs);
mutex_unlock(&dentry->d_inode->i_mutex);
err = vfs_unlink(parent.dentry->d_inode, dentry);
if (!err || err == -ENOENT)
deleted = 1;
}
} else {
err = -ENOENT;
}
dput(dentry);
mutex_unlock(&parent.dentry->d_inode->i_mutex);
path_put(&parent);
if (deleted && strchr(nodename, '/'))
delete_path(nodename);
return err;
}
设备目录的删除
该接口通过调用vfs_rmdir接口进行目录的删除,最终调用shmem_rmdir接口实现目录的删除操作
*/
static int dev_rmdir(const char *name)
{
struct path parent;
struct dentry *dentry;
int err;
dentry = kern_path_locked(name, &parent);
if (IS_ERR(dentry))
return PTR_ERR(dentry);
if (dentry->d_inode) {
if (dentry->d_inode->i_private == &thread)
err = vfs_rmdir(parent.dentry->d_inode, dentry);
else
err = -EPERM;
} else {
err = -ENOENT;
}
dput(dentry);
mutex_unlock(&parent.dentry->d_inode->i_mutex);
path_put(&parent);
return err;
}
kern_path_create接口分析
该接口用于设备文件及目录dentry的查找或创建:
1.调用do_path_lookup完成路径查找,其中LOOKUP_PARENT表示仅查找到最后一级路径的父路径的dentry
2.若最后一级路径的类型不是LAST_NORM,说明没有完成路径的查找操作,返回失败
3.调用lookup_hash进行最后一级路径的查找,若路径不存在,则创建相应的dentry;若存在则返回dentr。
4.若本次是文件查找,且最后一级路径是目录,则返回失败。
该接口主要调用path_lookupat进行路径的查找,本次对该接口进行分析下。
struct dentry *kern_path_create(int dfd, const char *pathname,
struct path *path, unsigned int lookup_flags)
{
struct dentry *dentry = ERR_PTR(-EEXIST);
struct nameidata nd;
int err2;
int error;
bool is_dir = (lookup_flags & LOOKUP_DIRECTORY);
/*
* Note that only LOOKUP_REVAL and LOOKUP_DIRECTORY matter here. Any
* other flags passed in are ignored!
*/
lookup_flags &= LOOKUP_REVAL;
/*调用do_path_lookup完成路径查找,其中LOOKUP_PARENT表示仅查找到最后一级路径的父路径的dentry*/
error = do_path_lookup(dfd, pathname, LOOKUP_PARENT|lookup_flags, &nd);
if (error)
return ERR_PTR(error);
/*
* Yucky last component or no last component at all?
* (foo/., foo/.., /)
*/
/*若最后一级路径的类型不是LAST_NORM,说明没有完成路径的查找操作,返回失败*/
if (nd.last_type != LAST_NORM)
goto out;
nd.flags &= ~LOOKUP_PARENT;
nd.flags |= LOOKUP_CREATE | LOOKUP_EXCL;
/* don't fail immediately if it's r/o, at least try to report other errors */
err2 = mnt_want_write(nd.path.mnt);
/*
* Do the final lookup.
*/
mutex_lock_nested(&nd.path.dentry->d_inode->i_mutex, I_MUTEX_PARENT);
/*调用lookup_hash,首先在父目录的dcache中查找子dentry,若查找到则返回;
若没有查找到,则基于最后一级路径的名称,创建一个dentry,并返回该dentry*/
dentry = lookup_hash(&nd);
if (IS_ERR(dentry))
goto unlock;
error = -EEXIST;
if (dentry->d_inode)
goto fail;
/*
* Special case - lookup gave negative, but... we had foo/bar/
* From the vfs_mknod() POV we just have a negative dentry -
* all is fine. Let's be bastards - you had / on the end, you've
* been asking for (non-existent) directory. -ENOENT for you.
*/
if (unlikely(!is_dir && nd.last.name[nd.last.len])) {
error = -ENOENT;
goto fail;
}
if (unlikely(err2)) {
error = err2;
goto fail;
}
*path = nd.path;
return dentry;
fail:
dput(dentry);
dentry = ERR_PTR(error);
unlock:
mutex_unlock(&nd.path.dentry->d_inode->i_mutex);
if (!err2)
mnt_drop_write(nd.path.mnt);
out:
path_put(&nd.path);
return dentry;
}
path_lookupat接口分析
该接口通过调用link_path_walk、follow_link、walk_component接口实现路径查找操作,而这些接口,在之前的文章中已经分析过,此处不再分析。
六、设备驱动与VFS之间的关联以及处理等
针对设备节点创建时,则会根据创建文件的类型(块设备、字符设备),设置inode节点的额i_fops调用接口,这样在进行系统调用open时(针对open系统调用的分析,已经在前几篇文章中分析过,需要了解的可查看前几张的介绍),实现对块设备或者字符设备的打开操作。
在上面几个章节的介绍中,我们知道针对文件的inode节点创建时,通过调用接口shmem_mknod接口(实际由shmem_get_inode)实现的,在shmem_get_inode的定义如下:
该接口通过调用init_special_inode,实现对块设备或字符设备的i_fops接口的赋值。
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 = new_inode(sb);
if (inode) {
inode->i_ino = get_next_ino();
inode_init_owner(inode, dir, mode);
inode->i_blocks = 0;
inode->i_mapping->backing_dev_info = &shmem_backing_dev_info;
inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
inode->i_generation = get_seconds();
info = SHMEM_I(inode);
memset(info, 0, (char *)inode - (char *)info);
spin_lock_init(&info->lock);
info->flags = flags & VM_NORESERVE;
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;
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;
}
} else
shmem_free_inode(sb);
return inode;
}
init_special_inode接口分析
该接口的定义如下:
1.针对字符设备,其i_fops为def_chr_fops;
2.针对块设备,其i_fops为def_blk_fops。
3.设置inode->i_rdev,与设备变量进行关联,以便获取设备驱动的ops接口
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))
inode->i_fop = &bad_sock_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);
}
在系统调用open的分析中,我们知道open接口最终调用do_dentry_open接口,获取inode->i_fops指针,并与struct file变量的f_op关联,接着调用inode->i_fops->open,完成open操作。下面我们看下块设备和字符设备的默认open接口。
字符设备的open接口
该接口主要用与将设备文件注册的文件操作接口赋值给struct file变量的f_op指针,其具体操作如下:
1.通过inode->r_dev,获取设备变量cdev;
2.将cdev的文件操作函数指针ops赋值为f_op
3.调用具体文件的open函数。
static int chrdev_open(struct inode *inode, struct file *filp)
{
struct cdev *p;
struct cdev *new = NULL;
int ret = 0;
spin_lock(&cdev_lock);
/*根据inode->i_cdev,获取设备文件的cdev指针*/
p = inode->i_cdev;
/*若inode->i_cdev为空,则通过inode->r_dev变量,借助kobj_lookup获取其cdev变量*/
if (!p) {
struct kobject *kobj;
int idx;
spin_unlock(&cdev_lock);
kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);
if (!kobj)
return -ENXIO;
new = container_of(kobj, struct cdev, kobj);
spin_lock(&cdev_lock);
/* Check i_cdev again in case somebody beat us to it while
we dropped the lock. */
p = inode->i_cdev;
if (!p) {
inode->i_cdev = p = new;
list_add(&inode->i_devices, &p->list);
new = NULL;
} else if (!cdev_get(p))
ret = -ENXIO;
} else if (!cdev_get(p))
ret = -ENXIO;
spin_unlock(&cdev_lock);
cdev_put(new);
if (ret)
return ret;
ret = -ENXIO;
/*重新设置filp->f_op指针,指向具体设备的ops指针(设备的ops指针在设备驱动中执行cdev_init接口时设置)*/
filp->f_op = fops_get(p->ops);
if (!filp->f_op)
goto out_cdev_put;
/*然后执行相应设备文件的open操作,并返回操作结果*/
if (filp->f_op->open) {
ret = filp->f_op->open(inode, filp);
if (ret)
goto out_cdev_put;
}
return 0;
out_cdev_put:
cdev_put(p);
return ret;
}
块设备的open接口
七、创建inode节点的方式
针对创建设备文件有两种方式,一种是通过系统命令mknod实现,第二种是通过在设备驱动中调用devtmpfs的devtmpfs_create_node接口实现。
1.mknod方式
其用法如下
mknod [选项]... 名称 类型 [主设备号 次设备号]
2.devtmpfs_create_node接口调用方式
目前通过在设备驱动中,调用device_create接口,即可实现设备文件的创建,无需在应用层执行mknod命令,针对device_create接口,其调用流程如下所示,在device_create_file中,实现设备文件的创建操作。
至此,完成了设备文件系统的分析操作
更多推荐
所有评论(0)