c语言:
memory section:
.bss
.data
.text
C语言编译流程:
pre-compiler:
compiler: 检查语法问题
link: 将symbol转化为实际函数/变量地址,map file里面可以看到
预编译在做什么: (#define)
【C语言关键字 / keyword】
Macro写一个函数: Macro本质就是pre-processor将内容进行文本替换
typedef 和 macro哪个好
const的含义
volatile的含义
const 和 volatile公用
对固定地址赋值
extern: change visible scope
static 修饰
全局变量: visibility
局部变量
函数
sizeof (sizeof也是个keyword): 返回的是byte数
sizeof(int)
sizeof(Node) //typedef struct{} Node;
【位操作】
&
|
^ (exclusive or)
~
【数据类型】
数组
字符和字符串
结构体
union
data alignment: data will be padding
指针
void
wild
NULL
dangling
typecasting:
int will be promoted to unsigned int
栈
队列
链表
【库函数】
strlen(const char* str);
void* malloc(size_t size);
void free(void *ptr);
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*));
【恶心的】
auto: default storage class, for local variable 【storage duration is automatic,be created when the program execution enters the block in which it is defined and destroyed when the block exits】
register: only for local variables, store in cpu register instead of RAM 【& cannot be used on register variable】
enum:
bit field:
switch case:
Exp1 ? Exp2 : Exp3;
i++ / ++i
0xFF
0b011
%s
%d
%f
char 1bit
short 2bit
int: 4 bytes
short vs long:8 bytes
double:
float:
runtime error: X is local variable
OS:
【核心】
basic/extended task:
- basic task: 在running状态下,只有terminate和被更高优先级任务抢占,进入ready状态两种 释放cpu的方式 【不涉及等待共享资源的简单任务】【反正我看到的AUTOSAR project里面都是extended task】
- extended task:在running状态下,有主动释放cpu,进入wait状态的能力【等待共享资源的释放】
deadlock:
前提: 有两个共享的resource A和 B,task 1和2 都需要 A和B 两个资源
task1使用了resource A,然后被task2抢占,task使用了resource B,然后发现resource A不可用,于是进入wait 模式【此时B没有被释放】。 task1拿回cpu控制权继续执行,然后发现 resource B不可用,互相wait
解决: timeout,如果等待时间timeout以后,task需要释放自己的resource
优先级反转:
Priority Inversion
前提:有一个共享的resource 【优先级3>2>1】
优先级1的task 被 优先级3的task抢占,但task3在执行过程中发现share resource不可用【被task1用着呢】,于是释放了cpu【进入wait,任务就绪表置0,触发scheduler】,scheduler根据任务就绪表让task1继续执行,但此时task2又抢占了task1并执行完成,
解决:临时提升低优先级任务的优先级。 task3在因为resource释放cpu的时候,把持有resource的task1优先级临时提升到3,这样task2 就不能抢占他了
【因为basic task中没法处理share resource,所以只有extended task需要考虑deadlock 和优先级反转】
ECU多核之间通讯:
多核间通讯: IOC, spinlock
就一个core,task发现resource 不能用的时候,就只应该立刻释放cpu 【mutex + semaphore】
多核的时候,才有一个core 来while(1),等待另一个core的task运行完release resource的道理【spinlock】
因为spinlock用于多核cpu,其中一个core一直while(1)等待另一个core释放资源,也无所谓 【Spin locks are a low-level synchronization mechanism suitable primarily for use on shared memory multiprocessors. When the calling thread requests a spin lock that is already held by another thread, the calling thread spins in a loop to test if the lock has become available】
进程间通讯:
mutex VS semaphore
都是用来做进程间同步的,区别是mutex必须是进程自己释放,semaphore可以是别的进程释放,而且可以大于1
- mutex: 主要的purpose是protect shared resources
- semaphore: 主要的purpose是notice一个event已经发生了,比如taskA 在等一个semaphore,然后一个传感器触发了ISR,ISR去把semaphore置1了,然后taskA就可以继续执行了,这就是为什么说semaphore可以是别的进程释放
semaphore: 【由计数器和 任务等待表 两部分组成,也就是说每个信号量都有自己的任务等待表】
- 如果信号量的值为0,任务进入wait状态,并在任务等待表上面被标记,然后触发scheduler
- 当其他任务释放了信号量后,会在该信号量的任务等待表中找到最高优先级的任务,并将其从wait转为ready状态,然后触发scheduler
所以scheduler的工作很简单,在任务就绪表里面找最高优先级的任务【有更高的就上下文切换】
- 想让任务wait,并交出cpu控制权很简单:把它在任务就绪表自己的格子中置0,在任务等待表中置1,然后触发scheduler即可
- 想让任务从wait到ready,更简单:把它在任务就绪表自己的格子中置1,在任务等待表中置0,然后触发scheduler即可
说白了extended task,就是task可以【因为共享资源不可用】主动放弃cpu控制权,进入wait状态,让优先级低的先去执行【任务就绪表置0,任务等待表置1】
basic task,task没法自己主动放弃cpu,只能是被动的被其他优先级更高的抢占
任务调度的原理:
scheduler被调用的场景:
- timer ISR调用scheduler【周期性的任务切换】
- task结束,调用scheduler
- extended task因为share resource不可用,放弃cpu进入wait状态,然后触发scheduler
任务就绪表【1张】 + 任务等待表【多张,每个信号量都有自己的1张任务等待表,信号量在被task释放的时候,该task会去该信号量的任务等待表唤醒最高优先级的任务进入任务就绪表,然后触发一次scheduler】
scheduler做的事情:
查找任务就绪表优先级最高的任务,如果需要切换,就context switch切换。否则就继续执行当前任务,开销很小
【杂项】
big/little endian
针对超过一个byte的数据而言。most significant byte在低地址的为big endian
检测方法:
int a = 0x12345678;
char *p = (char*)&a; //(char*) 告诉编译器用char类型来解析a地址的数据
注:
通过char型指针p变量,获取第一个byte,既可判断
inline 函数
a suggestion to the compiler that it should generate code for the function at the call site, instead of generating a separate function call. for better performance
only a suggestion, compiler will make the final decision
include <> 和 “” 的区别
<>: 寻找 system path(编译器的安装目录文件夹)
"": 寻找当前project path(当前工程文件夹),找不到再找system path
上电流程/startup phase
【以下针对嵌入式MCU而言】
单核:
- reset vector
- startup code
- init clock
- memory setup: copy from FLASH to RAM + init stack pointer(stack信息在linker里面定义的 .lsl文件) (.bss .data 会在 RAM运行, .text还是在FLASH运行)
- init peripheral: init GPIO, CAN (如果需要bootloader功能的话)
- system init: 中断向量表初始化,enable 中断
- main()
【linker里面配置了stack的大小,startup phase把stack pointer指向了该位置, 之后main函数入的就是这个栈。只是说操作系统在任务切换的时候,会把cpu register内该task的信息copy到那个task自己的任务控制块里面零时存起来,保护现场。之后再copy回来,就等于恢复现场,可以继续运行了】
【对于操作系统,每个task都要有自己的任务栈,运行的时候,cpu的stack pointer要指过去,是为了方便任务切换。如果所有task公用一个栈,假如task1先入栈运行,然后被优先级更高的task2抢占,入栈到它上面,然后task2运行一半休眠了,那么就没法access到task1的栈内容了。就算你把task2的栈pop出来去存储,也太麻烦了,消耗时间太多】
多核:
- 硬件启动master core0, core0去唤醒slave core1,2,3 。【唤醒的含义是硬件初始化+startOS】 在所以核心的OS start以后,会进行第一次同步。
- 第一次同步后,各个core会去call application startup hook,然后进行第二次同步。从而确保所有core的OS kernel一起开始运行。
- 【第一次同步是OS 初始化完成(OS的stack什么的)【EcuM startupOne】,第二次同步是进程初始化完成(OS-Application,Task的堆栈,控制块初始化等等)【EcuM startupTwo】,然后同时开启时间片进行Task调用】
中断向量表:
中断向量表网站链接:(array of function pointers)
- 一个用来存储各个中断服务函数地址的内存区域
- 中断向量表区间默认是空着的
- 用户在c文件里面创建了ISR函数,那么MCU上电的时候,startup code就要根据用户创建的各个ISR的地址,把这些函数地址都注册到 中断向量表 里面
- 中断向量表在code/FLASH section
Interrupts and exceptions:
-
interrupt: trigger by external event, save context and jump to the ISR
- system exceptions: trigger by cpu (divided by zero, invalid memory access), save context and jumps to an exception handler routine
下图可以看到,Interrupts and exceptions 都在 中断向量表里面
抢占式: (Preemptive)
-
Preemptive OSes are often used in real-time systems where tasks must meet strict deadlines. The OS can guarantee that high-priority tasks will run when required.
-
some preemptive OSes use time slicing, where each task is given a fixed time quantum to run. When the time quantum expires, the task is preempted even if it hasn't finished.
函数入栈流程:
函数入栈流程网站链接:
函数栈从高地址到低地址增长
return address, actual parameters and local variable are pushed into stack【返回地址就是下一行该执行的代码的地址】
ebp, esp stands for base pointer and stack pointer
一道很好的题目
call by value/reference:
- by value: a copy of the actual argument's value is passed to the function, any changes made to the parameter (in the function) do not affect the original argument
- by reference: pointer to the actual argument is passed to the function, It is useful when you want a function to modify the original data
actual and formal parameters:
-
formal: variables or placeholders that are declared as part of a function's definition
- actual: the values passed to a function when it is called
context switch:
save the current status of task into control blocks 【register, task stacks】
trigger by interrupt, pre-emptive multi-task,
reentrant function
- can be safely called simultaneously by multiple threads,
- Reentrant functions use only local data (variables or memory) and do not rely on global or shared data, do not use static variables
#pragma
memory mapping 的时候,用到过
- variable placement:
- code section placement:
原码,反码,补码
正数的原码,反码,补码都一样
负数的原码:和正数一样,除了符号位置1 【问题,正负数原码相加不为0】
反码(ones' complement):直接把正数的原码反过来【问题,正负数反码相加为0xFFFF】
补码(twos' complement):反码+1 【正负数补码相加不为0】
虚拟内存: paged memory
数据结构:
位操作:
数组操作:
指针:
qsort
链表:
malloc
free