Linux文件系统之sysfs

原文链接:http://blog.chinaunix.net/uid-20543183-id-1930812.html

源码文件:
./fs/sysfs/

前言

在设备模型中,sysfs文件系统用来表示设备的结构,将设备的层次结构形象的反应到用户空间中。用户空间可以修改sysfs中的文件属性来修改设备的属性值,今天我们就来详细分析一下sysfs的实现。

sysfs的初始化和挂载

sysfs文件系统的初始化是在sysfs_init()中完成的,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
int __init sysfs_init(void)
{
int err = -ENOMEM;
//创建一个分配sysfs_dirent的cache
sysfs_dir_cachep = kmem_cache_create("sysfs_dir_cache",
sizeof(struct sysfs_dirent),
0, 0, NULL);
if (!sysfs_dir_cachep)
goto out;
err = sysfs_inode_init();
if (err)
goto out_err;
//注册sysfs文件系统s
err = register_filesystem(&sysfs_fs_type);
if (!err) {
//挂载sysfs文件系统
sysfs_mount = kern_mount(&sysfs_fs_type);
if (IS_ERR(sysfs_mount)) {
printk(KERN_ERR "sysfs: could not mount!\n");
err = PTR_ERR(sysfs_mount);
sysfs_mount = NULL;
unregister_filesystem(&sysfs_fs_type);
goto out_err;
}
} else
goto out_err;
out:
return err;
out_err:
kmem_cache_destroy(sysfs_dir_cachep);
sysfs_dir_cachep = NULL;
goto out;
}

每个kobject对应sysfs中的一个目录,kobject的每个属性对应sysfs文件系统中的文件。
struct sysfs_dirent就是用来做kobjectdentry的互相转换用的,它们的关系如下图所示:
sysfs.jpg
上图表示的是一个kobject的层次结构,dentryd_fsdata字段指定该结点所表示的sysfs_direntsysfs_dirent.s_parent表示它的父kobjectsysfs_dirent.s_sibling表示它的兄弟结点,sysfs_dirent.s_dir.children表示它所属的子节点。

从上图可知,如果要遍历一个结点下面的子结点,只需要找到sysfs_dirent.s_dir.children结点,然后按着子节点的s_sibling域遍历即可。当然,有时候也需要从struct sysfs_dirent导出它所属的dentry结点,我们在代码中遇到的时候再进行分析。
sysfs文件系统的file_system_type定义如下:

1
2
3
4
5
static struct file_system_type sysfs_fs_type = {
.name = "sysfs",
.get_sb = sysfs_get_sb,
.kill_sb = kill_anon_super,
};

通过前面文件系统的相关分析,我们知道在sys_mount()中最终会调用struct file_system_typeget_sb函数来实现文件系统的挂载.它的代码如下:

1
2
3
4
5
static int sysfs_get_sb(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data, struct vfsmount *mnt)
{
return get_sb_single(fs_type, flags, data, sysfs_fill_super, mnt);
}

get_sb_single()的代码在前面已经涉及到,它对super_block以及挂载的dentryinode的赋值是在回调函数sysfs_fill_supermnt中完成的.代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
static int sysfs_fill_super(struct super_block *sb, void *data, int silent)
{
struct inode *inode;
struct dentry *root;
sb->s_blocksize = PAGE_CACHE_SIZE;
sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
sb->s_magic = SYSFS_MAGIC;
sb->s_op = &sysfs_ops;
sb->s_time_gran = 1;
sysfs_sb = sb;
/* get root inode, initialize and unlock it */
inode = sysfs_get_inode(&sysfs_root);
if (!inode) {
pr_debug("sysfs: could not get root inode\n");
return -ENOMEM;
}
/* instantiate and link root dentry */
root = d_alloc_root(inode);
if (!root) {
pr_debug("%s: could not get root dentry!\n",__FUNCTION__);
iput(inode);
return -ENOMEM;
}
//将sysfs_root关联到root
root->d_fsdata = &sysfs_root;
sb->s_root = root;
return 0;
}

