Linux底层之Sysfs

文章目录

  • Sysfs简介
    • sysfs目录层次图
  • Linux设备底层模型
    • Linux 统一设备模式的基本结构
    • Kobject
    • Kobj_type
    • Kset
    • kset与kobject的关系图
    • Subsystem
  • 设备模型上层容器
    • bus
    • device
    • driver
  • 文件系统
  • dentry与inode
    • 磁盘文件
    • superblock
    • vfsmount

Sysfs简介

Sysfs文件系统是类似于proc文件系统的特殊文件系统,用于将系统的设备组织成层次结构,并向用户模式程序提供详细的内核数据结构信息.

ls /sys/
  • block目录:包含所有的块设备
  • devices目录:包含系统所有的设备,并根据设备挂接的总线类型组织成层次结构
  • bus目录:包含系统中所有的总线类型
  • drivers目录:包含内核中所有已注册的设备驱动程序
  • class目录:系统中的设备类型,如网卡设备和声卡设备等
/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 系统加载固件机制的对用户空间的接口
/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 中:1、编译为外部模块(ko文件)在加载后会出现对应的 /sys/module//, 并且在这个目录下会出现一些属性文件和属性目录来表示此外部模块的一些信息,如版本号、加载状态、所提供的驱动程序等;2、编译为内联方式的模块则只在当它有非0属性的模块参数时会出现对应的 /sys/module/, 这些模块的可用参数会出现在 /sys/modules//parameters/ 中,如 /sys/module/printk/parameters/time 这个可读写参数控制着内联模块 printk 在打印内核消息时是否加上时间前缀;所有内联模块的参数也可以由 “.=” 的形式写在内核启动参数上,如启动内核时加上参数 “printk.time=1” 与 向 “/sys/module/printk/parameters/time” 写入1的效果相同;3、没有非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/ 接口中的各项信息和可调整项显得更为清晰
#在 /sys/devices/ 目录下是按照设备的基本总线类型分类的目录
$ ls -F /sys/devices/
isa/  LNXSYSTM:00/  pci0000:00/  platform/  pnp0/  pnp1/  system/  virtual/
#在 /sys/devices/pci0000:00/ 目录下是按照 PCI 总线接入的设备号分类存放的目录
$ ls -F /sys/devices/pci0000:00/
0000:00:00.0/  0000:00:02.5/  0000:00:03.1/  0000:00:0e.0/   power/
0000:00:01.0/  0000:00:02.7/  0000:00:03.2/  firmware_node@  uevent
0000:00:02.0/  0000:00:03.0/  0000:00:03.3/  pci_bus/
#在 /sys/devices/pci0000:00/0000:00:01.0/ 目录下,其中有一个目录 0000:01:00.0/, 其它都是属性文件和属性组
$ ls -F /sys/devices/pci0000:00/0000:00:01.0/
0000:01:00.0/         device         local_cpus  power/            subsystem_vendor
broken_parity_status  enable         modalias    resource          uevent
class                 irq            msi_bus     subsystem@        vendor
config                local_cpulist  pci_bus/    subsystem_device

sysfs目录层次图

Linux底层之Sysfs_第1张图片

Linux设备底层模型

Linux 统一设备模式的基本结构

