本文的内容对于一个 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

首先,了解一下 attributeattribute 是定义用来对应一个 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);
};

这个是设备目录下通用的定义,除了基本的属性外,还定义了 showstore 两个方法属性,顾名思义,一个是用来展示,一个是用来设置的,也就对应到了一个 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_showqueue_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_showqueue_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 中的属性的名字和代码中的成员名字不一定一致,所以掌握这个代码查找技能是很必要的。


知识共享许可协议本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。