uclinux的启动主要分为两个阶段: 第一部分bootloader启动阶段 第二部分linux 内核初始化和启动阶段 第一节:start_kernel 第二节:用户模式( user_mode )开始,start_kernel结束 第三节:加载linux内核完毕,转入cpu_idle进程
Specification of the machine
- CPU: S3C2410 - MEM: 64MB (bank 0) - ROM: 64MB (Inte Starata Flash Memory) - got UART ports
64MB RAM (DRAM)
0x3400 0000 +----------------------+ |VIVI RAM (size: 1M) | 0x33F0 0000 +----------------------+ VIVI_RAM_BASE |HEAP AREA(size: 1M) | 0x33E0 0000 +----------------------+ HEAP_BASE |MMU TABLE(size: 16k) | 0x33DF C000 +----------------------+ MMU_TABLE_BASE | | mtd partition table (size: 16k)| MTD_PART_SIZE -----/ 0x33DF 8000 +----------------------+ | vivi parameter table(size: 16k)| PARAMETER_TLB_SIZE | vivi pri data 0x33DF 4000 |----------------------+ | linux command line (size: 16k) | LINUX_CMD_SIZE -----/ | | 0x33DF 0000 +----------------------+ VIVI_PRIV_RAM_BASE | | | free memory | | | 0x3010 8000 +----------------------+ | kernel (size: 1MB) | 0x3000 8000 +----------------------+ | | | free memory | | | 0x3000 0100 +----------------------+ kernel param (size 256Byte) | 0x3000 0000 +----------------------+ DRAM_BASE
64MB ROM (NAND Flash)
* 0x0400 0000 +-----------------------------+ * | | * | jffs2 (size: 61M) | user * | | * 0x0030 0000 +-----------------------------+ * | root (size: 2048k) | * 0x0010 0000 +-----------------------------+ * | kernel (size: 832k) | * 0x0003 0000 +-----------------------------+ * | param (size: 64k) | * 0x0002 0000 +-----------------------------+ * | vivi (size: 128k) | * 0x0000 0000 +-----------------------------+
[root@localhost root]# minicom Welcome to minicom 2.00.0
OPTIONS: History Buffer, F-key Macros, Search History Buffer, I18nCompiled on Jan 25 2003, 00:15:18.Press CTRL-A Z for help on special keys
minicom信息
VIVI version 0.1.4 (root@BC) (gcc version 2.95.2 20000516 (release) [Rebel.com]5
Bootloader头信息,版本等,这个因不同的bootloader的设计而有所不同,由此你能看出bootloader的版本信息,有很多使用的是通用的bootloader,如u-boot,redboot等。
链接
Boot Loader VIVI
源代码: vivi/lib/version.c vivi/main.c
#define VIVI_RELEASE "0.1.4" #define VIVI_COMPILE_BY "root" #define VIVI_COMPILE_HOST "localhost.localdomain" #define VIVI_COMPILER "gcc version 2.95.2 20000516 (release)[Rebel.com]" #define UTS_VERSION "#0.1.4 六 6月 5 05:27:07 CST 2004"
const char *vivi_banner = "VIVI version " VIVI_RELEASE " (" VIVI_COMPILE_BY "@" VIVI_COMPILE_HOST ") (" VIVI_COMPILER ") " UTS_VERSION "/r/n";putstr(vivi_banner);
MMU table base address = 0x33DFC000 Succeed memory mapping.
链接
MMU
内存映射(memory map)就是指在整个4GB物理地址空间中有哪些地址范围被分配用来寻址系统的RAM单元。比如,在SA-1100CPU中,从0xC000,0000开始的512M地址空间被用作系统的RAM地址空间,而在Samsung S3C44B0X CPU中,从0x0c00,0000到0x1000,0000之间的64M地址空间被用作系统的RAM地址空间。虽然 CPU 通常预留出一大段足够的地址空间给系统 RAM,但是在搭建具体的嵌入式系统时却不一定会实现CPU预留的全部RAM地址空间。也就是说,具体的嵌入式系统往往只把CPU预留的全部RAM地址空间中的一部分映射到RAM单元上,而让剩下的那部分预留RAM地址空间处于未使用(unused)状态。由于上述这个事实,因此Boot Loader的stage2必须在它想干点什么之前——比如,将存储在flash上的内核映像读到RAM空间中——检测整个系统的内存映射情况,也即它必须知道CPU预留的全部RAM地址空间中的哪些被真正映射到RAM地址单元,哪些是处于unused状态的。 VIVI中用CONFIG_BOOTUP_MEMTEST中的memtest完成基本的RAM检测,针对
Samsung
S3C2410
CPU
,
从0x3000,0000到0x3400,0000之间的64M地址空间被用作系统的RAM地址空间。
源代码:vivi/arch/s3c2410/mmu.c
#define MMU_TABLE_BASE (HEAP_BASE - MMU_TABLE_SIZE) #define HEAP_BASE (VIVI_RAM_BASE - HEAP_SIZE) #define VIVI_RAM_BASE (DRAM_BASE + DRAM_SIZE - VIVI_RAM_SIZE) #define DRAM_BASE DRAM_BASE0 #define DRAM_BASE0 0x30000000 /* base address of dram bank 0 */ #define DRAM_SIZE SZ_64M #define SZ_8M 0x00800000 #define VIVI_RAM_SIZE SZ_1M #define HEAP_SIZE SZ_1M #define SZ_1M 0x00100000 #define MMU_TABLE_SIZE SZ_16K #define SZ_16K 0x00004000static unsigned long *mmu_tlb_base = (unsigned long *) MMU_TABLE_BASE; putstr_hex("MMU table base address = 0x", (unsigned long)mmu_tlb_base);
源代码:vivi/main.c
mem_map_init(); mmu_init(); putstr("Succeed memory mapping./r/n");
NAND device: Manufacture ID: 0xec, Chip ID: 0x76 (Samsung K9D1208V0M)
链接:
闪存芯片
NAND Flash:1989年,东芝公司发明。是以块和页为单位来读写的,不能随机访问某个指定的点。因而相对来说读取速度较慢,而擦除和写入的速度则比较快。一般适用在大容量的多媒体应用中,如:CF,SM。 NOR Flash:Intel于1988年发明.随机读取的速度比较快,随机按字节写,写入和擦除速度很低。一般适合应用于数据/程序的存贮应用中,如:手机,机顶盒。NOR还可以片内执行(execute-in-place)XIP。
简单的说,NAND类似于硬盘,NOR类似于内存
源代码:vivi/drivers/mtd/nand/smc_core.c
struct nand_flash_dev { char * name; int manufacture_id; int model_id; int chipshift; char page256; char pageadrlen; unsigned long erasesize; }; static struct nand_flash_dev nand_flash_ids[] = { {"Toshiba TC5816BDC", NAND_MFR_TOSHIBA, 0x64, 21, 1, 2, 0x1000}, // 2Mb 5V {"Toshiba TC58V16BDC", NAND_MFR_TOSHIBA, 0xea, 21, 1, 2, 0x1000}, // 2Mb 3.3V {"Toshiba TC5832DC", NAND_MFR_TOSHIBA, 0x6b, 22, 0, 2, 0x2000}, // 4Mb 5V {"Toshiba TC58V32DC", NAND_MFR_TOSHIBA, 0xe5, 22, 0, 2, 0x2000}, // 4Mb 3.3V {"Toshiba TC58V64AFT/DC", NAND_MFR_TOSHIBA, 0xe6, 23, 0, 2, 0x2000}, // 8Mb 3.3V {"Toshiba TH58V128DC", NAND_MFR_TOSHIBA, 0x73, 24, 0, 2, 0x4000}, // 16Mb {"Toshiba TC58256FT/DC", NAND_MFR_TOSHIBA, 0x75, 25, 0, 2, 0x4000}, // 32Mb {"Toshiba TH58512FT", NAND_MFR_TOSHIBA, 0x76, 26, 0, 3, 0x4000}, // 64Mb {"Toshiba TH58NS100/DC", NAND_MFR_TOSHIBA, 0x79, 27, 0, 3, 0x4000}, // 128Mb {"Samsung KM29N16000", NAND_MFR_SAMSUNG, 0x64, 21, 1, 2, 0x1000}, // 2Mb 5V {"Samsung KM29W16000", NAND_MFR_SAMSUNG, 0xea, 21, 1, 2, 0x1000}, // 2Mb 3.3V {"Samsung unknown 4Mb", NAND_MFR_SAMSUNG, 0x6b, 22, 0, 2, 0x2000}, // 4Mb 5V {"Samsung KM29W32000", NAND_MFR_SAMSUNG, 0xe3, 22, 0, 2, 0x2000}, // 4Mb 3.3V {"Samsung unknown 4Mb", NAND_MFR_SAMSUNG, 0xe5, 22, 0, 2, 0x2000}, // 4Mb 3.3V {"Samsung KM29U64000", NAND_MFR_SAMSUNG, 0xe6, 23, 0, 2, 0x2000}, // 8Mb 3.3V {"Samsung KM29U128T", NAND_MFR_SAMSUNG, 0x73, 24, 0, 2, 0x4000}, // 16Mb {"Samsung KM29U256T", NAND_MFR_SAMSUNG, 0x75, 25, 0, 2, 0x4000}, // 32Mb {"Samsung K9D1208V0M", NAND_MFR_SAMSUNG, 0x76, 26, 0, 3, 0x4000}, // 64Mb {"Samsung K9D1G08V0M", NAND_MFR_SAMSUNG, 0x79, 27, 0, 3, 0x4000}, // 128Mb {NULL,} }; mtd->name = nand_flash_ids[i].name; printk("NAND device: Manufacture ID:" / " 0x%02x, Chip ID: 0x%02x (%s)/n", nand_maf_id, nand_dev_id, mtd->name);
Could not found stored vivi parameters. Use default vivi parameters.
vivi参数
vivi_parameter_t default_vivi_parameters[] = { { "mach_type", MACH_TYPE, NULL }, { "media_type", MT_S3C2410, NULL }, { "boot_mem_base", 0x30000000, NULL }, { "baudrate", UART_BAUD_RATE, NULL }, { "xmodem_one_nak", 0, NULL }, { "xmodem_initial_timeout", 300000, NULL }, { "xmodem_timeout", 1000000, NULL }, { "ymodem_initial_timeout", 1500000, NULL }, { "boot_delay", 0x1000000, NULL } }; #define MACH_TYPE 193 #define MT_S3C2410 MT_SMC_S3C2410 #define UART_BAUD_RATE 115200
源代码:vivi/lib/priv_data.c
int init_priv_data(void) { int ret_def; #ifdef CONFIG_PARSE_PRIV_DATA //#define CONFIG_PARSE_PRIV_DATA 1 int ret_saved; #endif ret_def = get_default_priv_data(); #ifdef CONFIG_PARSE_PRIV_DATA ret_saved = load_saved_priv_data(); if (ret_def && ret_saved) { printk("Could not found vivi parameters./n"); return -1; } else if (ret_saved && !ret_def) { printk("Could not found stored vivi parameters."); printk(" Use default vivi parameters./n"); } else { printk("Found saved vivi parameters./n"); } #else if (ret_def) { printk("Could not found vivi parameters/n"); return -1; } else { printk("Found default vivi parameters/n"); } #endif
#ifdef CONFIG_DEBUG_VIVI_PRIV display_param_tlb(); display_mtd_partition(); #endif return 0;
Press Return to start the LINUX now, any other key for vivi
按回车进入linux,其它键进vivi。当然,从vivi中boot也可以。
源代码:vivi/main.c
void boot_or_vivi(void) { char c; int ret; ulong boot_delay;
boot_delay = get_param_value("boot_delay", &ret); if (ret) boot_delay = DEFAULT_BOOT_DELAY; /* If a value of boot_delay is zero, * unconditionally call vivi shell */ if (boot_delay == 0) vivi_shell();
/* * wait for a keystroke (or a button press if you want.) */ printk("Press Return to start the LINUX now, any other key for vivi/n"); c = awaitkey(boot_delay, NULL); if (((c != '/r') && (c != '/n') && (c != '/0'))) { printk("type /"help/" for help./n"); vivi_shell(); } // 有问题,'/r' '/n'都是回车,'/0'是什么? run_autoboot();
return; }
【注:这里还有个小问题。就是在这里添加的时候,/n/r要合起来用,不能只用/n。原因如下: 计算机还没有出现之前,有一种叫做电传打字机(Teletype Model 33)的玩意,每秒钟可以打10个字符。但是它有一个问题,就是打完一行换行的时候,要用去0.2秒,正好可以打两个字符。要是在这0.2秒里面,又有新的字符传过来,那么这个字符将丢失。 于是,研制人员想了个办法解决这个问题,就是在每行后面加两个表示结束的字符。一个叫做“回车”,告诉打字机把打印头定位在左边界;另一个叫做“换行”,告诉打字机把纸向下移一行。 这就是“换行”和“回车”的来历,从它们的英语名字上也可以看出一二。 后来,计算机发明了,这两个概念也就被般到了计算机上。那时,存储器很贵,一些科学家认为在每行结尾加两个字符太浪费了,加一个就可以。于是,就出现了分歧。Unix 系统里,每行结尾只有“<换行>”,即“/n”;Windows系统里面,每行结尾是“<换行><回车>”,即“/ n/r”;Mac系统里,每行结尾是“<回车>”。一个直接后果是,Unix/Mac系统下的文件在Windows里打开的话,所有文字会变成一行;而Windows里的文件在Unix/Mac下打开的话,在每行的结尾可能会多出一个^M符号。 这几个地方我都遇到过,不过一直没有搞清楚。现在才算是找到根源了。】
Copy linux kernel from 0x00030000 to 0x30008000, size = 0x00100000 ... done
启动linux kernel,kernel映像必须被放到MTD设备的一个分区中,from、size分别表示linux kernel起始地址和kernel的大小。为什么要指定kernel大小呢?因为kernel首先要被copy到boot_mem_base + 0x8000的地方,然后在boot_mem_base + 0x100开始的地方设置内核启动参数。要拷贝 kernel,当然需要知道kernel的大小啦,这个大小不一定非要和kernel实际大小一样,但是必须大于等于kernel的大小。(单位字节)
media_type是指定的媒介类型,因为boot命令对不同媒介的处理方式是不同的,例如如果kernel在 SDRAM中,那么boot执行的过程中就可以跳过拷贝kernel映像到SDRAM中这一步骤了 Boot命令识别的媒介类型有以下三种: ram 表示从RAM中启动linux kernel,linux kernel必须要放在RAM中,我的S3C2410平台就是如此。 nor 表示从NOR Flash中启动linux kernel,linux kernel必须已经被烧写到了NOR Flash中 smc 表示从NAND Flash中启动linux kernel,linux kernel必须已经被烧写到了NAND Flash中
源代码:vivi/lib/boot-kernel.c
int boot_kernel(ulong from, size_t size, int media_type) { int ret; ulong boot_mem_base; /* base address of bootable memory */ ulong to;
boot_mem_base = get_param_value("boot_mem_base", &ret); if (ret) { printk("Can't get base address of bootable memory/n"); printk("Get default DRAM address. (0x%08lx/n", DRAM_BASE); boot_mem_base = DRAM_BASE; } //boot_mem_base = 0x30000000
/* copy kerne image */ to = boot_mem_base + LINUX_KERNEL_OFFSET; // #define LINUX_KERNEL_OFFSET 0x8000 // to = 0x30008000 printk("Copy linux kernel from 0x%08lx to 0x%08lx, size = 0x%08lx ... ",from, to, size); ret = copy_kernel_img(to, (char *)from, size, media_type);
//
将内核镜像从NAND FLASH拷入RAM
//
media_type=
MT_SMC_S3C2410
if (ret) { printk("failed/n"); return -1; } else { printk("done/n"); }
return 0; }
void command_boot(int argc, const char **argv) { int media_type = 0; ulong from = 0; size_t size = 0; mtd_partition_t *kernel_part; int ret; media_type = get_param_value("media_type", &ret); if (ret) { printk("Can't get default 'media_type'/n"); return; } kernel_part = get_mtd_partition("kernel"); if (kernel_part == NULL) { printk("Can't find default 'kernel' partition/n"); return; } from = kernel_part->offset; size = kernel_part->size; boot_kernel(from, size, media_type); }
user_command_t boot_cmd = { "boot", command_boot, NULL, "boot [{cmds}] /t/t/t-- Booting linux kernel" };
zImage magic = 0x016f2818
判断内核文件是否为压缩镜像,而当前文件是压缩镜像
源文件: vivi/lib/boot-kernel.c
#define LINUX_ZIMAGE_MAGIC 0x016f2818
if (*(ulong *)(to + 9*4) != LINUX_ZIMAGE_MAGIC) { printk("Warning: this binary is not compressed linux kernel image/n"); printk("zImage magic = 0x%08lx/n", *(ulong *)(to + 9*4)); } else { printk("zImage magic = 0x%08lx/n", *(ulong *)(to + 9*4)); }
Setup linux parameters at 0x30000100 linux command line is: "noinitrd root=/dev/bon/3 init=/linuxrc console=ttyS0"
设置linux参数和控制行
static void setup_linux_param(ulong param_base) { /*
linux parameters*/
struct param_struct *params = (struct param_struct *)param_base; char *linux_cmd; printk("Setup linux parameters at 0x%08lx/n", param_base); memset(params, 0, sizeof(struct param_struct)); params->u1.s.page_size = LINUX_PAGE_SIZE; params->u1.s.nr_pages = (DRAM_SIZE >> LINUX_PAGE_SHIFT);
/* set linux command line */ linux_cmd = get_linux_cmd_line();//
linux_cmd =
0x33DF8008 if (linux_cmd == NULL) { printk("Wrong magic: could not found linux command line/n"); } else { memcpy(params->commandline, linux_cmd, strlen(linux_cmd) + 1); printk("linux command line is: /"%s/"/n", linux_cmd); } }
//
#define
boot_mem_base 0x30000000
//#define LINUX_PARAM_OFFSET 0x100
setup_linux_param(boot_mem_base + LINUX_PARAM_OFFSET);
MACH_TYPE = 193
/* Get machine type */ mach_type = get_param_value("mach_type", &ret); printk("MACH_TYPE = %d/n", mach_type);
NOW, Booting Linux......
激动人心的消息,终于可以引导linux了。。。
源代码: vivi/lib/boot-kernel.c
printk("NOW, Booting Linux....../n"); call_linux(0, mach_type, to);
#elif defined(CONFIG_ARCH_S3C2410) void call_linux(long a0, long a1, long a2) { cache_clean_invalidate(); tlb_invalidate();
__asm__( "mov r0, %0/n" "mov r1, %1/n" "mov r2, %2/n" "mov ip, #0/n" "mcr p15, 0, ip, c13, c0, 0/n" /* zero PID */ "mcr p15, 0, ip, c7, c7, 0/n" /* invalidate I,D caches */ "mcr p15, 0, ip, c7, c10, 4/n" /* drain write buffer */ "mcr p15, 0, ip, c8, c7, 0/n" /* invalidate I,D TLBs */ "mrc p15, 0, ip, c1, c0, 0/n" /* get control register */ "bic ip, ip, #0x0001/n" /* disable MMU */ "mcr p15, 0, ip, c1, c0, 0/n" /* write control register */ "mov pc, r2/n" "nop/n" "nop/n" : /* no outpus */ : "r" (a0), "r" (a1), "r" (a2) ); //全是汇编,有空再研究
Boot Loader 调用 Linux 内核的方法是直接跳转到内核的第一条指令处,也即直接跳转到 MEM_START+0x8000 地址处。在跳转时,下列条件要满足:
1. CPU 寄存器的设置: R0=0; R1=机器类型 ID;关于 Machine Type Number,可以参见 linux/arch/arm/tools/mach-types。 R2=启动参数标记列表在 RAM 中起始基地址;
2. CPU 模式: 必须禁止中断(IRQs和FIQs); CPU 必须 SVC 模式;
3. Cache 和 MMU 的设置: MMU 必须关闭; 指令 Cache 可以打开也可以关闭; 数据 Cache 必须关闭;
|
|
发表于: 2007-07-16,修改于: 2008-02-26 19:46 已浏览1054次,有评论4条 推荐 投诉 |
网友评论 |
justin_1222 |
时间:2007-07-16 15:50:42 IP地址:220.205.138.★ |
|
|
我在网上搜了一圈,发现相似内容的文章仅有一篇,而且是针对kernel-2.4.20-uc0的,不知道是什么原因,是否大家认为研究此内容没有意义?
我的kernel是2.4.18-rmk7-pxa1的,是博创提供的原版内核。
我准备利用半个月时间,结合kernel的配置文件,仔细研究下。
|
|
|
justin_1222 |
时间:2007-07-30 08:40:15 IP地址:211.94.88.★ |
|
|
规划内存占用的布局
这里包括两个方面:(1)内核映像所占用的内存范围;(2)根文件系统所占用的内存范围。在规划内存占用的布局时,主要考虑基地址和映像的大小两个方面。
对于内核映像,一般将其拷贝到从(MEM_START+0x8000) 这个基地址开始的大约1MB大小的内存范围内(嵌入式 Linux 的内核一般都不操过 1MB)。为什么要把从 MEM_START 到 MEM_START+0x8000 这段 32KB 大小的内存空出来呢?这是因为 Linux 内核要在这段内存中放置一些全局数据结构,如:启动参数和内核页表等信息。
而对于根文件系统映像,则一般将其拷贝到 MEM_START+0x0010,0000 开始的地方。如果用 Ramdisk 作为根文件系统映像,则其解压后的大小一般是1MB。 |
|
|
|
|