电子产业一站式赋能平台

PCB联盟网

搜索
查看: 329|回复: 0
收起左侧

关于共享资源保护的思考

[复制链接]

567

主题

567

帖子

4209

积分

四级会员

Rank: 4

积分
4209
发表于 2022-11-7 08:30:00 | 显示全部楼层 |阅读模式
引言* t+ V  H1 w7 |- w. k
先聊聊分享这篇文章的原因,在使用STM32时,我发现对于GPIO输出操作,可以使用GPIOx_ODR寄存器,也可以使用GPIOx_BSRR寄存器。# [0 ?  a5 C2 u8 E

cgzi30l0ebm64014187051.png

cgzi30l0ebm64014187051.png

8 |3 S; J! ~5 L, U6 F对应的标准外设库API接口有
5 b* Z* g7 T$ b0 m0 W4 C& z% o' y, z3 Y
  • void GPIO_ToggleBits(GPIO_TypeDef* GPIOx, uint16_t PortVal)void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)对于我来说,我一直在用GPIO_SetBits和GPIO_ResetBits接口,一直对GPIO_ToggleBits无感。最近注意的这个问题,经过查资料和FAE确认,这样做的,目的是防止同一个port的其他GPIO被篡改。
    2 t- V; B0 J6 J看下GPIO_ToggleBits的具体实现8 u- J: z" A5 U& M; P% _) Y' W
  • void GPIO_ToggleBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin){  /* Check the parameters */  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));  GPIOx->ODR ^= GPIO_Pin;}GPIOx->ODR ^= GPIO_Pin;等于先读取GPIOx->ODR,再修改对应的GPIO的值,最后写入GPIOx->ODR。这就是一个读-写-改的常规操作。这个操作是存在风险的。在我们读取时是存在被其他代码中断的情况的。ODR ^= GPIO_Pin;等于先读取GPIOx->ODR,在修改对应的GPIO的值,最后写入GPIOx->ODR。这就是一个读-写-改的常规操作。这个操作是存在风险的。在我们读取时是存在被其他代码中断的情况的。","marks":[]}]}],"state":{}},{"type":"block","id":"zgBv-1667658713078","name":"paragraph","data":{},"nodes":[{"type":"text","id":"PxVt-1667658713077","leaves":[{"text":"举栗子,假设我们想要修改GPIO0,且bit1是一个重要的GPIO,比如电源的使能引脚。我们读出的GPIOx->ODR是0x0001,也就是bit0为1,bit1为0。这个时候被某个而中断,在中断里我们需要给某个系统上电,我们将GPIO1拉高了。退出中断继续执行刚才的代码,读出的GPIOx->ODR是0x0001,将bit0清零,也就是将0写入GPIOx->ODR。","marks":[]}]}],"state":{}},{"type":"block","id":"UL3k-1667659089052","name":"paragraph","data":{},"nodes":[{"type":"text","id":"MjIP-1667659089051","leaves":[{"text":"那么这个时候问题就大了啊,GPIO1被拉低了,等于没给另外的系统上电。而且这种bug不易察觉,且一般情况下不必现,在客户现场偶现,这就很抓狂了。","marks":[]}]}],"state":{}},{"type":"block","id":"MODx-1667659179276","name":"paragraph","data":{},"nodes":[{"type":"text","id":"frjr-1667659179275","leaves":[{"text":"所以看到这里大家也就明白了芯片厂家为什么设计GPIOx_BSRR寄存器操作GPIO原因了。GPIOx_BSRR寄存器可以直接操作对应的GPIO,不需要读写改操作,就避免了上面的bug。","marks":[]}]}],"state":{}},{"type":"block","id":"0BqQ-1667659300969","name":"paragraph","data":{},"nodes":[{"type":"text","id":"em3q-1667659300967","leaves":[{"text":"当然,你也可以在使用GPIOx->ODR ^= GPIO_Pin;先屏蔽所有中断,操作后再打开所有中断,这是共享资源保护的一种常规操作,但GPIO作为一个使用频率很高的外设,频繁关闭中断是不好的,所以还使用GPIO_SetBits和GPIO_ResetBits接口为好。","marks":[]}]}],"state":{}},{"type":"block","id":"foxI-1667659407178","name":"paragraph","data":{},"nodes":[{"type":"text","id":"BX08-1667659407176","leaves":[{"text":"那么GPIOx->ODR 存在即合理,它对应的是GPIO_Write接口,可以一次写入一个port的所有GPIO数据,这对于一些特殊场景是非常有用的,有些场景需要一次性写入同一个port的所有GPIO,类似并口操作,这里效率很高。","marks":[]}]}],"state":{}}]">举个栗子,假设我们想要修改GPIO0,且bit1是一个重要的GPIO,比如电源的使能引脚。我们读出的GPIOx->ODR是0x0001,也就是bit0为1,bit1为0。这个时候触发了某个中断,在中断里我们需要给某个系统上电,我们将GPIO1拉高了。退出中断继续执行刚才的代码,读出的GPIOx->ODR是0x0001,将bit0清零,也就是将0写入GPIOx->ODR。那么这个时候问题就大了啊,GPIO1被拉低了,等于没给另外的系统上电。而且这种bug不易察觉,且一般情况下不必现,在客户现场偶现,这就很抓狂了。所以看到这里大家也就明白了芯片厂家为什么设计GPIOx_BSRR寄存器操作GPIO原因了。GPIOx_BSRR寄存器可以直接操作对应的GPIO,不需要读写改操作,就避免了上面的bug。当然,你也可以在使用GPIOx->ODR ^= GPIO_Pin;先屏蔽所有中断,操作后再打开所有中断,这是共享资源保护的一种常规操作,但GPIO作为一个使用频率很高的外设,频繁关闭中断是不好的,所以还是使用GPIO_SetBits和GPIO_ResetBits接口为好。那么GPIOx->ODR 存在即合理,它对应的是GPIO_Write接口,可以一次写入一个port的所有GPIO数据,这对于一些特殊场景是非常有用的,有些场景需要一次性写入同一个port的所有GPIO,类似并口操作,这里效率很高。1 {8 ^0 N: y& f5 s
    共享资源的保护
    ; p7 ~+ g& i4 y/ T5 R上文我们提到了共享资源保护,linux中采用原子操作,FreeRTOS中一般采用互斥信号量,也称互斥锁。希望大家都要有一种意识,像ODR这样的寄存器也是一种共享资源。
    ) E. d' l5 j' A6 lODR ^= GPIO_Pin;操作这么明显。","marks":[]}]}],"state":{}},{"type":"block","id":"bfSZ-1667660888272","name":"paragraph","data":{"version":1},"nodes":[{"type":"text","id":"rAUq-1667660888271","leaves":[{"text":"大家要明确,","marks":[]},{"text":"判断语句","marks":[{"type":"bold"}]},{"text":"也是读操作。","marks":[]}]}],"state":{}},{"type":"block","id":"gd5j-1667660903416","name":"paragraph","data":{"version":1},"nodes":[{"type":"text","id":"MEeT-1667660903415","leaves":[{"text":"假设在RTOS中有个全局变量event_flg,如果它为1时,在两个任务中都要进行一段操作,比如像语音芯片发送一段语音。发送完毕将event_flg清零,并且这两个任务中的语音不能都播放。伪代码如下","marks":[]}]}],"state":{}}]" style="margin-bottom: 24px;">对于共享资源的操作都是需要保护的,如果使用RTOS,对于串口,SPI等这样外设一定要注意共享资源的保护。
    1 b* ~+ y, d1 S. M像是ODR寄存器,一些在RTOS多个任务都要读写的全局变量都需要进行保护的。在一些读写操作,并不是我们刚看到的GPIOx->ODR ^= GPIO_Pin;操作这么明显。
    , d6 B/ E/ j& x7 @/ u大家要明确,判断语句也是读操作
    + C" N8 f  I, j# o( [假设在RTOS中有个全局变量event_flg,如果它为1时,在两个任务中都要进行一段操作,比如向语音芯片发送一段语音。发送完毕将event_flg清零,并且这两个任务中的语音不能都播放。伪代码如下
    4 k6 I4 r1 o# f% E* J& [
  • void low_task_entry(void *pvParameters){  while(1)  {    if(event_flg)    {        /*发送语音1*/        event_flg =0;    }    vTaskDelay(500);  }}void high_Task_entry(void *pvParameters){  while(1)  {    if(event_flg)    {        /*发送语音2*/        event_flg =0;    }    vTaskDelay(100);  }}那么就存在low_task_entry执行完第5句代码,判断event_flg为1,即执行下一段代码时,被high_Task_entry打断,并且在high_Task_entry中成功播放了语音,且将event_flg清零。( C/ A8 ~4 X4 `. w- U) Z
    当回到任务low_task_entry时,虽然event_flg已经是0了,但是不好意思,退出low_task_entry已经判断过了,现在回到函数会直接往下执行第6行代码,播放语音。这样神奇的bug就出来了。) O# f2 Q# E5 S. h2 O1 U" v# ]
    那么有同学说,在high_Task_entry播放语音前,将某个全局变量置为1,在low_task_entry播放语音前,再判断这个全局变量。是的,可以的,这是软件层的解决办法,能解决问题就行。% Z! q, J; J# K* _4 \. N3 v% V* F
    本例的是希望大家体会到判断语句也是读操作,注意共享资源的保护。5 v7 T& D- `+ @# d1 \: {, H
    大家留意看一些RTOS源码时,某个简单的if判断语句也要进行保护,就是这个原因
    5 H5 M( K: k7 @, V后记! P3 \5 D/ U6 r  o1 ]
    今天没有特殊的后记内容,之前我看到一个RTOS源码经常对简单的if语句进行保护不懂其中奥秘,今天也算是明白了。
    & E1 Z- {+ q3 x9 F0 n如果对大家有所帮助,希望能多多分享,不想分享的,点个赞也好。
    2 y; H% q4 ^6 h) X9 F* b: W* Y6 jEND
    ! Y& O8 H% w" u

    hfpoufbfgls64014187151.gif

    hfpoufbfgls64014187151.gif
    4 D# m1 A8 O2 H1 U

    w4dlzswkeio64014187251.gif

    w4dlzswkeio64014187251.gif

    . l" v" Q6 b! X) D" E; ^4 S?聊聊数据溢出的事 必读# G, t1 j/ h. X& V& f
    ?VS code调试C代码' \- V$ u# w4 \5 j1 s) m
    ?关于软件定时器的一些讨论/ E5 \( d+ _: g: k) P
    ?深入讨论下如何高效的打断点 必读: M' @* \" u/ n3 x; e7 Z: J3 v: \
    ?C语言数组为什么不检查下标
  • 回复

    使用道具 举报

    发表回复

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则


    联系客服 关注微信 下载APP 返回顶部 返回列表