在这里要注意几个全局量. sysfs_sb表示sysfs文件系统的super_blocksysfs_root表示sysfs文件系统根目录的struct sysfs_direntsysfs_get_inode(&sysfs_root)用来将sysfs_root导出相应的inode,代码如下:

1
2
3
4
5
6
7
8
9
10
11
struct inode * sysfs_get_inode(struct sysfs_dirent *sd)
{
struct inode *inode;
//以super_block和sd->s_ino为哈希值,到哈希表中寻找相应的inode.如果不存在,则新建
inode = iget_locked(sysfs_sb, sd->s_ino);
//对新生成的inode进行初始化
if (inode && (inode->i_state & I_NEW))
sysfs_init_inode(sd, inode);
return inode;
}

首先,它以sysfs文件系统的super_blockstruct sysfs_dirents_ino成员的值做为哈希值到哈希表中寻找相应的inode。如果在哈希表中不存在这个inode,那就新建一个,并将它链入到哈希表.之后,调用sysfs_init_inode()对生成的inode进行初始化.显然.在mount的时候是不会生成inode的.必定会进入sysfs_init_inode()函数.代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
static void sysfs_init_inode(struct sysfs_dirent *sd, struct inode *inode)
{
struct bin_attribute *bin_attr;
inode->i_blocks = 0;
inode->i_mapping->a_ops = &sysfs_aops;
inode->i_mapping->backing_dev_info = &sysfs_backing_dev_info;
inode->i_op = &sysfs_inode_operations;
inode->i_ino = sd->s_ino;
lockdep_set_class(&inode->i_mutex, &sysfs_inode_imutex_key);
if (sd->s_iattr) {
/* sysfs_dirent has non-default attributes
* get them for the new inode from persistent copy
* in sysfs_dirent
*/
set_inode_attr(inode, sd->s_iattr);
} else
set_default_inode_attr(inode, sd->s_mode);
/* initialize inode according to type */
switch (sysfs_type(sd)) {
case SYSFS_DIR:
inode->i_op = &sysfs_dir_inode_operations;
inode->i_fop = &sysfs_dir_operations;
inode->i_nlink = sysfs_count_nlink(sd);
break;
case SYSFS_KOBJ_ATTR:
inode->i_size = PAGE_SIZE;
inode->i_fop = &sysfs_file_operations;
break;
case SYSFS_KOBJ_BIN_ATTR:
bin_attr = sd->s_bin_attr.bin_attr;
inode->i_size = bin_attr->size;
inode->i_fop = &bin_fops;
break;
case SYSFS_KOBJ_LINK:
inode->i_op = &sysfs_symlink_inode_operations;
break;
default:
BUG();
}
unlock_new_inode(inode);
}

在这里,我们可以看到sysfs文件系统中的各种操作函数了..

在syfs文件系统中,怎么样判断一个目录下是否有这个文件呢?
在前面有关文件系统的分析中我们可以看.有关文件的查找实际上都会由inod->i_op->lookup()函数进行判断.在sysfs中,这个函数对应为sysfs_lookup().代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
static struct dentry * sysfs_lookup(struct inode *dir, struct dentry *dentry,
struct nameidata *nd)
{
struct dentry *ret = NULL;
//取得父结点对应的sysfs_dirent
struct sysfs_dirent *parent_sd = dentry->d_parent->d_fsdata;
struct sysfs_dirent *sd;
struct inode *inode;
mutex_lock(&sysfs_mutex);
//父结点的sysfs_dirent中是否有相应的子结点
sd = sysfs_find_dirent(parent_sd, dentry->d_name.name);
/* no such entry */
//如果没有.这个结点是不存在的
if (!sd) {
ret = ERR_PTR(-ENOENT);
goto out_unlock;
}
/* attach dentry and inode */
//如果有这个结点,为之生成inod结构.
inode = sysfs_get_inode(sd);
if (!inode) {
ret = ERR_PTR(-ENOMEM);
goto out_unlock;
}
/* instantiate and hash dentry */
dentry->d_op = &sysfs_dentry_ops;
//关联dentry与sysfs_dirent
dentry->d_fsdata = sysfs_get(sd);
d_instantiate(dentry, inode);
d_rehash(dentry);
out_unlock:
mutex_unlock(&sysfs_mutex);
return ret;
}

