目前正在调试initrd,向其中添加initrd添加一些必要的动态库以及系统命令;使用chroot 命令测试也可以用。

但是放在设备上就是不能起来!

5.074563] registered taskstats version 1
[ 5.079539] Freeing unused kernel memory: 536k freed
[ 5.084648] Failed to execute /init
[ 5.088243] Kernel panic - not syncing: No init found. Try passing init= option to kernel. See Linux Documentation/init.txt for guidance.
[ 5.100858] Pid: 1, comm: swapper Not tainted 2.6.39-gentoo-r3-nsfocus #19
[ 5.107865] Call Trace:
[ 5.110352] [] panic+0x8c/0x19f
[ 5.115211] [] init_post+0xbf/0xbf
[ 5.120352] [] kernel_init+0x153/0x15a
[ 5.125864] [] kernel_thread_helper+0x4/0x10
[ 5.131895] [] ? start_kernel+0x375/0x375
[ 5.137666] [] ? gs_change+0x13/0x13


没有找到init进程,但是不对啊!!

chroot都能运行,也是有init进程的,应该是解压的问题了,怀疑是rootfs内存空间问题,于是将initrd.gz文件继续裁剪为5M就好了!!

但是当initrd.gz为36M时,还是存在问题,同时修改了ramdisk_size为 100M大小,还是存在问题!

分析 drivers/block/brd.c: 中的代码

unsigned long rd_size = CONFIG_BLK_DEV_RAM_SIZE;
module_param(rd_size, ulong, 0444);
MODULE_PARM_DESC(rd_size, "Size of each RAM disk in kbytes.");
[...]
/* Legacy boot options - nonmodular */
static int __init ramdisk_size(char *str)
{
rd_size = simple_strtol(str, NULL, 0);
return 1;
}
__setup("ramdisk_size=", ramdisk_size);

ramdisk_size有个默认值;其在make menuconfig的时候可以设置;目前默认是16M大小;但是按道理

kernel /boot/bzImage loglevel=7 ramdisk_size=512000

这个内存应该是足够的啊!!为啥还有问题呢? 难道是设置为512M太大了不能分配??

来分析一下:

首先弄清楚initrd initramfs的区别

