ramdisk構成、解凍、rootfsの作成、簡単な分析の開始

1. ramdisk機能をオンにする方法は?

ramdisk関数を使用する場合は、2つの手順を実行する必要があります。1つはカーネルのbootargsを変更し、rdinitオプションを追加することです。もう1つは、uImageのコンパイル時にrootfs.cpioを埋め込むことです。

以下は、ramdiskを使用して起動する構成とeMMCを起動メディアとして使用する構成の2つです。ramdiskはrdinitオプションを指定する必要があり、ルートデバイスは/ dev / ram0になります。

bootargs = "console=ttyS0,115200 rdinit=/sbin/init root=/dev/ram0 quiet";
bootargs = "console=ttyS0,115200 root=/dev/mmcblk1p2 rw rootfstype=ext4  rootflags=data=journal,barrier=1  rootwait";

rootfs.cpioをカーネルイメージに埋め込む必要があります。カーネルイメージは、buildrootを介して構成できます。

 

config BR2_TARGET_ROOTFS_INITRAMFS
    bool "initial RAM filesystem linked into linux kernel"
    depends on BR2_LINUX_KERNEL
    select BR2_TARGET_ROOTFS_CPIO
    help
      Integrate the root filesystem generated by Buildroot as an
      initramfs inside the kernel image. This integration will
      take place automatically.

      A rootfs.cpio file will be generated in the images/ directory.
      This is the archive that will be included in the kernel image.
      The default rootfs compression set in the kernel configuration
      is used, regardless of how buildroot's cpio archive is configured.

      Note that enabling initramfs together with another filesystem
      formats doesn't make sense: you would end up having two
      identical root filesystems, one embedded inside the kernel
      image, and one separately.

 

カーネルをコンパイルするときに、次のコンパイルオプションを使用することもできます。

make uImage -j16 CONFIG_BLK_DEV_INITRD=y CONFIG_INITRAMFS_SOURCE="${BR_BINARIES_DIR}/rootfs.cpio" KCPPFLAGS=-DCONFIG_BLK_DEV_INITRD

カーネルでrdinitとrootがどのように処理されるかを見てみましょう。rdinitとrootがbootargsに設定されている場合、カーネルは起動フェーズでramdisk_execute_commandとsaved_root_nameをそれぞれramdisk_execute_commandとsaved_root_nameに解析して割り当てます。

これらの2つの重要なパラメーターは、後のカーネル起動プロセスの分析で使用されます。

 

static int __init rdinit_setup(char *str)
{
    unsigned int i;

    ramdisk_execute_command = str;--------------------------------此例中ramdisk_execute_command对应/sbin/init。
    /* See "auto" comment in init_setup */
    for (i = 1; i < MAX_INIT_ARGS; i++)
        argv_init[i] = NULL;
    return 1;
}
__setup("rdinit=", rdinit_setup);

static int __init root_dev_setup(char *line)
{
    strlcpy(saved_root_name, line, sizeof(saved_root_name));------saved_root_name对应/dev/ram0。
    return 1;
}

__setup("root=", root_dev_setup);

 

 

2. RAMディスクはどこに保存されますか?

vmlinux.lds.hファイルから、ramfsがCONFIG_BLK_DEV_INITRD定義に従って使用されていることがわかります。

INIT_RAM_FSは、2つのsections.init.ramfsと.init.ramfs.infoを含むramfs関連のコンテンツを格納します。

 

SECTIONS
{
    . = PAGE_OFFSET + PHYS_OFFSET_OFFSET;

    _stext = .;
    __init_begin = .;
...
    INIT_DATA_SECTION(PAGE_SIZE)
...
    . = ALIGN(PAGE_SIZE);
    __init_end = .;------------------------------从__init_begin到__init_end部分的空间会在free_initmem()中被释放。

    .text : AT(ADDR(.text) - LOAD_OFFSET) {
...
    } = 0
    _etext = .;
...
}

#define INIT_DATA_SECTION(initsetup_align)                \
    .init.data : AT(ADDR(.init.data) - LOAD_OFFSET) {        \
...
        INIT_RAM_FS                        \
    }

#ifdef CONFIG_BLK_DEV_INITRD
#define INIT_RAM_FS                            \
    . = ALIGN(4);                            \
    VMLINUX_SYMBOL(__initramfs_start) = .;                \
    KEEP(*(.init.ramfs))                        \
    . = ALIGN(8);                            \
    KEEP(*(.init.ramfs.info))
#else
#define INIT_RAM_FS
#endif

 

.init.ramfsと.init.ramfs.infoの2つのセクションは、initramfs_data.Sで定義されています。

 

.section .init.ramfs,"a"
__irf_start:
.incbin __stringify(INITRAMFS_IMAGE)------------------原封不动的将INITRAMFS_IMAGE对应的二进制文件编译到当前文件中。
__irf_end:
.section .init.ramfs.info,"a"
.globl VMLINUX_SYMBOL(__initramfs_size)
VMLINUX_SYMBOL(__initramfs_size):
#ifdef CONFIG_64BIT
    .quad __irf_end - __irf_start
#else
    .long __irf_end - __irf_start
#endif

 

INITRAMFS_IMAGEはどこから来たのですか?/ usr /ディレクトリのMakefileを確認する必要があります。

