Linux 2.5的内核引入了一种新的设备模型,目的是对计算机上的所有设备进行统一的管理。
它包含以下基础结构:
类型 |
说明 |
设备Devices
|
设备是此模型中最基本的类型,以设备本身的连接按层次组织 |
设备驱动Device Drivers
|
在一个系统中安装多个相同设备,只需要一份驱动程序的支持 |
总线类型Bus Types
|
在整个总线级别对此总线上连接的所有设备进行管理 |
设备类别Device Classes
|
这是按照功能进行分类组织的设备层次树;如 USB 接口和 PS/2 接口的鼠标都是输入设备,都会出现在 /sys/class/input/ 下 |
Linux通过以上模型把实现设备的统一管理,使驱动和设备自动的匹配。
sysfs是一种基于内存的虚拟文件系统,它提供目录、属性文件、链接文件等,让用户空间能便捷地访问kernel的设备信息。
在内部实现上,sysfs以kobject数据结构为最小的粒度,进而封装成kset、subsystem、bus_type、class等模型来组织设备间的拓扑关系。
/sys的顶层目录结构如下:
/sys 下的子目录 |
所包含的内容 |
/sys/devices |
这是内核对系统中所有设备的分层次表达模型,也是 /sys 文件系统管理设备的最重要的目录结构,下文会对它的内部结构作进一步分析; |
/sys/dev |
这个目录下维护一个按字符设备和块设备的主次号码(major:minor)链接到真实的设备(/sys/devices下)的符号链接文件,它是在内核 2.6.26 首次引入; |
/sys/bus |
这是内核设备按总线类型分层放置的目录结构, devices 中的所有设备都是连接于某种总线之下,在这里的每一种具体总线之下可以找到每一个具体设备的符号链接,它也是构成 Linux 统一设备模型的一部分; |
/sys/class |
这是按照设备功能分类的设备模型,如系统所有输入设备都会出现在 /sys/class/input 之下,而不论它们是以何种总线连接到系统。它也是构成 Linux 统一设备模型的一部分; |
/sys/block |
这里是系统中当前所有的块设备所在,按照功能来说放置在 /sys/class 之下会更合适,但只是由于历史遗留因素而一直存在于 /sys/block, 但从 2.6.22 开始就已标记为过时,只有在打开了 CONFIG_SYSFS_DEPRECATED 配置下编译才会有这个目录的存在,并且在 2.6.26 内核中已正式移到 /sys/class/block, 旧的接口 /sys/block 为了向后兼容保留存在,但其中的内容已经变为指向它们在 /sys/devices/ 中真实设备的符号链接文件; |
/sys/firmware |
这里是系统加载固件机制的对用户空间的接口,关于固件有专用于固件加载的一套API,在附录 LDD3 一书中有关于内核支持固件加载机制的更详细的介绍; |
/sys/fs |
这里按照设计是用于描述系统中所有文件系统,包括文件系统本身和按文件系统分类存放的已挂载点,但目前只有 fuse,gfs2 等少数文件系统支持 sysfs 接口,一些传统的虚拟文件系统(VFS)层次控制参数仍然在 sysctl (/proc/sys/fs) 接口中中; |
/sys/kernel |
这里是内核所有可调整参数的位置,目前只有 uevent_helper, kexec_loaded, mm, 和新式的 slab 分配器等几项较新的设计在使用它,其它内核可调整参数仍然位于 sysctl (/proc/sys/kernel) 接口中 ; |
/sys/module |
这里有系统中所有模块的信息,不论这些模块是以内联(inlined)方式编译到内核映像文件(vmlinuz)中还是编译为外部模块(ko文件),都可能会出现在 /sys/module 中: 编译为外部模块(ko文件)在加载后会出现对应的 /sys/module/ 编译为内联方式的模块则只在当它有非0属性的模块参数时会出现对应的 /sys/module/ 如 /sys/module/printk/parameters/time 这个可读写参数控制着内联模块 printk 在打印内核消息时是否加上时间前缀; 所有内联模块的参数也可以由 " 没有非0属性参数的内联模块不会出现于此。
|
/sys/power |
这里是系统中电源选项,这个目录下有几个属性文件可以用于控制整个机器的电源状态,如可以向其中写入控制命令让机器关机、重启等。 |
/sys/slab (对应 2.6.23 内核,在 2.6.24 以后移至 /sys/kernel/slab) |
从2.6.23 开始可以选择 SLAB 内存分配器的实现,并且新的 SLUB(Unqueued Slab Allocator)被设置为缺省值;如果编译了此选项,在 /sys 下就会出现 /sys/slab ,里面有每一个 kmem_cache 结构体的可调整参数。对应于旧的 SLAB 内存分配器下的 /proc/slabinfo 动态调整接口,新式的 /sys/kernel/slab/ |
首先看fs/sysfs/mount.c中的sysfs_init函数
int __init sysfs_init(void)
{
int err = -ENOMEM;
/*创建高速缓存*/
sysfs_dir_cachep = kmem_cache_create("sysfs_dir_cache",
sizeof(struct sysfs_dirent),
0, 0, NULL);
if (!sysfs_dir_cachep)
goto out;
/*初始化sysfs bdi设备信息*/
err = sysfs_inode_init();
if (err)
goto out_err;
/*注册sysfs文件系统*/
err = register_filesystem(&sysfs_fs_type);
if (!err) {
sysfs_mnt = kern_mount(&sysfs_fs_type);
if (IS_ERR(sysfs_mnt)) {
printk(KERN_ERR "sysfs: could not mount!\n");
err = PTR_ERR(sysfs_mnt);
sysfs_mnt = 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;
}
sysfs_dir_cachep是一个高速缓存,后续在sysfs中需要用到的对象内存,都会在这个缓存中开辟。
sysfs_inode_init函数初始化backing_dev_info对象,它描述磁盘块设备的相关信息,暂不深究。
register_filesystem函数向系统注册sysfs文件系统,其中sysfs_fs_type是一个file_system_type对象,用来描述sysfs文件系统信息:
struct file_system_type {
/*名称*/
const char *name;
/*指定文件系统的一些特性*/
int fs_flags;
/*文件系统加载函数*/
struct dentry *(*mount) (struct file_system_type *, int,
const char *, void *);
void (*kill_sb) (struct super_block *);
/*指向module的指针,如果是非module方式注册的,owner为NULL*/
struct module *owner;
/*所有的file_system通过next指针组成链表,如ext2、ext3、proc等*/
struct file_system_type * next;
/*superblock链表 */
struct list_head fs_supers;
struct lock_class_key s_lock_key;
struct lock_class_key s_umount_key;
struct lock_class_key s_vfs_rename_key;
struct lock_class_key i_lock_key;
struct lock_class_key i_mutex_key;
struct lock_class_key i_mutex_dir_key;
struct lock_class_key i_alloc_sem_key;
};
/*sysfs file_system*/
static struct file_system_type sysfs_fs_type = {
.name = "sysfs",
.mount = sysfs_mount,
.kill_sb = sysfs_kill_sb,
};
需要注意的是,系统把所有的file_system_type通过next指针链接成链表。
sysfs_fs_type还定义了其mout成员函数为sysfs_mount。
register_filesystem函数的实现如下:
/**
* register_filesystem - register a new filesystem
* @fs: the file system structure
*
* Adds the file system passed to the list of file systems the kernel
* is aware of for mount and other syscalls. Returns 0 on success,
* or a negative errno code on an error.
*
* The &struct file_system_type that is passed is linked into the kernel
* structures and must not be freed until the file system has been
* unregistered.
*/
int register_filesystem(struct file_system_type * fs)
{
int res = 0;
struct file_system_type ** p;
BUG_ON(strchr(fs->name, '.'));
if (fs->next)
return -EBUSY;
INIT_LIST_HEAD(&fs->fs_supers);
write_lock(&file_systems_lock);
/*所有filesystem通过next组成链表,在链表中查找名称为fs->name的filestem
如果找到,则返回filestem地址;没找到,则返回链表尾的next指针地址*/
p = find_filesystem(fs->name, strlen(fs->name));
if (*p)
res = -EBUSY;
else
*p = fs;/*链接到链表尾*/
write_unlock(&file_systems_lock);
return res;
}
register_filesystem通过调用find_filesystem在文件系统链表中查找名称为fs->name的文件系统,如果找到,则返回file_system_type地址。如果没找到,则返回链表尾的next指针地址,并把当前的file_system_type链接到链表尾端,这样就完成了sysfs文件系统的注册。
以下是find_filesystem函数的具体实现:
static struct file_system_type **find_filesystem(const char *name, unsigned len)
{
struct file_system_type **p;
/*通过next成员遍历file_systems链表*/
for (p=&file_systems; *p; p=&(*p)->next)
if (strlen((*p)->name) == len &&
strncmp((*p)->name, name, len) == 0)
break;
return p;
}
回到sysfs_fs_type的sysfs_mount函数,顾名思义,它完成了sysfs文件系统的加载,以下是具体实现:
static struct dentry *sysfs_mount(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data)
{
struct sysfs_super_info *info;
enum kobj_ns_type type;
struct super_block *sb;
int error;
info = kzalloc(sizeof(*info), GFP_KERNEL);
if (!info)
return ERR_PTR(-ENOMEM);
for (type = KOBJ_NS_TYPE_NONE; type < KOBJ_NS_TYPES; type++)
info->ns[type] = kobj_ns_grab_current(type);
/*创建superblock*/
sb = sget(fs_type, sysfs_test_super, sysfs_set_super, info);
if (IS_ERR(sb) || sb->s_fs_info != info)
free_sysfs_super_info(info);
if (IS_ERR(sb))
return ERR_CAST(sb);
if (!sb->s_root) {
sb->s_flags = flags;
/*设置superblock,初始化inode和dentry*/
error = sysfs_fill_super(sb, data, flags & MS_SILENT ? 1 : 0);
if (error) {
deactivate_locked_super(sb);
return ERR_PTR(error);
}
sb->s_flags |= MS_ACTIVE;
}
return dget(sb->s_root);
}
mount函数先是通过sget函数创建supserblock,再调用sysfs_fill_super初始化inode和dentry信息,下面是sysfs_fill_super的具体实现:
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;
/*设置superblock的操作函数为sysfs_ops*/
sb->s_op = &sysfs_ops;
sb->s_time_gran = 1;
/* get root inode, initialize and unlock it */
mutex_lock(&sysfs_mutex);
/*创建和初始化inode*/
inode = sysfs_get_inode(sb, &sysfs_root);
mutex_unlock(&sysfs_mutex);
if (!inode) {
pr_debug("sysfs: could not get root inode\n");
return -ENOMEM;
}
/* instantiate and link root dentry */
/*创建dentry并关联到inode*/
root = d_alloc_root(inode);
if (!root) {
pr_debug("%s: could not get root dentry!\n",__func__);
iput(inode);
return -ENOMEM;
}
/*dentry关联到sysfs_root*/
root->d_fsdata = &sysfs_root;
/*superblock关联到dentry*/
sb->s_root = root;
return 0;
}
在文件系统中,inode(索引项)用来描述文件在物理存储介质中的索引信息,dentry(目录项)用来描述文件的逻辑信息如文件名等。
sysfs_fill_super先是调用sysfs_get_inode创建和初始化inode,接着通过d_alloc_root函数创建dentry,这里创建的inode和dentry都是针对root结点的。
下面是创建inode的过程:
/**
* sysfs_get_inode - get inode for sysfs_dirent
* @sb: super block
* @sd: sysfs_dirent to allocate inode for
*
* Get inode for @sd. If such inode doesn't exist, a new inode
* is allocated and basics are initialized. New inode is
* returned locked.
*
* LOCKING:
* Kernel thread context (may sleep).
*
* RETURNS:
* Pointer to allocated inode on success, NULL on failure.
*/
struct inode * sysfs_get_inode(struct super_block *sb, struct sysfs_dirent *sd)
{
struct inode *inode;
/*从superblock中查找inode,没找到则创建一个新的inode并返回*/
inode = iget_locked(sb, sd->s_ino);
if (inode && (inode->i_state & I_NEW))
sysfs_init_inode(sd, inode);/*初始化inode*/
return inode;
}
sysfs_get_inode函数调用sysfs_init_inode来初始化inode,详细如下:
static void sysfs_init_inode(struct sysfs_dirent *sd, struct inode *inode)
{
struct bin_attribute *bin_attr;
inode->i_private = sysfs_get(sd);
inode->i_mapping->a_ops = &sysfs_aops;
inode->i_mapping->backing_dev_info = &sysfs_backing_dev_info;
inode->i_op = &sysfs_inode_operations;
set_default_inode_attr(inode, sd->s_mode);
sysfs_refresh_inode(sd, inode);
/* initialize inode according to type */
/*根据sysfs_dirent的类型,设置inode不同的操作函数,分别有目录,属性文件,BIN文件,链接文件
1)目录对应kobject
2)属性文件对应kobject的attribute
3)BIN文件对应kobject的bin_attribute
4)链接文件对应*/
switch (sysfs_type(sd)) {
case SYSFS_DIR:
inode->i_op = &sysfs_dir_inode_operations;
inode->i_fop = &sysfs_dir_operations;
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_dirent类型所对应的inode操作函数。sysfs的文件类型分别有以下几种:
目录文件
属性文件
BIN文件
链接文件
这几种文件与kobject的对应关系如下(kobject将在下一章中详细介绍):
1)目录对应kobject
2)属性文件对应kobject的attribute
3)BIN文件对应kobject的bin_attribute
4)链接文件对应kobject的sysfs_elem_bin_attr
参考文章:使用 /sys 文件系统访问 Linux 内核