[Bug]Linux內核啟動過程中,ramdisk加載失敗,系統崩潰
Bug描述:Linux內核啟動過程中,ramdisk加載失敗,系統崩潰
日志信息:
- RAMDISK: Couldn't find valid RAM disk image starting at 0.
- UDF-fs: No partition found (1)
- NILFS: Can't find nilfs on dev ram0.
- (1,15):ocfs2_fill_super:1001 ERROR: superblock probe
- VFS: Cannot open root device "ram0" or unknown-block(1,0)
- Please append a correct "root=" boot option; here are the available partitions:
- 0800 8003520 sda driver: sd
- 0801 14048 sda1
- 0804 1 sda4
- 0805 393057 sda5
- 0806 102400 sda6
- 0807 7488690 sda7
- Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(1,0)
- Unable to load '/system/dump '.
- Pid: 1, comm: swapper Not tainted 2.6.32.15-hermes-1 #23
- Call Trace:
- UTC time : 2005-1-1 0:14:52
- [] panic+0x7a/0x12d
- [] mount_block_root+0x257/0x275
- [] mount_root+0x56/0x5a
- [] prepare_namespace+0x16b/0x198
- [] kernel_init+0x178/0x188
- [] child_rip+0xa/0x20
- [] ? kernel_init+0x0/0x188
- [] ? child_rip+0x0/0x20
首先看到Couldn't find valid RAM disk image starting at 0.
順籐摸瓜,找到打印此信息的代碼:
- static int __init
- identify_ramdisk_image(int fd, int start_block, decompress_fn *decompressor)
- {
- const int size = 512;
- struct minix_super_block *minixsb;
- struct ext2_super_block *ext2sb;
- struct romfs_super_block *romfsb;
- struct cramfs_super *cramfsb;
- struct squashfs_super_block *squashfsb;
- int nblocks = -1;
- unsigned char *buf;
- const char *compress_name;
- int i = 0;
- buf = kmalloc(size, GFP_KERNEL);
- if (!buf)
- return -1;
- minixsb = (struct minix_super_block *) buf;
- ext2sb = (struct ext2_super_block *) buf;
- romfsb = (struct romfs_super_block *) buf;
- cramfsb = (struct cramfs_super *) buf;
- squashfsb = (struct squashfs_super_block *) buf;
- memset(buf, 0xe5, size);
- /*
- * Read block 0 to test for compressed kernel
- */
- sys_lseek(fd, start_block * BLOCK_SIZE, 0);
- sys_read(fd, buf, size);
- // Eric Ju Jul 27th 2016
- printk("start_block:%d\n",start_block);
- for(i=0;i
- printk("0x%x ",*(buf+i));
- printk("\n");
- *decompressor = decompress_method(buf, size, &compress_name);
- if (compress_name) {
- printk(KERN_NOTICE "RAMDISK: %s image found at block %d\n",
- compress_name, start_block);
- if (!*decompressor)
- printk(KERN_EMERG
- "RAMDISK: %s decompressor not configured!\n",
- compress_name);
- nblocks = 0;
- goto done;
- }
- /* romfs is at block zero too */
- if (romfsb->word0 == ROMSB_WORD0 &&
- romfsb->word1 == ROMSB_WORD1) {
- printk(KERN_NOTICE
- "RAMDISK: romfs filesystem found at block %d\n",
- start_block);
- nblocks = (ntohl(romfsb->size)+BLOCK_SIZE-1)>>BLOCK_SIZE_BITS;
- goto done;
- }
- if (cramfsb->magic == CRAMFS_MAGIC) {
- printk(KERN_NOTICE
- "RAMDISK: cramfs filesystem found at block %d\n",
- start_block);
- nblocks = (cramfsb->size + BLOCK_SIZE - 1) >> BLOCK_SIZE_BITS;
- goto done;
- }
- /* squashfs is at block zero too */
- if (le32_to_cpu(squashfsb->s_magic) == SQUASHFS_MAGIC) {
- printk(KERN_NOTICE
- "RAMDISK: squashfs filesystem found at block %d\n",
- start_block);
- nblocks = (le64_to_cpu(squashfsb->bytes_used) + BLOCK_SIZE - 1)
- >> BLOCK_SIZE_BITS;
- goto done;
- }
- /*
- * Read block 1 to test for minix and ext2 superblock
- */
- sys_lseek(fd, (start_block+1) * BLOCK_SIZE, 0);
- sys_read(fd, buf, size);
- /* Try minix */
- if (minixsb->s_magic == MINIX_SUPER_MAGIC ||
- minixsb->s_magic == MINIX_SUPER_MAGIC2) {
- printk(KERN_NOTICE
- "RAMDISK: Minix filesystem found at block %d\n",
- start_block);
- nblocks = minixsb->s_nzones << minixsb->s_log_zone_size;
- goto done;
- }
- /* Try ext2 */
- if (ext2sb->s_magic == cpu_to_le16(EXT2_SUPER_MAGIC)) {
- printk(KERN_NOTICE
- "RAMDISK: ext2 filesystem found at block %d\n",
- start_block);
- nblocks = le32_to_cpu(ext2sb->s_blocks_count) <<
- le32_to_cpu(ext2sb->s_log_block_size);
- goto done;
- }
- printk(KERN_NOTICE
- "RAMDISK: Couldn't find valid RAM disk image starting at %d.\n",
- start_block);
- done:
- sys_lseek(fd, start_block * BLOCK_SIZE, 0);
- kfree(buf);
- return nblocks;
- }
可以看到,打印此日志,是由於本函數中所有的分支都沒有成功匹配。正常情況下,該函數應該走入第一個分支,並跳轉至done處。
為什麼沒有走入第一個分支呢?猜測fd應該是指向initrd的文件描述符,第一個分支之前的read應該為讀取initrd的第一個扇區內容,並進行magic比較,當匹配成功,說明initrd為正確的鏡像文件,並調用相應解壓函數進行解壓縮。通過打印讀取出的buf內容來確認,initrd文件是否正確。經過實驗,打印內容全部為0xFF,證明該initrd文件錯誤。
為什麼initrd文件會錯誤呢?磁盤上initrd.img文件都是正確的。繼續跟蹤identify_ramdisk_image的調用處,看看fd到底是什麼?經過跟蹤,發現以下函數,位於內核源碼/init/do_mounts_initrd.c中。
- int __init initrd_load(void)
- {
- if (mount_initrd) {
- create_dev("/dev/ram", Root_RAM0);
- /*
- * Load the initrd data into /dev/ram0. Execute it as initrd
- * unless /dev/ram0 is supposed to be our actual root device,
- * in that case the ram disk is just set up here, and gets
- * mounted in the normal path.
- */
- if (rd_load_image("/initrd.image") && ROOT_DEV != Root_RAM0) {
- sys_unlink("/initrd.image");
- handle_initrd();
- return 1;
- }
- }
- sys_unlink("/initrd.image");
- return 0;
- }
initrd.image文件?不對啊,我們磁盤上的initrd文件名為initrd.img怎麼會變為initrd.image呢?而且路徑也不對。猜測,initrd.image文件應該是有啟動部分代碼創建了符號鏈接到了initrd.img上。繼續查找initrd.image的創建是在哪裡?找到如下代碼,位於/init/initramfs.c
- static int __init populate_rootfs(void)
- {
- int i=0;
- char *err = unpack_to_rootfs(__initramfs_start,
- __initramfs_end - __initramfs_start);
- if (err)
- panic(err);/* Failed to decompress INTERNAL initramfs */
- if (initrd_start) {
- #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);
- if (!err) {
- free_initrd();
- return 0;
- } else {
- clean_rootfs();
- unpack_to_rootfs(__initramfs_start,
- __initramfs_end - __initramfs_start);
- }
- printk(KERN_INFO "rootfs image is not initramfs (%s)"
- "; looks like an initrd\n", err);
- fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 0700);
- if (fd >= 0) {
- sys_write(fd, (char *)initrd_start,
- initrd_end - initrd_start);
- sys_close(fd);
- free_initrd();
- }
- #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
- }
- return 0;
- }
在這裡創建了initrd.image文件。另外還有一句:sys_write(fd, (char *)initrd_start,initrd_end - initrd_start); 看來是內核從內存中將相關數據寫到/init.image中的。並非軟鏈接。那initrd_start又是哪裡呢?哪裡來的數據呢?將initrd_start嘗試打印後,發現initrd_start為0xffff880000100000,估計是已經轉換完的虛擬地址。既然知道initrd.image是從內存寫入根文件系統的,那麼一定有其他程序將我們的initrd.img讀入內存。initrd.img是在哪裡被讀入內存的呢?這個文件的路徑是在哪裡被提供的呢?想起來,lilo.multi.conf文件中有指定initrd.img文件的路徑。那一定是lilo在啟動時,將initrd.img讀入內存,並將地址傳遞給內核。那就繼續查看串口日志。在剛剛啟動的日志中有如下信息:
- RAMDISK: 7fa36000 - 7ffff40e
- Allocated new RAMDISK: 00100000 - 006c940e
- Move RAMDISK from 000000007fa36000 - 000000007ffff40d to 00100000 - 006c940d
可以看到,RAMDISK的起始地址為0x7fa36000,那是不是剛剛的虛擬地址就是從這個物理地址轉換過去的呢?仔細看第三行,貌似內核將RAMDISK的內容移動到了0x00100000地址處。在對比剛的虛擬地址0xffff880000100000,可以確定該虛擬地址一定是從0x00100000處映射的。因為內核在物理低地址處地址映射的習慣是,設定虛擬高端地址後,偏移實際的物理地址。那麼打印以下RAMDISK被移動之前、移動之後的內容,看看是不是移動時除了錯誤。結果發現RAMDISK被移動之前就是0xFF。可以斷定,LILO將initrd.img時就已經錯了。從0xFF上看,物理內存應該是沒有被寫過,是上電後的初始狀態。突然想到還有另外一個信息,在替換內核後,執行lilo64 -C lilo.multi.conf -s `pwd`時,lilo報了一個警告信息:
- Normally any initial ramdisk (initrd) loaded with a kernel is loaded as
- high in memory as possible, but never above 15Mb. This is due to a BIOS
- limitation on older systems. On newer systems, this option enables using
- memory above 15Mb (up to a kernel imposed limit, around 768Mb) for
- passing the initrd to the kernel. The presence of this option merely
- indicates that your system does not have the old BIOS limitation.
再看RAMDISK的初始起始地址:0x7fa36000,很顯然該地址高於15MB處地址,說明LILO認為內核和initrd.img的大小超過了某個固定限制,將initrd放在了高端內存中。那為什麼LILO寫入內存失敗呢?從上面的提示信息來看,應該是在剛上電,啟動時BIOS不支持訪問高端內存,所以LILO在調用BIOS的寫入程序時發生了錯誤,但LILO並沒有關心這個錯誤。
在LILO的HomePage上找到一篇技術文檔,其中很明確的表述了LILO會將initrd.img加載在內存的低端地址的尾部處(16MB以下)。16MB的限制是因為BIOS只使用24位的地址空間來傳輸數據。後經過閱讀LILO的代碼,LILO會按照內核鏡像大小的3倍與initrd.img的大小計算總和,當總和大於14MB,LILO認為14MB以下的低地址空間無法放入內核和initrd鏡像文件,便認為BIOS是支持16MB以上地址空間的,於是LILO在加載initrd鏡像時,將initrd放在高地址空間中。
LILO的技術文檔:http://lilo.alioth.debian.org/olddoc/html/tech_21-5.html,感興趣的同學可以拿來看看哦。