Makefileから、CONFIG_INITRAMFS_SOURCEに対応するrootfs.cpioファイルが入力として使用され、gen_init_cpioとgen_initramfs_list.shが呼び出されてinitramfs_data.cpio.gzファイルが生成されていることがわかります。

その場合、INITRAMFS_IMAGEは/usr/initramfs_data.cpio$(suffix_y)ファイルに対応します。

最後に、INITRAMFS_IMAGEは、.init.ramfsセクションに対応する.incbinを介してinitramfs_data.oファイルにコンパイルされます。

 

800308cc T __security_initcall_start
800308d0 T __initramfs_start
800308d0 t __irf_start---------------------------ramfs区域起始地址。
800308d0 T __security_initcall_end
814ed9c0 T __initramfs_size----------------------ramfs文件大小。
814ed9c0 t __irf_end-----------------------------ramfs区域结束地址。
814ee000 T __init_end

 

 

3. ramdiskを起動する方法は?

initデータの一部として、ramfsは__init_beginと__init_endの最後にあり、free_initmem()でリリースされます。

ramfsは__initramfs_startと__initramfs_sizeの間に圧縮パッケージの形式で格納され、kernel_init()-> kernel_init_freeable()-> do_basic_setup()-> populate_rootfs()でunpack_to_rootfs()を呼び出すことによって解凍されます。

 

kernel_init()
  -->kernel_init_freeable()-------------------------------在执行完do_basic_setup(),即完成各种initcall之后,判断ramdisk_execute_command命令。
  -->free_initmem()---------------------------------------释放__init_begin到__init_end之间的内存。
  -->do_basic_setup()
    -->populate_rootfs()---------------------------------解压__initramfs_start包含的ramdisk到rootfs中。
  -->run_init_process(ramdisk_execute_command)------------执行ramdisk_execute_command命令替代当前进程。

 

 

3.1initrd_startとinitrd_endの分析

start_kernel()の前に、initrdおよびroot関連のパラメーターがdtsから解析されます。

调用early_init_dt_scan()-> Early_init_dt_scan_nodes-> Early_init_dt_scan_nodes():

 

void __init early_init_dt_scan_nodes(void)
{
    /* Retrieve various information from the /chosen node */
    of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
...
}

int __init early_init_dt_scan_chosen(unsigned long node, const char *uname,
                     int depth, void *data)
{
...
    early_init_dt_check_for_initrd(node);
...
}

static void __init early_init_dt_check_for_initrd(unsigned long node)
{
    u64 start, end;
    int len;
    const __be32 *prop;

    pr_debug("Looking for initrd properties... ");

    prop = of_get_flat_dt_prop(node, "linux,initrd-start", &len);
    if (!prop)
        return;
    start = of_read_number(prop, len/4);

    prop = of_get_flat_dt_prop(node, "linux,initrd-end", &len);
    if (!prop)
        return;
    end = of_read_number(prop, len/4);

    __early_init_dt_declare_initrd(start, end);

    pr_debug("initrd_start=0x%llx  initrd_end=0x%llx\n",
         (unsigned long long)start, (unsigned long long)end);
}

 

initrd_startとinitrd_endに関しては、early_init_dt_check_for_initrd()から、「linux、initrd-start」と「linux、initrd-end」がdtsに設定されていない場合、initrd_startとinitrd_endの2つのパラメーターが元の値であることがわかります。 0の。

 

#ifdef CONFIG_BLK_DEV_INITRD
#ifndef __early_init_dt_declare_initrd
static void __early_init_dt_declare_initrd(unsigned long start,
                       unsigned long end)
{
    initrd_start = (unsigned long)__va(start);
    initrd_end = (unsigned long)__va(end);
    initrd_below_start_ok = 1;
}
#endif

 

 

3.2rootfsおよびramfsファイルシステム

Rootfsは実際には実際のファイルシステムではなく、実際の状況に応じてramfsまたはtmpfsを使用する場合があります。

これは、rootfsがramfsにどのように対応するかを分析し、ramfsの簡単な紹介です。

3.2.1rootfsファイルシステム

start_kernel()-> vfs_caches_init()-> mnt_init()で、rootfsタイプのファイルシステムを登録します。

 

void __init mnt_init(void)
{
...
    fs_kobj = kobject_create_and_add("fs", NULL);
    if (!fs_kobj)
        printk(KERN_WARNING "%s: kobj create error\n", __func__);
    init_rootfs();
    init_mount_tree();
}

int __init init_rootfs(void)
{
    int err = register_filesystem(&rootfs_fs_type);

    if (err)
        return err;

    if (IS_ENABLED(CONFIG_TMPFS) && !saved_root_name[0] &&
        (!root_fs_names || strstr(root_fs_names, "tmpfs"))) {---------没有指定saved_root_name并且root_fs_names为tmpfs时候,初始化tmpfs文件系统。
        err = shmem_init();-------------------------------------------初始化tmpfs文件系统。
        is_tmpfs = true;----------------------------------------------后面rootfs_mount()会需要判断是使用tmpfs还是ramfs作为文件系统类型。
    } else {
        err = init_ramfs_fs();----------------------------------------初始化ramfs文件系统。
    }
...
}

