天天看點

【函數指針的進階應用】嵌入式系統如何從boot程式運作kernel程式?

在之前的一篇文章 【C語言】沒想到指針還能這麼用 @!!! 中介紹了【函數指針】的基本概念和簡單應用;今天再給大家分享一個【函數指針】的進階應用;在嵌入式系統開發中,此類用法非常地常見,但如果對【函數指針】的了解不夠透徹,很有可能會看得一頭霧水。

代碼片段如下:

typedef void (*kernel_func)(void);

void jump_to_kernel(void)
{
	uint32_t *kernel_start = (uint32_t *)0x410000;	
	kernel_func func = (kernel_func)kernel_start[0];
	printk("%s()%d: %08x\n", __FUNCTION__, __LINE__, func);
	local_irq_disable();
	func();
}
           

我們來分析下這段代碼:

從函數名,我們可以知道,這個函數的功能就是實作從boot程式到kernel程式的跳轉,即boot程式将kernel程式跑起來。

函數的第一句代碼: uint32_t *kernel_start = (uint32_t *)0x410000; 告訴我們kernel程式的執行位址是0x410000;這是一個絕對位址,在C語言中,位址就是可運作代碼的起始位置,它一般就是一個整型數,比如在32位的CPU上,它就是一個32位的整型數。

函數的第二句代碼:kernel_func func = (kernel_func)kernel_start[0]; 這裡用到了一個typedef定義的函數指針别名,它的定義為:typedef void (*kernel_func)(void); 它定義了函數指針,此指針指向一種函數,這種函數傳回值為void型,入參也為void。是以 kernel_func func = (kernel_func)kernel_start[0];  這整一句代碼的意思就是: 定義一個名稱為func的函數指針,它指向一個由kernel_start這個變量(位址為0x410000)代表的函數,為了保證函數指針指派的正确性,還加上了(kernel_func)做強制類型轉換。

函數的第三局代碼為printk列印輸出,不在此讨論範圍内。

函數的第四句代碼:local_irq_disable(); 表示關閉目前系統的中斷,為kernel程式的運作創造幹淨的環境,也不在此讨論的範圍。

函數的第五句代碼:func(); 非常的幹淨、簡單。就一個簡單的func執行就完成了從boot程式切換到kernel程式運作。這就是【函數指針】的魅力所在,在執行func()之前,它已經指向了kernel程式的起始位址0x41000,根據【函數指針】的文法規則,執行func(),實則就是執行0x410000這個位址對應的函數,也就把kernel程式跑起來了。

以上分析,相信有一定C語言基礎的童鞋,都能分析得出來。但是,當我接觸到這段代碼的時候,看了下那句printk調試輸出,我發現了一個疑問: %08x輸出func時,居然輸出的不是0x410000,而是一個可能跟0x410000毫不相幹的數值。

why ? 到底是為什麼啊? 當時我好納悶,函數的第一句不是把kernel_start變量指派為0x410000,然後函數的第二句不是把func這個函數指針變量指派為kernel_start,這不就是相當于func就等于0x41000嗎?這也有錯?

為了一探究竟,我特意請教了一個别的部門同僚,當時他幫我捋了捋,兩人最後得出的一緻結論就是: 既然是函數指針,終究是個指針,那麼按照我們的需求,應該了解為 “指針的内容是0x41000”,而根據指針的通路規則,通路其内容應該要加*符号,是以*func的輸出才是0x410000,func的輸出是其他值。當時這個說法,我也是認同的;不過抱着嚴謹好學的态度,我還是決定在代碼是調試調試,一試便知真假。

但是,不試不知道,一試吓一跳。我将printk那句代碼,改了下:

【函數指針的進階應用】嵌入式系統如何從boot程式運作kernel程式?

輸出的結果:竟然是隻有kernel_start輸出的是0x410000,其他的幾個輸出都是同樣的一個别的數值。

【函數指針的進階應用】嵌入式系統如何從boot程式運作kernel程式?

kernel程式的bin檔案的hexdump圖檔: 【小端模式存儲】

【函數指針的進階應用】嵌入式系統如何從boot程式運作kernel程式?

為什麼啊?

我們重新捋一捋這段代碼:

先明确各個變量是什麼東西:

func是一個函數指針變量

kernel_start是一個指針變量,即它是一個位址,說白了就是一個數值

kernel_start[0] 可以這麼了解,kernel_start是一個數組名,即數組的首位址,取它的第一個元素

再分析下每句列印語句:

printk("%s()%d: %08x\n", __FUNCTION__, __LINE__, kernel_start); 輸出00410000最好了解,它就是列印一個數值,肯定就是00410000

printk("%s()%d: %08x\n", __FUNCTION__, __LINE__, *kernel_start); 輸出00419690,需要轉換下思路,kernel_start是一個指針;

是以*kernel_start列印的是指針指向的内容,列印不是00410000,而是00410000位址存放的内容,即00419690

printk("%s()%d: %08x\n", __FUNCTION__, __LINE__, kernel_start[0]); 把kernel_start了解成一個數組名(指針和數組名有相通之處)

我們也不難推斷出kernel_start[0]列印的應該是00419690,而不是00410000

最後分析有關func的輸出,為何都是00419690,而不是00410000:

函數指針的特殊性,與普通指針不太一樣, 如一個函數指針p指向了一個已經定義函數test_func,那麼相當于 *p 等同于 test_func (與指針的基本概念一緻) 是以調用函數時,可以使用 test_func(param_in),也可以使用(*p)(param_in);兩者是等價的。

根據指針與數組名的關系類比,調用函數也可以使用 p(param_in) 和 (*test_func)(param_in)。

根據上面的分析,我們可以得出結論,當是用%08x列印func和*func的時候,列印的都是位址00410000指向的内容00419690,而不是位址值00410000

以下圖檔是從C語言的教科書截取的;

【函數指針的進階應用】嵌入式系統如何從boot程式運作kernel程式?

函數指針,不知你繞暈了沒?

延伸閱讀:

【C語言】沒想到指針還能這麼用 @!!!

繼續閱讀