initrd:   initrd 的英文含义是 boot loader iniTIalized RAM disk,就是由 boot loader 初始化的内存盘。在 linux内核启动前, boot loader 会将存储介质中的 initrd 文件加载到内存,内核启动时会在访问真正的文件系统前先访问该内存中的 initrd 文件系统。 内核在启动过程中会解压initrd ,软后挂载initrd为根目录,然后执行对应的脚本。 分cpio-initrd和 image-initrd cpio initrd (cpio包的gzip压缩文件) 内核将cpio initrd(由bootloader 将cpio initrd加载到内存) 释放到rootfs(/),结束内核对cpio initrd的操作 cpio initrd : bios->grub-kernel>cpio initrd(加载访问real rootfs的必备驱动等)->/init 脚本(加载real rootfs,并启动了init进程(/sbin/init)

initramfs:

作用和initrd类似,只是会和内核编译在一起,initramfs是经历过gzip压缩后的cpio格式数据文件,该文件被连接到内核的特殊数据段。

(内核+cpio包编译在一起然后一起进行内核压缩)内核文件包含了的一个cpio归档文件,该归档文件可能被外部的一个cpio包替换由initramfs里的/init 挂真实的根文件并启动init进程/sbin/init initramfs和cpio-initrd的区别, initramfs是将cpio rootfs编译进内核一起压缩,而 cpio-initrd中cpio rootfs是不编译入内核,是外部

initramfs存放在哪里?

从vmlinux.lds.h文件可知,ramfs根据CONFIG_BLK_DEV_INITRD定义是否使用。INIT_RAM_FS存放ramfs相关内容,包括.init.ramfs和.init.ramfs.info两个段。

#define INIT_DATA_SECTION(initsetup_align)                \
.init.data : AT(ADDR(.init.data) - LOAD_OFFSET) { \
INIT_DATA \
INIT_SETUP(initsetup_align) \
INIT_CALLS \
CON_INITCALL \
SECURITY_INITCALL \
INIT_RAM_FS \
}
#ifdef CONFIG_BLK_DEV_INITRD
#define INIT_RAM_FS \
. = ALIGN(4); \
VMLINUX_SYMBOL(__initramfs_start) = .; \
*(.init.ramfs) \
. = ALIGN(8); \
*(.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段

。 ramdisk的启动

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中。

rootfs和ramfs文件系统

rootfs其实不是一种实际的文件系统,他根据实际情况可能使用ramfs或者tmpfs。 rootfs文件系统

内核启动时:start_kernel()-->vfs_caches_init()-->mnt_init()中,注册rootfs类型的文件系统。

void __init mnt_init(void) { unsigned u; int err;

---------------------

/*sysfs依赖于kernfs,这里的初始化是为sysfs的初始化做准备,
sysfsroot 节点分配来之kernfs cache高速分配*/
kernfs_init();//当该函数执行完成,就分配了kernfs_node_cache缓存
err = sysfs_init(); //初始化并注册sysfs到linux内核中,注意sysfs先于rootfs注册
if (err)
printk(KERN_WARNING "%s: sysfs_init error: %d\n",
__func__, err);
fs_kobj = kobject_create_and_add("fs", NULL);///创建fs条目,并将其添加到/sys路径下
if (!fs_kobj)
printk(KERN_WARNING "%s: kobj create error\n", __func__);
init_rootfs();//注册rootfs ;1.
init_mount_tree(); //挂载rootfs;2.在fs/namespace.c中

}

如何初始化fs init_rootfs

static struct file_system_type rootfs_fs_type = { .name = "rootfs", .mount = rootfs_mount,//--->在后面init_mount_tree 的时候会执行vfs_kern_mount--->mount_fs;最后被调用 .kill_sb = kill_litter_super, }; //rootfs和我们说的initrd不是一个概念,rootfs是一个文件系统类型 int __init init_rootfs(void) { ////1.1注册rootfs_fs_type,即把rootfs_fs_type添加到全局变量中 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"))) {
err = shmem_init();
is_tmpfs = true;
} else {
err = init_ramfs_fs();//也将ramfs注册到系统中去
}

if (err)
unregister_filesystem(&rootfs_fs_type);

return err;

}

如何挂载?

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");
if (!type)
panic("Can't find rootfs type");

/*mount操作包括remount,move_mount, mount块设备,mountloop设备等,都由内核函数do_mount完成。 本质上,mount操作的过程就是通过 vfs_kern_mount新建一个vfsmount结构,然后将此结构和挂载点关联。 关联之后,目录查找时就能沿着vfsmount挂载点一级级向下查找文件了 */ mnt = vfs_kern_mount(type, 0, "rootfs", NULL);// 得到rootfs的vfsmount结构,设置里面的超级块等信息 -->2.1 put_filesystem(type); if (IS_ERR(mnt)) panic("Can't create rootfs");

ns = create_mnt_ns(mnt); // // 根据mnt新建一个mnt_namespace 创建一个命名空间
if (IS_ERR(ns))
panic("Can't allocate initial namespace");

init_task.nsproxy->mnt_ns = ns; //设置init_task的命名空间
get_mnt_ns(ns);

root.mnt = mnt;
root.dentry = mnt->mnt_root;
mnt->mnt_flags |= MNT_LOCKED;
/*/ 由于调init_mount_tree时init进程还没启动,
下面的current宏由最开始时设置的栈指针决定,应该就是init_task的taskstruct结构体。*/
set_fs_pwd(current->fs, &root);//设置init_task的当前目录
set_fs_root(current->fs, &root);//设置init_task的根目录

/*mount操作包括remount,move_mount, mount块设备,mountloop设备等,都由内核函数do_mount完成。
本质上,mount操作的过程就是通过 vfs_kern_mount新建一个vfsmount结构,然后将此结构和挂载点关联。
关联之后,目录查找时就能沿着vfsmount挂载点一级级向下查找文件了
*/

}

2.1 rootfs mount的过程