static void __init init_mount_tree(void)
{
    struct vfsmount *mnt;
    struct mnt_namespace *ns;
    struct path root;
    struct file_system_type *type;

    type = get_fs_type("rootfs");-------------------------------------获取rootfs对应的file_system_type,这里对应的是ramfs操作函数。
    if (!type)
        panic("Can't find rootfs type");
    mnt = vfs_kern_mount(type, 0, "rootfs", NULL);--------------------这里会调用mount_fs(),进而调用rootfs_fs_type->mount(),即rootfs_mount()。
    put_filesystem(type);
    if (IS_ERR(mnt))
        panic("Can't create rootfs");

    ns = create_mnt_ns(mnt);
    if (IS_ERR(ns))
        panic("Can't allocate initial namespace");

    init_task.nsproxy->mnt_ns = ns;
    get_mnt_ns(ns);

    root.mnt = mnt;
    root.dentry = mnt->mnt_root;
    mnt->mnt_flags |= MNT_LOCKED;

    set_fs_pwd(current->fs, &root);
    set_fs_root(current->fs, &root);
}

 

rootfsファイルシステムがどのようにマウントされているかを見てみましょう。Rootfsには独自の固定タイプがないか、ramfsまたはtmpfsを使用します。

 

static bool is_tmpfs;
static struct dentry *rootfs_mount(struct file_system_type *fs_type,
    int flags, const char *dev_name, void *data)
{
    static unsigned long once;
    void *fill = ramfs_fill_super;

if (test_and_set_bit(0, &once))
        return ERR_PTR(-ENODEV);

    if (IS_ENABLED(CONFIG_TMPFS) && is_tmpfs)
        fill = shmem_fill_super;

    return mount_nodev(fs_type, flags, data, fill);--------------这里的fill究竟用的是ramfs还是tmpfs,在init_roofs()中已经决定。
}

static struct file_system_type rootfs_fs_type = {
    .name        = "rootfs",
    .mount        = rootfs_mount,
    .kill_sb    = kill_litter_super,
};

struct dentry *mount_nodev(struct file_system_type *fs_type,
    int flags, void *data,
    int (*fill_super)(struct super_block *, void *, int))
{
    int error;
    struct super_block *s = sget(fs_type, NULL, set_anon_super, flags, NULL);

if (IS_ERR(s))
        return ERR_CAST(s);

    error = fill_super(s, data, flags & MS_SILENT ? 1 : 0);------调用ramfs_fill_super()或者shmem_fill_super()。
    if (error) {
        deactivate_locked_super(s);
        return ERR_PTR(error);
    }
    s->s_flags |= MS_ACTIVE;
    return dget(s->s_root);
}

int ramfs_fill_super(struct super_block *sb, void *data, int silent)
{
    struct ramfs_fs_info *fsi;
    struct inode *inode;
    int err;

    save_mount_options(sb, data);

    fsi = kzalloc(sizeof(struct ramfs_fs_info), GFP_KERNEL);
    sb->s_fs_info = fsi;
    if (!fsi)
        return -ENOMEM;

    err = ramfs_parse_options(data, &fsi->mount_opts);
    if (err)
        return err;

    sb->s_maxbytes        = MAX_LFS_FILESIZE;
    sb->s_blocksize        = PAGE_SIZE;
    sb->s_blocksize_bits    = PAGE_SHIFT;
    sb->s_magic        = RAMFS_MAGIC;
    sb->s_op        = &ramfs_ops;--------------------------rootfs最终使用的还是ramfs文件系统类型的操作函数,如果是tmpfs则使用shmem_ops。
    sb->s_time_gran        = 1;

    inode = ramfs_get_inode(sb, NULL, S_IFDIR | fsi->mount_opts.mode, 0);
    sb->s_root = d_make_root(inode);-----------------------创建根节点"/"。
if (!sb->s_root)
        return -ENOMEM;

    return 0;
}

struct dentry *d_make_root(struct inode *root_inode)
{
    struct dentry *res = NULL;
if (root_inode) {
        res = __d_alloc(root_inode->i_sb, NULL);-----------在name参数为NULL的时候,即创建根节点"/"。
        if (res)
            d_instantiate(res, root_inode);
        else
            iput(root_inode);
    }
return res;
}

 

要約すると、カーネルが起動すると、init_rootfs()は最初にパラメーターに従ってtmpfsとramfsのどちらを使用するかを決定し、次にinit_mount_tree()にマウントします。

3.2.2ramfsファイルシステム

ramfsは、要求されたモードタイプに応じて、適切なiノードまたはファイル操作タイプを選択します。

 

struct inode *ramfs_get_inode(struct super_block *sb,
                const struct inode *dir, umode_t mode, dev_t dev)
{
    struct inode * inode = new_inode(sb);

    printk("lubaoquan %s line=%d\n", __func__, __LINE__);
    if (inode) {
        inode->i_ino = get_next_ino();
        inode_init_owner(inode, dir, mode);
        inode->i_mapping->a_ops = &ramfs_aops;
        mapping_set_gfp_mask(inode->i_mapping, GFP_HIGHUSER);
        mapping_set_unevictable(inode->i_mapping);
        inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode);
        switch (mode & S_IFMT) {
        default:
            init_special_inode(inode, mode, dev);---------------------处理char、block、pipefifo等类型的文件。
            break;
        case S_IFREG:-------------------------------------------------处理普通文件。
            inode->i_op = &ramfs_file_inode_operations;
            inode->i_fop = &ramfs_file_operations;
            break;
        case S_IFDIR:-------------------------------------------------处理目录。
            inode->i_op = &ramfs_dir_inode_operations;
            inode->i_fop = &simple_dir_operations;

            /* directory inodes start off with i_nlink == 2 (for "." entry) */
            inc_nlink(inode);
            break;
        case S_IFLNK:-------------------------------------------------处理link文件。
            inode->i_op = &page_symlink_inode_operations;
            inode_nohighmem(inode);
            break;
        }
    }
    return inode;
}