由此可见,它的判断会转入到相应的sysfs_dirent中进行判断.如果设备模型在创建目录/文件的时候并不会创建dentry或者inode.只会操作sysfs_dirent结构. 如果找到了这个结构,就为这个结构生成inode,并将其关联到denry中.
sysfs_find_dirent()如下:

1
2
3
4
5
6
7
8
9
10
struct sysfs_dirent *sysfs_find_dirent(struct sysfs_dirent *parent_sd,
const unsigned char *name)
{
struct sysfs_dirent *sd;
for (sd = parent_sd->s_dir.children; sd; sd = sd->s_sibling)
if (!strcmp(sd->s_name, name))
return sd;
return NULL;
}

它用的搜索方法就是我们在之前分析sysfs_dirent结构所讲述的.分析到这里,sysfs的大概轮廓就出现在我们的眼前了.^_^.接下来分析sysfs文件系统中目录的创建过程

在sysfs文件系统中创建目录

在linux设备模型中,每注册一个kobject.就会为之创建一个目录.具体的流程在分析linux设备模型的时候再给出详细的分析.创建目录的接口为: sysfs_create_dir()。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int sysfs_create_dir(struct kobject * kobj)
{
struct sysfs_dirent *parent_sd, *sd;
int error = 0;
BUG_ON(!kobj);
//如果kobject没有指定父结点,则将其父结点指定为sysfs的根目录syfs_root
if (kobj->parent)
parent_sd = kobj->parent->sd;
else
parent_sd = &sysfs_root;
//创建目录
error = create_dir(kobj, parent_sd, kobject_name(kobj), &sd);
//kobj->sd 指向对应的sysfs_dirent
if (!error)
kobj->sd = sd;
return error;
}

在这里,先为结点指定父目录,然后调用create_dir()在父目录下生成结点.代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
static int create_dir(struct kobject *kobj, struct sysfs_dirent *parent_sd,
const char *name, struct sysfs_dirent **p_sd)
{
//指定目录的模式
umode_t mode = S_IFDIR| S_IRWXU | S_IRUGO | S_IXUGO;
struct sysfs_addrm_cxt acxt;
struct sysfs_dirent *sd;
int rc;
/* allocate */
//分配并初始化一个sysfs_dirent
sd = sysfs_new_dirent(name, mode, SYSFS_DIR);
if (!sd)
return -ENOMEM;
//初始化sd->s_dir.kobj字段
sd->s_dir.kobj = kobj;
/* link in */
//acxt是一个临时变量.它用来存放父结点的相关信息
//设置acxt->parent_sd为父结点的sysfs_dirent, acxt->parent_inode为父结点的inode
sysfs_addrm_start(&acxt, parent_sd);
//设置sd->s_parent.并按inod值按顺序链入父结点的children链表
rc = sysfs_add_one(&acxt, sd);
sysfs_addrm_finish(&acxt);
if (rc == 0)
*p_sd = sd;
else
sysfs_put(sd);
return rc;
}

在这里,为子节点生成了对应的sysfs_dirent.设置了它的父结点域,并将其链入到父结点的children链表.这样,在文件系统中查找父目录下面的子结点了.

在sysfs中创建一般属性文件

kobject的每一项属性都对应在sysfs文件系统中kobject对应的目录下的一个文件,文件名称与属性名称相同。
创建一般属性的接口为sysfs_create_file(),代码如下:

1
2
3
4
5
6
7
8
int sysfs_create_file(struct kobject * kobj, const struct attribute * attr)
{
BUG_ON(!kobj || !kobj->sd || !attr);
//kobject->sd: 为kobject表示目录的struct sysfs_dirent结构
return sysfs_add_file(kobj->sd, attr, SYSFS_KOBJ_ATTR);
}

