您的位置:首页 > 编程语言 > C语言/C++

【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宏就是关闭了中断(记得要能够嵌套使用);

如果操作系统有自己的信号量服务,那么直接用系统的信号。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: