先大体上看一下vmlinux的启动流程
在/work/ct/lichee/linux-3.3/arch/arm/kernel/vmlinux.lds.S中标明了内始的起始位置:
ENTRY(stext)
a. 开始__lookup_processor_type,查找cpu的型号
b. __create_page_tables创建页表
c.把开启mmu后的跳转地址__mmap_switched放在r13中
d.__enable_mmu开启mmu
e. 执行 __mmap_switched
在/work/ct/lichee/linux-3.3/arch/arm/kernel/head-common.S中
a. 清bss
b. 设置sp
c. 将start_kernel的参数,保存一下
d. 跳到start_kernel中执行
一. initramfs的地址传递
1. 解析initramfs的大小与地址
从u-boot传给内核的参数中解析出initramfs在内存中的地址与大小
在arch/arm/mm/init.c中
-
static int __init parse_tag_initrd2(const struct tag *tag)
-
{
-
//uboot传给内核的参数tag中保存了initramfs的起始地址(0x41000000)与大小
-
phys_initrd_start = tag->u.initrd.start; //(物理地址)
-
phys_initrd_size = tag->u.initrd.size;
-
return 0;
-
}
-
__tagtable(ATAG_INITRD2, parse_tag_initrd2);
2. 将参数中的物理地址转为虚拟地址
start_kernel
--> setup_arch
-->
arm_memblock_init
在arch/arm/mm/init.c中
-
void __init arm_memblock_init(struct meminfo *mi, struct machine_desc *mdesc)
-
{
-
for (i = 0; i < mi->nr_banks; i++)
-
memblock_add(mi->bank[i].start, mi->bank[i].size);
-
-
memblock_reserve(__pa(_stext), _end - _stext); //将内核的代码段保留
-
-
if (phys_initrd_size) {
-
memblock_reserve(phys_initrd_start, phys_initrd_size); //将initramfs的内存保留
-
initrd_start = __phys_to_virt(phys_initrd_start); //将物理地址转为虚所地址
-
initrd_end = initrd_start + phys_initrd_size;
-
}
-
}
二.文件系统的建立
在调用initramfs之前,系统中己经建立了虚拟的根文件系统("/").
-
start_kernel
-
--> vfs_cache_init
-
--> mnt_init
-
--> init_rootfs(); //注册根文件系统这个类型
-
--> init_mount_tree(); //创建根文件系统"/"
-
--> rest_init //创建线程kernel_thread
-
--> do_basic_setup
-
--> populate_rootfs
在init/initramfs.c中
-
static int __init populate_rootfs(void)
-
{
-
unpack_to_rootfs(__initramfs_start, __initramfs_size); //2.1
-
unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start); //2.2
-
free_initrd(); //将initramfs保留的内存释放
-
return 0;
-
}
-
rootfs_initcall(populate_rootfs);
populate_rootfs会两次调用unpack_to_rootfs把initramfs_datp.cpio与ramdisk.img释放到/下.
2.1 第一次调用unpack_to_rootfs
其中第一个unpack_to_rootfs是把initramfs_data.cpio解出来
cong@dell:/work/ct/lichee$ ls -l ./linux-3.3/usr/initramfs_data.cpio
-rw-rw-r-- 1 cong cong 512 May 20 17:00 ./linux-3.3/usr/initramfs_data.cpio
initramfs_data.cpio是链接在内核中的,
其地址__initramfs_start是在./arch/arm/kernel/vmlinux.lds中定义 __initramfs_start = .
其大小__initramfs_size就是initramfs_data.cpio这个文件的大小
在linux-3.3/scripts/gen_initramfs_list.sh中
-
default_initramfs() {
-
cat <<-EOF >> ${output}
-
# This is a very simple, default initramfs
-
-
dir /dev 0755 0 0
-
nod /dev/console 0600 0 0 c 5 1
-
dir /root 0700 0 0
-
# file /kinit usr/kinit/kinit 0755 0 0
-
# slink /init kinit 0755 0 0
-
EOF
-
}
说明initramfs_data.cpio中有/dev/ /root两个目录和/dev/console一个设备文件
unpack_to_rootfs会把这些文件都释放到 / 下
2.2第二次调用unpack_to_rootfs
-
static char * __init unpack_to_rootfs(char *buf, unsigned len)
-
{
-
int written, res;
-
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);
-
-
state = Start;
-
this_header = 0;
-
message = NULL;
-
while (!message && len) {
-
loff_t saved_offset = this_header;
-
if (*buf == '0' && !(this_header & 3)) { //通过判断buf[0]是否是字符'0',来判断是initramfs_data.cpio还是ramdisk.img
-
state = Start; //如果是initramfs_datap.cpio己加载到内存且开头是"070701",buf[0]=='0'
-
written = write_buffer(buf, len); //如果是ramdisk.img己加载到内存且开头是0x1f, 0x8b,gzip压缩的
-
buf += written;
-
len -= written;
-
continue;
-
}
-
if (!*buf) {
-
buf++;
-
len--;
-
this_header++;
-
continue;
-
}
-
this_header = 0;
-
decompress = decompress_method(buf, len, &compress_name); //根据buf[0],buf[1]查找表,得到压缩类型,及解压函数指针
-
//调用解压函数进行解压,同时回调函数flush_buffer,会将刚解压出来的文件填充到"/"下.
-
decompress(buf, len, NULL, flush_buffer, NULL, &my_inptr, error);
-
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;
-
}
unpack_to_rootfs
--> write_buffer
--> while (!actions[state]())
count = len; //剩余initram的长度
victim = buf; //初始指向initram的首地址,解析时指向的位置依次递增
this_header = 0;
state = Start
do_start
如果剩余的数据长度足够
collected=victim; //指向initram的文件头
victim+=110, this_header+=110, count-=110;
state = GotHeader
如果剩余的数据长度不够
下一步调用do_collect将这不足的数据存在临时buff中,return 1,即结束action的循环,
等有数据之后,当前状态为collect,即再调一次collect将不足的数据补齐,之后再进入下一状态
do_header
判断前6个字节是 "070701" (cpio的头标志)
解析header中的信息相应的全局变量中
获取下一个header的位置 next_header
state=SkipIt
判断文件是符号链接还是普通文件
符号: next_state=GotSymlink state=Collect
文件: collected指向文件名字符串, victim+=sizeof(name) this_header+=sizeof(name),
count-=sizeof(name) state=GotName
do_collect(符号连接)
将符号连接的名字memcpy到collect中(即symblink_buf中)
state = next_sate=GotSymlink
do_symlink
清路径上的文件,并在路径上建立符号连接
state = SkipIt; next_state = Reset;
do_name (若是文件,非符号连接)
判断是不是结尾
若路径上己有文件,则清除己有的文件
若是普通文件,则在该路径上创建文件类似于touch, state = CopyFile;
若是目录,则创建目录 state = SkipIt; next_state = Reset;
若是设备文件(或FIFO等)则创建 state = SkipIt; next_state = Reset;
do_copy
将文件拷贝到/路径下
do_skip
eat(next_header-this_header); state=Reset
do_reset
注:
1. 把文件拷贝到/下,有点不好理解:
其实这类似于上层应用程序调用open, write,close,其实也就是调用内核层的sys_open,sys_write, sys_close
2. 这儿的sys_write(wfd, victim, body_len);其实wfd与victim都是放在内存中,只不过wfd所代表的文件没有放在/的路径下,
sys_write就是把wfd写到/的指定路径下.
附录1. 关于unpack_to_rootfs中的三个buf
header_buf symlink_buf 与 name_buf的作用:
要知道 header_buf symlink_buf name_buf,这三个buf的作用需要对cpio的格式有所了解.
CPIO 的结构是这样的:
110字节的Head(6 + 8*13)
不定长的文件名(文件名的长度是olen)
结束字符 \0
文件的内容
.... //重复上面4个
最后的文件名是一个 TRAILER!!!
了解了cpio的格式之后就很容易知道:
1. header_buf就是一个文件在cpio中的head,共110个字节
header_buf = kmalloc(110, GFP_KERNEL);
2. symlink_buf就是符号链接所指的路径
假设有这样的符号连接: ln -s /home/cong/Desktop/record.txt link
symlink_buf="/home/cong/Desktop/record.txt"
3. name_buf 就是文件名(除符号链接之外的文件名),
可以是普通文件名,目录名,FIFO名等反正就是存名字
这些都很容易理解, 就是有一点需要注意: 什么时候会用到这些buffer?
以header_buf为例说明一下:
内核两次调用了unpack_to_rootfs,
第一次解压usr/initramfs_data.cpio,这时候head_buff压根就没有用到.
第二次是解压initramfs,这个rootfs是gzip压缩的,内核会一边解压缩initramfs一边把解压出来的文件填充到文件系统中.
解压后并不是每一次都把110个字节拷贝到header_buf中,这样没有必要
而是在gunzip中每次解压出的32K的buffer, 只有当这32k剩下的数据是cpio的头且不足110个字节时,才会用到header_buf.
先将不足的字节临时放在header_buf中,然后再调用gunzip,从解出32K的buffer中拿走剩下的字节填充到header_buf中凑満110个字节.
symlink_buf, name_buf同样也是这种情况下才使用.
附录2:关于宏__tagtable
lichee/linux-3.3/arch/arm/include/asm/setup.h