0

Sysfs Note -- 2

 1 year ago
source link: https://diabloneo.github.io//2023/03/17/sysfs-note-2/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

本文的内容对于一个 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 中的属性的名字和代码中的成员名字不一定一致,所以掌握这个代码查找技能是很必要的。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK