嵌入式与C语言

时间:2021-04-12 19:25:04

Volatile

Volatile 英文单词的意思是不稳定的,反复无常的. 在C语言中用volatile的关键字声明变量,就是说这个变量会发生意想不到的变化,每次用这个值的时候都需要重新去读取. 精确的讲应该是优化器用这个变量的时候每次都需要重新去读取这个值,而不能用保存在寄存器里的变量。这是编译器优化器的副作用,因此有些变量要定义为volatile, 特别是在嵌入式编程中, 这是区分嵌入式工程师和C语言工程师之间最基本的一点

那什么样的变量需要是volatile类型呢?

  1. 硬件寄存器
  2. 中断处理程序访问的非自动变量(静态变量,全局变量)
  3. 多线程共同访问的变量

再稍微深入的讲一些,先提几个问题

  1. 一个变量可以既是volatile又是const的吗?why?
  2. 指针可以使volatile的吗?why?

在讲第一个问题的时候, 先说下const这个关键字

const: 一般人直接说它是常量,更精确的讲法应该是只读,很多函数传入的是个const类型,就是防止你改变它。

const的作用:

  1. 一个参数是const就是为了告诉用户这个参数的应用目的,给读代码的人传达很有用的信息
  2. 合理使用const可以使编译器很自然地保护不希望被改变的参数,防止其被无意修改,减少了bug的出现

回到正题: 答案是肯定的,一个变量既可以是const又是volatile,const 是指程序不应该去修改它,volatile是表明这个变量可能会发生意想不到的改变。只读寄存器就可以是这样的一个例子。

那个一个指针可以使volatile么? 显然是可以的,只是不大常见而已,指针可能会发生意想不到的改变,例如中断程序中修改了一个指向buffer的指针

一个经典的面试题

int square(volatile int *ptr)
{
return *ptr * *ptr;
}
上面函数有问题吗? 如果有,错在哪里,怎么改?

square 函数的想法是求一个数的平方,但是*ptr可能会发生意向不到的改变,导致两次读出来的值不一样,比如硬件寄存器的值,那么这个函数返回的就不是一个数的平方了

对于上述函数编译器产生的代码类似于

int square(volatile int *ptr)
{
int a, b;
a = *ptr;
b = *ptr;
return a * b; // a是第一次读到的值,b是第二次读到的值
}

正确的代码应该是

int square(volatile int *ptr)
{
int a = *ptr;
return a * a;
}

中断

中断处理流程

  1. 中断请求:中断条件满足产生中断,中断源向CPU报告,请求CPU暂时停下当前任务来处理中断
  2. 中断屏蔽:中断请求报告给CPU,但不一定能得到CPU的响应,只有相应的中断位没有被屏蔽,才能真正到达CPU并得到相应
  3. 中断响应:CPU响应中断,记录被打断的任务点,把断点地址保护到堆栈中(中断响应结束后可以返回到这个点继续处理),屏蔽所有其他中断(如果是允许终端嵌套的话,即高优先级的中断可以中断低优先级的中断,那只屏蔽自身和比自身优先级低的中断)
  4. 保护现场: 在处理中断时可能破坏原有的工作现场,因此需要对工作现场进行适当的保护
  5. 调查中断源: 检查中断来自哪个中断源
  6. 中断处理: 对查明的中断源进行针对性的中断服务处理
  7. 清除中断标志:中断处理完成之后,清除中断标志,以免造成重复响应
  8. 恢复现场: 恢复被打断的任务的工作现场,以便继续执行被中断的任务
  9. 中断返回:从堆栈中恢复断点地址,并清除中断屏蔽,继续执行之前被打断的任务

中断程序是为了快速响应处理,因此中断函数有以下几个原则

  1. 中断随时都可能会产生,当中断产生,系统直接运行中断服务子程序,由于可能没有调用者,因此中断函数不应该有参数和返回值,因为系统不知道应该传入什么参数,同样中断服务程序也不知道返回值应该返回给谁
  2. 由于要快速响应处理,因此中断程序要短而高效,对于在中断中调用print函数和浮点运算都是不明智的,影响性能
  3. 函数应该是可重入的

以下函数就违背了上述几个原则

__interrupt double compute_area (double radius) 
{
double area = PI * radius * radius;
printf("/nArea = %f", area);
return area;
}