类型 所包含的内容 对应内核数据结构 对应/sys项
设备(Devices) 设备是此模型中最基本的类型,以设备本身的连接按层次组织 struct device /sys/devices/*/*/…/
设备驱动(Device Drivers) 在一个系统中安装多个相同设备,只需要一份驱动程序的支持 struct device_driver /sys/bus/pci/drivers/*/
总线类型(Bus Types) 在整个总线级别对此总线上连接的所有设备进行管理 struct bus_type /sys/bus/*/
设备类别(Device Classes) 按照功能进行分类组织的设备层次树;如 USB 接口和 PS/2 接口的鼠标都是输入设备,都会出现在 /sys/class/input/ 下 struct class /sys/class/*/

Kobject

  • Kobject是linux2.6引入的新设备管理机制,在内核中由struct kobject表示,通过这个数据结构使所有设备在底层具有统一的接口,提供基本的对象管理,每个在内核注册的kobject对象都对应于sysfs文件系统中的一个目录。如bus、devices、drivers就是通过kobject连接起来,形成树状结构。
  • kobject结构为一些大的数据结构和子系统提供了基本的对象管理,如对象引用计数、维护对象链表、对象上锁、在用户空间表示等。

Kobject结构体:

struct kobject {
	char * k name; //指向设备名称的指针
	char name[KOBJ NAME LEN]; //设备名称
	struct kref kref; //对象引用计数
	struct list_head entry; //挂接到所在 kset 中去的单元
	struct kobject * parent; //指向父对象的指针
	struct kset * kset; //所属 kset 的指针
	struct kobj_type * ktype; //指向其对象类型描述符的指针
	struct dentry * dentry; //sysfs 文件系统中与该对象对应的文件节点路径指针
};
  • kref 域表示该对象引用的计数,内核通过 kref 实现对象引用计数管理,内核提供两个函数kobject_get()、kobject_put()分别用于增加和减少引用计数,当引用计数为 0 时,所有该对象使用的资源释放。
  • Ktype 域是一个指向 kobjtype 结构的指针,表示该对象的类型。

相关函数

//kobject 初始化函数
void kobject_init(struct kobject * kobj);
//设置指定 kobject 的名称。
int kobject_set_name(struct kobject *kobj, const char *format, ...);
//将 kobj 对象的引用计数加 1,同时返回该对象的指针。
struct kobject *kobject_get(struct kobject *kobj);
//将 kobj 对象的引用计数减 1,如果引用计数降为 0,则调用 kobject release()释放该 kobject 对象。
void kobject_put(struct kobject * kobj);
//将kobj对象加入Linux设备层次。挂接该kobject对象到kset的list链中,增加父目录各级 kobject 的引用计数,在其parent指向的目录下创建文件节点,并启动该类型内核对象的hotplug函数。
int kobject_add(struct kobject * kobj);
//从 Linux 设备层次(hierarchy)中删除 kobj 对象。
void kobject_del(struct kobject * kobj);
//kobject注册函数。通过调用kobject_init()初始化kobj,再调用kobject_add()完成该内核对象的注册。
int kobject_register(struct kobject * kobj);
//kobject 注销函数。与kobject_register()相反,它首先调用kobject_del从设备层次中删除该对象,再调用 kobject_put()减少该对象的引用计数,如果引用计数降为0,则释放kobject 对象。
void kobject_unregister(struct kobject * kobj);

Kobj_type

kobj_type数据结构

struct kobj_type {
	//release方法用于释放kobject占用的资源
	void (*release)(struct kobject *);
	/*sysfs_ops指针指向sysfs操作表和一个sysfs文件系统缺省属性列表。
	Sysfs 操作表包括两个函数store()和show()。
	当用户态读取属性时,show()函数被调用,该函数编码指定属性值存入buffer中返回给用户态;
	store()函数用于存储用户态传入的属性值*/
	struct sysfs_ops * sysfs_ops;
	struct attribute ** default_attrs;
};
/*attribute属性。它以文件的形式输出到sysfs 的目录当中。
在kobject对应的目录下面,文件名就是name。文件读写的方法对应于kobj_type中的sysfs_ops*/
attribute
	struct attribute {
	char * name;
	struct module * owner;
	mode_t mode;
};

Kset

  • kset 最重要的是建立上层(sub-system)和下层的(kobject)的关联性。kobject也会利用它了分辨自已是属于那一個类型,然後在/sys 下建立正确的目录位置。
  • kset的优先权比较高,kobject 会利用自已的kset 找到自已所属的kset,并把ktype 指定成该kset下的 ktype,除非沒有定义 kset,才会用 ktype 來建立关系。
  • Kobject 通过 kset 组织成层次化的结构,kset 是具有相同类型的kobject 的集合。

kset数据结构

struct kset {
	struct subsystem * subsys; 所在的 subsystem 的指针
	struct kobj_type * ktype; 指向该 kset 对象类型描述符的指针
	struct list_head list; 用于连接该 kset 中所有 kobject 的链表头
	struct kobject_kobj; 嵌入的 kobject
	struct kset_hotplug ops * hotplug ops; 指向热插拔操作表的指针
};
  • kset中的所有 kobject 被组织成一个双向循环链表,list 域正是该链表的头。
  • Ktype域指向一个kobj_type 结构,被该 kset 中的所有 kobject 共享,表示这些对象的类型。
  • Kset 数据结构还内嵌了一个 kobject 对象(由 kobj 域表示),所有属于这个 kset 的 kobject 对象的 parent 域均指向这个内嵌的对象。
  • kset 还依赖于 kobj 维护引用计数:kset 的引用计数实际上就是内嵌的kobject 对象的引用计数

相关函数

  • 与 kobject 相似,kset_init()完成指定 kset 的初始化
  • kset_get()和 kset_put()分别增加和减少 kset 对象的引用计数
  • Kset_add()和 kset_del()函数分别实现将指定 keset 对象加入设备层次和从其中删除
  • kset_register()函数完成 kset的注册而 kset_unregister()函数则完成 kset 的注销。

kset与kobject的关系图

Linux底层之Sysfs_第2张图片

Subsystem

  • kset是管理kobject的集合,同理subsystem 就是管理kset的集合
  • 描述系统中某一类设备子系统,如 block subsys 表示所有的块设备,对应于sysfs文件系统中的block目录。类似的,devices subsys 对应于sysfs中的devices目录,描述系统中所有的设备

