嵌入式Linux——分析kernel執行過程(2):kernel第二階段
簡介:
本文主要介紹在2.6.22.6版核心中,程式碼的執行過程。而在kernel的第二階段主要介紹在核心程式碼中如何解析從u-boot和前面彙編程式碼中獲得TAG引數與machine_desc結構體。
宣告:
本文主要是看了韋東山老師的視訊後所寫,希望對你有所幫助。
u-boot版本 : u-boot-1.1.6
開發板 : JZ2440
Linux核心 : Linux-2.6.22.6
start_kernel :
從start_kernel函式開始,程式就是C語言程式碼了。而相較於前面的彙編程式碼,這裡的C語言程式碼可以實現更加複雜的功能,同時也更好理解一些。我們知道核心要做的事有兩件:第一件是掛載根檔案系統,而第二件則是執行第一個應用程式。而核心掛載什麼樣的根檔案系統,同時核心執行的第一個應用程式是什麼?這些都是我們想要弄清楚的。同時我們在上面一篇文章:
我們初看start_kernel函式會覺得這個函式很大,同時內部的函式很多,但是如果我們帶著上面的問題來看這些程式碼,並將那些沒用的函式刪除,那麼start_kernel可以包含下面一些函式:
asmlinkage void __init start_kernel(void) { char * command_line; extern struct kernel_param __start___param[], __stop___param[]; printk(KERN_NOTICE); printk(linux_banner); setup_arch(&command_line); setup_command_line(command_line); page_alloc_init(); printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line); parse_early_param(); parse_args("Booting kernel", static_command_line, __start___param, __stop___param - __start___param, &unknown_bootoption); /* Do the rest non-__init'ed, we're now alive */ rest_init(); }
而在上面的函式中主要做兩件事:
1. 解析TAG引數與machine_desc結構體,獲得單板資訊
2. 掛接根檔案系統以及執行第一個應用程式
解析TAG引數與machine_desc結構體主要由setup_arch函式和setup_command_line函式來完成。我們先看在setup_arch函式中做了什麼樣的設定(arch\arm\kernel\setup.c):
void __init setup_arch(char **cmdline_p) { struct tag *tags = (struct tag *)&init_tags; struct machine_desc *mdesc; char *from = default_command_line; setup_processor(); mdesc = setup_machine(machine_arch_type); machine_name = mdesc->name; if (mdesc->soft_reboot) reboot_setup("s"); if (mdesc->boot_params) tags = phys_to_virt(mdesc->boot_params); /* * If we have the old style parameters, convert them to * a tag list. */ if (tags->hdr.tag != ATAG_CORE) convert_to_tag_list(tags); if (tags->hdr.tag != ATAG_CORE) tags = (struct tag *)&init_tags; if (mdesc->fixup) mdesc->fixup(mdesc, tags, &from, &meminfo); if (tags->hdr.tag == ATAG_CORE) { if (meminfo.nr_banks != 0) squash_mem_tags(tags); parse_tags(tags); } init_mm.start_code = (unsigned long) &_text; init_mm.end_code = (unsigned long) &_etext; init_mm.end_data = (unsigned long) &_edata; init_mm.brk = (unsigned long) &_end; memcpy(boot_command_line, from, COMMAND_LINE_SIZE); boot_command_line[COMMAND_LINE_SIZE-1] = '\0'; parse_cmdline(cmdline_p, from); paging_init(&meminfo, mdesc); request_standard_resources(&meminfo, mdesc); /* * Set up various architecture-specific pointers */ init_arch_irq = mdesc->init_irq; system_timer = mdesc->timer; init_machine = mdesc->init_machine; }
從上面的程式可以看出在setup_arch函式中不僅有對TAG引數的解析,而其中更為重要的是對machine_desc結構體的解析,而我們從上一章獲得的關於本開發板的machine_desc結構體為:
#define MACHINE_START(_type,_name) \
static const struct machine_desc __mach_desc_##_type \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = MACH_TYPE_##_type, \
.name = _name,
#define MACHINE_END \
};
MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch
* to SMDK2410 */
/* Maintainer: Jonas Dietsche */
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.map_io = smdk2410_map_io,
.init_irq = s3c24xx_init_irq,
.init_machine = smdk2410_init,
.timer = &s3c24xx_timer,
MACHINE_END
所以我們要對照著machine_desc結構體的內容來分析setup_arch函式。setup_arch函式做的第一件事就是通過引數machine_arch_type來找到單板對應的machine_desc結構體,並獲得machine_desc結構體中單板的名稱。而對於我們所使用的單板,他的name為:SMDK2410 ,而上面兩步操作的程式碼為:
mdesc = setup_machine(machine_arch_type);
machine_name = mdesc->name;
static struct machine_desc * __init setup_machine(unsigned int nr)
{
struct machine_desc *list;
/*
* locate machine in the list of supported machines.
*/
list = lookup_machine_type(nr);
if (!list) {
printk("Machine configuration botched (nr %d), unable "
"to continue.\n", nr);
while (1);
}
printk("Machine: %s\n", list->name);
return list;
}
觀察machine_desc結構體發現其中有一個用於存放TAG引數的首地址的項:boot_params ,而程式可以通過訪問這項的內容來獲得TAG引數所存放的首地址,進而獲得TAG引數的內容。而此時程式已經執行在了核心中,所以這時程式碼執行在虛擬地址空間。而從boot_params中獲得的地址為實體地址,因此要使用phys_to_virt函式將實體地址轉化為虛擬地址:
#define __phys_to_virt(x) ((x) - PHYS_OFFSET + PAGE_OFFSET)
static inline void *phys_to_virt(unsigned long x)
{
return (void *)(__phys_to_virt((unsigned long)(x)));
}
if (mdesc->boot_params)
tags = phys_to_virt(mdesc->boot_params);
獲得TAG引數首地址後,就要從TAG首地址開始解析從u-boot傳入到核心的TAG引數了。而TAG引數有兩種形式,一種是老的TAG引數形式(他沒有 ATAG_CORE引數,因此不能生成列表形式),而另一種是新的TAG引數形式(這種新的TAG引數可以使用列表來列出這些引數)。TAG引數的列表形式為:
如果從u-boot傳入的TAG引數為老式的,那麼程式要將其轉化為有列表的TAG引數形式,而如果是列表形式的TAG引數,就可以直接對其進行操作。上面所講的功能的程式碼為:
/*
* If we have the old style parameters, convert them to
* a tag list.
*/
if (tags->hdr.tag != ATAG_CORE)
convert_to_tag_list(tags);
if (tags->hdr.tag != ATAG_CORE)
tags = (struct tag *)&init_tags;
if (mdesc->fixup)
mdesc->fixup(mdesc, tags, &from, &meminfo);
if (tags->hdr.tag == ATAG_CORE) {
if (meminfo.nr_banks != 0)
squash_mem_tags(tags);
parse_tags(tags);
}
程式在上面將TAG引數放入到列表中方便查詢,而在下面程式中要做的是:查詢並設定預設的命令列引數。如果u-boot沒有向核心傳遞命令列引數,這時預設的命令列引數就會派上用場。而下面的程式主要就是介紹如何解析並獲得預設的命令列引數:
/* Untouched command line saved by arch-specific code. */
char __initdata boot_command_line[COMMAND_LINE_SIZE];
char *from = default_command_line;
memcpy(boot_command_line, from, COMMAND_LINE_SIZE);
boot_command_line[COMMAND_LINE_SIZE-1] = '\0';
parse_cmdline(cmdline_p, from);
上面程式首先做的事情是獲得預設的命令列引數:default_command_line 。然後將預設的命令列引數拷貝到cmdline_p中。
做完上面的事情,對於傳入的引數的解析就完成的差不多了,而下面要做的就是初始化頁表了,程式在前面彙編階段就初始化過頁表,但是那個頁表只是暫時使用,而現在函式中要做的才是真正的初始化頁表,而此時初始化的頁表將覆蓋原有的頁表。由於我們單板中有mmu(記憶體管理單元),因此我們使用路徑:arch\arm\kernel\setup.c下的頁表初始化函式:
/*
* paging_init() sets up the page tables, initialises the zone memory
* maps, and sets up the zero page, bad page and bad page tables.
*/
void __init paging_init(struct meminfo *mi, struct machine_desc *mdesc)
{
void *zero_page;
build_mem_type_table();
prepare_page_table(mi);
bootmem_init(mi);
devicemaps_init(mdesc);
top_pmd = pmd_off_k(0xffff0000);
/*
* allocate the zero page. Note that we count on this going ok.
*/
zero_page = alloc_bootmem_low_pages(PAGE_SIZE);
memzero(zero_page, PAGE_SIZE);
empty_zero_page = virt_to_page(zero_page);
flush_dcache_page(empty_zero_page);
}
初始化好頁表之後,程式就要使用虛擬地址去為核心的程式碼和資料去申請資源了:
static void __init
request_standard_resources(struct meminfo *mi, struct machine_desc *mdesc)
{
struct resource *res;
int i;
kernel_code.start = virt_to_phys(&_text);
kernel_code.end = virt_to_phys(&_etext - 1);
kernel_data.start = virt_to_phys(&__data_start);
kernel_data.end = virt_to_phys(&_end - 1);
for (i = 0; i < mi->nr_banks; i++) {
unsigned long virt_start, virt_end;
if (mi->bank[i].size == 0)
continue;
virt_start = __phys_to_virt(mi->bank[i].start);
virt_end = virt_start + mi->bank[i].size - 1;
res = alloc_bootmem_low(sizeof(*res));
res->name = "System RAM";
res->start = __virt_to_phys(virt_start);
res->end = __virt_to_phys(virt_end);
res->flags = IORESOURCE_MEM | IORESOURCE_BUSY;
request_resource(&iomem_resource, res);
if (kernel_code.start >= res->start &&
kernel_code.end <= res->end)
request_resource(res, &kernel_code);
if (kernel_data.start >= res->start &&
kernel_data.end <= res->end)
request_resource(res, &kernel_data);
}
if (mdesc->video_start) {
video_ram.start = mdesc->video_start;
video_ram.end = mdesc->video_end;
request_resource(&iomem_resource, &video_ram);
}
/*
* Some machines don't have the possibility of ever
* possessing lp0, lp1 or lp2
*/
if (mdesc->reserve_lp0)
request_resource(&ioport_resource, &lp0);
if (mdesc->reserve_lp1)
request_resource(&ioport_resource, &lp1);
if (mdesc->reserve_lp2)
request_resource(&ioport_resource, &lp2);
}
其中最主要的還是為核心的程式碼和資料申請資源,同時也會為裝置申請video資源。
做完上面這些事情之後,最後程式將單板特有的中斷函式,定時器函式以及單板初始化函式賦給核心中對應的函式,這樣在核心中就不必關心硬體中這些函式的具體實現細節,而他只要呼叫核心提供的函式指標就好。具體程式碼為:
/*
* Set up various architecture-specific pointers
*/
init_arch_irq = mdesc->init_irq;
system_timer = mdesc->timer;
init_machine = mdesc->init_machine;
講到這裡setup_arch函式就講完了。而setup_command_line函式中所做的是儲存修改和沒有修改的命令列引數。分析這兩個函式我們可以知道在這兩個函式中主要做的還是解析工作,即將單板特有的資訊machine_desc結構體以及TAG引數分別進行解析,並將重要的資訊放到核心的引數或者呼叫函式中。但是我們觀察會發現上面做的事情還只是解析,而並沒有運用這些資訊,更沒有講到掛載根檔案系統。
rest_init : 掛載根檔案系統並執行第一個應用程式
不好意思,寫到這裡發現下面的程式碼有些難懂,因此不敢將他們寫出來誤導大家,所以這部分的程式碼就寫到這裡,十分抱歉。