ev/hda2 /mnt/C
我们就知道挂载在/mnt/C下的是一个vfat类型的文件系统,它的信息来源是在第一块硬盘的第2个分区。
但是,你可能根本没有去关心过sysfs的挂载过程,她是这样被挂载的。
mount -t sysfs sysfs /sys
ms看不出她的信息来源在哪。sysfs是一个特殊文件系统,并没有一个实际存放文件的介质。断电后就玩完了。简而言之,sysfs的信息来源是kobject层次结构,读一个sysfs文件,就是动态的从kobject结构提取信息,生成文件。
文件系统
文件系统是个很模糊广泛的概念,'文件'狭义地说,是指磁盘文件,广义理解,可以是有组织有次序地存储与任何介质(包括内存)的一组信息。
linux把所有的资源都看成是文件,让用户通过一个统一的文件系统操作界面,也就是同一组系统调用,对属于不同文件系统的文件进行操作。这样,就可以对
用户程序隐藏各种不同文件系统的实现细节,为用户程序提供了一个统一的,抽象的,虚拟的文件系统界面,这就是所谓'VFS(Virtual
Filesystem Switch)'。这个抽象出来的接口就是一组函数操作。
我们要实现一种文件系统就是要实现VFS所定义的一系列 接口,file_operations, dentry_operations,
inode_operations等,供上层调用。file_operations是对每个具体文件的读写操作,dentry_operations,
inode_operations则是对文件的属性,如改名字,建立或删除的操作。
struct file_operations {
ssize_t (*read) (struct file *, char __user *, size_t, loff_t
*);
ssize_t (*write) (struct file *, const char __user *, size_t,
loff_t *);
int (*open) (struct inode *, struct file *);
...
};
struct dentry_operations {
...
};
struct inode_operations {
int (*create) (struct inode *,struct dentry *,int, struct nameidata
*);
struct dentry * (*lookup) (struct inode *,struct dentry *, struct
nameidata *);
int (*link) (struct dentry *,struct inode *,struct dentry *);
int (*mkdir) (struct inode *,struct dentry *,int);
int (*rmdir) (struct inode *,struct dentry *);
...}
举个例子,我们写C程序,open(“hello.c”, O_RDONLY),它通过系统调用的流程是这样的
open() ->
-> 系统调用->
sys_open() -> filp_open()-> dentry_open() ->
file_operations->open()
不同的文件系统,调用不同的file_operations->open(),在sysfs下就是sysfs_open_file()。
我们在进程中要怎样去描述一个文件呢?我们用目录项(dentry)和索引节点(inode)。它们的定义如下:
struct dentry {
struct inode
*d_inode;
struct list_head
d_child;
struct dentry_operations
*d_op;
struct super_block
*d_sb;
void
*d_fsdata;
unsigned char
d_iname[DNAME_INLINE_LEN_MIN];
......
};
struct inode {
unsigned long
i_ino;
atomic_t
i_count;
umode_t
i_mode;
unsigned int
i_nlink;
uid_t
i_uid;
gid_t
i_gid;
dev_t
i_rdev;
loff_t
i_size;
struct timespec
i_atime;
unsigned long
i_blocks;
unsigned short
i_bytes;
unsigned char
_sock;
12
struct inode_operations *i_op;
struct file_operations *i_fop;
struct super_block *i_sb;
......
};
所谓'文件',
就是按一定的形式存储在介质上的信息,所以一个文件其实包含了两方面的信息,一是存储的数据本身,二是有关该文件的组织和管理的信息。在内存中,
每个文件都有一个dentry(目录项)和inode(索引节点)结构,dentry记录着文件名,上级目录等信息,正是它形成了我们所看到的树状结构;
而有关该文件的组织和管理的信息主要存放inode里面,它记录着文件在存储介质上的位置与分布。同时dentry->d_inode指向相应的
inode结构。dentry与inode是多对一的关系,因为有可能一个文件有好几个文件名(硬链接, hard link,
可以参考这个网页
http://www.ugrad.cs.ubc.ca/~cs219/CourseNotes/Unix/commands-links.html
)。
所有的dentry用d_parent和d_child连接起来,就形成了我们熟悉的树状结构。
inode代表的是物理意义上的文件,通过inode可以得到一个数组,这个数组记录了文件内容的位置,如该文件位于硬盘的第3,8,10块,那么
这个数组的内容就是3,8,10。其索引节点号inode->i_ino,在同一个文件系统中是唯一的,内核只要根据i_ino,就可以计算出它对
应的inode在介质上的位置。就硬盘来说,根据i_ino就可以计算出它对应的inode属于哪个块(block),从而找到相应的inode结构。但
仅仅用inode还是无法描述出所有的文件系统,对于某一种特定的文件系统而言,比如ext3,在内存中用ext3_inode_info描述。他是一个
包含inode的'容器'。
struct ext3_inode_info {
__le32
i_data[15];
......
struct inode
vfs_inode;
};
le32 i data[15]这个数组就是上一段中所提到的那个数组。
注意,在遥远的2.4的古代,不同文件系统索引节点的内存映像
(ext3_inode_info,reiserfs_inode_info,msdos_inode_info
...)都是用一个union内嵌在inode数据结构中的.
但inode作为一种非常基本的数据结构而言,这样搞太大了,不利于快速的分配和回收。但是后来发明了container_of(...)这种方法后,就
把union移到了外部,我们可以用类似container of(inode, struct ext3_inode_info,
vfs_inode),从inode出发,得到其的'容器'。
dentry和inode终究都是在内存中的,它们的原始信息必须要有一个载体。否则断电之后岂不是玩完了?且听我慢慢道来。
文件可以分为磁盘文件,设备文件,和特殊文件三种。设备文件暂且不表。
磁盘文件
就磁盘文件而言,dentry和inode的载体在存储介质(磁盘)上。对于像ext3这样的磁盘文件来说,存储介质中的目录项和索引节点载体如下,
struct ext3_inode {
__le16 i_mode;
__le16 i_uid;
__le32 i_size;
__le32 i_atime;
__le32 i_ctime;
__le32 i_mtime;
__le32 i_dtime;
__le16 i_gid;
__le16 i_links_count;
......
__le32 i_block[EXT2_N_BLOCKS];
......
}
struct ext3_dir_entry_2 {
__u32 inode;
__u16 rec_len;
__u8 name_len;
__u8 file_type;
char name[EXT3_NAME_LEN];
};
le32 i block[EXT2 N BLOCKS];
i_block数组指示了文件的内容所存放的地点(在硬盘上的位置)。
ext3_inode是放在索引节点区,而ext3_dir_entry_2是以文件内容的形式存放在数据区。我们只要知道了ino,由于
ext3_inode大小已知,我们就可以计算出ext3_inode在索引节点区的位置( ino *
sizeof(ext3_inode)
),而得到了ext3_inode,我们根据i_block就可以知道这个文件的数据存放的地点。将磁盘上ext3_inode的内容读入到
ext3_inode_info中的函数是ext3_read_inode()。以一个有100
block的硬盘为例,一个文件系统的组织布局大致如下图。位图区中的每一位表示每一个相应的对象有没有被使用。
特殊文件
特殊文件在内存中有inode和dentry数据结构,但是不一定在存储介质上有'索引节
点',它断电之后的确就玩完了,所以不需要什么载体。当从一个特殊文件读时,所读出的数据是由系统内部按一定的规则临时生成的,或从内存中收集,加工出来
的。sysfs里面就是典型的特殊文件。它存储的信息都是由系统动态的生成的,它动态的包含了整个机器的硬件资源情况。从sysfs读写就相当于向
kobject层次结构提取数据。
还请注意,
我们谈到目录项和索引节点时,有两种含义。一种是在存储介质(硬盘)中的(如ext3_inode),一种是在内存中的,后者是根据在前者生成的。内存中
的表示就是dentry和inode,它是VFS中的一层,不管什么样的文件系统,最后在内存中描述它的都是dentry和inode结构。我们使用不同
的文件系统,就是将它们各自的文件信息都抽象到dentry和inode中去。这样对于高层来说,我们就可以不关心底层的实现,我们使用的都是一系列标准
的函数调用。这就是VFS的精髓,实际上就是面向对象。
我们在进程中打开一个文件F,实际上就是要在内存中建立F的dentry,和inode结构,并让它们与进程结构联系来,把VFS中定义的接口给接起来。我们来看一看这个经典的图。这张图之于文件系统,就像每天爱你多一些之于张学友,番茄炒蛋之于复旦南区食堂,刻骨铭心。
前面说过,只要知道文件的索引节点号,就可以得到那个文件。但是我们在操作文件时,从没听说谁会拿着索引节点号来操作文件,我们只知道文件名而已。
它们是如何'和谐'起来的呢?linux把目录也看成一种文件,里面记录着文件名与索引节点号的对应关系。比如在ext3文件系统中,如果文件是一个目
录,那么它的内容就是一系列ext3_dir_entry_2的结构
struct ext3_dir_entry_2 {
__u32 inode;
__u16 rec_len;
__u8 name_len;
__u8 file_type;
char name[EXT3_NAME_LEN];
};
举个例子,比如要打开/home/test/hello.c。首先,找到‘/’,读入其内容,找到名为'home'的文件的索引节点号,打
开/home这个'文件',读入内容,找到名为 'test'
的的文件的索引节点号,同理,再打开文件'/home/test',找到找到名为'hello.c”的文件的索引节点号,最后就得到
/home/test/hello.c了。这就是path_walk()函数的原理。
其中,根据一个文件夹的inode,和一个文件名来获取该文件的inode结构的函数,就叫lookup,它是inode_operations里面的函数。
struct dentry * (*lookup) (struct inode *,struct dentry *, struct
nameidata *);
lookup,
顾名思义,就是查找,比如查查在test这个文件夹下,有没有叫hello.c的文件,有的话,就从存储介质中读取其inode结构。并用
dentry->d_inode指向它。所以,我们只要知道了文件的路径和名字,总可以从根目录开始,一层一层的往下走,定位到某一个文件。
superblock与vfsmount
接下来还要介绍两个数据结构,superblock和vfsmount。super_block结构是从所有具体的文件系统所抽象出来的一个结构,
每一个文件系统实例都会有一对应super_block结构。比如每一个ext2的分区就有一个super_block结构,它记录了该文件系统实例(分
区)的某些描述性的信息,比如该文件系统实例的文件系统类型,有多大,磁盘上每一块的大小,
还有就是super_operations。它与inode,dentry一样,只是某些内容在内存中的映像。就ext2文件系统而言,设备上的超级块为
ext2_super_block。由于sysfs是虚拟的文件系统,独一无二,
并且只能被mount一次,sysfs的super_block结构是sysfs_sb。sysfs_sb也是动态的从内存中生成的。
还有要提一下super_operations,它也算是VFS的一个接口。实现一个文件系统file_operations,
dentry_operations, inode_operations,
super_operations这四个结构都要实现。
把一个设备安装到一个目录节点时要用一个vfsmount的作为连接件。vfsmount结构定义如下:
struct vfsmount {
struct list_head
mnt_hash;
struct vfsmount
*mnt_parent;
struct dentry
*mnt_mountpoint;
struct dentry
*mnt_root;
struct super_block
*mnt_sb;
..........
}
对于某个文件系统实例,内存中super_block和vfsmount都是唯一的。比如,我们将某个挂载硬盘分区mount -t vfat
/dev/hda2 /mnt/d。实际上就是新建一个vfsmount结构作为连接件,vfsmount->mnt_sb =
/dev/hda2的超级块结构;vfsmount->mntroot =
/dev/hda2的'根'目录的dentry;vfsmount->mnt_mountpoint = /mnt/d的dentry;
vfsmount->mnt_parent =
/mnt/d所属的文件系统的vfsmount。并且把这个新建的vfsmount连入一个全局的hash表mount_hashtable中。
从而我们就可以从总根’/’开始,沿着dentry往下找。假如碰到一个某个目录的dentry是被mount了的,那么我们就从
mount_hashtable表中去寻找相应的vfsmount结构 (函数是lookup_mnt())。然后我们得到vfsmount
->mnt_root,就可以找到mount在该目录的文件系统的'根'dentry结构。然后又继续往下走,就可以畅通无阻了。
关于path_walk()的代码我就不贴了,太长了。其实懂了原理后再去看,很简单,跟看故事会差不多。我当年就是看完这个函数后,信心倍增阿。pathwalk,不管前面是高速公路,或是泥泞的乡间小路,我们都要走到底。
最近Linus炮轰C++,“C++是一种糟糕的(horrible)语言。而且因为有大量不够标准的程序员在使用而使许多真正懂得底层问题,而不会折腾那些白痴‘对象模型’”。牛人就是牛气冲天阿。
在fs/sysfs/下面,除去makefile,还有8个文件。其中, bin.c, file.c, dir.c,
symblink.c分别代表了在sysfs文件系统中当文件类型为二进制文件,普通文件,目录,符号连接时的各自的file
operations结构体的实现。inode.c则是inode
oprations的实现,还有创建和删除inode。mount.c包括了sysfs的初始化函数。sysfs.h就是头文件,里面有函数的原形,并将
其extern出去。
sysfs的文件系统的所读写的信息是存放在kobject当中,那么dentry是如何与kobject联系起来的呢?是通过sysfs_dirent。
sysfs_dirent
sysfs文件系统有自己的dirent结构,dirent = directory entry
(目录实体)。sysfs中,每一个dentry对应了一个dirent结构,dentry->d
_fsdata是一个void的指针,它指向sysfs_dirent结构。
struct sysfs_dirent {
atomic_t
s_count;
struct list_head
s_sibling;
struct list_head
s_children;
void *
s_element;
int
s_type;
umode_t
s_mode;
struct dentry *
s_dentry;
struct iattr *
s_iattr;
atomic_t
s_event;
};
s_count是引用计数,s_sibling,s_children指针是这些sysfs_dirent
连成一个树状结构。s_type则说明了这个dirent具体的类型:
#define SYSFS_ROOT 0x0001
#define SYSFS_DIR 0x0002
#define SYSFS_KOBJ_ATTR 0x0004
#define SYSFS_KOBJ_BIN_ATTR 0x0008
#define SYSFS_KOBJ_LINK 0x0020
s_element就是指向相应与s_type类型的数据结构。如DIR(就是kobject,一个kobject对应一个
DIR),KOBJ_ATTR(attribute属性,代表一个文件)。sysfs_dirent是kobject和sysfs联系的一个中间连接结
构。它通过s_sibling,s_children连接成一个层次结构。而且它的层次结构与sysfs完全一致的,它就是一个连接kobject和
dentry结构的连接件。
举个例子总结一下这些数据结构的连接关系。在sysfs中的文件结构如下
/sys/bus/ldd/
|--device
|--driver
`--version
它对应的dentry,dirent,kobject的连接图如图1,2,3

图1: dentry连接图
图2: dirent连接图
图3: kobject连接图
对比一下可以发现不同之处。向version这样用bus_create_file()创建的文件,或曰属性,只停留在sysfs_dirent这一层。
对于sysfs下的文件夹而言,denrty, dirent, kobject之间通过指针相互联系起来。
dentry->d_fsdata = &dirent;
dirent->element = &kobject;
kobject->dentry = &dentry;
每当我们新增一个kobject结构的时候,同时会在/sys下创建一个目录。
kobject_add()
->
create_dir() ->
sysfs_create_dir()
此时,我还想重申,kernel代码的更新换代是很快的,我们的目的是懂得代码背后的原理,知识,或曰哲学。我不想讲的太细,因为关于sysfs的
部分从2.6.10到现在2.6.22已经改了很多了。但其总体架构没变。写此文的目的是让您跟着我的思路走一遍,对sysfs有了一个总体上的认识。然
后自己就可以去看最新的代码了。最新的代码肯定是效率更高,条理逻辑更清晰。
sysfs_create_dir()流程图如下:
-> create_dir()
-> *d = sysfs_get_dentry()
->
lookup_hash()
-> __lookup_hash()
-> cached_lookup()
-> new = d_alloc(base, name);
-> inode->i_op->lookup(inode, new,
nd)
-> sysfs_create(*d, mode, init_dir)
->
sysfs_new_inode(mode)
->
init_dir(inode);
// Call back function
-> sysfs_make_dirent()
->
sysfs_new_dirent()
->
dentry->d_fsdata = sysfs_get(sd);
->
dentry->d_op = &sysfs_dentry_ops;
-> (*d)->d_op = &sysfs_dentry_ops;
135 int sysfs_create_dir(struct kobject *
kobj)
136 {
137
struct dentry * dentry = NULL;
138
struct dentry * parent;
139
int error = 0;
140
141
BUG_ON(!kobj);
142
143
if (kobj->parent)
144
parent = kobj->parent->dentry;
145
else if (sysfs_mount &&
sysfs_mount->mnt_sb)
146
parent =
sysfs_mount->mnt_sb->s_root;
147
else
148
return -EFAULT;
149
150
error =
create_dir(kobj,parent,kobject_name(kobj),&dentry);
151
if (!error)
152
kobj->dentry = dentry;
153
return error;
154 }
143-148就是找到父辈的kobject,再调用create_dir();
95 static int create_dir(struct
kobject * k, struct dentry * p,
96
const char * n, struct dentry ** d)
97 {
98
int error;
99
umode_t mode = S_IFDIR| S_IRWXU |
S_IRUGO | S_IXUGO;
100
101
down(&p->d_inode->i_sem);
102
*d = sysfs_get_dentry(p,n);
103
if (!IS_ERR(*d)) {
104
error = sysfs_create(*d, mode,
init_dir);
105
if (!error) {
106
error = sysfs_make_dirent(p->d_fsdata,
*d, k, mode,
107
SYSFS_DIR);
108
if (!error) {
109
p->d_inode->i_nlink++;
110
(*d)->d_op =
&sysfs_dentry_ops;
111
d_rehash(*d);
112
}
113
}
114
if (error && (error !=
-EEXIST))
115
d_drop(*d);
116
dput(*d);
117
} else
118
error = PTR_ERR(*d);
119
up(&p->d_inode->i_sem);
120
return error;
121 }
99行,设置‘文件’ 属性,101获取信号量。
(1)sysfs_get_dentry()
102行sysfs_get_dentry()。它的作用是根据父辈dentry和文件名得到
dentry结构。首先在缓存中找,如果找到就返回,找不到就用d_alloc()新建一个dentry结构。我们是新建文件夹,缓存中自然是没有的,所
以要用d_alloc()来新建一个。接着我们调用lookup函数,它定义如下。
struct inode_operations sysfs_dir_inode_operations = {
.lookup = sysfs_lookup,
};
204 static struct dentry *
sysfs_lookup(struct inode *dir, struct dentry *dentry,
205
struct nameidata *nd)
206 {
207
struct sysfs_dirent * parent_sd =
dentry->d_parent->d_fsdata;
208
struct sysfs_dirent * sd;
209
int err = 0;
210
211
list_for_each_entry(sd,
&parent_sd->s_children, s_sibling) {
212
if (sd->s_type & SYSFS_NOT_PINNED)
{
213
const unsigned char * name =
sysfs_get_name(sd);
214
215
if (strcmp(name,
dentry->d_name.name))
216
continue;
217
218
if (sd->s_type &
SYSFS_KOBJ_LINK)
219
err = sysfs_attach_link(sd, dentry);
220
else
221
err = sysfs_attach_attr(sd, dentry);
222
break;
223
}
224
}
225
226
return ERR_PTR(err);
227 }
前 面讲过lookup函数的作用。它在inode代表的文件夹下查找有没有名为dentry.d
name.name的文件。如果有,就将其对应的inode结构从信息的载体中读出来。由于是新建的文件夹,所以lookup函数在我们这个故事里根本没
做事。但是我还是忍不住想分析一下lookup函数。
sysfs文件系统中,文件夹的inode和dentry结构一直都是存在于内存中的,所以不用再进行读取了。而文件,链接的inode事先是没有的,需要从载体中读出。这就是212行这个判断的作用。可以看出,如果是文件夹,循环里面啥都没做。
#define SYSFS_NOT_PINNED /
(SYSFS_KOBJ_ATTR | SYSFS_KOBJ_BIN_ATTR | SYSFS_KOBJ_LINK)
但是sysfs的lookup还有它不同之处。其他文件系统像ext3格式中普通文件的inode,在文件创建之时就已经创建了。但是,sysfs
不一样,它在创建普通文件时,只是先创建一个sysfs_dirent结构。创建inode的工作是推迟到lookup函数来完成的。在下一节
sysfs_create_file()会看到这一点。
sysfs_attach_attr()和sysfs_attach_link()的作用就是根据dentry和sysfs_dirent新建一个inode。
总之,我们通过sysfs_get_dentry()得到了一个新建的dentry结构。
(2)sysfs_create()分析 (104行)
sysfs_create()->sysfs_new_inode(mode) ->
new_inode(sysfs_sb)
创建一个新的索引节点inode。sysfs_sb是sysfs的超级块(super_block)结构。mode则是inode的属性,它记录了如下信息,比如,文件类型(是文件夹,链接,还是普通文件),inode的所有者,创建时间等等。
(3)sysfs make dirent()分析 (104行)
至此,我们得到了一个dirent结构,初始化,再把它连接到上层目录的sysfs_dirent的s_children链表里去。sysfs_make_dirent()为刚刚新建出来的dentry建立一个dirent结构。并将dentry和dirent联系起来。
(4)总结
在sysfs下创建一个目录,提供的函数是sysfs_create_dir()。创建了dentry, dirent,
inode
结构, 它们之间的连接关系见图1
最近彭宇的案件炒得沸沸扬扬,究竟这个社会怎么了?
sysfs文件系统中,普通文件对应于kobject中的属性。用sysfs_create_file(),参数如下:
sysfs_create_file(struct kobject * kobj, const struct attribute *
attr)
传给它的参数是kobj和attr,其中,kobject对应的是文件夹,attribute对应的是该文件夹下的文件。
int sysfs_create_file(struct kobject * kobj, const struct attribute
* attr)
{
BUG_ON(!kobj || !kobj->dentry ||
!attr);
return
sysfs_add_file(kobj->dentry, attr, SYSFS_KOBJ_ATTR);
}
它直接调用sysfs_add_file()
int sysfs_add_file(struct dentry * dir, const struct attribute *
attr, int type)
{
struct sysfs_dirent * parent_sd =
dir->d_fsdata;
umode_t mode = (attr->mode &
S_IALLUGO) | S_IFREG;
int error = 0;
down(&dir->d_inode->i_sem);
error = sysfs_make_dirent(parent_sd,
NULL, (void *) attr, mode, type);
up(&dir->d_inode->i_sem);
return error;
}
int sysfs_make_dirent(struct sysfs_dirent * parent_sd, struct
dentry * dentry,
void * element, umode_t mode, int
type)
{
struct sysfs_dirent * sd;
sd = sysfs_new_dirent(parent_sd, element);
if (!sd)
return -ENOMEM;
sd->s_mode = mode;
sd->s_type = type;
sd->s_dentry = dentry;
if (dentry) {
dentry->d_fsdata =
sysfs_get(sd);
dentry->d_op =
&sysfs_dentry_ops;
}
return 0;
}
sysfs_create_file()仅仅是调用了sysfs_make_dirent()创建了一个sysfs_dirent结构。与
sysfs_create_dir()不同,它甚至没有在sysfs文件系统下创建inode结构。这项工作被滞后了,在
sysfs_lookup()->sysfs_attach_attr()里面完成的。
上回我们说到,如何创建文件夹和文件。我们发现,在sysfs中,inode并不那么重要。这是因为我们所要读写的信息已经就在内存中,并且已经形
成了层次结构。我们只需有dentry,就可以dentry->fsdata,就能找到我们读些信息的来源 ---
sysfs_dirent结构。这也是我觉得有必要研究
sysfs的原因之一,因为它简单,而且不涉及具体的硬件驱动,但是从这个过程中,我们可以把文件系统中的一些基本数据结构搞清楚。接下来,我以读取
sysfs文件和文件夹的内容为例子,讲讲文件读的流程。那么关于写,还有关于symblink的东西完全可以以此类推了。
我们新建文件夹时,设置了
inode->i_op = &sysfs_dir_inode_operations;
inode->i_fop = &sysfs_dir_operations;
struct file_operations sysfs_dir_operations = {
.open =
sysfs_dir_open,
.release =
sysfs_dir_close,
.llseek =
sysfs_dir_lseek,
.read =
generic_read_dir,
.readdir =
sysfs_readdir,
};
用一个简短的程序来做实验。
#include
<
sys
/
types.h
>
#include
<
dirent.h
>
#include
<
unistd.h
>
int
main()
{
DIR *
dir;
struct
dirent * ptr;
dir =
opendir( ' /sys/bus/ ' );
while ((ptr
= readdir(dir)) != NULL)
{
printf( ' d_name :%s
' ,ptr -> d_name);
}
closedir(dir);
return
- 1 ;
}
在用户空间,用gcc编译执行即可。我们来看看它究竟做了什么。
(1)sysfs_dir_open()
这是个用户空间的程序。opendir()是glibc的函数,glibc也就是著名的标准c库。至于opendir ()是如何与sysfs
dir open
()接上头的,那还得去看glibc的代码。我就不想分析了...glibc可以从gnu的网站上自己下载源代码,编译。再用gdb调试,就可以看得跟清
楚。
函数流程如下:
opendir('/sys/bus/') ->
-> 系统调用->
sys_open() -> filp_open()-> dentry_open() ->
sysfs_dir_open()
static int
sysfs_dir_open(
struct
inode
*
inode,
struct file
*
file)
{
struct
dentry * dentry
= file -> f_dentry;
struct
sysfs_dirent * parent_sd
= dentry ->
d_fsdata;
down( & dentry
-> d_inode -> i_sem);
file -> private_data
= sysfs_new_dirent(parent_sd,
NULL);
up( & dentry ->
d_inode -> i_sem);
return
file -> private_data ?
0 : -
ENOMEM;
}
内核空间:新建一个dirent结构,连入父辈的dentry中,并将它地址保存在file->private_data中。这个dirent的具体作用待会会讲。
用户空间:新建了一个DIR结构,DIR结构如下。
#define __dirstream DIR
struct __dirstream
{
int fd;
char *data;
size_t allocation;
size_t size;
size_t offset;
off_t filepos;
__libc_lock_define (, lock)
};
(2)sysfs_readdir()
流程如下:
readdir(dir) -> getdents() ->
-> 系统调用->
sys32 readdir() -> vfs readdir() -> sysfs readdir()
readdir(dir)这个函数有点复杂,虽然在main函数里的while循环中,readdir被执行了多次,我们看看glibc里面的代码
readdir(dir)
{
......
if
(dirp -> offset >=
dirp -> size) {
......
getdents()
......
}
......
}
实际上,getdents() -> ... ->
sysfs_readdir()只被调用了两次,getdents()一次就把所有的内容都读完,存在DIR结构当中,readdir()只是从DIR结
构当中每次取出一个。DIR(dirstream)结构就是一个流。而回调函数filldir的作用就是往这个流中填充数据。第二次调用
getdents()是用户把DIR里面的内容读完了,所以它又调用getdents()但是这次getdents()回返回NULL。
static int
sysfs_readdir(
struct
file
*
filp,
void *
dirent,
filldir_t
filldir)
{
struct
dentry * dentry =
filp -> f_dentry;
struct
sysfs_dirent * parent_sd
= dentry ->
d_fsdata;
struct
sysfs_dirent * cursor =
filp -> private_data;
struct
list_head * p, *
q = &
cursor -> s_sibling;
ino_t ino;
int
i = filp ->
f_pos;
switch (i)
{
case 0
:
ino = dentry ->
d_inode -> i_ino;
if (filldir(dirent,
' . ' , 1
, i, ino, DT_DIR) <
0 )
break ;
filp -> f_pos ++ ;
i ++ ;
case 1
:
ino =
parent_ino(dentry);
if (filldir(dirent,
' .. ' , 2
, i, ino, DT_DIR) <
0 )
break ;
filp -> f_pos ++ ;
i ++ ;
default :
if (filp ->
f_pos == 2 )
{
list_del(q);
list_add(q,
& parent_sd ->
s_children);
}
for (p = q
-> next; p !=
& parent_sd -> s_children;
p = p -> next)
{
struct sysfs_dirent
* next;
const char
* name;
int len;
next
= list_entry(p,
struct sysfs_dirent,
s_sibling);
if ( ! next
-> s_element)
continue ;
name
= sysfs_get_name(next);
len =
strlen(name);
if (next ->
s_dentry)
ino = next
-> s_dentry -> d_inode
-> i_ino;
else
ino =
iunique(sysfs_sb, 2 );
if (filldir(dirent, name,
len, filp -> f_pos,
ino,dt_type(next)) <
0 )
return
0 ;
list_del(q);
list_add(q,
p);
p =
q;
filp ->
f_pos ++ ;
}
}
return 0 ;
}
看sysfs_readdir()其实很简单,它就是从我们调用sysfs_dir_open()时新建的一个sysfs_dirent结构开始,
便利当前dentry->dirent下的所有子sysfs_dirent结构。读出名字,再回调函数filldir()将文件名,文件类型等信
息,按照一定的格式写入某个缓冲区。
一个典型的filldir()就是filldir64(),它的作用的按一定格式向缓冲区写数据,再把数据复制到用户空间去。
跟上回一样,我用这个小程序来读
#include
<
stdio.h
>
#include
<
fcntl.h
>
#include
<
unistd.h
>
int
main()
{
char
* name =
' /sys/bus/ldd/version '
;
char
buf[ 500 ];
int
fd;
int
size;
fd =
open(name, O_RDONLY);
printf( '
fd:%d ' ,fd);
size =
read(fd,buf, sizeof
(buf));
printf( '
size:%d ' ,size);
printf( ' %s
' ,buf);
close(fd);
return
- 1 ;
}
(1)sysfs_open_file()
open() ->
-> 系统调用->
sys_open() -> filp_open()-> dentry_open() ->
sysfs_open_file()
static int sysfs_open_file(struct inode * inode, struct file *
filp)
{
return check_perm(inode,filp);
}
static int
check_perm(
struct
inode
*
inode,
struct file
*
file)
{
struct
kobject * kobj =
sysfs_get_kobject(file -> f_dentry
-> d_parent);
struct
attribute * attr
= to_attr(file ->
f_dentry);
struct
sysfs_buffer * buffer;
struct
sysfs_ops * ops
= NULL;
int
error = 0
;
if (
! kobj || !
attr)
goto Einval;
if ( !
try_module_get(attr -> owner))
{
error
= - ENODEV;
goto Done;
}
if (kobj
-> kset &&
kobj -> kset ->
ktype)
ops
= kobj -> kset ->
ktype -> sysfs_ops;
else
if (kobj ->
ktype)
ops
= kobj -> ktype ->
sysfs_ops;
else
ops
= &
subsys_sysfs_ops;
if (
! ops)
goto Eaccess;
if (file ->
f_mode & FMODE_WRITE)
{
if ( ! (inode
-> i_mode &
S_IWUGO) || !
ops -> store)
goto
Eaccess;
}
if (file ->
f_mode & FMODE_READ)
{
if ( ! (inode
-> i_mode &
S_IRUGO) || !
ops -> show)
goto
Eaccess;
}
buffer =
kmalloc( sizeof (
struct
sysfs_buffer),GFP_KERNEL);
if (buffer)
{
memset(buffer, 0 , sizeof
( struct
sysfs_buffer));
init_MUTEX(
& buffer -> sem);
buffer
-> needs_read_fill =
1 ;
buffer
-> ops = ops;
file
-> private_data =
buffer;
}
else
error
= - ENOMEM;
goto
Done;
Einval:
error =
- EINVAL;
goto
Done;
Eaccess:
error =
- EACCES;
module_put(attr ->
owner);
Done:
if
(error && kobj)
kobject_put(kobj);
return
error;
}
check_perm()检查一下权限,创建一个sysfs的缓冲区sysfs_buffer buffer,并设置其sysfs_ops
sysfs_buffer->ops。在我们这个故事里,sysfs_buffer->ops被设置成bus_sysfs_ops。最后让
file->private_data = buffer。
(2)sysfs read file()
流程如下:
read()->
-> 系统调用->
sys_read() -> vfs_read() -> sysfs_read_file()
看看sysfs_read_file()函数,
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 ;
down( & buffer
-> sem);
if (buffer ->
needs_read_fill) {
if ((retval =
fill_read_buffer(file ->
f_dentry,buffer)))
goto
out ;
}
pr_debug( ' %s: count
= %d, ppos = %lld, buf
= %s ' ,
__FUNCTION__,count, * ppos,buffer ->
page);
retval =
flush_read_buffer(buffer,buf,count,ppos);
out :
up( & buffer ->
sem);
return
retval;
}
顺着sysfs_read_file()往下走:
sysfs_read_file()
--->
fill_read_buffer()
--->
sysfs_buffer->bus_sysfs_ops->bus_attr_show()
--->
bus_attribute->show_bus_version()
//注意这个函数是我们在lddbus.c里面定义的
--->
flush_read_buffer()
fill_read_buffer()的是真正的读,它把内容读到sysfs定义的缓冲区sysfs_buffer。flush_read_buffer()是把缓冲区copy到用户空间。