grub-install源码分析---3

方和豫
2023-12-01

grub-install源码分析—3

本章之前的分析介绍了grub-install如何解析输入参数,并打开相应的存储设备,本章主要看grub如何识别根设备上的文件系统。

grub2-install第五部分
util/grub-install.c

    ...

    grub_fs = grub_fs_probe (grub_dev);
    grub_install_push_module (grub_fs->name);
    grub_install_push_module (disk_module);

    relative_grubdir = grub_make_system_path_relative_to_its_root (grubdir);

    char *platname =  grub_install_get_platform_name (platform);
    char *platdir;

    char *t = grub_util_path_concat (2, grubdir, platname);
    platdir = grub_canonicalize_file_name (t);
    free (t);

    load_cfg = grub_util_path_concat (2, platdir, "load.cfg");
    grub_util_unlink (load_cfg);

    grub_util_pull_device (install_device);
    install_drive = grub_util_get_grub_dev (install_device);

    ...

首先通过grub_fs_probe函数查找根设备grub_dev对应的文件系统,假设该文件系统为ext2,则返回值grub_fs为grub_ext2_fs。根据上一章的分析,传入的参数grub_dev为设备打开函数grub_device_open的返回值,内部存储了设备的信息。

往下的grub_install_push_module函数将模块名添加到全局数组中,后续要加载对应的模块,传入的参数grub_fs的name字段保存了文件系统名称,例如ext2、ext3,disk_module默认值为biosdisk。

如果grubdir属于某个挂载设备,grub_make_system_path_relative_to_its_root函数返回以挂点为根目录的路径,传入的参数grubdir为grub文件所在目录,默认为”/boot/grub”。

grub_install_get_platform_name函数根据平台索引platform获得平台名称platname,假设platform为GRUB_INSTALL_PLATFORM_I386_PC,则该函数返回i386-pc。在获得grub目录下对应cpu体系的目录名称platdir,假设为”/boot/grub/i386-pc”。然后检查platdir目录下是否存在load.cfg文件,如果存在,则通过grub_util_unlink函数删除该文件。

最后,grub_util_pull_device函数打开设备install_device,该参数由命令行中的参数指定,表示grub文件被安装的设备文件,例如”/dev/sda1”,再通过grub_util_get_grub_dev函数将该设备文件转化为驱动号,例如hd0。

下面重点分析grub_fs_probe函数。

grub_fs_probe

grub2-install->grub_fs_probe
grub-core/kern/fs.c