void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
    inode->i_mode = mode;
    if (S_ISCHR(mode)) {
        inode->i_fop = &def_chr_fops;
        inode->i_rdev = rdev;
    } else if (S_ISBLK(mode)) {
        inode->i_fop = &def_blk_fops;
        inode->i_rdev = rdev;
    } else if (S_ISFIFO(mode))
        inode->i_fop = &pipefifo_fops;
    else if (S_ISSOCK(mode))
        ;    /* leave it no_open_fops */
    else
        printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o) for"
                  " inode %s:%lu\n", mode, inode->i_sb->s_id,
                  inode->i_ino);
}

const struct file_operations ramfs_file_operations = {
    .read_iter    = generic_file_read_iter,
    .write_iter    = generic_file_write_iter,
    .mmap        = generic_file_mmap,
    .fsync        = noop_fsync,
    .splice_read    = generic_file_splice_read,
    .splice_write    = iter_file_splice_write,
    .llseek        = generic_file_llseek,
    .get_unmapped_area    = ramfs_mmu_get_unmapped_area,
};

const struct inode_operations ramfs_file_inode_operations = {
    .setattr    = simple_setattr,
    .getattr    = simple_getattr,
};

static const struct inode_operations ramfs_dir_inode_operations = {
    .create        = ramfs_create,
    .lookup        = simple_lookup,
    .link        = simple_link,
    .unlink        = simple_unlink,
    .symlink    = ramfs_symlink,
    .mkdir        = ramfs_mkdir,
    .rmdir        = simple_rmdir,
    .mknod        = ramfs_mknod,
    .rename        = simple_rename,
};

const struct inode_operations page_symlink_inode_operations = {
    .readlink    = generic_readlink,
    .get_link    = page_get_link,
};

 

さまざまなタイプのiノード-> i_modeに応じて、さまざまなiノード-> i_fopおよびiノード-> i_opが採用されます。

3.3カーネルでのrootfs_initcall()の呼び出しシーケンス

すべてのinitcallは、start_kernel()-> reset_init()-> kernel_init()-> kernel_init_freeable()-> do_basic_setup()で順番にinitcallを呼び出します。

その中で、rootfs_initcall()はfs_initcall()の後、device_initcall()の前にあります。

#define fs_initcall(fn)            __define_initcall(fn, 5)
#define fs_initcall_sync(fn)        __define_initcall(fn, 5s)
#define rootfs_initcall(fn)        __define_initcall(fn, rootfs)
#define device_initcall(fn)        __define_initcall(fn, 6)
#define device_initcall_sync(fn)    __define_initcall(fn, 6s)

 

3.4ramfsの解凍

rootfs_initcall()は、CONFIG_BLK_DEV_INITRDが定義されていない場合、default_rootfs()を呼び出します

default_rootfs()は、主に2つのディレクトリ/ devと/ root、およびデバイスファイル/ dev / consoleを生成します。

 

static int __init default_rootfs(void)
{
    int err;

    err = sys_mkdir((const char __user __force *) "/dev", 0755);
    if (err < 0)
        goto out;

    err = sys_mknod((const char __user __force *) "/dev/console",
            S_IFCHR | S_IRUSR | S_IWUSR,
            new_encode_dev(MKDEV(5, 1)));
    if (err < 0)
        goto out;

    err = sys_mkdir((const char __user __force *) "/root", 0700);
    if (err < 0)
        goto out;

    return 0;

out:
    printk(KERN_WARNING "Failed to create a rootfs\n");
    return err;
}

 

CONFIG_BLK_DEV_INITRDを定義する場合は、populate_rootfs()を呼び出してRAMディスクをRAMに解凍します。 

unpack_to_rootfs()は、パラメーター__initramfs_startおよび__initramfs_sizeに従ってヘッダーから解凍のタイプを取得し、次にdecompress_fnを呼び出して解凍します。

 

static int __init populate_rootfs(void)
{
    char *err = unpack_to_rootfs(__initramfs_start, __initramfs_size);

if (err)
        panic("%s", err); /* Failed to decompress INTERNAL initramfs */
    if (initrd_start) {---------------------------------------------判断是否特别指定了initrd_start。如果指定,就对initrd进行单独处理。
#ifdef CONFIG_BLK_DEV_RAM
        int fd;
        printk(KERN_INFO "Trying to unpack rootfs image as initramfs...\n");
        err = unpack_to_rootfs((char *)initrd_start,
            initrd_end - initrd_start);-----------------------------判断加载的是不是initramfs CPIO文件。
        if (!err) {
            free_initrd();------------------------------------------如果解压成功,释放image中initrd对应内存。
            goto done;
        } else {
            clean_rootfs();
            unpack_to_rootfs(__initramfs_start, __initramfs_size);--可能是initrd文件。
        }
        printk(KERN_INFO "rootfs image is not initramfs (%s)"
                "; looks like an initrd\n", err);
        fd = sys_open("/initrd.image",
                  O_WRONLY|O_CREAT, 0700);--------------------------创建文件/initrd.image。
        if (fd >= 0) {
            ssize_t written = xwrite(fd, (char *)initrd_start,
                        initrd_end - initrd_start);-----------------将intird_start到initrd_end内容保存到/initrd.image文件中。

            if (written != initrd_end - initrd_start)
                pr_err("/initrd.image: incomplete write (%zd != %ld)\n",
                       written, initrd_end - initrd_start);

            sys_close(fd);
            free_initrd();------------------------------------------关闭文件并释放image中initrd对应内存。
        }
    done:
#else
        printk(KERN_INFO "Unpacking initramfs...\n");
        err = unpack_to_rootfs((char *)initrd_start,
            initrd_end - initrd_start);
        if (err)
            printk(KERN_EMERG "Initramfs unpacking failed: %s\n", err);
        free_initrd();
#endif
        load_default_modules();
    }
    return 0;
}

