什么是互斥锁
互斥锁包含加锁和解锁两个操作,一般操作系统都有自带的API函数。在所有使用数据A之前先加锁,使用后解锁。当有任务1和任务2都用到数据A时,如果任务1加锁,即使任务2这时候抢占任务1要用数据A,这时他也用不了,需要等待任务1用完数据A后解锁,任务2才能继续执行使用数据A。从而保证了数据A的使用安全。
//互斥锁伪代码
int DataALock;
void Lock(int *Lock)
{
while(1)
{
if(0 == (*Lock))
{
*Lock = 1;
break;
}
else
{
//等待解锁
delay(1);
}
}
}
void Unlock(int *Lock)
{
*Lock = 0;
}
为什么要互斥锁
在实时操作系统中,由于任务与任务间的切换是由MCU控制的,对于一个全局变量被对个任务使用时就必须对其增加一个互斥锁以保证数据的安全。对于没有操作系统的框架,当有全局变量同时在正常程序和中断服务函数中使用是也需要增加互斥锁(当然一般对于没有使用操作系统的框架,为了系统安全,个人建议不要在中断服务和正常程序中同时使用一个变量)。
int A,B;
void Task1()
{
if(10 == A)
{
B = A - 10;
}
else if(A > 10)
{
A = 0;
}
else
{
A++;
}
}
void Task2()
{
A++;
}
对于上述代码如果将Task1和Task2中没有互斥锁,就会出问题。当Task1中A正好为10,且执行完“if(10 == A)”后被Task2抢占执行,那么B的值就会变成1,而程序原本的逻辑B是一直为0的不会出现1的情况,这个就破坏了正常程序的逻辑。
如何使用互斥锁
对于上述情况就需要对代码中的全局变量加上互斥锁以保证变量的安全,伪代码如下:
int A,B;
void Task1()
{
Lock(&DataALock);
if(10 == A)
{
B = A - 10;
}
else if(A > 10)
{
A = 0;
}
else
{
A++;
}
Unlock(&DataALock);
}
void Task2()
{
Lock(&DataALock);
A++;
Unlock(&DataALock);
}
结构体变量的互斥逻辑
上面都是针对单个变量的互斥锁,那如果我们是一个全局的结构体变量呢。目前我想到以下几种方法:
1.直接对整个结构体加锁解锁。那有时我只用到结构体的某个成员变量,可因为加锁导致其他成员变量一起被锁住,不合理
2.结构体中的每个成员变量单独加锁。这样锁太多了,而且实际写代码时很不方便,要用到某个成员变量都要调用他指定的锁
3.单独拷贝两份该结构体变量,一个用于读,一个用于写,目前我使用的是这种方案,如果有更好的方案欢迎大佬评论区告诉我一声。
typedef struct
{
int A;
char B;
float C;
int D;
//......
int Z;
int Array[10];
} SETTING;
SETTING SettingRead,SettingWrite;
/*FLASH_PARAM_BEGIN是参数存储的Flash地址*/
void init()
{
memcpy((void *)&SettingWrite, (void *)FLASH_PARAM_BEGIN, sizeof(SETTING));
memcpy((void *)&SettingRead, (void *)FLASH_PARAM_BEGIN, sizeof(SETTING));
}
SETTING *GetSystemSetting(void)
{
Lock(&DataALock);
if(memcmp((void *)&SettingRead, (void *)FLASH_PARAM_BEGIN, sizeof(SETTING)) != 0)
{
memcpy((void *)&SettingRead, (void *)FLASH_PARAM_BEGIN, sizeof(SETTING));
}
Unlock(&DataALock);
return &SettingRead;
}
void SetSystemSetting(uint8_t *pSettingWrite)
{
Lock(&DataALock);
Sys_Flash_ErasePage(FLASH_PARAM_BEGIN, sizeof(FF_SETTING));
Sys_Flash_WriteByte(FLASH_PARAM_BEGIN, pSettingWrite, sizeof(FF_SETTING));
Unlock(&DataALock);
}
void Task1()
{
SettingWrite.A = 1;
SettingWrite.B = 2;
memset(SettingWrite.Array, 0, sizeof(SettingWrite.Array));
SettingWrite.Array[2] = 2;
....../*SettingWrite的赋值*/
SetSystemSetting(&SettingWrite);
}
void Task2()
{
if(1 == GetSystemSetting()->A)
{
;/*执行相应的逻辑*/
}
if(2 == GetSystemSetting()->Array[2])
{
;/*执行相应的逻辑*/
}
}
void Task3()
{
if(1 == GetSystemSetting()->B)
{
SettingWrite.D = 4;
SetSystemSetting(&SettingWrite);
;/*执行相应的逻辑*/
}
}
上面伪代码通过SettingWrite和SettingRead两个变量对全局的设设置变量Setting形成了读写快照,锁住其面板。当执行GetSystemSetting()后就会形成一个当前Setting的面板,即使后续被其他任务打断修改了Setting值也不会受到影响。当Setting值改变后,下次GetSystemSetting()就会自动将SettingRead更新到最新的Setting;修改Setting只能通过SettingWrite进行修改,所以SettingWrite始终是最新的值。