最终会调用sysfs_add_file(), 参数attr是要生成文件的属性值.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int sysfs_add_file(struct sysfs_dirent *dir_sd, const struct attribute *attr,
int type)
{
//文件对应的属性
umode_t mode = (attr->mode & S_IALLUGO) | S_IFREG;
struct sysfs_addrm_cxt acxt;
struct sysfs_dirent *sd;
int rc;
//创建一个新的sysfs_dirent.对应的名称为attr->name.即属性的名称
sd = sysfs_new_dirent(attr->name, mode, type);
if (!sd)
return -ENOMEM;
//设置属性值
sd->s_attr.attr = (void *)attr;
//将子结点的struct sysfs_dirent结构关链到父结点
sysfs_addrm_start(&acxt, dir_sd);
rc = sysfs_add_one(&acxt, sd);
sysfs_addrm_finish(&acxt);
if (rc)
sysfs_put(sd);
return rc;
}

这个流程与创建目录的流程大部份相同.不相同的只是创建目录时,它的父目录为上一层结点,创建文件时,它的父目录就是kobject对应的struct sysfs_dirent.
这样,在kobject对应的目录下面就可以看到这个文件了.^_^

文件建好之后,要怎么样去读写呢? 回忆一下,在sysfs文件系统中,inode的初始化:

1
2
3
4
5
6
7
8
9
static void sysfs_init_inode(struct sysfs_dirent *sd, struct inode *inode)
{
……
…….
case SYSFS_KOBJ_ATTR:
inode->i_size = PAGE_SIZE;
inode->i_fop = &sysfs_file_operations;
……
}

sysfs_file_operations的定义如下:

1
2
3
4
5
6
7
8
const struct file_operations sysfs_file_operations = {
.read = sysfs_read_file,
.write = sysfs_write_file,
.llseek = generic_file_llseek,
.open = sysfs_open_file,
.release = sysfs_release,
.poll = sysfs_poll,
};

文件的操作全部都在这里了,我们从打开文件说起.
sysfs_open_file()代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
static int sysfs_open_file(struct inode *inode, struct file *file)
{
struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata;
struct kobject *kobj = attr_sd->s_parent->s_dir.kobj;
struct sysfs_buffer *buffer;
struct sysfs_ops *ops;
int error = -EACCES;
/* need attr_sd for attr and ops, its parent for kobj */
if (!sysfs_get_active_two(attr_sd))
return -ENODEV;
/* every kobject with an attribute needs a ktype assigned */
//将buffer->ops设置为kobj->ktype->sysfs_ops
if (kobj->ktype && kobj->ktype->sysfs_ops)
ops = kobj->ktype->sysfs_ops;
else {
printk(KERN_ERR "missing sysfs attribute operations for "
"kobject: %s\n", kobject_name(kobj));
WARN_ON(1);
goto err_out;
}
/* File needs write support.
* The inode's perms must say it's ok,
* and we must have a store method.
*/
if (file->f_mode & FMODE_WRITE) {
if (!(inode->i_mode & S_IWUGO) || !ops->store)
goto err_out;
}
/* File needs read support.
* The inode's perms must say it's ok, and we there
* must be a show method for it.
*/
if (file->f_mode & FMODE_READ) {
if (!(inode->i_mode & S_IRUGO) || !ops->show)
goto err_out;
}
/* No error? Great, allocate a buffer for the file, and store it
* it in file->private_data for easy access.
*/
error = -ENOMEM;
buffer = kzalloc(sizeof(struct sysfs_buffer), GFP_KERNEL);
if (!buffer)
goto err_out;
mutex_init(&buffer->mutex);
buffer->needs_read_fill = 1;
buffer->ops = ops;
file->private_data = buffer;
/* make sure we have open dirent struct */
//将buffer链至attr_sd->s_attr.open链表上
error = sysfs_get_open_dirent(attr_sd, buffer);
if (error)
goto err_free;
/* open succeeded, put active references */
sysfs_put_active_two(attr_sd);
return 0;
err_free:
kfree(buffer);
err_out:
sysfs_put_active_two(attr_sd);
return error;
}