static char * __init unpack_to_rootfs(char *buf, unsigned long len)
{
    long written;
    decompress_fn decompress;
    const char *compress_name;
    static __initdata char msg_buf[64];

    header_buf = kmalloc(110, GFP_KERNEL);
    symlink_buf = kmalloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1, GFP_KERNEL);
    name_buf = kmalloc(N_ALIGN(PATH_MAX), GFP_KERNEL);

    if (!header_buf || !symlink_buf || !name_buf)
        panic("can't allocate buffers");

    state = Start;
    this_header = 0;
    message = NULL;
    while (!message && len) {
...
        decompress = decompress_method(buf, len, &compress_name);------根据buf的第1、2个字节的magic来判断decompress类型。比如这里对应gzip,所以返回值decompress及对应gunzip()。
        pr_debug("Detected %s compressed data\n", compress_name);
        if (decompress) {
            int res = decompress(buf, len, NULL, flush_buffer, NULL,
                   &my_inptr, error);
            if (res)
                error("decompressor failed");
        } else if (compress_name) {
...
        } else
            error("junk in compressed archive");
        if (state != Reset)
            error("junk in compressed archive");
        this_header = saved_offset + my_inptr;
        buf += my_inptr;
        len -= my_inptr;
    }
    dir_utime();
    kfree(name_buf);
    kfree(symlink_buf);
    kfree(header_buf);
    return message;
}

 

 

3.4.1デコンプレッサ

カーネルでサポートされているデコンプレッサはstructcompress_formatで表され、コアはdecompress_fn()関数です。

 

struct compress_format {
    unsigned char magic[2];
    const char *name;
    decompress_fn decompressor;
};


typedef int (*decompress_fn) (unsigned char *inbuf, long len,
                  long (*fill)(void*, unsigned long),
                  long (*flush)(void*, unsigned long),
                  unsigned char *outbuf,
                  long *posp,
                  void(*error)(char *x));

/* inbuf   - input buffer
 *len     - len of pre-read data in inbuf
 *fill    - function to fill inbuf when empty
 *flush   - function to write out outbuf
 *outbuf  - output buffer
 *posp    - if non-null, input position (number of bytes read) will be
 *      returned here

 

decompress_methodは、着信inbufヘッダーの2バイトに従って、対応するスペースで使用されているデコンプレッサーを判断します。

decompressed_formats []は、システムでサポートされているデコンプレッサタイプを保存します。

 

static const struct compress_format compressed_formats[] __initconst = {
    { {0x1f, 0x8b}, "gzip", gunzip },
    { {0x1f, 0x9e}, "gzip", gunzip },
    { {0x42, 0x5a}, "bzip2", bunzip2 },
    { {0x5d, 0x00}, "lzma", unlzma },
    { {0xfd, 0x37}, "xz", unxz },
    { {0x89, 0x4c}, "lzo", unlzo },
    { {0x02, 0x21}, "lz4", unlz4 },
    { {0, 0}, NULL, NULL }
};

decompress_fn __init decompress_method(const unsigned char *inbuf, long len,
                const char **name)
{
...
    pr_debug("Compressed data magic: %#.2x %#.2x\n", inbuf[0], inbuf[1]);

    for (cf = compressed_formats; cf->name; cf++) {
        if (!memcmp(inbuf, cf->magic, 2))------------------------遍历compressed_formats[]知道找到吻合的magic作为后续ramfs解压工具。
            break;
    }
    if (name)
        *name = cf->name;
    return cf->decompressor;
}

 

 gzipタイプに対応するdecompres_fn()はgunzipであり、ここでは詳しく説明しませんが、入力パラメーターのflush()関数はramfsと密接に関連しています。 

 

STATIC int INIT gunzip(unsigned char *buf, long len,
               long (*fill)(void*, unsigned long),
               long (*flush)(void*, unsigned long),
               unsigned char *out_buf,
               long *pos,
               void (*error)(char *x))
{
    return __gunzip(buf, len, fill, flush, out_buf, 0, pos, error);
}

STATIC int INIT __gunzip(unsigned char *buf, long len,
               long (*fill)(void*, unsigned long),
               long (*flush)(void*, unsigned long),
               unsigned char *out_buf, long out_len,
               long *pos,
               void(*error)(char *x)) {
    u8 *zbuf;
    struct z_stream_s *strm;
    int rc;

    rc = -1;
    if (flush) {
        out_len = 0x8000; /* 32 K */
        out_buf = malloc(out_len);-----------------------以32K为单位进行处理。
    } else {
        if (!out_len)
            out_len = ((size_t)~0) - (size_t)out_buf; /* no limit */
    }