/*vfs_kern_mount主要完成三件事:

  1. alloc_vfsmnt创造一个新的struct mount结构
  2. 在mount_fs函数里调用特定文件系统的mount回调函数构造一个root dentry,包含特定文件系统的super block信息
  3. 用第二步得到的结果完成对struct mount的构造,返回vfsmnt结构。
par name:表示/dev/sda设备名称
*/
struct vfsmount *
vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)
{
struct mount *mnt;
struct dentry *root;
if (!type)
return ERR_PTR(-ENODEV);
// alloc一个新的struct mount结构,并初始化里面一部分(如链表指针、mnt_devname等成员内容
mnt = alloc_vfsmnt(name);
if (!mnt)
return ERR_PTR(-ENOMEM);
if (flags & MS_KERNMOUNT)
mnt->mnt.mnt_flags = MNT_INTERNAL;
/* 调用具体文件系统的mount回调函数type->mount,继续挂载操作 ;比如ext2_fs_type->ext2_mount
ext4_fs_type->ext4_mount/
/
ext2_fs_type->ext2_mount----->
ext4_fs_type->ext4_mount ---->
1. 

其最后调用的是 mount_bdev 以及其对应的ext2/ext4_fill_super 回调
*/
root = mount_fs(type, flags, name, data);// 调用具体的fs mount函数 2.1.1
if (IS_ERR(root)) {
mnt_free_id(mnt);
free_vfsmnt(mnt);
return ERR_CAST(root);
}
// 完成mnt结构的最后赋值,并返回vfsmount结构
mnt->mnt.mnt_root = root;//mount关联根dentry (使用vfsmount关联)
mnt->mnt.mnt_sb = root->d_sb;//mount关联超级块
mnt->mnt_mountpoint = mnt->mnt.mnt_root;// mount关联挂载点
mnt->mnt_parent = mnt;//父挂载指向自己 (临时指向 后面会设置
lock_mount_hash();
list_add_tail(&mnt->mnt_instance, &root->d_sb->s_mounts);
unlock_mount_hash();
return &mnt->mnt;
}

2.1.1 调用具体文件系统的mount

struct dentry *
mount_fs(struct file_system_type *type, int flags, const char *name, void *data)
{
struct dentry *root;
struct super_block *sb;
char *secdata = NULL;
int error = -ENOMEM;

//  //file_system_type rootfs_fs_type 中定义.mount = rootfs_mount,
root = type->mount(type, flags, name, data);
if (IS_ERR(root)) {
error = PTR_ERR(root);
goto out_free_secdata;
}
sb = root->d_sb;
BUG_ON(!sb);
WARN_ON(!sb->s_bdi);
sb->s_flags |= MS_BORN;
---------------------
}

2.1.2 调用mount_nodev

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;//后面会调用ramfs_fill_super,初始化super_block和root
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);//2.1.2

}

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);//2.1.3
if (error) {
deactivate_locked_super(s);
return ERR_PTR(error);
}
s->s_flags |= MS_ACTIVE;
return dget(s->s_root);

}

2.1.3 初始化super_block(ramfs_fill_super)并初始化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_CACHE_SIZE;
sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
sb->s_magic = RAMFS_MAGIC;
sb->s_op = &ramfs_ops;
sb->s_time_gran = 1;
//分配inode结构体并初始化inode->i_ino=1
inode = ramfs_get_inode(sb, NULL, S_IFDIR | fsi->mount_opts.mode, 0);
//初始化root的dentry结构体
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) {
static const struct qstr name = QSTR_INIT("/", 1);

res = __d_alloc(root_inode->i_sb, &name);
if (res)
d_instantiate(res, root_inode);
else
iput(root_inode);
}
return res;

}

所以:在内核启动是init_rootfs()首先根据参数来确定是使用tmpfs还是ramfs,然后在init_mount_tree()进行挂载;

后续在定义CONFIG_BLK_DEV_INITRD的情况下,调用populate_rootfs()将ramdisk解压到RAM中。

注意:加载initramfs, initramfs位于地址__initramfs_start处,是内核在编译过程中生成的,initramfs的是作为内核的一部分而存在的, 不是 boot loader加载的。前面提到了现在initramfs没有任何实质内容。一般都是外部的initrd是真正的实质的文件系统

在gzip解压后的数据经过复杂的mode跳转到不同函数处理buffer。

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,
};

最终还是通过内核中调用类似open()/write()/close()/mkdir()系统调用同样功能函数,创建完整的rootfs

ramdisk执行

kernel_init()是用户空间第一个进程,和ramdisk相关的有ramfs文件系统类型准备;ramdisk解压;启动ramdisk_execute_command来替代当前进程。

回到上述问题:目前看思路是没有问题的,目前还是怀疑内存分配的问题导致!!修改之后就好了