grub_fs_t grub_fs_probe (grub_device_t device){
    grub_fs_t p;

    if (device->disk){
        static int count = 0;

        for (p = grub_fs_list; p; p = p->next){
            (p->dir) (device, "/", probe_dummy_iter, NULL);
            if (grub_errno == GRUB_ERR_NONE)
                return p;

        ...
    }

    ...

}

从这段简单的代码就能看出grub如何探测硬盘的文件系统,传入的参数device->disk中存储了硬盘的基本信息,例如磁道数,扇区数等等。如果这些信息存在,就遍历grub_fs_list链表,该链表存储了各个文件系统对应的结构,然后调用结构中的dir函数查找根路径,如果找到,就返回该结构对应的文件系统。例如查找一个以ext2为文件系统的硬盘的根路径时,最终就返回grub_fs_list列表中包含的grub_ext2_fs结构。

grub_ext2_fs结构对应的dir函数为grub_ext2_dir,下面来看。

grub2-install->grub_fs_probe->grub_ext2_dir
grub-core/fs/ext2.c

static grub_err_t grub_ext2_dir (grub_device_t device, const char *path, 
                grub_fs_dir_hook_t hook, void *hook_data){

    struct grub_ext2_dir_ctx ctx = {
        .hook = hook,
        .hook_data = hook_data
    };
    struct grub_fshelp_node *fdiro = 0;

    ctx.data = grub_ext2_mount (device->disk);

    grub_fshelp_find_file (path, &ctx.data->diropen, &fdiro,
             grub_ext2_iterate_dir, grub_ext2_read_symlink,
             GRUB_FSHELP_DIR);

    grub_ext2_iterate_dir (fdiro, grub_ext2_dir_iter, &ctx);

    grub_free (ctx.data);
    grub_dl_unref (my_mod);
    return grub_errno;
}

传入的参数device为对应的设备,内部的disk存储了设备的基本信息,path为需要查找的路径,即根目录”/”,hook为probe_dummy_iter函数,hook_data为null。

首先通过grub_ext2_mount函数读取硬盘上的superblock,然后再获得根目录对应的inode信息。接着通过grub_fshelp_find_file函数查找路径path,返回的文件结点保存在参数fdiro中,内部存储了文件的具体信息,例如inode号等等。根据该inode信息,最后调用grub_ext2_iterate_dir函数修改文件的信息。

因此,如果某个文件系统是ext2,则调用grub_ext2_dir函数遍历根目录时就不会出错,最终返回的grub_errno为GRUB_ERR_NONE。

grub2-install->grub_fs_probe->grub_ext2_dir->grub_ext2_mount
grub-core/fs/ext2.c

static struct grub_ext2_data * grub_ext2_mount (grub_disk_t disk){
    struct grub_ext2_data *data;
    data = grub_malloc (sizeof (struct grub_ext2_data));

    grub_disk_read (disk, 1 * 2, 0, sizeof (struct grub_ext2_sblock),
                    &data->sblock);

    if (data->sblock.magic != grub_cpu_to_le16_compile_time (EXT2_MAGIC)
        || grub_le_to_cpu32 (data->sblock.log2_block_size) >= 16
        || data->sblock.inodes_per_group == 0
        || grub_le_to_cpu32 (data->sblock.log2_block_size) > 20
        || EXT2_INODE_SIZE (data) == 0
        || EXT2_BLOCK_SIZE (data) / EXT2_INODE_SIZE (data) == 0){
        goto fail;
    }

    ...

    data->disk = disk;
    data->diropen.data = data;
    data->diropen.ino = 2;
    data->diropen.inode_read = 1;
    data->inode = &data->diropen.inode;
    grub_ext2_read_inode (data, 2, data->inode);

    return data;
}

首先通过grub_disk_read函数读取superblock,superblock位于每个分区的第3个扇区,因此传入的偏移参数为2,返回的结果保存在data->sblock中。接下来的if语句检查魔数magic,log2_block_size表示记录硬盘每个block的大小需要的比特数,block的大小不能大于65536,即2的16次方,inodes_per_group表示每个group中的索引节点数,该值不能等于0,最后检查log2_block_size是否大于20,由于比较的出发点不同,暂时保留,在本次比较中没用。其中EXT2_BLOCK_SIZE用于获取每个block的大小,EXT2_INODE_SIZE宏用于获取每个inode的大小。

#define EXT2_INODE_SIZE(data)   \
  (data->sblock.revision_level \
   == grub_cpu_to_le32_compile_time (EXT2_GOOD_OLD_REVISION)    \
         ? EXT2_GOOD_OLD_INODE_SIZE \
         : grub_le_to_cpu16 (data->sblock.inode_size))

revision_level标识了superblock的版本,除了EXT2_GOOD_OLD_REVISION外,还有EXT2_DYNAMIC_REV。EXT2_GOOD_OLD_REVISION为较老的版本,只支持固定的inode大小EXT2_GOOD_OLD_INODE_SIZE,即128字节。新版本的EXT2_DYNAMIC_REV使用superblock中的inode_size字段记录每个inode的大小。

再往下省略了一些版本的检查。最后设置相关参数,调用grub_ext2_read_inode读取第二个inode,也即根目录对应的inode。

grub2-install->grub_fs_probe->grub_ext2_dir->grub_ext2_mount->grub_ext2_read_inode
grub-core/fs/ext2.c

static grub_err_t grub_ext2_read_inode (struct grub_ext2_data *data,
              int ino, struct grub_ext2_inode *inode){
    struct grub_ext2_block_group blkgrp;
    struct grub_ext2_sblock *sblock = &data->sblock;
    int inodes_per_block;
    unsigned int blkno;
    unsigned int blkoff;
    grub_disk_addr_t base;

    ino--;

    grub_ext2_blockgroup (data, ino / grub_le_to_cpu32 
        (sblock->inodes_per_group), &blkgrp);

    inodes_per_block = EXT2_BLOCK_SIZE (data) / EXT2_INODE_SIZE (data);
    blkno = (ino % grub_le_to_cpu32 (sblock->inodes_per_group)) / inodes_per_block;
    blkoff = (ino % grub_le_to_cpu32 (sblock->inodes_per_group)) % inodes_per_block;

    base = grub_le_to_cpu32 (blkgrp.inode_table_id);
    if (data->log_group_desc_size >= 6)
        base |= (((grub_disk_addr_t) grub_le_to_cpu32 (blkgrp.inode_table_id_hi)) << 32);

    if (grub_disk_read (data->disk, ((base + blkno) << LOG2_EXT2_BLOCK_SIZE (data)),
              EXT2_INODE_SIZE (data) * blkoff, sizeof (struct grub_ext2_inode), inode))
        return grub_errno;

    return 0;
}

由于inode号ino计数时是从1开始的, 因此该函数首先将ino减1。然后通过grub_ext2_blockgroup函数读取对应group的group descriptor结构,存放在返回参数blkgrp中。其中ino除inodes_per_group表示要读取的inode在哪个group。

接下来计算的inodes_per_block表示每个block里的inode数,ino%grub_le_to_cpu32 (sblock->inodes_per_group)表示inode在group中是第几个inode。因此blkno表示对应的inode在某个group中是第几个block,blkoff为block内的偏移。前面返回的group descriptor结构中的inode_table_id字段指向对应group的inode table的绝对位置,单位为block。

最后通过grub_disk_read函数在该inode table内读出该inode并返回。

grub2-install->grub_fs_probe->grub_ext2_dir->grub_ext2_mount->grub_ext2_read_inode->
grub-core/fs/ext2.c

inline static grub_err_t grub_ext2_blockgroup (struct grub_ext2_data *data, 
    grub_uint64_t group, struct grub_ext2_block_group *blkgrp){

    grub_uint64_t full_offset = (group << data->log_group_desc_size);
    grub_uint64_t block, offset;
    block = (full_offset >> LOG2_BLOCK_SIZE (data));
    offset = (full_offset & ((1 << LOG2_BLOCK_SIZE (data)) - 1));

    if ((data->sblock.feature_incompat
        & grub_cpu_to_le32_compile_time (EXT2_FEATURE_INCOMPAT_META_BG))
        && block >= grub_le_to_cpu32(data->sblock.first_meta_bg)){

        grub_uint64_t first_block_group;
        first_block_group = (block << (LOG2_BLOCK_SIZE (data)
                     - data->log_group_desc_size));

        block = (first_block_group * grub_le_to_cpu32(data->sblock.blocks_per_group));

        if (group_has_super_block (data, first_block_group))
            block++;
    }else
        block++;

    return grub_disk_read (data->disk, ((grub_le_to_cpu32 (data->sblock.first_data_block)
                            + block) << LOG2_EXT2_BLOCK_SIZE (data)), offset,
                            sizeof (struct grub_ext2_block_group), blkgrp);
}

group_desc_size表示group descriptor的大小,log_group_desc_size是其对应占用的比特数,full_offset是该group desciptor的起始地址,将该地址转化为block数block,对应的block内偏移为offset,full_offset按照block大小对齐。

接下来无论if还是else最后都递增block,略过superblock。对于block为1kB大小的文件系统,first_data_block为1,first_data_block对应superblock之前1024字节的引导扇区,即一个block。

grub_disk_read在上一张分析过了,最后读取的数据保存在地址blkgrp处,其实最终读取的就是某个group的group descriptor,这些descriptor被聚集起来,放在硬盘分区的开头部分,跟在superblock的后面。

grub2-install->grub_fs_probe->grub_ext2_dir->grub_fshelp_find_file
grub-core/fs/fshelp.c

grub_err_t grub_fshelp_find_file (const char *path, grub_fshelp_node_t rootnode,
               grub_fshelp_node_t *foundnode, iterate_dir_func iterate_dir,
               read_symlink_func read_symlink, enum grub_fshelp_filetype expecttype){

    return grub_fshelp_find_file_real (path, rootnode, foundnode,
                     iterate_dir, NULL,  read_symlink, expecttype);

}

static grub_err_t grub_fshelp_find_file_real (const char *path, grub_fshelp_node_t rootnode,
                grub_fshelp_node_t *foundnode, iterate_dir_func iterate_dir,
                lookup_file_func lookup_file, read_symlink_func read_symlink,
                enum grub_fshelp_filetype expecttype){

    struct grub_fshelp_find_file_ctx ctx = {
        .path = path,
        .rootnode = rootnode,
        .symlinknest = 0,
        .currnode = 0
    };
    grub_err_t err;
    enum grub_fshelp_filetype foundtype;
    char *duppath;

    go_to_root (&ctx);
    duppath = grub_strdup (path);

    find_file (duppath, iterate_dir, lookup_file, read_symlink, &ctx);
    grub_free (duppath);

    *foundnode = ctx.currnode->node;
    foundtype = ctx.currnode->type;

    ctx.currnode->node = 0;
    free_stack (&ctx);

    if (expecttype == GRUB_FSHELP_REG && foundtype != expecttype)
        return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a regular file"));
    else if (expecttype == GRUB_FSHELP_DIR && foundtype != expecttype)
        return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a directory"));

    return 0;
}

ctx为整个函数的上下文,其内部存放了一个节点链表,根节点为rootnode,首先通过go_to_root函数清空以根节点起始的所有节点,然后设置查找的根节点,并将当前节点指向根节点。

接着通过find_file函数查找文件,返回的foundnode表示文件结点,内部存储了inode号等信息,foundtype为文件类型,最后比较文件类型,如果不等于要查找的文件类型则报错,成功返回0。

grub2-install->grub_fs_probe->grub_ext2_dir->grub_fshelp_find_file->go_to_root
grub-core/fs/fshelp.c

static grub_err_t go_to_root (struct grub_fshelp_find_file_ctx *ctx){
    free_stack (ctx);
    return push_node (ctx, ctx->rootnode, GRUB_FSHELP_DIR);
}

static void free_stack (struct grub_fshelp_find_file_ctx *ctx){
    while (ctx->currnode)
        pop_element (ctx);
}

static grub_err_t push_node (struct grub_fshelp_find_file_ctx *ctx, grub_fshelp_node_t node, 
        enum grub_fshelp_filetype filetype){

    struct stack_element *nst;
    nst = grub_malloc (sizeof (*nst));

    nst->node = node;
    nst->type = filetype & ~GRUB_FSHELP_CASE_INSENSITIVE;
    nst->parent = ctx->currnode;
    ctx->currnode = nst;
    return GRUB_ERR_NONE;
}

首先通过free_stack释放ctx上下文中的节点列表,然后通过push_node函数创建一个node节点,其父节点parent指向当前节点currnode,再更新当前节点currnode指向新创建的节点nst。

grub2-install->grub_fs_probe->grub_ext2_dir->grub_fshelp_find_file->grub_fshelp_find_file_real
grub-core/fs/fshelp.c

static grub_err_t find_file (char *currpath, iterate_dir_func iterate_dir, 
                lookup_file_func lookup_file, read_symlink_func read_symlink,
                struct grub_fshelp_find_file_ctx *ctx){

    char *name, *next;
    grub_err_t err;
    for (name = currpath; ; name = next){
        char c;
        grub_fshelp_node_t foundnode = NULL;
        enum grub_fshelp_filetype foundtype = 0;

        while (*name == '/')
            name++;

        if (! *name)
            return 0;

        for (next = name; *next && *next != '/'; next++);

        if (ctx->currnode->type != GRUB_FSHELP_DIR)
            return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a directory"));

        if (next - name == 1 && name[0] == '.')
            continue;

        if (next - name == 2 && name[0] == '.' && name[1] == '.'){
            go_up_a_level (ctx);
            continue;
        }

        c = *next;
        *next = '\0';
        directory_find_file (ctx->currnode->node, name, &foundnode, &foundtype, iterate_dir);
        *next = c;

        if (!foundnode)
            break;

        push_node (ctx, foundnode, foundtype);

        if (ctx->currnode->type == GRUB_FSHELP_SYMLINK){
            ...
        }
    }
}

第一个for循环遍历路径currpath中的每个目录。首先略过路径中的文件分隔符,第二个for循环计算的next用于获取前后两个路径分隔符之间的文件名。如果当前的节点currnode不是目录,则报错,因为首先会从根目录开始查找,如果在某个目录中找到了最终文件或目录,则next为null,函数返回0,不会执行到这里。

如果两个路径分隔符之间为当前目录表示”.”,则遍历下一个目录,如果为”..”,则表示上一个目录,此时通过go_up_a_level函数返回上一级目录。接下来通过directory_find_file函数遍历当前目录,匹配name,查号文件,返回的foundnode中包含了查找到的文件对应的结点,结点内保存了诸如inode等信息。

如果没有找到foundnode,则表示路径中的某个目录或文件不存在,直接退出循环。如果找到了,则通过push_node函数将新结点foundnode加入到上下文ctx中。最后if语句中省略的部分用于处理文件链接,本章不关心。

grub2-install->grub_fs_probe->grub_ext2_dir->grub_fshelp_find_file->grub_fshelp_find_file_real->directory_find_file
grub-core/fs/fshelp.c

static grub_err_t directory_find_file (grub_fshelp_node_t node, const char *name, 
                grub_fshelp_node_t *foundnode, enum grub_fshelp_filetype *foundtype, 
                iterate_dir_func iterate_dir){

    int found;
    struct grub_fshelp_find_file_iter_ctx ctx = {
        .foundnode = foundnode,
        .foundtype = foundtype,
        .name = name
    };
    iterate_dir (node, find_file_iter, &ctx);
    return GRUB_ERR_NONE;
}

传入的函数指针iterate_dir为grub_ext2_iterate_dir,该函数遍历目录节点node下的目录结构dirent,找到name对应的文件,将结果保存在上下文ctx中并返回,传入的函数指针find_file_iter用于作匹配操作。

grub2-install->grub_fs_probe->grub_ext2_dir->grub_fshelp_find_file->grub_fshelp_find_file_real->directory_find_file->grub_ext2_iterate_dir
grub-core/fs/fshelp.c

static int grub_ext2_iterate_dir (grub_fshelp_node_t dir,
                    grub_fshelp_iterate_dir_hook_t hook, void *hook_data){

    unsigned int fpos = 0;
    struct grub_fshelp_node *diro = (struct grub_fshelp_node *) dir;

    if (! diro->inode_read){
        grub_ext2_read_inode (diro->data, diro->ino, &diro->inode);
    }

    while (fpos < grub_le_to_cpu32 (diro->inode.size)){
        struct ext2_dirent dirent;

        grub_ext2_read_file (diro, 0, 0, fpos, sizeof (struct ext2_dirent), (char *) &dirent);

        if (dirent.direntlen == 0)
            return 0;

        if (dirent.inode != 0 && dirent.namelen != 0){
            char filename[MAX_NAMELEN + 1];
            struct grub_fshelp_node *fdiro;
            enum grub_fshelp_filetype type = GRUB_FSHELP_UNKNOWN;

            grub_ext2_read_file (diro, 0, 0, fpos + sizeof (struct ext2_dirent), dirent.namelen, filename);

            fdiro = grub_malloc (sizeof (struct grub_fshelp_node));
            fdiro->data = diro->data;
            fdiro->ino = grub_le_to_cpu32 (dirent.inode);

            filename[dirent.namelen] = '\0';

            if (dirent.filetype != FILETYPE_UNKNOWN){
                fdiro->inode_read = 0;

                if (dirent.filetype == FILETYPE_DIRECTORY)
                    type = GRUB_FSHELP_DIR;
                else if (dirent.filetype == FILETYPE_SYMLINK)
                    type = GRUB_FSHELP_SYMLINK;
                else if (dirent.filetype == FILETYPE_REG)
                    type = GRUB_FSHELP_REG;
            } else {
                ...
            }

            if (hook (filename, type, fdiro, hook_data))
                return 1;
        }

        fpos += grub_le_to_cpu16 (dirent.direntlen);
    }

    return 0;
}

dir为当前目录对应的节点node,如果该节点对应的inode结构未读取,则通过grub_ext2_read_inode函数读取inode,该函数前面分析过了。接着通过fpos指针遍历目录,inode的size标识了该目录的大小,然后通过grub_ext2_read_file读取目录下每个文件的dirent结构,获取文件名长度信息namelen,然后根据namelen,再一次调用grub_ext2_read_file函数读取文件名,保存在filename中。

接着分配一个node结点,存储读取到的文件信息,然后设置读取到的文件类型。函数指针hook为find_file_iter函数,该函数匹配文件夹中的当前文件名filename和待查找的当前文件名name,如果找到了,则返回1 ,返回的hook_data中保存了新文件的信息,例如inode号等,如果没找到,则递增fpos,循环遍历下一个目录。

grub2-install->grub_fs_probe->grub_ext2_dir->grub_fshelp_find_file->grub_fshelp_find_file_real->directory_find_file->grub_ext2_iterate_dir->grub_ext2_read_file
grub-core/fs/ext2.c

static grub_ssize_t grub_ext2_read_file (grub_fshelp_node_t node,
                grub_disk_read_hook_t read_hook, void *read_hook_data,
                grub_off_t pos, grub_size_t len, char *buf){

    return grub_fshelp_read_file (node->data->disk, node,
                read_hook, read_hook_data,
                pos, len, buf, grub_ext2_read_block,
                grub_cpu_to_le32 (node->inode.size)
                | (((grub_off_t) grub_cpu_to_le32 (node->inode.size_high)) << 32),
                LOG2_EXT2_BLOCK_SIZE (node->data), 0);
}

grub_ssize_t grub_fshelp_read_file (grub_disk_t disk, grub_fshelp_node_t node,
               grub_disk_read_hook_t read_hook, void *read_hook_data,
               grub_off_t pos, grub_size_t len, char *buf,
               grub_disk_addr_t (*get_block) (grub_fshelp_node_t node, grub_disk_addr_t block),
               grub_off_t filesize, int log2blocksize,
               grub_disk_addr_t blocks_start){

    grub_disk_addr_t i, blockcnt;
    int blocksize = 1 << (log2blocksize + GRUB_DISK_SECTOR_BITS);

    if (pos > filesize){
        return -1;
    }

    if (pos + len > filesize)
        len = filesize - pos;

    blockcnt = ((len + pos) + blocksize - 1) >> (log2blocksize + GRUB_DISK_SECTOR_BITS);

    for (i = pos >> (log2blocksize + GRUB_DISK_SECTOR_BITS); i < blockcnt; i++){
        grub_disk_addr_t blknr;
        int blockoff = pos & (blocksize - 1);
        int blockend = blocksize;

        int skipfirst = 0;

        blknr = get_block (node, i);
        blknr = blknr << log2blocksize;

        if (i == blockcnt - 1){
            blockend = (len + pos) & (blocksize - 1);

            if (! blockend)
                blockend = blocksize;
        }

        if (i == (pos >> (log2blocksize + GRUB_DISK_SECTOR_BITS))){
            skipfirst = blockoff;
            blockend -= skipfirst;
        }

        if (blknr){
            disk->read_hook = read_hook;
            disk->read_hook_data = read_hook_data;

            grub_disk_read (disk, blknr + blocks_start, skipfirst, blockend, buf);
            disk->read_hook = 0;
        }else
            grub_memset (buf, 0, blockend);

        buf += blocksize - skipfirst;
    }

    return len;
}

传入的参数pos表示当前inode读取时的相对位置,从0开始。传入的参数log2blocksize标识一个block占用的扇区数对应的位数,加上GRUB_DISK_SECTOR_BITS标识一个block对应的字节数占用的bit位数。参数filesize标识文件大小,len是本次读取的长度,即一个ext2_dirent结构的大小。

首先计算的blockcnt表示读取的结束位置位于哪个block,并对齐。接下来的for循环逐个读取block,blockoff和blockend表示读取的起始和结束的相对位置。函数指针get_block为grub_ext2_read_block,用于获取当前将要读取的是第几个block,注意这里是绝对的block数,左移log2blocksize获取对应的扇区数。

接下来的第一个if语句调整最后一次读取的block,使得读取长度取一个block的大小。第二个if语句判断如果是第一次读取block,则要调整读取的起始偏移skipfirst和第一个读取的长度blockend。再往下调用grub_disk_read执行具体的读取数据,注意如果是第0个block,则直接填充0即可。传入的read_hook和read_hook_data参数为空,这里不管它。

grub2-install->grub_fs_probe->grub_ext2_dir->grub_fshelp_find_file->grub_fshelp_find_file_real->directory_find_file->grub_ext2_iterate_dir->grub_ext2_read_file->grub_ext2_read_block
grub-core/fs/ext2.c

static grub_disk_addr_t grub_ext2_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock){

    struct grub_ext2_data *data = node->data;
    struct grub_ext2_inode *inode = &node->inode;
    unsigned int blksz = EXT2_BLOCK_SIZE (data);
    grub_disk_addr_t blksz_quarter = blksz / 4;
    int log2_blksz = LOG2_EXT2_BLOCK_SIZE (data);
    int log_perblock = log2_blksz + 9 - 2;
    grub_uint32_t indir;
    int shift;

    if (inode->flags & grub_cpu_to_le32_compile_time (EXT4_EXTENTS_FLAG)){
        ...
    }

    if (fileblock < INDIRECT_BLOCKS)
        return grub_le_to_cpu32 (inode->blocks.dir_blocks[fileblock]);
    fileblock -= INDIRECT_BLOCKS;

    if (fileblock < blksz_quarter){
        indir = inode->blocks.indir_block;
        shift = 0;
        goto indirect;
    }
    fileblock -= blksz_quarter;

    if (fileblock < blksz_quarter * blksz_quarter){
        indir = inode->blocks.double_indir_block;
        shift = 1;
        goto indirect;
    }

    fileblock -= blksz_quarter * blksz_quarter;
    if (fileblock < blksz_quarter * blksz_quarter * (blksz_quarter + 1)){
        indir = inode->blocks.triple_indir_block;
        shift = 2;
        goto indirect;
    }

indirect:
    do {
        if (indir == 0)
            return 0;

        if (grub_disk_read (data->disk,
            ((grub_disk_addr_t) grub_le_to_cpu32 (indir))
            << log2_blksz,
            ((fileblock >> (log_perblock * shift))
             & ((1 << log_perblock) - 1))
            * sizeof (indir),
            sizeof (indir), &indir))
            return -1;
    } while (shift--);

    return grub_le_to_cpu32 (indir);
}