subsystem数据结构

struct subsystem {
	struct kset kset;//内嵌的 kset 对象
	struct rw_semaphore rwsem;//互斥访问信号量
};
  • subsystem 与 kset 的区别就是多了一个信号量,所以在后来的代码中,subsystem 已经完全被 kset 取缔了
  • 每个 kset 属于某个 subsystem,通过设置 kset 结构中的subsys域指向指定的subsystem可以将一个 kset 加入到该 subsystem
  • 所有挂接到同一 subsystem的kset共享同一个rwsem信号量,用于同步访问kset中的链表

相关函数

void subsystem_init(struct subsystem *subsys);
int subsystem_register(struct subsystem *subsys);
void subsystem_unregister(struct subsystem *subsys);
struct subsystem *subsys_get(struct subsystem *subsys);
void subsys_put(struct subsystem *subsys);

设备模型上层容器

bus

  • 每个bus_type对象都内嵌一个subsystem对象,bus_subsys对象管理系统中所有总线类型的subsystem对象
  • 每个bus_type对象都对应/sys/bus目录下的一个子目录,如PCI总线类型对应于/sys/bus/pci
  • 在每个这样的目录下都存在两个子目录:devices和drivers(分别对应于bus type结构中的devices和drivers域)。其中devices子目录描述连接在该总线上的所有设备,而drivers目录则描述与该总线关联的所有驱动程序
  • 与device_driver对象类似,bus_type结构还包含几个函数(match()、hotplug()等)处理相应的热插拔、即插即拔和电源管理事件。

bus_type数据结构

struct bus_type {
	char * name; //总线类型的名称
	struct subsystem subsys; //与该总线相关的subsystem
	struct kset drivers; //所有与该总线相关的驱动程序集合
	struct kset devices; //所有挂接在该总线上的设备集合
	struct bus attribute * bus_attrs; //总线属性
	struct device attribute * dev_attrs; //设备属性
	struct driver attribute * drv_attrs; //驱动程序属性
	int (*match)(struct device * dev, struct device_driver * drv);
	int (*hotplug) (struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size);
	int (*suspend)(struct device * dev, u32 state);
	int (*resume)(struct device * dev);
};

相关函数

  • bus_register()向系统注册这个总线类型
  • bus_create_file()这个就是向 sysfs中创建一个文件

device

  • 系统中的任一设备在设备模型中都由一个device对象描述
  • 通常 device 结构不单独使用,而是包含在更大的结构中作为一个子结构使用,比如描述PCI设备的 struct pci_dev,还有我们ldd_dev,其中的dev域就是一个device对象。

device数据结构

struct device {
	struct list_head g_list;
	struct list_head node;
	struct list_head bus_list;
	struct list_head driver_list;
	struct list_head children;
	struct device *parent;
	struct kobject kobj;
	char bus_id[BUS_ID_SIZE];
	struct bus_type *bus;
	struct device_driver *driver;
	void *driver_data;
/* Several fields omitted */
};
  • g_list将该device对象挂接到全局设备链表中,所有的device对象都包含在devices subsys中,并组织成层次结构
  • Node域将该对象挂接到其兄弟对象的链表中,而bus list则用于将连接到相同总线上的设备组织成链表
  • driver_list则将同一驱动程序管理的所有设备组织为链表
  • children域指向该device对象子对象链表头
  • parent域则指向父对象
  • Device对象还内嵌一个kobject对象,用于引用计数管理并通过它实现设备层次结构
  • Driver域指向管理该设备的驱动程序对象
  • driver data则是提供给驱动程序的数据
  • Bus域描述设备所连接的总线类型

内核提供了相应的函数用于操作 device 对象

  • 其中 device_register()函数将一个新的 device 对象插入设备模型,并自动在/sys/devices 下创建一个对应的目录
  • device_unregister()完成相反的操作,注销设备对象
  • get_device()和put_device()分别增加与减少设备对象的引用计数

driver

  • 系统中的每个驱动程序由一个device_driver对象描述
  • 与device 结构类似,device_driver对象依靠内嵌的kobject对象实现引用计数管理和层次结构组织

device_driver数据结构

struct device_driver {
	char *name; //设备驱动程序的名称
	struct bus_type *bus; //该驱动所管理的设备挂接的总线类型
	struct kobject kobj;//内嵌kobject对象
	struct list_head devices;//该驱动所管理的设备链表头
	int (*probe)(struct device *dev); //指向设备探测函数,用于探测设备是否可以被该驱动程序管理
	int (*remove)(struct device *dev);//用于删除设备的函数
/* some fields omitted*/
}

