The Linux kernel provides a virtual file system called sysfs. By providing virtual files, sysfs is able to export information about various kernel sub-systems, hardware devices and associated device drivers from the kernels device model to user space. To further explore sysfs, dive deep into this article.
There is a need to provide information related to each process, to the user space, which can then be used by programs such as ps. The /proc file system was created for this purpose. Through the proc file system, each process has its directory in the /proc folder. It was originally designed to provide process related information to user space. Adding directories and files to the /proc file system is easier; so, many kernel sub-systems started using this file system for displaying information to the user space. It is also used to take inputs from the user space to control settings inside the kernel modules. But the /proc file system is getting cluttered with lots of non-process related information.
From the Linux 2.5 development cycle, a new interface called the /sys file system has been introduced. Sysfs is a RAM based file system. It is designed to export the kernel data structures and their attributes from the kernel to the user space, which then avoids cluttering the /proc file system.
The advantages of sysfs over procfs are as follows:
- A cleaner, well-documented programming interface
- Automatic clean-up of directories and files, when the device is removed from the system
- The enforced one item per file rule, which makes for a cleaner user interface
The one item per file rule mandates that in each file of sysfs, there will be only one value that can be put in or read from it. This feature really makes it a cleaner interface. Through sysfs, user space programs can get information from the kernel sub-system like device drivers. Programs can also send values to the kernel sub-system and can control the internal settings. This is how programs talk to the Linux kernel.
Sysfs mounting
By default, sysfs is compiled in the Linux kernel. It is dependent on CONFIG_SYSFS being enabled in the kernel configuration. If sysfs is not already mounted, then you can do so by using the following command:
mount -t sysfs sysfs /sys
Linux Kernel objects
Folders in each sysfs directory are represented by kernel objects (kobjects) in Linux. These are represented in the kernel through the struct kobject (defined in include/linux/kobject.h). The important members of the struct kobject are given below.
char *name: This is the name of the kobject. Folders corresponding to the current kobject are created with this name in sysfs.
Struct kobject *parent: This is the parent of the current kobject being created. When a folder is created in sysfs, if this field is present, then the current kobject folder is created inside the parent kobject folder.
Struct kref: This is the reference counting mechanism for kobject. Whenever any kernel module refers to any kobject, its reference count is incremented, and whenever any kobject reference is released by any kernel module, then the reference count is decremented. When the reference count decrements to zero, memory related to the kobject is released.
Directory operations
The following are the interfaces that are used for directory related operations in sysfs:
int sysfs_create_dir(struct kobject *kobj); void sysfs_remove_dir(struct kobject *kobj); int sysfs_rename_dir(struct kobject *kobj. const char *new_name);
Sysfs directory creation
The sysfs_create_dir interface is used to create a directory in sysfs. The struct kobject parameter passed to it has two fields in it.
kobj->name: This is the name of the directory that is to be created in sysfs.
kobj->parent: This is the kobject pointer of the parent directory in which the current directory is to be created.
Since each directory in sysfs has a struct kobject associated with it, when the new directory is to be created in sysfs, the struct kobject pointer that is passed to sysfs_create_dir should have the parent kobject pointer pointing to the kobject of the directory in which the current directory is to be created. Then the name parameter of the kobject should be filled, with the proper name representing the functionality for which the directory is being created.
But the above interfaces are not directly used to create directories in sysfs when writing kernel modules; instead, the following interface is used:
struct kobject * kobject_create_and_add(const char *name, struct kobject *parent);
where,
name: is the name of the directory to be created in sysfs, and
parent: is the kobject of the parent directory in which the current directory is to be created.
The return value of the kobject_create_and_add is the kobject pointer of the created directory, and if it fails to create a directory then a NULL pointer is returned.
Kobject parent pointer: Since each folder in the sysfs file system is represented using a struct kobject, to create a new directory in sysfs, the parent pointer is to be initialised with the kobject of the directory in which the current directory is to be created. For example, to create a directory named example_dev in /sys/kernel, the following function call is used:
struct kobject *example_dev_kob; example_dev_kobj = kobject_create_and_add(example_dev, kernel_kobj);
where, kernel_kobj is the kobject pointer of the kernel directory in sysfs. So in order to create a directory in /sys/firmware, the firmware_kobj pointer will be used.
In order to create the directory named example_dev directly in /sys, the following function call is used:
kobject_create_and_add(example_dev, NULL);
so if the parent pointer is kept NULL, then the example_dev directory will be created in the /sys folder.
Sysfs directory removal
The sysfs_remove_dir interface is used to remove the directory from the sysfs file system. The parameter that is passed to the sysfs_remove_dir is the kobject pointer of the directory which is to be removed. The kobject pointer should be used to create the directory. Here, too, the same concept applies. Instead of directly calling this interface to remove the directory, the following kernel kobject interface is used, which internally calls the sysfs remove interface:
void kobject_del(struct kobject *kobj);
This interface will delete the directory of sysfs represented by the kobject pointer passed to it.
For example, to delete the example_dev directory that was created as an example, the following function call is used:
kobject_del(example_dev_kobj);
Sysfs directory renaming
In case there is a need to rename the directory created in sysfs, the following functions are available:
int sysfs_rename_dir(struct kobject *kobj, const char *new_name)
where, kobj is the kobject pointer corresponding to the directory that has to be renamed, and new_name is to be given to the directory. Since the sysfs interface is normally not directly called, the following kobject interface is called for the renaming purpose:
int kobject_rename(struct kobject *kobj, const char *new_name)
where,
kobject: This is the kobject pointer of the directory to be renamed.
new_name: This is the new name to be given to the directory.
Kobject reference counting
Each kobject has a member struct kref, which is a reference counter. Whenever any module wishes to use the kobject already created, this reference count is incremented, and whenever that module wishes to stop using kobject, the reference count is decremented to maintain the number of modules using the kobject or the sysfs directory represented by the kobject. Whenever the reference count reaches 0, the memory related to the kobject which was allotted by kobject_create_and_add is released, and whenever a kobject is created using kobject_create_and_add, it internally initialises the kref reference counter by one. This indicates the kobject is being used by the current module, and then kobject_create_and_add internally creates the directory for the kobject. Whenever any new module wishes to use the directory represented by that kobject, the reference counter has to be incremented.
The reference count is maintained in the kobject using the following functions:
struct kobject* kobject_get(struct kobject *kobj); struct kobject* kobject_put(struct kobject *kobj);
kobject_get increments the reference count of the kobject passed to it, whereas kobject_put decrements the reference count of the kobject passed to it. So while writing drivers or any kernel module, whenever any kobject of the other module is directly used, kobject_get should be called to increment the reference count of the kobject. When the use of the kobject is finished, kobject_put should be called to decrement its reference count.
Whenever kobject_put is called, passing the kobject pointer to it, it decrements the reference count. When the reference count reaches zero, it frees the memory of the kobject pointer, and the kobject pointer can no longer be used.
Files in sysfs
In the above example, we have created the example_dev directory in the /sys/kernel folder. Suppose the example had some attribute called example_info which we want to expose to the user space using sysfs, then the feature used is called sysfs attributes.
Attributes are represented as regular files in sysfs with one value per file. Attributes are of the type struct attribute (defined in include/linux/sysfs.h). Struct attribute has the following two important pieces of information.
name: This is the name of the attribute(file) to be created.
mode: This is the permission with which the file is to be created. A simple attribute has no means by which it can be read or written; it needs wrapper routines for reading and writing. For this purpose, kobject defines a special structure called struct kobj_attribute (defined in include/linux/kobject.h) as follows:
struct kobj_attribute { struct attribute attr; ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr, char *buf); ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count); };
where, attr is the attribute representing the file to be created, show is the pointer to the function that will be called when the file is read in sysfs, and store is the pointer to the function which will be called when the file is written in sysfs.
Suppose we need to create our example_info file in the /sysfs/kernel/example_dev directory that was created as an example; then the following method is the way to define the attribute:
struct kobj_attribute example_info_attr = __ATTR(example_info, 0666, example_show, example_store); where __ATTR is the macro, as shown below: __ATTR(name, permission, show_ptr, store_ptr)
where, name is the name with which the attribute is to be created, permission is file permission where 0666 is read and write permission, show_ptr is the function pointer called when the file is read, and store_ptr is the function pointer called when the file is written.
The example_attr defined above is then grouped in the struct attribute group as follows:
struct attribute *example_attrs[] = { &example_attr.attr, NULL, };
Note: In the struct attribute, multiple attributes can be grouped together and then be created in one single API call. Also, NULL termination is compulsory for the group.
The above attributes are then given to the attribute group as follows:
struct attribute_group example_attr_group = { .attrs = example_attrs, };
To create attributes, the following sysfs API is used:
int sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp);
where,
kobj: This is the pointer to the kobject of the directory in which the file is to be created.
grp: This is the group of attributes to be created, which will be created as files in sysfs.
To create the example_info file in our /sys/kernel/example_dev directory, the following sysfs API is called:
sysfs_create_group(example_dev_kobj,&example_attr_group);
After this function, a file named example_info will be created in the sys/kernel/example_dev directory.
Now, whenever the file is read, the example_show function will be called and whenever the file is written, the example_store function will be called, which needs to be defined. The function prototype for the show and store function is as follows:
ssize_t (*show)(struct kobject * kobj, struct attribute * attr, char * buff);
where,
kobj: This is the pointer to the kobject of the directory which was passed when the file was created.
attr: This is the attribute pointer of the file which was passed when file was created.
buff: This is the buffer in which the output should be written, which will be displayed as the result in user space. The value written should not exceed the size defined by the PAGE_SIZE macro.
ssize_t (*store)(struct kobject * kobj, struct attribute * attr, const char * buff, size_t size);
where, kobject and attr are as described above.
buff: This buffer contains the value that was written to the file from user space.
size: This is the size of the data written to the file through user space.
So our example_show function can be defined as follows:
ssize_t example_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, example show); /*Here any value of variable can be written and exposed to user space through this interface also return value is the size of data written to the buffer*/ }
and an example store can be written as follows:
static ssize_t example_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { //here the value from the buff can be read and //respective action can be taken by module return count; //return the number of bytes read, if all the //bytes are read the count parameter which //was received by store routine }
Driver sysfs interface
Drivers are modules written to interface user space with hardware devices. Hardware devices have information, which the user needs to know during their operation. So drivers generally create sysfs files for providing their information to user space.
Drivers don’t explicitly create directories in sysfs. Whenever drivers register with their sub-system, directories are internally created for the driver. Each driver has access to the struct device pointer for the device for which the driver is being written. This device pointer is used to create files in the driver interface.
Drivers create one file for each piece of device information that is to be exposed to the user space through sysfs. Drivers use a special API for file creation in the user space, and this is as follows:
int device_create_file(struct device *dev, const struct device_attribute *attr);
where,
dev: This is the pointer to the device structure of the driver module.
attr: This is the attribute which is to be created.
For removal of the file from the sysfs directory, the following interface can be used:
void device_remove_file (struct device *dev, const struct device_attribute *attr)
Sysfs object relationship
Lets suppose there is the directory /sys/kernel/example1, the kobject pointer of which is ex1_kobj and there is a need to create a link file in the /sys/kernel/example2 directory named ex2, which has to show the relationship with the example1 directory. This can be accomplished by symbolic links in sysfs. Also, the kobject pointer of the example2 directory is ex2_kobj.
Symbolic links are created in sysfs through the following interface:
int sysfs_create_link(struct kobject *kobj, struct kobject *target, char *name);
where,
kobj: This is the pointer to the kobject that represents the directory in which the link is to be created.
target: This is the target directory to which the link is to be pointed.
name: This is the name with which the link is to be created.
For our example, the link is created as follows:
sysfs_create_link(ex1_kobj, ex2_kobj, ex2);
To delete symbolic links, the following interface is available:
void sysfs_remove_link(struct kobject *kobj, char *name);
In our example, to delete the link, the following function is called:
sysfs_remove_link(ex2_kobj, ex2);
References
[1] https://www.kernel.org/doc/Documentation/filesystems/sysfs.txt
[2] http://lxr.missinglinkelectronics.com/linux/drivdri/rtc/rtc-ds1343.c
[3] https://www.kernel.org/pub/linux/kernel/pepepe/mochel/doc/papers/ols-2005/mochel.pdf