传入的参数fileblock表示要读取的是第几个block。log2_blksz是每个block占用的扇区数,log_perblock表示当某个block是用于存放其他block的地址时,每个block可以存储多少个block的地址,加上9将扇区数转化为字节数,减去2是因为每4个字节用于存放一个地址。inode的block数组指向文件数据所在的block。其中前INDIRECT_BLOCKS(默认为12)个block是可以直接访问的,后面的(默认情况下为3)block是要简介访问的,unix这么划分是出于性能的考虑。

如果要读取的数据在前12个block里,则直接获取,并返回。前12个block叫做data block。第13个block为indirect block,存储了data block的地址,其中blksz_quarter为一个block的大小除以4,因为每4字节用来保存地址,因此对于1KB大小的block,可以存储256个block地址。第14个block为doubly-indirect block,存储了indirect block的地址,类似的第15个block叫做triply indirect block,存储了doubly-indirect block的地址。

根据不同的block类型,计算的shift变量标识要读几次才能获得想要的数据,例如对于indirect block,需要读一次,对于doubly-indirect block,就需要读两次。接下来就根据该原理循环调用grub_disk_read读取数据,indir存储了标识当前读取的是第几个block,左移log2_blksz表示起始的扇区数绝对偏移。

grub2-install->grub_fs_probe->grub_ext2_dir->grub_fshelp_find_file->grub_fshelp_find_file_real->directory_find_file->grub_ext2_iterate_dir->find_file_iter
grub-core/fs/fshelp.c

