嵌入式题库总结
一、C语言
1. inline 和 static inline
inline的优缺点
inline是c99的特性。在c99中,inline是向编译器建议,将被inline修饰的函数以内联的方式嵌入到调用这个函数的地方。 而编译器会判断这样做是否合适,以此最终决定是否这么做。
- 好处:减少调用函数时的开销,如:减少传参时可能引起的压栈出栈的开销。减少PC跳转时对流水线的破坏。
- 坏处: 以空间换时间,代码所占体积会更大。
static inline和inline的区别
显然我们希望它们被其他文件访问。那么我们的inline函数原型就应该定义在头文件中,注意是函数原型而不是函数声明。因为开发者决定不了一个函数是否被内联,开发者只有建议权,只有编译器具有决定权。这就造成了一个很困扰的事情:除非你看到一个函数的反汇编代码,否则你很难确定他是不是内联函数。试想,如果头文件中的Inline函数在没有被编译成内联函数的情况下,被include到了多个源文件中,势必会产生函数重复定义的问题。 因此,我们要再加一个关键字static,才能避免这个问题。
static inline使用时机
static inline什么时候用比较好:
- 所修饰的函数语句较少时,尤其是只有一两条语句的函数。
- 所修饰的函数不是递归函数,因为递归不支持内联。
- 内联函数定义了但没有使用的时候不会和普通函数那样warning。
2. struct和union内存对齐
内存对齐的原因
- 平台原因:不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
- 性能原因 :数据结构( 尤其是栈 ) 应该尽可能地在自然边界上对齐。 原因在于,我们在访问结构体变量时,是按结构体中成员所占的最大字节数对结构体变量进行读取的。为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。这里归根结底的来说就是以空间换时间 。
struct对齐的规则
- 结构体中的第一个成员在与结构体变量偏移量为0的地址处。
- 其它成员变量要对齐到一个名叫“对齐数”的整数倍的地址处。补充:对齐数=编译器默认的一个对齐数与该成员变量类型中的较小值。
- 结构体总大小为最大对齐数(每个成员变量类型都有一个对齐数)的整数倍。
- 如果存在了嵌套结构体的情况,嵌套的结构体对齐到自己的成员变量类型中最大对齐数的整数倍处,包含该嵌套结构体的结构体大小为其成员变量类型(包含嵌套结构体)最大对齐数的整数倍。
1 | //结构体内存对齐 |
取消内存对齐
- #pragma pack(1) … #pragma pack()
- 一定要在结构体末尾加上#pragma pack()进行取消自定义字节对齐的命令,如果不取消可能会导致整个程序存在问题,因为会影响到其他结构体的字节对齐方式。
- __attribute__((packed)),GNU C特有的编译机制。
位域存储规则
- 若相邻的位域成员的类型相同,且其占用二进制位数未超过该类型可容纳的范围,则后面的成员紧接着前一个成员进行存储;
- 若相邻的位域成员的类型相同,但其占用二进制位数超过该类型可容纳的范围,则后面的成员从该类型占用空间之后的内存单元开始存储;
- 若相邻的位域成员的类型不同,则取决于编译器的实现。对于GCC编译器会尽量利用空闲的位对数据进行存储;
- 若位域之间定义有匿名位域成员,则匿名位域成员指定的空闲位不用于后续成员的数据存储,注意,他也要遵守条件2;
- 特别地,如果匿名成员占用的位数为0,当前类型的内存单元剩余空间都将不会被使用;
- 混合使用位域和普通成员变量,也要遵守结构体对齐的规则。
- 对于成员a、b、c再加上匿名成员占用的总内存空间并未超过unsigned short类型空间的大小,因此在unsigned short类型可容纳的范围内,这些成员可以紧挨着存放;
- 当后续存放成员d时,前一个unsigned short类型数据剩余的空间已不足以容纳d,因此选择下一个内存单元进行存放。
- 整个结构体的总大小为最宽基本类型成员大小的整数倍。所以总大小为4字节。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25//验证匿名位域也要遵守规则2:
struct foo{
unsigned short a : 3;
unsigned short b : 4;
unsigned short : 9;
unsigned short c : 3;
unsigned short d : 5;
}; //匿名位域9,当前类型剩余空间刚好放的下,总空间为4字节
struct foo{
unsigned short a : 3;
unsigned short b : 4;
unsigned short : 10;
unsigned short c : 3;
unsigned short d : 5;
}; //匿名位域10,当前类型剩余空间放不下,从byte2开始放匿名的,总空间为6字节
//验证位域的规则5:
struct foo{
unsigned short a : 3;
unsigned short b : 4;
unsigned short : 5;
unsigned short c : 3;
int d;
}; //int d内存对齐,从byte4开始存储,byte2、byte3不用,总空间为8字节
union对齐的规则
- 找到占用字节最多的成员类型;
- union大小必须是占最大的成员类型字节数的倍数,而且需要能够容纳其他的成员。
举个例子:
1 | //x64 |
- 首先找到占用字节最多的成员,本例中是long,占用8个字节,int k[5]中都是int类型,仍然是占用4个字节的。
- 然后union的字节数必须是最大的成员类型的倍数,而且需要能够容纳其他的成员,为了要容纳k(20个字节),就必须要保证是8的倍数的同时还要大于20个字节,所以是24个字节。
综合的栗子
1 | typedef struct{ |
判断机器大小端
小端存储:低地址内容存放在低地址,高地址内容存放在高地址
大端存储:低地址内容存放在高地址,高地址内容存放在低地址
方法一:联合体union
1 | int main() |
方法二:指针方法,将int强制类型转换成char单字节,p指向a的起始字节(低字节)
1 | int main () |
3. __attribute__机制
- GNU C的一大特色就是attribute机制。attribute可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)。
- attribute书写特征是:__attribute__前后都有两个下划线,并且后面会紧跟一对括弧,括弧里面是相应的attribute参数。
二、ARM架构
1. 异常处理
硬件自动完成:
- 保存CPSR到SPSR_mode
- 设置 CPSR 对应功能位:
①切换处理器进入ARM状态:T[5]
②根据需要,禁止中断位:F[6] / I[7]
③根据异常切换到对应的异常模式:M[4:0] - 保存返回地址:把当前 PC 保存到 lr_mode
- 设置PC = 存放跳转到对应的异常向量表的固定首地址。
异常处理程序(手动完成):
- 保护现场,将r0 ~ r12寄存器压栈
- 真正的异常处理程序
- 恢复现场
①恢复 SPSR_mode 到 CPSR
②恢复 lr_mode 到 PC
③操作sp将栈中r0 ~ r12寄存器数据恢复
1 | software_interrupt: |
三、Linux内核
1. 不同情形能否schedule
①中断、软中断、tasklet、timer、hrtimer 执行环境下、或者“内核线程、普通线程内核态环境下调用了preempt disable() / local irq_disable() / spin lock后”,能否调用schedule()函数 (包括usleep()、wait event等系列函数)?
答:不能调用schedule()函数,否则违反了linux kernel的设计理念,导致schedule出去后系统各种不稳定的异常。
比如内核能否抢占的条件就是当前没有持有锁,而是否持有锁的判断依据是preempt_count值是否为0,调用preempt disable()后preempt_count++,本来已经禁止抢占了,但是又主动调用schedule(),精神分裂症无疑了!
再比如spin lock成功后,spin lock内部调用了preempt disable,所以spin lock后,不能调用schedulel()。当然spin unlock会调用preempt enable,由于usleep()、wait_event等函数内部调用了schedule()函数,也不能使用。
为什么local irq_disable()后不能调用schedule(),我的解释是:根据《linux内核设计与实现》所述:“如果在调用local_irq_disable()之前就已经禁止了中断,那么该例程会带来危险”。因为schedule()中也会禁止中断,所以可能会导致系统不稳定。
②workqueue、kernel thread、普通线程的内核态这些context下,在没有调用preempt disable禁止抢占和local_irq_disable禁止中断等操作时,能否调用schedule()?
答:可以调用schedule()。前提是没有调用preempt disable禁止抢占和local_irq_disable禁止中断等操作时。