在这段代码中,需要注意以下几个操作,
1、buffer链接在file->private_databuffer还被链接在sysfs_dirent->s_attr.open,这样,VFS通过file,设备模型通过kobject->sd->s_attr.open都能找到这个要操作的 buffer
2、buffer->ops被设置为了kobject->ktype->sysfs_ops

文件的写操作入口如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static ssize_t
sysfs_write_file(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
struct sysfs_buffer * buffer = file->private_data;
ssize_t len;
mutex_lock(&buffer->mutex);
//将buf中的内容copy到了buffer->page
len = fill_write_buffer(buffer, buf, count);
//与设备模型的交互
if (len > 0)
len = flush_write_buffer(file->f_path.dentry, buffer, len);
//更新ppos
if (len > 0)
*ppos += len;
mutex_unlock(&buffer->mutex);
return len;
}

首先,调用fill_write_buffer()将用户空间传值下来的数值copy到buffer->page,然后再调用flush_write_buffer()与设备模型进行交互。
flush_wirte_buffer()代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static int
flush_write_buffer(struct dentry * dentry, struct sysfs_buffer * buffer, size_t count)
{
struct sysfs_dirent *attr_sd = dentry->d_fsdata;
struct kobject *kobj = attr_sd->s_parent->s_dir.kobj;
struct sysfs_ops * ops = buffer->ops;
int rc;
/* need attr_sd for attr and ops, its parent for kobj */
if (!sysfs_get_active_two(attr_sd))
return -ENODEV;
rc = ops->store(kobj, attr_sd->s_attr.attr, buffer->page, count);
sysfs_put_active_two(attr_sd);
return rc;
}

我们在分析open()操作的时候曾分析到,bufferopskobject->ktype->ops.在这里,它相当于调用了kobject->ktype->ops->store().参数分别为:操作的kobject,文件对应的属性,写入的值和值的长度。
Sysfs这样设计,主要是在VFS保持一个统一的接口,因为每一个kobject对应的属性值都不相同,相应的,操作方法也不一样,这样,在ktype中就区别开来了.

文件的读操作相应接口为sysfs_read_file(),代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static ssize_t
sysfs_read_file(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
struct sysfs_buffer * buffer = file->private_data;
ssize_t retval = 0;
mutex_lock(&buffer->mutex);
//从设备模型中将值取出.并存入buffer->page中
if (buffer->needs_read_fill) {
retval = fill_read_buffer(file->f_path.dentry,buffer);
if (retval)
goto out;
}
//将buffer->page中的值copy到用户空间的buf
pr_debug("%s: count = %zd, ppos = %lld, buf = %s\n",
__FUNCTION__, count, *ppos, buffer->page);
retval = simple_read_from_buffer(buf, count, ppos, buffer->page,
buffer->count);
out:
mutex_unlock(&buffer->mutex);
return retval;
}

读操作的流程刚好和写操作流程相反.它先从设备模型中取值,然后再copy到用户空间.
fill_read_buffer的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
static int fill_read_buffer(struct dentry * dentry, struct sysfs_buffer * buffer)
{
struct sysfs_dirent *attr_sd = dentry->d_fsdata;
struct kobject *kobj = attr_sd->s_parent->s_dir.kobj;
struct sysfs_ops * ops = buffer->ops;
int ret = 0;
ssize_t count;
if (!buffer->page)
buffer->page = (char *) get_zeroed_page(GFP_KERNEL);
if (!buffer->page)
return -ENOMEM;
/* need attr_sd for attr and ops, its parent for kobj */
if (!sysfs_get_active_two(attr_sd))
return -ENODEV;
buffer->event = atomic_read(&attr_sd->s_attr.open->event);
count = ops->show(kobj, attr_sd->s_attr.attr, buffer->page);
sysfs_put_active_two(attr_sd);
/*
* The code works fine with PAGE_SIZE return but it's likely to
* indicate truncated result or overflow in normal use cases.
*/
if (count >= (ssize_t)PAGE_SIZE) {
print_symbol("fill_read_buffer: %s returned bad count\n",
(unsigned long)ops->show);
/* Try to struggle along */
count = PAGE_SIZE - 1;
}
if (count >= 0) {
buffer->needs_read_fill = 0;
buffer->count = count;
} else {
ret = count;
}
return ret;
}