static int find_file_iter (const char *filename, enum grub_fshelp_filetype filetype, grub_fshelp_node_t node, void *data){

    struct grub_fshelp_find_file_iter_ctx *ctx = data;

    if (filetype == GRUB_FSHELP_UNKNOWN || ((filetype & GRUB_FSHELP_CASE_INSENSITIVE) ? grub_strcasecmp (ctx->name, filename) : grub_strcmp (ctx->name, filename))){
        grub_free (node);
        return 0;
    }
    *ctx->foundnode = node;
    *ctx->foundtype = filetype;
    return 1;
}

该函数首先比较文件名,检查遍历的文件filename是否就是要查找的文件name,如果不是则释放结点,返回0,如果找到了,则将结果存入上下文ctx中,其中包括文件节点foundnode和文件类型foundtype,并返回1。

grub2-install->grub_fs_probe->grub_ext2_dir->…->grub_ext2_dir_iter
grub-core/fs/ext2.c

static int grub_ext2_dir_iter (const char *filename, enum grub_fshelp_filetype filetype, grub_fshelp_node_t node, void *data){

    struct grub_ext2_dir_ctx *ctx = data;
    struct grub_dirhook_info info;

    grub_memset (&info, 0, sizeof (info));
    if (! node->inode_read){
        grub_ext2_read_inode (ctx->data, node->ino, &node->inode);
        if (!grub_errno)
            node->inode_read = 1;
        grub_errno = GRUB_ERR_NONE;
    }

    if (node->inode_read){
        info.mtimeset = 1;
        info.mtime = grub_le_to_cpu32 (node->inode.mtime);
    }

    info.dir = ((filetype & GRUB_FSHELP_TYPE_MASK) == GRUB_FSHELP_DIR);
    grub_free (node);
    return ctx->hook (filename, &info, ctx->hook_data);
}

如果还未读取inode,就通过grub_ext2_read_inode函数读取inode,并将inode_read置1。接下来修改inode的mtime字段,表示该文件的修改时间。上下文中的hook函数为probe_dummy_iter函数指针,该函数直接返回1。

grub2-install->grub_install_push_module
util/grub-install-common.c

void grub_install_push_module (const char *val){
    modules.is_default = 0;
    if (modules.n_entries + 1 >= modules.n_alloc){
        modules.n_alloc <<= 1;
        if (modules.n_alloc < 16)
            modules.n_alloc = 16;
        modules.entries = xrealloc (modules.entries, modules.n_alloc * sizeof (*modules.entries));
    }
    modules.entries[modules.n_entries++] = xstrdup (val);
    modules.entries[modules.n_entries] = NULL;
}

grub_install_push_module函数只是将模块名存入全局的modules变量的entries数组中,其中n_entries表示当前entries数组存储的长度,n_alloc表示数组的总长度,if语句判断数组是否已满,如果已满,就将数组的长度扩大一倍。

 类似资料: