上篇文章中讲到rootfs文件系统挂载后,会先创建”/dev”目录,之后会创建设备文件"/dev/console",本文就分析下该设备文件的建立过程,文中还会见到与字符设备密切相关的chrdev_open()函数
1.default_rootfs
noinitramfs.c-->default_rootfs()
S_IFCHR表示要建立字符设备文件,S_IRUSR表示文件拥有者可以读该文件,S_IWUSR表示文件拥有者可以写该文件。第三个参数是设别号,这里是0x501。2.6内核的设备号采用新的计算方式。
2.sys_mknod
另外创建目录”/dev”和创建设备文件”/dev/console”还有一个不同点,就是创建”/dev/console”时,因为”/dev”已经存在,所以user_path_parent()->do_path_lookup()->path_walk()->link_path_walk()->__link_path_walk()中的for循环会循环两次,而之前创建”/dev”时,只执行了一次,就退出循环了。
下面就分析下这两个不同点
不同点1.__link_path_walk
不同点2.vfs_mknod
因为设置了S_IFCHR,并分配了设备号,所以最后要调用vfs_mknod.创建设备文件对应的inode结构。
fs/ramfs/inode.c
1.default_rootfs
noinitramfs.c-->default_rootfs()
static int __init default_rootfs(void)
{
...
err = sys_mknod((const char __user *) "/dev/console",
S_IFCHR | S_IRUSR | S_IWUSR,
new_encode_dev(MKDEV(5, 1)));
...
}
S_IFCHR表示要建立字符设备文件,S_IRUSR表示文件拥有者可以读该文件,S_IWUSR表示文件拥有者可以写该文件。第三个参数是设别号,这里是0x501。2.6内核的设备号采用新的计算方式。
2.sys_mknod
SYSCALL_DEFINE3(mknod, const char __user *, filename, int, mode, unsigned, dev)
{
return sys_mknodat(AT_FDCWD, filename, mode, dev);
}
SYSCALL_DEFINE4(mknodat, int, dfd, const char __user *, filename, int, mode,
unsigned, dev)
{
int error;
char *tmp;
struct dentry *dentry;
struct nameidata nd;
if (S_ISDIR(mode))
return -EPERM;
error = user_path_parent(dfd, filename, &nd, &tmp);
if (error)
return error;
dentry = lookup_create(&nd, 0);
if (IS_ERR(dentry)) {
error = PTR_ERR(dentry);
goto out_unlock;
}
if (!IS_POSIXACL(nd.path.dentry->d_inode))
mode &= ~current_umask();
error = may_mknod(mode);
if (error)
goto out_dput;
error = mnt_want_write(nd.path.mnt);
if (error)
goto out_dput;
error = security_path_mknod(&nd.path, dentry, mode, dev);
if (error)
goto out_drop_write;
switch (mode & S_IFMT) {
case 0: case S_IFREG:
error = vfs_create(nd.path.dentry->d_inode,dentry,mode,&nd);
break;
case S_IFCHR: case S_IFBLK:
error = vfs_mknod(nd.path.dentry->d_inode,dentry,mode,
new_decode_dev(dev));
break;
case S_IFIFO: case S_IFSOCK:
error = vfs_mknod(nd.path.dentry->d_inode,dentry,mode,0);
break;
}
out_drop_write:
mnt_drop_write(nd.path.mnt);
out_dput:
dput(dentry);
out_unlock:
mutex_unlock(&nd.path.dentry->d_inode->i_mutex);
path_put(&nd.path);
putname(tmp);
return error;
}
比较sys_mknod()和sys_mkdir()的代码,会发现很相似。只不过sys_mkdir调用的是vfs_mkdir,而sys_mknod会调用vfs_create或者vfs_mknod.其实ramfs_mkdir也会调用ramfs_mknod,只不过参数不同而已。
另外创建目录”/dev”和创建设备文件”/dev/console”还有一个不同点,就是创建”/dev/console”时,因为”/dev”已经存在,所以user_path_parent()->do_path_lookup()->path_walk()->link_path_walk()->__link_path_walk()中的for循环会循环两次,而之前创建”/dev”时,只执行了一次,就退出循环了。
下面就分析下这两个不同点
不同点1.__link_path_walk
static int __link_path_walk(const char *name, struct nameidata *nd)
{
struct path next;
struct inode *inode;
int err;
unsigned int lookup_flags = nd->flags;
while (*name=='/')
name++;
if (!*name)
goto return_reval;
/*获取起始目录”/”的inode*/
inode = nd->path.dentry->d_inode;
if (nd->depth)
lookup_flags = LOOKUP_FOLLOW | (nd->flags & LOOKUP_CONTINUE);
/* At this point we know we have a real path component. */
for(;;) {
unsigned long hash;
struct qstr this;
unsigned int c;
nd->flags |= LOOKUP_CONTINUE;
/*检查目录的权限*/
err = exec_permission_lite(inode);
if (err)
break;
this.name = name;
c = *(const unsigned char *)name;
hash = init_name_hash();
do {
name++;
hash = partial_name_hash(c, hash);
c = *(const unsigned char *)name;
} while (c && (c != '/'));
this.len = name - (const char *) this.name;
this.hash = end_name_hash(hash);
/*第一次循环时,this.name=”dev/console”,this.len=3
*第二次循环时,this.name=”console”,this.len=7
*/
/* remove trailing slashes? */
/*第二次循环时,因为已查找完毕,会从此处跳走。第一次循环时会继续往下走*/
if (!c)
goto last_component;
while (*++name == '/');
if (!*name)
goto last_with_slashes;
/*
* "." and ".." are special - ".." especially so because it has
* to be able to know about the current root directory and
* parent relationships.
*/
if (this.name[0] == '.') switch (this.len) {
default:
break;
case 2:
if (this.name[1] != '.')
break;
follow_dotdot(nd);
inode = nd->path.dentry->d_inode;
/* fallthrough */
case 1:
continue;
}
/*
* See if the low-level filesystem might want
* to use its own hash..
*/
if (nd->path.dentry->d_op && nd->path.dentry->d_op->d_hash) {
err = nd->path.dentry->d_op->d_hash(nd->path.dentry,
&this);
if (err < 0)
break;
}
/* This does the actual lookups.. */
/*从dentry_hashtable表中查找”dev”目录的目录项,
*因为之前已建立”dev”目录,并将其插入到了dentry_hashtable表中
*所以可以找到”dev”的目录项。找到后,将其保存在path结构的next变量中,
*do_lookup的具体代码,这里不再分析
*/
err = do_lookup(nd, &this, &next);
if (err)
break;
err = -ENOENT;
/*获取”dev”目录对应的inode结构*/
inode = next.dentry->d_inode;
if (!inode)
goto out_dput;
/*为空*/
if (inode->i_op->follow_link) {
err = do_follow_link(&next, nd);
if (err)
goto return_err;
err = -ENOENT;
inode = nd->path.dentry->d_inode;
if (!inode)
break;
} else
/*将path结构的next变量中的内容赋给nameidata结构的nd变量
*即把起始目录改为”dev”目录,第二次循环时的父目录就是”dev”目录.
*这样,就实现每循环一次,目录就深入一次。
*/
path_to_nameidata(&next, nd);
err = -ENOTDIR;
if (!inode->i_op->lookup)
break;
/*第一次循环结束*/
continue;
/* here ends the main loop */
last_with_slashes:
lookup_flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
last_component:
/*第二次循环会跳到此处*/
/* Clear LOOKUP_CONTINUE iff it was previously unset */
nd->flags &= lookup_flags | ~LOOKUP_CONTINUE;
/*因为设置了LOOKUP_PARENT,即只需要获取父目录的信息,所以
*跳到 lookup_parent处
*/
if (lookup_flags & LOOKUP_PARENT)
goto lookup_parent;
if (this.name[0] == '.') switch (this.len) {
default:
break;
case 2:
if (this.name[1] != '.')
break;
follow_dotdot(nd);
inode = nd->path.dentry->d_inode;
/* fallthrough */
case 1:
goto return_reval;
}
if (nd->path.dentry->d_op && nd->path.dentry->d_op->d_hash) {
err = nd->path.dentry->d_op->d_hash(nd->path.dentry,
&this);
if (err < 0)
break;
}
err = do_lookup(nd, &this, &next);
if (err)
break;
inode = next.dentry->d_inode;
if (follow_on_final(inode, lookup_flags)) {
err = do_follow_link(&next, nd);
if (err)
goto return_err;
inode = nd->path.dentry->d_inode;
} else
path_to_nameidata(&next, nd);
err = -ENOENT;
if (!inode)
break;
if (lookup_flags & LOOKUP_DIRECTORY) {
err = -ENOTDIR;
if (!inode->i_op->lookup)
break;
}
goto return_base;
lookup_parent:
/*将最后的”console”等信息存放在nd->last中*/
nd->last = this;
nd->last_type = LAST_NORM;
if (this.name[0] != '.')
goto return_base;
if (this.len == 1)
nd->last_type = LAST_DOT;
else if (this.len == 2 && this.name[1] == '.')
nd->last_type = LAST_DOTDOT;
else
goto return_base;
return_reval:
/*
* We bypassed the ordinary revalidation routines.
* We may need to check the cached dentry for staleness.
*/
if (nd->path.dentry && nd->path.dentry->d_sb &&
(nd->path.dentry->d_sb->s_type->fs_flags & FS_REVAL_DOT)) {
err = -ESTALE;
/* Note: we do not d_invalidate() */
if (!nd->path.dentry->d_op->d_revalidate(
nd->path.dentry, nd))
break;
}
return_base:
return 0;
out_dput:
path_put_conditional(&next, nd);
break;
}
path_put(&nd->path);
return_err:
return err;
}
该函数循环两次,最后将父目录”dev”的信息存放在nd->path中,”console”等要创建的文件信息放在nd->last中。之后一路返回主函数,接着调用lookup_create(),查找是否存在所要创建的文件对应的dentry,没有则新建dentry结构,并初始化,具体见上篇文章。
不同点2.vfs_mknod
因为设置了S_IFCHR,并分配了设备号,所以最后要调用vfs_mknod.创建设备文件对应的inode结构。
fs/ramfs/inode.c
static int
ramfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)
{
struct inode * inode = ramfs_get_inode(dir->i_sb, mode, dev);
int error = -ENOSPC;
if (inode) {
if (dir->i_mode & S_ISGID) {
inode->i_gid = dir->i_gid;
if (S_ISDIR(mode))
inode->i_mode |= S_ISGID;
}
/*建立目录项和索引节点的关联*/
d_instantiate(dentry, inode);
dget(dentry); /* Extra count - pin the dentry in core */
error = 0;
dir->i_mtime = dir->i_ctime = CURRENT_TIME;
}
return error;
}
struct inode *ramfs_get_inode(struct super_block *sb, int mode, dev_t dev)
{
struct inode * inode = new_inode(sb);
if (inode) {
inode->i_mode = mode;
inode->i_uid = current_fsuid();
inode->i_gid = current_fsgid();
inode->i_mapping->a_ops = &ramfs_aops;
inode->i_mapping->backing_dev_info = &ramfs_backing_dev_info;
mapping_set_gfp_mask(inode->i_mapping, GFP_HIGHUSER);
mapping_set_unevictable(inode->i_mapping);
inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
switch (mode & S_IFMT) {
default:
init_special_inode(inode, mode, dev);
break;
case S_IFREG:
inode->i_op = &ramfs_file_inode_operations;
inode->i_fop = &ramfs_file_operations;
break;
case S_IFDIR:
inode->i_op = &ramfs_dir_inode_operations;
inode->i_fop = &simple_dir_operations;
/* directory inodes start off with i_nlink == 2 (for "." entry) */
inc_nlink(inode);
break;
case S_IFLNK:
inode->i_op = &page_symlink_inode_operations;
break;
}
}
return inode;
}
这里需要讲解的就只有init_special_inode(),但也很简单。
void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
/*设置mode,open时,会根据该mode判断是否是字符设备*/
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 = &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) for"
" inode %s:%lu\n", mode, inode->i_sb->s_id,
inode->i_ino);
}
最后看下所设置的文件操作函数集里的具体内容,
const struct file_operations def_chr_fops = {
.open = chrdev_open,
};
可以找到chrdev_open函数。该函数以后编写和分析字符设备时,再详细分析下。