在这里,我们看到,最终会调用kobject->ktype->ops->show()方法.参数含义同写操作中是一样的.

在sysfs中创建二进制属性文件

二制制属性通常用于firmware中,它用来更新firmware的固件。
它的接口为sysfs_create_bin_file(),代码如下:

1
2
3
4
5
6
int sysfs_create_bin_file(struct kobject * kobj, struct bin_attribute * attr)
{
BUG_ON(!kobj || !kobj->sd || !attr);
return sysfs_add_file(kobj->sd, &attr->attr, SYSFS_KOBJ_BIN_ATTR);
}

sysfs_add_file()这个函数我们在之前已经分析过,在这个地方,可能会引起迷糊,因为在sysfs_add_file()中有:

1
2
3
4
5
6
7
int sysfs_add_file(struct sysfs_dirent *dir_sd, const struct attribute *attr,
int type)
{
……
sd->s_attr.attr = (void *)attr;
……
}

这里为什么是sd->s_attr呢? 应该是sd-> s_bin_attr才对吧!
仔细观察struct sysfs_dirent的结构,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct sysfs_dirent {
atomic_t s_count;
atomic_t s_active;
struct sysfs_dirent *s_parent;
struct sysfs_dirent *s_sibling;
const char *s_name;
union {
struct sysfs_elem_dir s_dir;
struct sysfs_elem_symlink s_symlink;
struct sysfs_elem_attr s_attr;
struct sysfs_elem_bin_attr s_bin_attr;
};
unsigned int s_flags;
ino_t s_ino;
umode_t s_mode;
struct iattr *s_iattr;
};

注意中间是一个union结构,实际上只占用一个内存空间,而且s_attrs_bin_attr的第一个属性都为struct attribute,所以在这里,sd->s_attrsd-> s_bin_attr;的效果是一样的。内核这样处理,又少用了一个接口。看来作者在设计的时候,花了很多的心思.

二进制的文件读写与普通属性的文件读写方式大部份都一样,所不同的是,二进制文件的读写接口分别是:sysfs_dirent->s_bin_attr.bin_attr->readsysfs_dirent->s_bin_attr.bin_attr->write

sysfs文件系统中的链接文件

创建链接文件的接口为:sysfs_create_link().代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
int sysfs_create_link(struct kobject * kobj, struct kobject * target, const char * name)
{
struct sysfs_dirent *parent_sd = NULL;
struct sysfs_dirent *target_sd = NULL;
struct sysfs_dirent *sd = NULL;
struct sysfs_addrm_cxt acxt;
int error;
BUG_ON(!name);
if (!kobj)
parent_sd = &sysfs_root;
else
parent_sd = kobj->sd;
error = -EFAULT;
if (!parent_sd)
goto out_put;
/* target->sd can go away beneath us but is protected with
* sysfs_assoc_lock. Fetch target_sd from it.
*/
spin_lock(&sysfs_assoc_lock);
if (target->sd)
target_sd = sysfs_get(target->sd);
spin_unlock(&sysfs_assoc_lock);
error = -ENOENT;
if (!target_sd)
goto out_put;
error = -ENOMEM;
sd = sysfs_new_dirent(name, S_IFLNK|S_IRWXUGO, SYSFS_KOBJ_LINK);
if (!sd)
goto out_put;
sd->s_symlink.target_sd = target_sd;
target_sd = NULL; /* reference is now owned by the symlink */
sysfs_addrm_start(&acxt, parent_sd);
error = sysfs_add_one(&acxt, sd);
sysfs_addrm_finish(&acxt);
if (error)
goto out_put;
return 0;
out_put:
sysfs_put(target_sd);
sysfs_put(sd);
return error;
}

上面的操作大部份都与普通文件的创建相似,所不同的只是下面这段代码的区别:

1
sd->s_symlink.target_sd = target_sd;

就是在sd->s_symlink.target_sd保存到链接目的地的sysfs_dirent.
符号链接的操作如下所示:

1
2
3
4
5
const struct inode_operations sysfs_symlink_inode_operations = {
.readlink = generic_readlink,
.follow_link = sysfs_follow_link,
.put_link = sysfs_put_link,
};

在通过符号链接查找文件的时候,在VFS中会调用inod->i_op->readlink()进行操作.它的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int generic_readlink(struct dentry *dentry, char __user *buffer, int buflen)
{
struct nameidata nd;
void *cookie;
nd.depth = 0;
cookie = dentry->d_inode->i_op->follow_link(dentry, &nd);
if (!IS_ERR(cookie)) {
int res = vfs_readlink(dentry, buffer, buflen, nd_get_link(&nd));
if (dentry->d_inode->i_op->put_link)
dentry->d_inode->i_op->put_link(dentry, &nd, cookie);
cookie = ERR_PTR(res);
}
return PTR_ERR(cookie);
}

它的操作和其它文件系统一样,都是通用follow_link()取得目的地的路径,然后保存到nd->saved_names[]中,然后,调用vfs_readlink()将目标路径copy到buffer中.接着,调用put_link进行事后处理工作.

follow_link()的操作如下示:

1
2
3
4
5
6
7
8
9
static void *sysfs_follow_link(struct dentry *dentry, struct nameidata *nd)
{
int error = -ENOMEM;
unsigned long page = get_zeroed_page(GFP_KERNEL);
if (page)
error = sysfs_getlink(dentry, (char *) page);
nd_set_link(nd, error ? ERR_PTR(error) : (char *)page);
return NULL;
}

nd_set_link()是将page中的值copy到nd->saved_name[]中.
sysfs_getlink()的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
static int sysfs_getlink(struct dentry *dentry, char * path)
{
struct sysfs_dirent *sd = dentry->d_fsdata;
struct sysfs_dirent *parent_sd = sd->s_parent;
struct sysfs_dirent *target_sd = sd->s_symlink.target_sd;
int error;
mutex_lock(&sysfs_mutex);
error = sysfs_get_target_path(parent_sd, target_sd, path);
mutex_unlock(&sysfs_mutex);
return error;
}

sysfs_get_target_path()代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
static int sysfs_get_target_path(struct sysfs_dirent *parent_sd,
struct sysfs_dirent *target_sd, char *path)
{
struct sysfs_dirent *base, *sd;
char *s = path;
int len = 0;
/* go up to the root, stop at the base */
base = parent_sd;
while (base->s_parent) {
sd = target_sd->s_parent;
while (sd->s_parent && base != sd)
sd = sd->s_parent;
if (base == sd)
break;
strcpy(s, "../");
s += 3;
base = base->s_parent;
}
/* determine end of target string for reverse fillup */
sd = target_sd;
while (sd->s_parent && sd != base) {
len += strlen(sd->s_name) + 1;
sd = sd->s_parent;
}
/* check limits */
if (len < 2)
return -EINVAL;
len--;
if ((s - path) + len > PATH_MAX)
return -ENAMETOOLONG;
/* reverse fillup of target string from target to base */
sd = target_sd;
while (sd->s_parent && sd != base) {
int slen = strlen(sd->s_name);
len -= slen;
strncpy(s + len, sd->s_name, slen);
if (len)
s[--len] = '/';
sd = sd->s_parent;
}
return 0;
}

这段代码的逻辑比较简单.它先是找到目标路径和当前路径相同的父结点,然后再沿着目标结点往相同的父结点向上走,将路径依次从缓存区后面往前面保存.
例如: /sys/eric/kernel/test链接到了/sys/sys/device.
它先找到两个路径共有的父结点/sys此时缓存区为:/sys然后,沿着/sys/sys/device/sys移动,路径加从缓存区的后面往前面加.依次为:
1: /sys/ /device
2:/sys/sys/device
这样就找到了目的地的路径. ^_^.
后面sysfs_put_link()的操作就不再讲述了,它只是释放掉缓存区.

小结

在本小节里,我们深入探讨了sysfs文件系统的实现机理.这对于我们理解linux设备模型是很有帮助的.