ramdisk配置、解压、创建rootfs、启动简单分析
关键词:ramdisk、rdint、.init.ramfs、__initramfs_start、__initramfs_size、rootfs、ramfs、populate_rootfs()、gzip、actions[]、free_initmem()、run_init_process()等等。
本着了解ramdisk,带着如下几个问题进行分析:
- 如何打开ramdisk功能?
- ramdisk存放在哪里?
- ramdisk在什么时候解压?如何解压?
- 解压后ramdisk在什么位置?
- ramdisk如何启动?
1.如何打开ramdisk功能?
如果要使用ramdisk功能,需要做两步工作:一是修改Kernel的bootargs,增加rdinit选项;二是在编译uImage的时候将rootfs.cpio嵌入。
下面是使用ramdisk启动和使用eMMC作为启动介质的两种配置,ramdisk需要制定rdinit选项,并且root设备变成了/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嵌入到kernel image,可以通过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在内核中是如何被处理的,如果bootargs设置了rdinit和root,那么内核在启动阶段解析并分别赋给ramdisk_execute_command和saved_root_name。
在后面分析内核启动的过程中,这两个重要的参数会被用到。
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. ramdisk存放在哪里?
从vmlinux.lds.h文件可知,ramfs根据CONFIG_BLK_DEV_INITRD定义是否使用。
INIT_RAM_FS存放ramfs相关内容,包括.init.ramfs和.init.ramfs.info两个段。
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) {
...
} =
_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(); \
VMLINUX_SYMBOL(__initramfs_start) = .; \
KEEP(*(.init.ramfs)) \
. = ALIGN(); \
KEEP(*(.init.ramfs.info))
#else
#define INIT_RAM_FS
#endif
.init.ramfs和.init.ramfs.info两个段在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)文件。
最终通过.incbin将INITRAMFS_IMAGE编译到initramfs_data.o文件中,即对应.init.ramfs段。
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如何启动?
ramfs作为init数据的一部分,位于__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.1 initrd_start和initrd_end解析
在start_kernel()之前,从dts中解析出initrd和root相关参数。
调用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/); prop = of_get_flat_dt_prop(node, "linux,initrd-end", &len);
if (!prop)
return;
end = of_read_number(prop, len/); __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()可知,如果dts中没有设置"linux,initrd-start"和"linux,initrd-end",那么initrd_start和initrd_end这两个参数都是原始值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 = ;
}
#endif
3.2 rootfs和ramfs文件系统
rootfs其实不是一种实际的文件系统,他根据实际情况可能使用ramfs或者tmpfs。
这里分析rootfs是如何对应ramfs,并且简单介绍ramfs。
3.2.1 rootfs文件系统
在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[] &&
(!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, , "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(, &once))
return ERR_PTR(-ENODEV); if (IS_ENABLED(CONFIG_TMPFS) && is_tmpfs)
fill = shmem_fill_super; returnmount_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 ? : );------调用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 = ; inode = ramfs_get_inode(sb, NULL, S_IFDIR | fsi->mount_opts.mode, );
sb->s_root =d_make_root(inode);-----------------------创建根节点"/"。
if (!sb->s_root)
return -ENOMEM; return ;
} 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.2 ramfs文件系统
ramfs根据请求的mode类型选择合适的inode或者file操作类型。
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,
};
根据inode->i_mode不同类型,采取不同inode->i_fop和inode->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.4 ramfs的解压
rootfs_initcall()在没有定义CONFIG_BLK_DEV_INITRD的情况下,调用default_rootfs()。
default_rootfs()主要生成两个目录/dev和/root,以及一个设备文件/dev/console。
static int __init default_rootfs(void)
{
int err; err = sys_mkdir((const char __user __force *) "/dev", );
if (err < )
goto out; err = sys_mknod((const char __user __force *) "/dev/console",
S_IFCHR | S_IRUSR | S_IWUSR,
new_encode_dev(MKDEV(, )));
if (err < )
goto out; err = sys_mkdir((const char __user __force *) "/root", );
if (err < )
goto out; return ; out:
printk(KERN_WARNING "Failed to create a rootfs\n");
return err;
}
在定义CONFIG_BLK_DEV_INITRD的情况下,调用populate_rootfs()将ramdisk解压到RAM中。
unpack_to_rootfs()根据参数__initramfs_start和__initramfs_size,从头部获取decompress的类型;然后调用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, );--------------------------创建文件/initrd.image。
if (fd >= ) {
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 ;
} 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[]; header_buf = kmalloc(, GFP_KERNEL);
symlink_buf = kmalloc(PATH_MAX + N_ALIGN(PATH_MAX) + , 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 = ;
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 decompressor
内核中支持的decompressor用struct compress_format表示,核心是decompress_fn()函数。
struct compress_format {
unsigned char magic[];
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头部两字节来判断对应空间所采取的decompressor。
decompressed_formats[]保存了系统支持的decompressor类型。
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 },
{ {, }, 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[], inbuf[]); for (cf = compressed_formats; cf->name; cf++) {
if (!memcmp(inbuf, cf->magic, ))------------------------遍历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, , 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 = -;
if (flush) {
out_len = 0x8000; /* 32 K */
out_buf = malloc(out_len);-----------------------以32K为单位进行处理。
} else {
if (!out_len)
out_len = ((size_t)~) - (size_t)out_buf; /* no limit */
}
...
while (rc == Z_OK) {
...
rc = zlib_inflate(strm, ); /* 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 = -;
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 = ;
break;
} else if (rc != Z_OK) {
error("uncompression error");
rc = -;
}
} zlib_inflateEnd(strm);
if (pos)
/* add + 8 to skip over trailer */
*pos = strm->next_in - zbuf+; 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_start开始__initramfs_size大小的内存刷成rootfs文件系统的。
flush_buffer()调用write_buffer进行处理,这里一个核心是通过不同状态机state调用不同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 -;
while ((written = write_buffer(buf, len)) < len && !message) {
...
}
return origLen;
}
actions[]可以说是将解压后数据转换并生成rootfs的核心。
actions[]调用相应的系统调用,按照解压数据一步一步生成整个文件系统。
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, , GotHeader);----------------------读取开头110字节,用于解析cpio文件头。
return ;
} 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) != )
return ;
state = next_state;
return ;
} static int __init do_header(void)
{
if (memcmp(collected, "", )==) {---------------------cpio文件的magic,开头6个字节“070707”或者“070701”。
error("incorrect cpio method used: use -H newc option");
return ;
}
if (memcmp(collected, "", )) {
error("no cpio magic");
return ;
}
parse_header(collected);
next_header = this_header + N_ALIGN(name_len) + body_len;
next_header = (next_header + ) & ~;
state = SkipIt;
if (name_len <= || name_len > PATH_MAX)
return ;
if (S_ISLNK(mode)) {
if (body_len > PATH_MAX)
return ;
collect = collected = symlink_buf;
remains = N_ALIGN(name_len) + body_len;
next_state = GotSymlink;
state = Collect;
return ;
}
if (S_ISREG(mode) || !body_len)
read_into(name_buf, N_ALIGN(name_len), GotName);
return ;
} static int __init do_skip(void)
{
if (this_header + byte_count < next_header) {
eat(byte_count);
return ;
} else {
eat(next_header - this_header);
state = next_state;
return ;
}
} static int __init do_reset(void)
{
while (byte_count && *victim == '\0')
eat();
if (byte_count && (this_header & ))
error("broken padding");
return ;
} static int __init maybe_link(void)
{
if (nlink >= ) {
char *old = find_link(major, minor, ino, mode, collected);
if (old)
return (sys_link(old, collected) < ) ? - : ;
}
return ;
} 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!!!") == ) {
free_hash();
return ;
}
clean_path(collected, mode);
if (S_ISREG(mode)) {---------------------------------------如果是一个普通文件,调用sys_open()创建文件,并且通过sys_fchown()和sys_fchmod()等进行属性修改。
int ml = maybe_link();
if (ml >= ) {
int openflags = O_WRONLY|O_CREAT;
if (ml != )
openflags |= O_TRUNC;
wfd = sys_open(collected, openflags, mode); if (wfd >= ) {
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() == ) {
sys_mknod(collected, mode, rdev);
sys_chown(collected, uid, gid);
sys_chmod(collected, mode);
do_utime(collected, mtime);
}
}
return ;
} 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 ;
} else {
if (xwrite(wfd, victim, byte_count) != byte_count)
error("write error");
body_len -= byte_count;
eat(byte_count);
return ;
}
} static int __init do_symlink(void)
{
collected[N_ALIGN(name_len) + body_len] = '\0';
clean_path(collected, );
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 ;
}
通过上面一系列actions[]函数可知,gzip解压后的数据经过复杂的mode跳转到不同函数处理buffer。
最终还是通过内核中调用类似open()/write()/close()/mkdir()系统调用同样功能函数,创建完整的rootfs。
3.5 释放init内存
在所有的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) >> );
}
由于存放ramdisk的段.init.ramfs在__init_begin和__init_end之间,所有也会被一同释放。
3.6 ramdisk执行
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 ;
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.");
}
kernel_init_freeable()中注册ramfs文件系统类型,并且将vmlinux中__initramfs_start开始__initramfs_size大小的代码解压到rootfs。
然后sys_access()检查rootfs中是否存在ramdisk_execute_command,没有则需要prepare_namespace()准备rootfs。
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, ) != ) {
ramdisk_execute_command = NULL;
prepare_namespace();
}
...
}
run_init_process()根据init_filename从rootfs中启动,替代当前进程,作为用户空间第一个进程。
static int run_init_process(const char *init_filename)
{
argv_init[] = init_filename;
returndo_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, );
} 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(¤t_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 = ; 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[] == '/') {
bprm->filename = filename->name;
} else {
if (filename->name[] == '\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) < )
goto out; bprm->envc = count(envp, MAX_ARG_STRINGS);
if ((retval = bprm->envc) < )
goto out; retval = prepare_binprm(bprm);
if (retval < )
goto out; retval = copy_strings_kernel(, &bprm->filename, bprm);
if (retval < )
goto out; bprm->exec = bprm->p;
retval = copy_strings(bprm->envc, envp, bprm);
if (retval < )
goto out; retval = copy_strings(bprm->argc, argv, bprm);
if (retval < )
goto out; would_dump(bprm, bprm->file); retval = exec_binprm(bprm);
if (retval < )
goto out; /* execve succeeded */
current->fs->in_exec = ;
current->in_execve = ;
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, );
mmput(bprm->mm);
} out_unmark:
current->fs->in_exec = ;
current->in_execve = ; out_free:
free_bprm(bprm);
kfree(pathbuf); out_files:
if (displaced)
reset_files_struct(displaced);
out_ret:
putname(filename);
return retval;
}
4. 小结
综上所述,在buildroot或者kernel编译时打开ramdisk功能后,ramdisk会嵌入在vmlinux中。
在Linux启动阶段,通过populate_rootfs()将ramdisk从代码中读出。然后调用gzip decompressor解压到RAM中,解压后的数据经过actions[]解析转换成rootfs文件系统。
在init初始化完成后,ramfs相关内存随着init内存一起释放,回归totalram_pages。
在kernel_init()最后阶段通过run_init_process()执行ramdisk中的init进程,作为用户空间第一个进程。
ramdisk配置、解压、创建rootfs、启动简单分析的更多相关文章
- linux系统下安装配置解压版的MySQL数据库
一.解压文件到当前目录 命令:tar -zxvf mysql....tar.gz 二.移动解压完成的文件夹到目标目录并更名mysql 命令:mv mysql-版本号 /usr/local/mysql ...
- mysql解压版服务启动方式
使用mysql解压版,在不安装为windows服务时,使用下面的方式启动. 1.打开命令行,首先进入mysql解压目录的bin目录下 d:\mysql\bin 2.输入mysqld --console ...
- linux下的压缩解压命令 tar 的简单描述
命令名称:tar 命令所在路径:/bin/tar 语法:tar选项·「-zcf」·「压缩后文件名」「目录」 -c 打包 -v 显示详细信息 -f 指定文件名 -z 打包同时压缩 tar命令解压缩语法: ...
- ref:Spring Integration Zip 不安全解压(CVE-2018-1261)漏洞分析
ref:https://mp.weixin.qq.com/s/SJPXdZWNKypvWmL-roIE0Q 0x00 漏洞概览 漏洞名称:Spring Integration Zip不安全解压 漏洞编 ...
- busybox rootfs 启动脚本分析(一)
imx6文件系统启动脚本分析.开机运行/sbin/init,读取/etc/inittab文件,进行初始化. 参考链接 http://blog.163.com/wghbeyond@126/blog/st ...
- busybox rootfs 启动脚本分析(二)
上次分析了busybox的启动脚本,这次分析一下init.d中一些脚本的内容. 参考链接 http://www.cnblogs.com/helloworldtoyou/p/6169678.html h ...
- Mysql5.7.26解压版(免安装版)简单快速配置步骤,5分钟搞定(win10-64位系统)
第一次安装mysql环境的时候,总会遇到各种各样的坑,在尝试了安装版和解压版的数据库之后,感觉mysql的解压版更加的简单方便,省去好多时间做专业的事情 我这里选择的是5.7.26版本,解压版下载地址 ...
- shell 脚本解压war包+备份+tomcat自动关闭+启动
公司的开发环境每次替换war包时候,老是需要重新上传并且手动解压,然后再去重启tomcat.觉得这样子太麻烦了,于是写了一个shell脚本,自动解压+备份+tomcat自动关闭+启动.代码如下: #关 ...
- Windows系统下MySQL添加到系统服务方法(mysql解压版)
MySQL软件版本:64位 5.7.12 1.首先配置MySQL的环境变量,在系统环境变量Path的开头添加MySQL的bin目录的路径,以“;”结束,我的路径配置如下: 2.修改MySQL根目录下的 ...
随机推荐
- JavaScript中常用的字符串方法
1. charAt(x) charAt(x)返回字符串中x位置的字符,下标从 0 开始. //charAt(x) var myString = 'jQuery FTW!!!'; console.log ...
- React中refs持久化
根据使用React的版本,选择合适的方法. 字符串模式 :废弃不建议使用 回调函数,React版本 < 16.3 React.createRef() :React版本 >= 16.3 回调 ...
- 如何Windows下配置Prometheus的监控数据文件为3天
如上图,prometheus的data文件夹时间久了会变得很大,听说是保留15天的数据.但是实际上,我只需要保留3天的数据就够了,之前试过用批处理文件清理,但是强行删除会导致peometheus崩溃, ...
- Hyperledger Fabric:最简单的方式测试你的链码
一直以来,写完链码进行测试都要先搭建一个Fabric环境,然后安装链码进行测试,实际上Fabric提供了最为简单的方式可以允许我们对编写的应用链码进行功能测试,不需要搭建一个完整的Fabeic环境.而 ...
- Eureka+SpringBoot2.X版本实现优雅停服
在客户端添加如下配置 pom依赖 actuator.jar包 <dependency> <groupId>org.springframework.cloud</group ...
- Ligg.EasyWinApp-102-Ligg.EasyWinForm:Function--ControlBox、Tray、Resize、Menu
首先请在VS里打开下面的文件,我们将对源码分段进行说明: Function(功能):一个应用的功能界面,一个应用对应多个Function(功能):如某应用可分为管理员界面.用户界面. 首先我们来看一下 ...
- Redis Python(二)
Infi-chu: http://www.cnblogs.com/Infi-chu/ 一.NoSQL(Not only SQL)1.泛指非关系数据库2.不支持SQL语法3.存储结构与传统的关系型数据库 ...
- 关于for循环中使用setTimeout的四种解决方案
我们先来简单了解一下setTimeout延时器的运行机制.setTimeout会先将回调函数放到等待队列中,等待区域内其他主程序执行完毕后,按时间顺序先进先出执行回调函数.本质上是作用域的问题. 因此 ...
- C#报Lc.exe已退出 代码为-1 错误解决方法
解决方法一:用记事本打开*.licx,里面写的全是第三方插件的指定DLL,删除错误信息,保存,关闭,重新生成解决方案. 解决方法二:把项目文件夹下Properties文件夹下的licenses.lic ...
- 邬江兴院士:工业互联网安全&拟态防御
尊敬的郑院士.曹书记.张秘书长,各位学术界的同仁们,很高兴在第一届工业互联网学术专题论坛上发言.我今天想谈的问题是工业互联网,这个概念很热,前景也很美好,很诱人.但是我认为工业互联网的安全挑战更严峻, ...