Chinaunix首页 | 论坛 | 博客
  • 博客访问: 404388
  • 博文数量: 120
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 741
  • 用 户 组: 普通用户
  • 注册时间: 2014-03-27 18:15
文章分类

全部博文(120)

文章存档

2016年(13)

2015年(41)

2014年(66)

我的朋友

分类: Android平台

2014-08-12 00:22:53

先大体上看一下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中
  1. static int __init parse_tag_initrd2(const struct tag *tag)
  2. {
  3.     //uboot传给内核的参数tag中保存了initramfs的起始地址(0x41000000)与大小
  4.     phys_initrd_start = tag->u.initrd.start;   //(物理地址)
  5.     phys_initrd_size = tag->u.initrd.size;
  6.     return 0;
  7. }
  8. __tagtable(ATAG_INITRD2, parse_tag_initrd2);
2. 将参数中的物理地址转为虚拟地址
start_kernel
    --> setup_arch
       --> arm_memblock_init
在arch/arm/mm/init.c中
  1. void __init arm_memblock_init(struct meminfo *mi, struct machine_desc *mdesc)
  2. {
  3.     for (i = 0; i < mi->nr_banks; i++)
  4.         memblock_add(mi->bank[i].start, mi->bank[i].size);

  5.     memblock_reserve(__pa(_stext), _end - _stext);     //将内核的代码段保留

  6.     if (phys_initrd_size) {
  7.         memblock_reserve(phys_initrd_start, phys_initrd_size);   //将initramfs的内存保留
  8.         initrd_start = __phys_to_virt(phys_initrd_start);        //将物理地址转为虚所地址
  9.         initrd_end = initrd_start + phys_initrd_size;
  10.     }
  11. }

二.文件系统的建立
在调用initramfs之前,系统中己经建立了虚拟的根文件系统("/").
  1. start_kernel
  2.   --> vfs_cache_init
  3.      --> mnt_init
  4.        --> init_rootfs();      //注册根文件系统这个类型
  5.        --> init_mount_tree();  //创建根文件系统"/"
  6.   --> rest_init //创建线程kernel_thread
  7.      --> do_basic_setup
  8.         --> populate_rootfs
在init/initramfs.c中
  1. static int __init populate_rootfs(void)
  2. {
  3.     unpack_to_rootfs(__initramfs_start, __initramfs_size); //2.1
  4.     unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start);  //2.2
  5.     free_initrd();        //将initramfs保留的内存释放
  6.     return 0;
  7. }
  8. 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中
  1. default_initramfs() {
  2.     cat <<-EOF >> ${output}
  3.         # This is a very simple, default initramfs

  4.         dir /dev 0755 0 0
  5.         nod /dev/console 0600 0 0 c 5 1
  6.         dir /root 0700 0 0
  7.         # file /kinit usr/kinit/kinit 0755 0 0
  8.         # slink /init kinit 0755 0 0
  9.     EOF
  10. }
说明initramfs_data.cpio中有/dev/ /root两个目录和/dev/console一个设备文件
unpack_to_rootfs会把这些文件都释放到 / 下
2.2第二次调用unpack_to_rootfs
  1. static char * __init unpack_to_rootfs(char *buf, unsigned len)
  2. {
  3.     int written, res;
  4.     decompress_fn decompress;
  5.     const char *compress_name;
  6.     static __initdata char msg_buf[64];

  7.     header_buf = kmalloc(110, GFP_KERNEL);
  8.     symlink_buf = kmalloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1, GFP_KERNEL);
  9.     name_buf = kmalloc(N_ALIGN(PATH_MAX), GFP_KERNEL);

  10.     state = Start;
  11.     this_header = 0;
  12.     message = NULL;
  13.     while (!message && len) {
  14.         loff_t saved_offset = this_header;
  15.         if (*buf == '0' && !(this_header & 3)) {   //通过判断buf[0]是否是字符'0',来判断是initramfs_data.cpio还是ramdisk.img
  16.             state = Start;                         //如果是initramfs_datap.cpio己加载到内存且开头是"070701",buf[0]=='0'
  17.             written = write_buffer(buf, len);      //如果是ramdisk.img己加载到内存且开头是0x1f, 0x8b,gzip压缩的
  18.             buf += written;
  19.             len -= written;
  20.             continue;
  21.         }
  22.         if (!*buf) {
  23.             buf++;
  24.             len--;
  25.             this_header++;
  26.             continue;
  27.         }
  28.         this_header = 0;
  29.         decompress = decompress_method(buf, len, &compress_name);    //根据buf[0],buf[1]查找表,得到压缩类型,及解压函数指针
  30.         //调用解压函数进行解压,同时回调函数flush_buffer,会将刚解压出来的文件填充到"/"下.
  31.         decompress(buf, len, NULL, flush_buffer, NULL, &my_inptr, error); 
  32.         this_header = saved_offset + my_inptr;
  33.         buf += my_inptr;
  34.         len -= my_inptr;
  35.     }
  36.     dir_utime();
  37.     kfree(name_buf);
  38.     kfree(symlink_buf);
  39.     kfree(header_buf);
  40.     return message;
  41. }

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
阅读(794) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~