天天看点

【函数指针的高级应用】嵌入式系统如何从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语言】没想到指针还能这么用 @!!!

继续阅读