【C语言常识】原子性问题一
2016-12-29 20:02
246 查看
这篇博客写的会很散,因为都是一些琐碎的东西,看客们就直接飞过就可以了。
一、对数据的原子性访问
对数据的原子性访问,也即是对数据完整性问题的探究。打个比方ISR对uint16_t型数据写操作,
用户TASK对去读取,并且做判断,根据判断结果进行操作。这是一个很常见的应用,如果实在8
位机上,那么对于变量的读取需要两个周期完成。如果刚读完一个字节,被ISR打断,ISR对数据
进行了写操作后返回,继续读取后一个字节,那个这个数据就是错误的。所以,在用户读取数据的
时候,要进行原子性保护,保证读取的数据完整性。
数据对齐到ALU地址总线,并且地址对齐到本身字长,那么对它的读和写访问都可以保证原子性,
但是对它的读取-修改-会写这个过程是不能保证原子性的。
对于共享资源,全局标志必须考虑其原子性问题。
二、对IO的原子性操作
什么意思呢,假如IO口不能位操作,必须通过对其到位宽的数据操作,那么我们一定要保证其原子性
操作。也就是说不是分配给自己的IO口引脚,必须不能改变它们的值,也可以叫“别人的奶露动不得”。
在读取的时候,问题不大,直接过滤掉其它值就可以,只看自己关心的。但是写的时候,必须十分小心
,必须先读-修改自己关心的,其它不动-在回写。
三、对寄存器的原子性操作
其原理同IO操作一样。我举个具体的例子。
我的任务为了防止多任务干扰,对关键性代码加入了原子性保护,开始我是这么定义的:
#define DIS_INT() {INTCON &=0b00111111;}
#define EN_INT() {INTCON |=0b11000000;}
程序没有问题。但是这个宏不能嵌套使用,就是说内部函数在退出关键性代码的时候,直接打开了中断。
因此,我改为了这样:
#define GET_GLOBAL_INTERRUPT_STATE() (INTCON)
#define SET_GLOBAL_INTERRUPT_STATE(__STATE)
{INTCON = __STATE;}
#define DISABLE_GLOBAL_INTERRUPT() DisINT()
#define EXIT_GLOBAL_INTERRUPT()
SET_GLOBAL_INTERRUPT_STATE(tState)
并用下面宏封装了下:
#define SAFE_ATOM_CODE(__CODE) {\
istate_t tState = GET_GLOBAL_INTERRUPT_STATE();\
DISABLE_GLOBAL_INTERRUPT();\
{\
__CODE;\
}\
SET_GLOBAL_INTERRUPT_STATE(tState);\
}
接着调试就发现定时器时间不准,老是飘动,怎么回事呢?经过查看手册发现,INTCON里面bit2是TIMER0
的中断标志位,因为备份INTCON的时候,标志位为0,所以还原的时候,直接就清零了,导致中断时间老是飘动。
因此改为下面:
#define GET_GLOBAL_INTERRUPT_STATE() (INTCON & 0xC0)
#define SET_GLOBAL_INTERRUPT_STATE(__STATE)
{INTCON = INTCON|__STATE;}
#define DISABLE_GLOBAL_INTERRUPT() DisINT()
#define EXIT_GLOBAL_INTERRUPT()
SET_GLOBAL_INTERRUPT_STATE(tState)
再查看示波器,没有问题了。
道理很简单,也不复杂,但是用的时候不注意,导致一些奇怪的问题,然后就感觉很稀奇,其实都是一些我们
知道的常识,在编程的时候没有刻意去遵守。
四、谁偷走了我的"apple"
在进行通信时候利用缓存先暂存起来,一边往里面写入数据,一边从里面读出数据。这是个很常见的应用,同时
也是一个原子性保护很典型的例子;
bool enqueue_byte_queue(...)
{
if(满){
return false;
}
入队操作;
num++;
。。。
}
bool dequeue_byte_queue(...)
{
if(空){
return false;
}
出队操作;
num--;
}
上面代码,看着没有问题,我们进一步分析。入队操作我们放到uart的接收中断里,出队操作放到用户APP里面。
假设:num = 10;缓存大小为100
用户APP执行到出队操作:
出队操作;
num--;//num->读取到SFR=10被中断
然后去执行中中断函数
入队操作;
num++;
//执行完毕后 num=11;
返回中断点继续执行:
num--;
//执行完毕后num=9;
那么我入对的数据跑哪里去了?
五、怎么多了一个UART
硬件UART只有一个,用户有两个TASK都需要用,一般用一个互斥信号量来做。假设这两个
任务可以相互被打断。
bool uart_is_busy = false;
void task1_tx(...)
{
...
if(uart_is_busy){
return ;
}
uart_is_busy = true;
...
}
void task2_tx(...)
{
...
if(uart_is_busy){
return ;
}
uart_is_busy = true;
...
}
那么上面的代码看着没有问题,我们分析下:
task1执行到:
if(uart_is_busy){
return;
}
//被打断,去执行task2
uart_is_busy = true;
task2执行:
if(uart_is_busy){
return ;
}
uart_is_busy = true;
。。。
//被打断,返回中断点继续
task1:
uart_is_busy = true;
...
看到没有,明明就一个UART,也做了判断,为什么TASK1和TASK2还可以同时使用UART呢?
这个问题怎么解决呢?其实也简单,那就是用“单一职责”原则去解决。为什么这么说呢?
因为上面的uart_is_busy 有两个只能:
1、判断现在UART是否忙?
2、申请UART资源?
第二个职能其实是隐含的,因此为了解决“线程安全”问题,我们必须把它们分开,才能解决问题。
具体做法,在uart.c中定义:
static struct{
bool bIsBusy;
uint16_t hwTxdLong;
uint16_t hwTxdCnt;
uint8_t *pchTxdBuffer;
void (*pRxCallBack)(uint8_t);
}s_tUART1Parameter;
实现如下接口函数:
/*****************************************************************************
* Function: uart1_apply_semaphore
* PreCondition: None
* Input: void
* Output: void
* Side Effects: None
* Overview: USRT1申请资源
* Note: 保证线程安全,但是如果申请到了,不用,就需要手动释放
*****************************************************************************/
bool uart1_apply_semaphore(void)
{
bool bTemp = false;
#if(UART1_SERVICE_IS_ENABLE_LOCK && UART1_IS_BUSY_IS_ADD_LOCK)
SAFE_ATOM_CODE(
bTemp = s_tUART1Parameter.bIsBusy;
s_tUART1Parameter.bIsBusy = true;
)
#else
bTemp = s_tUART1Parameter.bIsBusy;
s_tUART1Parameter.bIsBusy = true;
#endif
return (!bTemp);
}
/*****************************************************************************
* Function: uart1_is_busy
* PreCondition: None
* Input: void
* Output: void
* Side Effects: None
* Overview: USRT1是否发送忙
* Note:
*****************************************************************************/
bool uart1_is_busy(void)
{
return s_tUART1Parameter.bIsBusy;
}
/*****************************************************************************
* Function: uart1_release_the_semaphore
* PreCondition: None
* Input: void
* Output: void
* Side Effects: None
* Overview: USRT1手动清除标志位
* Note: None
*****************************************************************************/
void uart1_release_the_semaphore(void)
{
s_tUART1Parameter.bIsBusy = false;
}
SAFE_ATOM_CODE宏就是关闭了中断(记得要能够嵌套使用);
如果操作系统有自己的信号量服务,那么直接用系统的信号。
一、对数据的原子性访问
对数据的原子性访问,也即是对数据完整性问题的探究。打个比方ISR对uint16_t型数据写操作,
用户TASK对去读取,并且做判断,根据判断结果进行操作。这是一个很常见的应用,如果实在8
位机上,那么对于变量的读取需要两个周期完成。如果刚读完一个字节,被ISR打断,ISR对数据
进行了写操作后返回,继续读取后一个字节,那个这个数据就是错误的。所以,在用户读取数据的
时候,要进行原子性保护,保证读取的数据完整性。
数据对齐到ALU地址总线,并且地址对齐到本身字长,那么对它的读和写访问都可以保证原子性,
但是对它的读取-修改-会写这个过程是不能保证原子性的。
对于共享资源,全局标志必须考虑其原子性问题。
二、对IO的原子性操作
什么意思呢,假如IO口不能位操作,必须通过对其到位宽的数据操作,那么我们一定要保证其原子性
操作。也就是说不是分配给自己的IO口引脚,必须不能改变它们的值,也可以叫“别人的奶露动不得”。
在读取的时候,问题不大,直接过滤掉其它值就可以,只看自己关心的。但是写的时候,必须十分小心
,必须先读-修改自己关心的,其它不动-在回写。
三、对寄存器的原子性操作
其原理同IO操作一样。我举个具体的例子。
我的任务为了防止多任务干扰,对关键性代码加入了原子性保护,开始我是这么定义的:
#define DIS_INT() {INTCON &=0b00111111;}
#define EN_INT() {INTCON |=0b11000000;}
程序没有问题。但是这个宏不能嵌套使用,就是说内部函数在退出关键性代码的时候,直接打开了中断。
因此,我改为了这样:
#define GET_GLOBAL_INTERRUPT_STATE() (INTCON)
#define SET_GLOBAL_INTERRUPT_STATE(__STATE)
{INTCON = __STATE;}
#define DISABLE_GLOBAL_INTERRUPT() DisINT()
#define EXIT_GLOBAL_INTERRUPT()
SET_GLOBAL_INTERRUPT_STATE(tState)
并用下面宏封装了下:
#define SAFE_ATOM_CODE(__CODE) {\
istate_t tState = GET_GLOBAL_INTERRUPT_STATE();\
DISABLE_GLOBAL_INTERRUPT();\
{\
__CODE;\
}\
SET_GLOBAL_INTERRUPT_STATE(tState);\
}
接着调试就发现定时器时间不准,老是飘动,怎么回事呢?经过查看手册发现,INTCON里面bit2是TIMER0
的中断标志位,因为备份INTCON的时候,标志位为0,所以还原的时候,直接就清零了,导致中断时间老是飘动。
因此改为下面:
#define GET_GLOBAL_INTERRUPT_STATE() (INTCON & 0xC0)
#define SET_GLOBAL_INTERRUPT_STATE(__STATE)
{INTCON = INTCON|__STATE;}
#define DISABLE_GLOBAL_INTERRUPT() DisINT()
#define EXIT_GLOBAL_INTERRUPT()
SET_GLOBAL_INTERRUPT_STATE(tState)
再查看示波器,没有问题了。
道理很简单,也不复杂,但是用的时候不注意,导致一些奇怪的问题,然后就感觉很稀奇,其实都是一些我们
知道的常识,在编程的时候没有刻意去遵守。
四、谁偷走了我的"apple"
在进行通信时候利用缓存先暂存起来,一边往里面写入数据,一边从里面读出数据。这是个很常见的应用,同时
也是一个原子性保护很典型的例子;
bool enqueue_byte_queue(...)
{
if(满){
return false;
}
入队操作;
num++;
。。。
}
bool dequeue_byte_queue(...)
{
if(空){
return false;
}
出队操作;
num--;
}
上面代码,看着没有问题,我们进一步分析。入队操作我们放到uart的接收中断里,出队操作放到用户APP里面。
假设:num = 10;缓存大小为100
用户APP执行到出队操作:
出队操作;
num--;//num->读取到SFR=10被中断
然后去执行中中断函数
入队操作;
num++;
//执行完毕后 num=11;
返回中断点继续执行:
num--;
//执行完毕后num=9;
那么我入对的数据跑哪里去了?
五、怎么多了一个UART
硬件UART只有一个,用户有两个TASK都需要用,一般用一个互斥信号量来做。假设这两个
任务可以相互被打断。
bool uart_is_busy = false;
void task1_tx(...)
{
...
if(uart_is_busy){
return ;
}
uart_is_busy = true;
...
}
void task2_tx(...)
{
...
if(uart_is_busy){
return ;
}
uart_is_busy = true;
...
}
那么上面的代码看着没有问题,我们分析下:
task1执行到:
if(uart_is_busy){
return;
}
//被打断,去执行task2
uart_is_busy = true;
task2执行:
if(uart_is_busy){
return ;
}
uart_is_busy = true;
。。。
//被打断,返回中断点继续
task1:
uart_is_busy = true;
...
看到没有,明明就一个UART,也做了判断,为什么TASK1和TASK2还可以同时使用UART呢?
这个问题怎么解决呢?其实也简单,那就是用“单一职责”原则去解决。为什么这么说呢?
因为上面的uart_is_busy 有两个只能:
1、判断现在UART是否忙?
2、申请UART资源?
第二个职能其实是隐含的,因此为了解决“线程安全”问题,我们必须把它们分开,才能解决问题。
具体做法,在uart.c中定义:
static struct{
bool bIsBusy;
uint16_t hwTxdLong;
uint16_t hwTxdCnt;
uint8_t *pchTxdBuffer;
void (*pRxCallBack)(uint8_t);
}s_tUART1Parameter;
实现如下接口函数:
/*****************************************************************************
* Function: uart1_apply_semaphore
* PreCondition: None
* Input: void
* Output: void
* Side Effects: None
* Overview: USRT1申请资源
* Note: 保证线程安全,但是如果申请到了,不用,就需要手动释放
*****************************************************************************/
bool uart1_apply_semaphore(void)
{
bool bTemp = false;
#if(UART1_SERVICE_IS_ENABLE_LOCK && UART1_IS_BUSY_IS_ADD_LOCK)
SAFE_ATOM_CODE(
bTemp = s_tUART1Parameter.bIsBusy;
s_tUART1Parameter.bIsBusy = true;
)
#else
bTemp = s_tUART1Parameter.bIsBusy;
s_tUART1Parameter.bIsBusy = true;
#endif
return (!bTemp);
}
/*****************************************************************************
* Function: uart1_is_busy
* PreCondition: None
* Input: void
* Output: void
* Side Effects: None
* Overview: USRT1是否发送忙
* Note:
*****************************************************************************/
bool uart1_is_busy(void)
{
return s_tUART1Parameter.bIsBusy;
}
/*****************************************************************************
* Function: uart1_release_the_semaphore
* PreCondition: None
* Input: void
* Output: void
* Side Effects: None
* Overview: USRT1手动清除标志位
* Note: None
*****************************************************************************/
void uart1_release_the_semaphore(void)
{
s_tUART1Parameter.bIsBusy = false;
}
SAFE_ATOM_CODE宏就是关闭了中断(记得要能够嵌套使用);
如果操作系统有自己的信号量服务,那么直接用系统的信号。
相关文章推荐
- C语言测试:想成为嵌入式程序员应知道的0x10个基本问题
- 我用c语言写了一个关于商人过河的问题
- c语言学习零碎整理(2):结构体对齐问题
- C语言小问题-不可小看
- C语言上机练习常见问题
- C语言之编译器引出的问题
- 一些内存使用问题(c语言)
- C语言指针问题
- C语言中的typedef问题.
- C语言的问题
- C语言之可变参数问题
- 水滴石穿C语言之typedef的问题
- 我有个问题想请高手解答一下!是C语言的!谢谢了!我有急用
- 一个c语言构造函数调用的问题(有趣)
- 关于C语言For循环的一个问题~!
- 水滴石穿C语言之typedef的问题
- C语言测试:想成为嵌入式程序员应知道的0x10个基本问题
- 一道C语言面试题——邮票组合问题
- C语言测试:想成为嵌入式程序员应知道的0x10个基本问题
- C语言编程中的重要问题