Sysfs Note -- 2
本文的内容对于一个 block device 设备的 sysfs 文件,如何找到对应的代码。
kobject 的相关函数
在 kernel 的文档 Documentation/core-api/kobject.rst 中,可以读到 kobject 操作的一些函数,对于阅读内核代码来说,kobject_add
函数是比较重要的,它表示添加了一个目录到 sysfs 下。
int kobject_add(struct kobject *kobj, struct kobject *parent,
const char *fmt, ...);
如何找到一个 block device sysfs 文件对应的操作代码
queue 目录
Sysfs 中的目录对应的是 kobject,该目录下的文件对应的是这个 kobject 的属性。
我们以一个 nvme 硬盘的 queue/discard_max_bytes 文件为例,来找到对应的代码。首先在 block 目录下搜索 kobject_add
的调用,找到将 queue 添加到 sysfs 的地方,在 block/blk-sysfs.c 文件中
/**
* blk_register_queue - register a block layer queue with sysfs
* @disk: Disk of which the request queue should be registered with sysfs.
*/
int blk_register_queue(struct gendisk *disk)
{
struct request_queue *q = disk->queue;
int ret;
mutex_lock(&q->sysfs_dir_lock);
kobject_init(&disk->queue_kobj, &blk_queue_ktype);
ret = kobject_add(&disk->queue_kobj, &disk_to_dev(disk)->kobj, "queue");
if (ret < 0)
goto out_put_queue_kobj;
然后你可以搜索调用 blk_register_queue
的地方,是 device_add_disk
函数,然后再到 nvme 驱动目录下搜索调用 device_add_disk
的地方,最终你会得到一个调用链:
nvme_scan_work
-> nvme_scan_ns_list
-> nvme_scan_ns
-> nvme_alloc_ns
-> device_add_disk
-> blk_register_queue
简单的说,驱动发现一个 nvme 硬盘后,就会将信息添加到内核中,然后会在 sysfs 里创建这个硬盘的下的 queue 目录。
queue/discard_max_bytes 文件
我们以 queue/discard_max_bytes 属性为例,来看看它是如何在代码中实现的。
device_attribute
首先,了解一下 attribute
。 attribute
是定义用来对应一个 sysfs 中的文件,即 kobject 的一个属性的。attribute
单独使用时没有意义,一般需要加上操作函数,所以 block device 自己定义了如下的 device_attribute
( include/linux/device.h ):
struct device_attribute {
struct attribute attr;
ssize_t (*show)(struct device *dev, struct device_attribute *attr,
char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count);
};
这个是设备目录下通用的定义,除了基本的属性外,还定义了 show
和 store
两个方法属性,顾名思义,一个是用来展示,一个是用来设置的,也就对应到了一个 sysfs 文件的读写操作。
discard_max_bytes 属性
找代码的方式是在 block/ 目录下搜索 discard_max_bytes,你就可以找到很多相关的代码。下面这行代码说明这个是 queue/ 目录下的一个 RW 属性:
QUEUE_RW_ENTRY(queue_discard_max, "discard_max_bytes");
QUEUE_RW_ENTRY 这个宏的定义如下:
#define QUEUE_RW_ENTRY(_prefix, _name) \
static struct queue_sysfs_entry _prefix##_entry = { \
.attr = { .name = _name, .mode = 0644 }, \
.show = _prefix##_show, \
.store = _prefix##_store, \
};
可以看到注册的函数名字应该是 queue_discard_max_show
和 queue_discard_max_store
。
属性的 show
函数是被 queue_attr_show
函数使用的,store
函数则是被 queue_attr_store
函数使用:
static ssize_t
queue_attr_show(struct kobject *kobj, struct attribute *attr, char *page)
{
struct queue_sysfs_entry *entry = to_queue(attr);
struct gendisk *disk = container_of(kobj, struct gendisk, queue_kobj);
struct request_queue *q = disk->queue;
ssize_t res;
if (!entry->show)
return -EIO;
mutex_lock(&q->sysfs_lock);
res = entry->show(q, page); /* 这行是调用各个属性 show 函数的地方 */
mutex_unlock(&q->sysfs_lock);
return res;
}
static ssize_t
queue_attr_store(struct kobject *kobj, struct attribute *attr,
const char *page, size_t length)
{
struct queue_sysfs_entry *entry = to_queue(attr);
struct gendisk *disk = container_of(kobj, struct gendisk, queue_kobj);
struct request_queue *q = disk->queue;
ssize_t res;
if (!entry->store)
return -EIO;
mutex_lock(&q->sysfs_lock);
res = entry->store(q, page, length); /* 这行是调用各个属性 store 函数的地方 */
mutex_unlock(&q->sysfs_lock);
return res;
}
回过头来看 queue_discard_max_show
和 queue_discard_max_store
。
show
函数实现比较简单,就是将内容打印到提供的字符数组里:
static ssize_t queue_discard_max_show(struct request_queue *q, char *page)
{
return sprintf(page, "%llu\n",
(unsigned long long)q->limits.max_discard_sectors << 9);
}
store
函数比较长一点,但是逻辑也简单:
static ssize_t queue_discard_max_store(struct request_queue *q,
const char *page, size_t count)
{
unsigned long max_discard;
ssize_t ret = queue_var_store(&max_discard, page, count);
if (ret < 0)
return ret;
if (max_discard & (q->limits.discard_granularity - 1))
return -EINVAL;
max_discard >>= 9; /* 512 bytes 一个 sector */
if (max_discard > UINT_MAX)
return -EINVAL;
if (max_discard > q->limits.max_hw_discard_sectors)
max_discard = q->limits.max_hw_discard_sectors;
/*
* 上面都是关于数据合法性的判断,这里做了设置
* 从这里就可以知道,后面要知道设置的 discard_max_bytes 如何使用,
* 要在代码里搜索 max_discard_sectors
*/
q->limits.max_discard_sectors = max_discard;
return ret;
}
Summary
在 kernel 中查找 sysfs 的对应代码还是比较容易的,代码的接口都很规范,而且容易查找。 但是可以发现,sysfs 中的属性的名字和代码中的成员名字不一定一致,所以掌握这个代码查找技能是很必要的。