Author:
jimmy.li
Date: 2007-06-12
-----------------------------------------
Linux
initrd
如果linux嵌入式系统采用initrd作为实际根文件系统可用之前的初始根文件系统,那么一般在bootloader引导过程中,除了会把kernel从flash中加载到RAM,同时也会把initrd映像从flash中加载到RAM中。那么kernel在加载这个initrd根文件系统时要去打开/dev/ram设备,然后要read里面的数据,以判断这个映象是什么文件系统(minix?
ext2?),这就需要内核知道initrd映像放置在内存中的位置和其大小。
本文重点不在分析内核是如何挂载initrd的,而是在于分析内核挂载initrd前,如何获取initrd映像在内存中的位置等相关数据结构信息。
在fixup_xxx中(xxx为你的平台名,添加新平台时由FIXUP宏指定)通过调用setup_ramdisk和setup_initrd函数对initrd文件被放置在内存中的位置及大小进行指定:
static void
__init
fixup_xxx(struct machine_desc *desc,
struct param_struct *params,
char **cmdline, struct
meminfo *mi)
{
SET_BANK (0, 0xa0000000,
64*1024*1024);
mi->nr_banks = 1;
setup_ramdisk (1, 0, 0,
2*1024);
setup_initrd
(__phys_to_virt(0xa1000000), 1*1024*1024);
ROOT_DEV =
MKDEV(RAMDISK_MAJOR,0);
} |
setup_ramdisk()定义于arch/arm/kernel/setup.c中:
void __init
setup_ramdisk(int doload, int prompt,
int image_start, unsigned int rd_sz)
{
#ifdef
CONFIG_BLK_DEV_RAM
extern int rd_size, rd_image_start,
rd_prompt, rd_doload;
rd_image_start =
image_start;
rd_prompt =
prompt;
rd_doload =
doload;
if (rd_sz)
rd_size =
rd_sz;
#endif
} |
其中的rd_doload,rd_image_start,rd_prompt变量定义于init/do_mounts.c:
int __initdata rd_doload;
/* 1 = load RAM disk, 0 = don't load */
int __initdata rd_image_start; /* starting block # of image
*/
int __initdata rd_prompt = 1;
/* 1 = prompt for RAM disk, 0 = don't prompt
*/ |
rd_size定义于ramdisk的驱动代码里(drivers/block/rd.c)
int rd_size = CONFIG_BLK_DEV_RAM_SIZE; /* Size of the RAM disks
*/ |
同样Setup_ramdisk()也是定义于arch/arm/kernel/setup.c中:
/*
* initial ram disk
*/
void __init setup_initrd(unsigned int
start, unsigned int size)
{
#ifdef
CONFIG_BLK_DEV_INITRD
if (start ==
0)
size =
0;
phys_initrd_start =
__virt_to_phys(start);
phys_initrd_size =
size;
#endif
}
|
其中phys_initrd_start和phys_initrd_size定义于arch/arm/kernel/setup.c中:
unsigned long phys_initrd_start __initdata
= 0;
unsigned long phys_initrd_size __initdata
= 0; |
phys_initrd_start表示initrd的起始位转置,它是一个物理地址;phys_initr_size表示initrd的大小,单位是字节。
内核对/dev/ram设备进行read时,会调用initrd_read函数。
static ssize_t initrd_read(struct file *file, char
*buf,
size_t count, loff_t
*ppos)
{
int left;
left = initrd_end - initrd_start -
*ppos;
if (count > left) count =
left;
if (count == 0) return
0;
if (copy_to_user(buf, (char *)initrd_start + *ppos,
count))
return
-EFAULT;
*ppos +=
count;
return count;
} |
其中initrd_start和initrd_end在drivers/block/rd.c中被定义:
unsigned long initrd_start, initrd_end; |
Initrd_start表示initrd映像在内存中的起始位置,而initrd_end则表示initrd映像在内存中的结束位置,这两个地址都是虚拟地址。看起来Initrd_start与前面的phys_inird_start应该是有联系的,只不过一个是虚拟地址,一个是物理地址,它们在arch/arm/mm/init.c中的bootmem_init中建立了联系:
/*
* Initialise the bootmem allocator for all
nodes. This is
called
* early during the architecture specific
initialisation.
*/
void __init bootmem_init(struct
meminfo *mi)
{
….
#ifdef
CONFIG_BLK_DEV_INITRD
if (phys_initrd_size &&
initrd_node >= 0) {
reserve_bootmem_node(NODE_DATA(initrd_node),
phys_initrd_start,
phys_initrd_size);
initrd_start =
__phys_to_virt(phys_initrd_start);
initrd_end = initrd_start +
phys_initrd_size;
}
#endif
if (map_pg != bootmap_pfn +
bootmap_pages)
BUG();
} |
加载 initrd 是通过调用initrd_load函数(init/do_mounts.c)来实现的。initrd_load函数调用了rd_load_image函数(init/do_mounts.c),它通过调用identify_ramdisk_image函数(init/do_mounts.c)来确定要加载哪个 RAM 磁盘。这个函数会检查映像文件的 magic 号来确定它是 minux、etc2、romfs、cramfs 或 gzip 格式。在返回到 initrd_load_image 之前,它还会调用crd_load函数(init/do_mounts.c),这个函数负责为 RAM 磁盘分配空间,并计算循环冗余校验码(CRC),然后对 RAM 磁盘映像进行解压,并将其加载到内存中。现在,我们在一个适合挂载的块设备中就有了这个 initrd 映像。