内核提供类似的函数

  • get_driver()增加引用计数
  • driver_register()用于向设备模型插入新的driver对象,同时在sysfs文件系统中创建对应的目录
  • device_driver()结构还包括几个函数,用于处理热拔插、即插即用和电源管理事件

文件系统

  • sysfs就是利用VFS的接口去读取kobject的层次结构。
  • 实现一种文件系统就要实现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 *); 
	...
}
  • 系统调用流程:open() -> 系统调用 -> sys_open() -> filp_open() ->dentry_open() -> file_operations.open(),不同的文件系统,调用不同的file_operations.open(),在sysfs下就是sysfs_open_file()。

dentry与inode

目录项dentry结构体

struct dentry { 
	struct inode *d_inode; /* Where the name belongs to - NULL is */
	struct dentry *d_parent; /* parent directory */ 
	struct list_head d_child; /* child of parent list */ 
	struct dentry_operations *d_op; 
	struct super_block *d_sb; /* The root of the dentry tree */ 
	void *d_fsdata; /* fs-specific data */ 
	unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* small names */ 
	...... 
};

索引节点inode结构体

struct inode { 
	unsigned long i_ino; /*在同一文件系统中是唯一的*/
						/*内核根据该值,可以计算出对应的inode在介质上的位置*/
						/*在硬盘上,可以计算出对应的inode属于那个块,从而找到对应的inode结构*/
	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; 
	struct inode_operations *i_op; 
	struct file_operations *i_fop; /* former ->i_op->default_file_ops */ 
	struct super_block *i_sb; 
	...... 
};
  • 在Linux内存中,每个文件都有一个dentry(目录项)和inode(索引节点)结构,dentry记录文件名,上级目录等信息,以形成文件树形结构,而inode记录文件的组织和管理信息,以及文件在存储介质上的位置与分布。
  • dentry->d_inode指向相应的inode结构,dentry与inode是多对一的关系,因为有可能一个文件有多个文件名,如硬连接。

磁盘文件

struct ext3_inode { 
	__le16 i_mode; /* File mode */ 
	__le16 i_uid; /* Low 16 bits of Owner Uid */ 
	__le32 i_size; /* Size in bytes */ 
	__le32 i_atime; /* Access time */ 
	__le32 i_ctime; /* Creation time */ 
	__le32 i_mtime; /* Modification time */ 
	__le32 i_dtime; /* Deletion Time */ 
	__le16 i_gid; /* Low 16 bits of Group Id */ 
	__le16 i_links_count; /* Links count */ 
	...... 
	__le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */ 
	...... 
} 
//目录文件中记录文件名与对应的索引节点号
struct ext3_dir_entry_2 { 
	__u32 inode; /* Inode number */ 
	__u16 rec_len; /* Directory entry length */ 
	__u8 name_len; /* Name length */ 
	__u8 file_type; 
	char name[EXT3_NAME_LEN]; /* File name */ 
}; 
le32 i_block[EXT2 N BLOCKS];/* Pointers to blocks */
  • i_block数组指示了文件的内容所存放的地点(在硬盘上的位置)
  • ext3_inode是存放在索引节点区,而exts_entry_2是以文件内容的形式存放在数据区
  • 内核直到inode->i_ino由于ext3——inode大小已知,就可以计算出ext3_inode在索引节点区的位置,从而得到ext3_inode,然后根据i_block就可以知道文件的数据存放地点。
    Linux底层之Sysfs_第3张图片
  • 在进程中打开一个文件,实际就要在内存中建立文件的dentry和inode结构,并与进程结构管理起来
    Linux底层之Sysfs_第4张图片
  • Linux把目录也看成一种文件,里面记录着文件名与索引节点号的对应关系。

superblock

  • super_block 结构是从所有具体的文件系统所抽象出来的一个结构,每一个文件系统实例都会有一对应 super_block 结构。
  • 记录该文件系统实列(分区)的某种描述性信息,如文件系统的文件系统类型、空间大小、磁盘块大小、以及super_operations。
  • 由于sysfs是虚拟的文件系统,并且只能被mount一次,sysfs的super_block结构是sysfs_sb,动态从内存中生成。
  • super_operations是VFS的一个接口,要实现一个文件系统,file_opertations、dentry_operations、inode_operations、super_operations这四个结构体都要实现。

vfsmount

  • 把一个设备安装到一个目录时要用一个vfsmount作为连接件
struct vfsmount { 
	struct list_head mnt_hash; 
	struct vfsmount *mnt_parent; /* fs we are mounted on */ 
	struct dentry *mnt_mountpoint; /* dentry of mountpoint */ 
	struct dentry *mnt_root; /* root of the mounted tree */ 
	struct super_block *mnt_sb; /* pointer to superblock */ 
	.......... 
}
  • 对于某个文件系统,内存中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 结构,继续往下走,就可以找到对应的文件。

你可能感兴趣的:(Linux)