...
    while (rc == Z_OK) {
...
        rc = zlib_inflate(strm, 0);

        /* Write any data generated */
        if (flush && strm->next_out > out_buf) {
            long l = strm->next_out - out_buf;
            if (l != flush(out_buf, l)) {-----------------将解压后的数据刷出,这里即调用flush_buffer()进行处理。
                rc = -1;
                error("write error");
                break;
            }
            strm->next_out = out_buf;
            strm->avail_out = out_len;
        }

        /* after Z_FINISH, only Z_STREAM_END is "we unpacked it all" */
        if (rc == Z_STREAM_END) {
            rc = 0;
            break;
        } else if (rc != Z_OK) {
            error("uncompression error");
            rc = -1;
        }
    }

    zlib_inflateEnd(strm);
    if (pos)
        /* add + 8 to skip over trailer */
        *pos = strm->next_in - zbuf+8;

gunzip_5:
    free(strm->workspace);
gunzip_nomem4:
    free(strm);
gunzip_nomem3:
    if (!buf)
        free(zbuf);
gunzip_nomem2:
    if (flush)
        free(out_buf);
gunzip_nomem1:
    return rc; /* returns Z_OK (0) if successful */
}

 

 

3.4.2 flush_buffer

上記の分析から、rootfsがramfsファイルシステムタイプを使用していることがわかります。

ramfs部分はgzipで解凍され、解凍されたコンテンツはflush_bufferでフラッシュされます。

flush_buffer()が__initramfs_sizeのサイズのメモリを__initramfs_startからrootfsファイルシステムにフラッシュする方法を見てみましょう。

flush_buffer()は、処理のためにwrite_bufferを呼び出します。ここで、1つのコアは、さまざまなステートマシン状態を処理するためにさまざまなactions [state]を呼び出すことです。

 

static long __init write_buffer(char *buf, unsigned long len)
{
    byte_count = len;
    victim = buf;

    while (!actions[state]())
        ;
    return len - byte_count;
}

static long __init flush_buffer(void *bufv, unsigned long len)
{
    char *buf = (char *) bufv;
    long written;
    long origLen = len;
    if (message)
        return -1;
    while ((written = write_buffer(buf, len)) < len && !message) {
...
    }
    return origLen;
}

 

Actions []は、解凍されたデータの変換とrootfsの生成の中核であると言えます。

action []は、対応するシステムコールを呼び出し、解凍されたデータに従ってファイルシステム全体を段階的に生成します。

 

static __initdata int (*actions[])(void) = {
    [Start]        = do_start,
    [Collect]    = do_collect,
    [GotHeader]    = do_header,
    [SkipIt]    = do_skip,
    [GotName]    = do_name,
    [CopyFile]    = do_copy,
    [GotSymlink]    = do_symlink,
    [Reset]        = do_reset,
};

static int __init do_start(void)
{
    read_into(header_buf, 110, GotHeader);----------------------读取开头110字节,用于解析cpio文件头。
    return 0;
}

static int __init do_collect(void)
{
    unsigned long n = remains;
if (byte_count < n)
        n = byte_count;
    memcpy(collect, victim, n);
    eat(n);
    collect += n;
    if ((remains -= n) != 0)
        return 1;
    state = next_state;
    return 0;
}

static int __init do_header(void)
{
    if (memcmp(collected, "070707", 6)==0) {---------------------cpio文件的magic,开头6个字节“070707”或者“070701”。
        error("incorrect cpio method used: use -H newc option");
        return 1;
    }
    if (memcmp(collected, "070701", 6)) {
        error("no cpio magic");
        return 1;
    }
    parse_header(collected);
    next_header = this_header + N_ALIGN(name_len) + body_len;
    next_header = (next_header + 3) & ~3;
    state = SkipIt;
    if (name_len <= 0 || name_len > PATH_MAX)
        return 0;
    if (S_ISLNK(mode)) {
        if (body_len > PATH_MAX)
            return 0;
        collect = collected = symlink_buf;
        remains = N_ALIGN(name_len) + body_len;
        next_state = GotSymlink;
        state = Collect;
        return 0;
    }
    if (S_ISREG(mode) || !body_len)
        read_into(name_buf, N_ALIGN(name_len), GotName);
    return 0;
}

static int __init do_skip(void)
{
if (this_header + byte_count < next_header) {
        eat(byte_count);
        return 1;
    } else {
        eat(next_header - this_header);
        state = next_state;
        return 0;
    }
}

static int __init do_reset(void)
{
while (byte_count && *victim == '\0')
        eat(1);
    if (byte_count && (this_header & 3))
        error("broken padding");
    return 1;
}

static int __init maybe_link(void)
{
if (nlink >= 2) {
        char *old = find_link(major, minor, ino, mode, collected);
        if (old)
            return (sys_link(old, collected) < 0) ? -1 : 1;
    }
    return 0;
}

static void __init clean_path(char *path, umode_t fmode)
{
    struct stat st;

if (!sys_newlstat(path, &st) && (st.st_mode ^ fmode) & S_IFMT) {
        if (S_ISDIR(st.st_mode))--------------------------------删除目录,如果确实是一个目录调用sys_rmdir();如果是一个link,只需要sys_unlink()。
            sys_rmdir(path);
        else
            sys_unlink(path);
    }
}

