【C语言常识】原子性问题一

      这篇博客写的会很散,因为都是一些琐碎的东西,看客们就直接飞过就可以了。

一、对数据的原子性访问

对数据的原子性访问,也即是对数据完整性问题的探究。打个比方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宏就是关闭了中断(记得要能够嵌套使用);

如果操作系统有自己的信号量服务,那么直接用系统的信号。




  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值