Как я могу получить загрузочный аргумент 'root' в пространстве ядра? - PullRequest
0 голосов
/ 27 марта 2020

Я пишу драйвер, и мне нужно знать, какой диск / раздел содержит файловую систему root. Это можно просмотреть в пользовательском пространстве, используя:

cat /proc/cmdline 
root=/dev/mmcblk0p1

Как я могу получить значение root в пространстве ядра?

1 Ответ

2 голосов
/ 29 марта 2020

Чтобы получить dev_t для устройства, на котором смонтирована ваша файловая система root, вы можете использовать стратегию, аналогичную той, которую выполняет системный вызов stat(). Единственным исключением является то, что вы не работаете с __user буферами, поэтому вам нужно будет использовать немного другие API.

Короче говоря, вы хотите:

  1. Call kern_path(), чтобы получить struct path для пути "/".
  2. Вызов vfs_getattr(), проходящий этот путь, чтобы получить struct kstat.
  3. Проверьте ->dev член struct kstat, который является устройством root (dev_t).
  4. Если вы хотите, Вы также можете использовать bdget(), чтобы найти блочное устройство, соответствующее полученному dev_t, затем используйте bdevname(), чтобы получить его имя (например, sda1).

Это означает следующее:

struct path root_path;
struct kstat root_stat;
struct block_device *root_device;
char root_device_name[BDEVNAME_SIZE];

kern_path("/", 0, &root_path);

// KERNEL > 4.10
// vfs_getattr(&root_path, &root_stat, STATX_BASIC_STATS, AT_NO_AUTOMOUNT|AT_SYMLINK_NOFOLLOW);

// KERNEL <= 4.10
vfs_getattr(&root_path, &root_stat);

pr_info("root device number is 0x%08x; major = %d, minor = %d\n", root_stat.dev, MAJOR(root_stat.dev), MINOR(root_stat.dev));

root_device = bdget(root_stat.dev);
bdevname(root_device, root_device_name);

pr_info("root device name: %s, path: /dev/%s\n", root_device_name, root_device_name);

bdput(root_device);
path_put(&root_path);

Некоторые примечания:

  1. Я не делал ничего проверка ошибок в приведенном выше примере, но вы, безусловно, должны! См. Полный пример ниже.

  2. kern_path(), кажется, принимает LOOKUP_* флаги, определенные в linux/namei.h в качестве второго аргумента, но передача значения по умолчанию 0 также приемлемо.

  3. Аналогично, vfs_getattr() принимает STATX_* флагов, определенных в linux/stat.h в качестве третьего аргумента. Системный вызов stat() проходит STATX_BASIC_STATS, но передача 0 здесь тоже должна быть в порядке, поскольку нам не нужно знать ничего, кроме устройства (->dev field), которое кажется для заполнения независимо от флагов. Я не смог проверить это, хотя мое ядро ​​<= 4.10, и эти флаги необходимы только для ядра> 4.10.

Рабочий пример

Вот полный пример работающий модуль ядра, который выполняет вышеупомянутое, также применяя надлежащую проверку ошибок.

// SPDX-License-Identifier: GPL-3.0
#include <linux/kernel.h>     // printk(), pr_*()
#include <linux/module.h>     // THIS_MODULE, MODULE_VERSION, ...
#include <linux/init.h>       // module_{init,exit}
#include <linux/types.h>      // dev_t
#include <linux/kdev_t.h>     // MAJOR(), MINOR(), MKDEV()
#include <linux/path.h>       // struct path
#include <linux/namei.h>      // kern_path(), path_put()
#include <linux/stat.h>       // struct kstat, STATX_*
#include <linux/fs.h>         // vfs_getattr(), struct block_device, bdget(),...
#include <uapi/linux/fcntl.h> // AT_*

#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

static int __init findrootdev_init(void)
{
    struct path root_path;
    struct kstat root_stat;
    struct block_device *root_device;
    char root_device_name[BDEVNAME_SIZE];
    int err = 0;

    pr_info("init\n");

    err = kern_path("/", 0, &root_path);
    if (err) {
        pr_err("kern_path error %d\n", err);
        goto kern_path_fail;
    }

    // KERNEL > 4.10
    // err = vfs_getattr(&root_path, &root_stat, STATX_BASIC_STATS,
    //        AT_NO_AUTOMOUNT|AT_SYMLINK_NOFOLLOW);

    // KERNEL <= 4.10
    err = vfs_getattr(&root_path, &root_stat);

    if (err) {
        pr_err("vfs_getattr error %d\n", err);
        goto vfs_getattr_fail;
    }

    pr_info("root device number is 0x%08x; major = %d, minor = %d\n",
        root_stat.dev, MAJOR(root_stat.dev), MINOR(root_stat.dev));

    root_device = bdget(root_stat.dev);
    if (!root_device) {
        pr_err("bdget failed\n");
        err = -1;
        goto bdget_fail;
    }

    if (!bdevname(root_device, root_device_name)) {
        pr_err("bdevname failed\n");
        err = -1;
        goto bdevname_fail;
    }

    pr_info("root device name: %s, path: /dev/%s\n",
        root_device_name, root_device_name);

bdevname_fail:
    bdput(root_device);

bdget_fail:
vfs_getattr_fail:
    path_put(&root_path);

kern_path_fail:
    return err;
}

static void __exit findrootdev_exit(void)
{
    // This function is only needed to be able to unload the module.
    pr_info("exit\n");
}

module_init(findrootdev_init);
module_exit(findrootdev_exit);
MODULE_VERSION("0.1");
MODULE_DESCRIPTION("Silly test module to find the root device and its name.");
MODULE_AUTHOR("Marco Bonelli");
MODULE_LICENSE("GPL");

Компиляция и загрузка вышеупомянутого модуля с insmod генерирует этот вывод в dmesg:

[12664.685699] findrootdev: init
[12664.685703] findrootdev: root device number is 0x00800003; major = 8, minor = 3
[12664.685706] findrootdev: root device name: sda3, path: /dev/sda3
[12671.671038] findrootdev: exit
...