static __initdata int wfd;

static int __init do_name(void)
{
    state = SkipIt;
    next_state = Reset;
    if (strcmp(collected, "TRAILER!!!") == 0) {
        free_hash();
        return 0;
    }
    clean_path(collected, mode);
    if (S_ISREG(mode)) {---------------------------------------如果是一个普通文件,调用sys_open()创建文件,并且通过sys_fchown()和sys_fchmod()等进行属性修改。
        int ml = maybe_link();
        if (ml >= 0) {
            int openflags = O_WRONLY|O_CREAT;
            if (ml != 1)
                openflags |= O_TRUNC;
            wfd = sys_open(collected, openflags, mode);

            if (wfd >= 0) {
                sys_fchown(wfd, uid, gid);
                sys_fchmod(wfd, mode);
                if (body_len)
                    sys_ftruncate(wfd, body_len);
                vcollected = kstrdup(collected, GFP_KERNEL);
                state = CopyFile;-----------------------------然后进行do_copy()将gzip解压的数据写入wfd中。
            }
        }
    } else if (S_ISDIR(mode)) {-------------------------------如果是一个目录则调用sys_mkdir()创建目录。
        sys_mkdir(collected, mode);
        sys_chown(collected, uid, gid);
        sys_chmod(collected, mode);
        dir_add(collected, mtime);
    } else if (S_ISBLK(mode) || S_ISCHR(mode) ||
           S_ISFIFO(mode) || S_ISSOCK(mode)) {
        if (maybe_link() == 0) {
            sys_mknod(collected, mode, rdev);
            sys_chown(collected, uid, gid);
            sys_chmod(collected, mode);
            do_utime(collected, mtime);
        }
    }
    return 0;
}

static int __init do_copy(void)
{
    if (byte_count >= body_len) {-----------------------------将数据写入wfd中,如果遇到写完则关闭文件,并且更新do_utime()。
        if (xwrite(wfd, victim, body_len) != body_len)
            error("write error");
        sys_close(wfd);
        do_utime(vcollected, mtime);
        kfree(vcollected);
        eat(body_len);
        state = SkipIt;
        return 0;
    } else {
        if (xwrite(wfd, victim, byte_count) != byte_count)
            error("write error");
        body_len -= byte_count;
        eat(byte_count);
        return 1;
    }
}

static int __init do_symlink(void)
{
    collected[N_ALIGN(name_len) + body_len] = '\0';
    clean_path(collected, 0);
    sys_symlink(collected + N_ALIGN(name_len), collected);-------对于符号链接调用sys_symlink()创建符号。
    sys_lchown(collected, uid, gid);
    do_utime(collected, mtime);
    state = SkipIt;
    next_state = Reset;
    return 0;
}

上記の一連のactions []関数から、gzip解凍後のデータが複雑なモードになり、さまざまな関数にジャンプしてバッファーを処理していることがわかります。

最終的に、完全なrootfsは、open()/ write()/ close()/ mkdir()のようなカーネル内の同じ関数を呼び出すことによって作成されます。

3.5リリース初期化メモリ

すべてのinitcallが実行された後、free_initmem()が呼び出されてメモリが解放されます。

 

void free_initmem(void)
{
    unsigned long addr;

    addr = (unsigned long) &__init_begin;
    while (addr < (unsigned long) &__init_end) {
            ClearPageReserved(virt_to_page(addr));
            init_page_count(virt_to_page(addr));
            free_page(addr);---------------------每次释放一个页面。
            totalram_pages++;--------------------totalram_pages递增。
            addr += PAGE_SIZE;-------------------addr后移一个页面。
    }
    pr_info("Freeing unused kernel memory: %dk freed\n",
            ((unsigned int)&__init_end - (unsigned int)&__init_begin) >> 10);
}

 

ramdiskを格納するセグメント.init.ramfsは__init_beginと__init_endの間にあるため、すべてが一緒に解放されます。

3.6RAMディスクの実行

Kernel_init()はユーザースペースの最初のプロセスであり、ramdiskはramfsファイルシステムタイプの準備に関連しています。ramdiskの解凍; ramdisk_execute_commandを開始して現在のプロセスを置き換えます。

 

static int __ref kernel_init(void *unused)
{
    int ret;

    kernel_init_freeable();--------执行各种initcall,包括对ramfs注册和populate_rootfs()解压ramdisk;以及判断ramdisk_execute_command是否存在,否则prepare_namespace()
...
    if (ramdisk_execute_command) {
        ret = run_init_process(ramdisk_execute_command);
        if (!ret)
            return 0;
        pr_err("Failed to execute %s (error %d)\n",
               ramdisk_execute_command, ret);
    }...
    panic("No working init found.  Try passing init= option to kernel. "
          "See Linux Documentation/init.txt for guidance.");
}

 

ramfsファイルシステムタイプをkernel_init_freeable()に登録し、vmlinuxの__initramfs_startからrootfsに__initramfs_startのサイズのコードを抽出します。

次に、sys_access()は、ramdisk_execute_commandがrootfsに存在するかどうかを確認します。存在しない場合は、rootfsを準備するためにprepare_namespace()が必要です。

 

static noinline void __init kernel_init_freeable(void)
{
...
    if (!ramdisk_execute_command)
        ramdisk_execute_command = "/init";

    if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
        ramdisk_execute_command = NULL;
        prepare_namespace();
    }
...
}

 

run_init_process()は、init_filenameに従ってrootfsから開始され、現在のプロセスをユーザースペースの最初のプロセスとして置き換えます。 

 

static int run_init_process(const char *init_filename)
{
    argv_init[0] = init_filename;
    return do_execve(getname_kernel(init_filename),--------------------------init_filename对应/sbin/init。
        (const char __user *const __user *)argv_init,------------------------argv_init[0]对应/sbin/init,其他为空。
        (const char __user *const __user *)envp_init);-----------------------envp_init[0]对应"HOME=/",envp_init[1]对应"TERM=linux"。
}

int do_execve(struct filename *filename,
    const char __user *const __user *__argv,
    const char __user *const __user *__envp)
{
    struct user_arg_ptr argv = { .ptr.native = __argv };
    struct user_arg_ptr envp = { .ptr.native = __envp };
    return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
}

static int do_execveat_common(int fd, struct filename *filename,
                  struct user_arg_ptr argv,
                  struct user_arg_ptr envp,
                  int flags)
{
    char *pathbuf = NULL;
    struct linux_binprm *bprm;
    struct file *file;
    struct files_struct *displaced;
    int retval;

    if (IS_ERR(filename))
        return PTR_ERR(filename);

    if ((current->flags & PF_NPROC_EXCEEDED) &&
        atomic_read(&current_user()->processes) > rlimit(RLIMIT_NPROC)) {
        retval = -EAGAIN;
        goto out_ret;
    }

    current->flags &= ~PF_NPROC_EXCEEDED;

    retval = unshare_files(&displaced);
    if (retval)
        goto out_ret;

    retval = -ENOMEM;
    bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);
    if (!bprm)
        goto out_files;

    retval = prepare_bprm_creds(bprm);
    if (retval)
        goto out_free;

    check_unsafe_exec(bprm);
    current->in_execve = 1;

    file = do_open_execat(fd, filename, flags);
    retval = PTR_ERR(file);
    if (IS_ERR(file))
        goto out_unmark;

    sched_exec();

    bprm->file = file;
    if (fd == AT_FDCWD || filename->name[0] == '/') {
        bprm->filename = filename->name;
    } else {
        if (filename->name[0] == '\0')
            pathbuf = kasprintf(GFP_TEMPORARY, "/dev/fd/%d", fd);
        else
            pathbuf = kasprintf(GFP_TEMPORARY, "/dev/fd/%d/%s",
                        fd, filename->name);
        if (!pathbuf) {
            retval = -ENOMEM;
            goto out_unmark;
        }

        if (close_on_exec(fd, rcu_dereference_raw(current->files->fdt)))
            bprm->interp_flags |= BINPRM_FLAGS_PATH_INACCESSIBLE;
        bprm->filename = pathbuf;
    }
    bprm->interp = bprm->filename;

    retval = bprm_mm_init(bprm);
    if (retval)
        goto out_unmark;

    bprm->argc = count(argv, MAX_ARG_STRINGS);
    if ((retval = bprm->argc) < 0)
        goto out;

    bprm->envc = count(envp, MAX_ARG_STRINGS);
    if ((retval = bprm->envc) < 0)
        goto out;

    retval = prepare_binprm(bprm);
    if (retval < 0)
        goto out;

    retval = copy_strings_kernel(1, &bprm->filename, bprm);
    if (retval < 0)
        goto out;

    bprm->exec = bprm->p;
    retval = copy_strings(bprm->envc, envp, bprm);
    if (retval < 0)
        goto out;

    retval = copy_strings(bprm->argc, argv, bprm);
    if (retval < 0)
        goto out;

    would_dump(bprm, bprm->file);

    retval = exec_binprm(bprm);
    if (retval < 0)
        goto out;

    /* execve succeeded */
    current->fs->in_exec = 0;
    current->in_execve = 0;
    acct_update_integrals(current);
    task_numa_free(current);
    free_bprm(bprm);
    kfree(pathbuf);
    putname(filename);
    if (displaced)
        put_files_struct(displaced);
    return retval;

out:
    if (bprm->mm) {
        acct_arg_size(bprm, 0);
        mmput(bprm->mm);
    }

out_unmark:
    current->fs->in_exec = 0;
    current->in_execve = 0;

out_free:
    free_bprm(bprm);
    kfree(pathbuf);

out_files:
    if (displaced)
        reset_files_struct(displaced);
out_ret:
    putname(filename);
    return retval;
}

 

 

4.まとめ

要約すると、buildrootまたはカーネルのコンパイル中にramdisk機能がオンになった後、ramdiskはvmlinuxに埋め込まれます。

Linuxのブートフェーズでは、RAMディスクはpopulate_rootfs()を介してコードから読み取られます。次に、gzip decompressorを呼び出してRAMに解凍します。解凍されたデータは解析され、actions []を介してrootfsファイルシステムに変換されます。

initの初期化が完了すると、ramfs関連のメモリがinitメモリとともに解放され、totalram_pagesに戻ります。

kernel_init()の最終段階では、ramdiskのinitプロセスは、ユーザースペースの最初のプロセスとしてrun_init_process()を介して実行されます。

おすすめ

転載: blog.csdn.net/daocaokafei/article/details/114845111