lab7 信号量与同步 信号量结构初始化 新建 lab7/src/include/prt_sem_external.h 头文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #ifndef PRT_SEM_EXTERNAL_H #define PRT_SEM_EXTERNAL_H #include "prt_sem.h" #include "prt_task_external.h" #if defined(OS_OPTION_POSIX) #include "bits/semaphore_types.h" #endif #define OS_SEM_UNUSED 0 #define OS_SEM_USED 1 #define SEM_PROTOCOL_PRIO_INHERIT 1 #define SEM_TYPE_BIT_WIDTH 0x4U #define SEM_PROTOCOL_BIT_WIDTH 0x8U #define OS_SEM_WITH_LOCK_FLAG 1 #define OS_SEM_WITHOUT_LOCK_FLAG 0 #define MAX_POSIX_SEMAPHORE_NAME_LEN 31 #define GET_SEM_LIST(ptr) LIST_COMPONENT(ptr, struct TagSemCb, semList) #define GET_SEM(semid) (((struct TagSemCb *)g_allSem) + (semid)) #define GET_SEM_TSK(semid) (((SEM_TSK_S *)g_semTsk) + (semid)) #define GET_TSK_SEM(tskid) (((TSK_SEM_S *)g_tskSem) + (tskid)) #define GET_SEM_TYPE(semType) (U32)((semType) & ((1U << SEM_TYPE_BIT_WIDTH) - 1)) #define GET_MUTEX_TYPE(semType) (U32)(((semType) >> SEM_TYPE_BIT_WIDTH) & ((1U << SEM_TYPE_BIT_WIDTH) - 1)) #define GET_SEM_PROTOCOL(semType) (U32)((semType) >> SEM_PROTOCOL_BIT_WIDTH)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 struct TagSemCb { U16 semStat; U16 semId;#if defined(OS_OPTION_SEM_RECUR_PV) U32 recurCount;#endif U32 semCount; struct TagListObject semList; struct TagListObject semBList; U32 semOwner; enum SemMode semMode; U32 semType;#if defined(OS_OPTION_POSIX) char name[MAX_POSIX_SEMAPHORE_NAME_LEN + 1 ]; sem_t handle;#endif };
[!NOTE]
关键字段 :
semCount
:信号量的计数值(例如,二进制信号量为0或1)。
semList
:阻塞在该信号量上的任务链表(当信号量不可用时挂起任务)。
semOwner
:记录当前持有信号量的任务(用于互斥锁)。
semType
:通过位掩码存储信号量类型和协议。
1 2 3 4 5 6 7 8 9 10 11 12 extern U16 g_maxSem; extern struct TagSemCb *g_allSem; extern U32 OsSemCreate (U32 count, U32 semType, enum SemMode semMode, SemHandle *semHandle, U32 cookie) ;extern bool OsSemBusy (SemHandle semHandle) ;#endif
[!NOTE]
OsSemCreate
:创建信号量。
参数:初始计数值(count
)、信号量类型(semType
)、模式(semMode
)、返回句柄(semHandle
)、可选参数(cookie
)。
OsSemBusy
:检查信号量是否被占用(例如,互斥锁是否已被任务持有)。
新建 src/kernel/sem/prt_sem_init.c 文件。
1 2 3 4 5 6 7 8 9 #include "prt_sem_external.h" #include "os_attr_armv8_external.h" #include "os_cpu_armv8_external.h" OS_SEC_BSS struct TagListObject g_unusedSemList; OS_SEC_BSS struct TagSemCb *g_allSem;extern void *OsMemAllocAlign (U32 mid, U8 ptNo, U32 size, U8 alignPow) ;
信号量初始化函数 初始化信号量模块,分配内存并设置所有信号量为空闲状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 OS_SEC_L4_TEXT U32 OsSemInit (void ) { struct TagSemCb *semNode = NULL ; U32 idx; U32 ret = OS_OK; g_allSem = (struct TagSemCb *)OsMemAllocAlign ((U32)OS_MID_SEM, 0 , 4096 , OS_SEM_ADDR_ALLOC_ALIGN); if (g_allSem == NULL ) { return OS_ERRNO_SEM_NO_MEMORY; } g_maxSem = 4096 / sizeof (struct TagSemCb); char *cg_allSem = (char *)g_allSem; for (int i = 0 ; i < 4096 ; i++) cg_allSem[i] = 0 ; INIT_LIST_OBJECT (&g_unusedSemList); for (idx = 0 ; idx < g_maxSem; idx++) { semNode = ((struct TagSemCb *)g_allSem) + idx; semNode->semId = (U16)idx; ListTailAdd (&semNode->semList, &g_unusedSemList); } return ret; }
信号量创建函数 从空闲链表中分配一个信号量,并初始化其属性。
[!NOTE] 参数
count
:信号量初始计数值(如二进制信号量为0或1)。
semType
:信号量类型(如SEM_TYPE_COUNT
计数型、SEM_TYPE_BIN
二进制型)。
semMode
:阻塞任务唤醒模式(如SEM_MODE_FIFO
先进先出)。
semHandle
:输出参数,返回创建的信号量句柄。
cookie
:预留参数(未使用)。 ``
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 OS_SEC_L4_TEXT U32 OsSemCreate(U32 count, U32 semType, enum SemMode semMode, SemHandle *semHandle, U32 cookie) { uintptr_t intSave; struct TagSemCb *semCreated = NULL; struct TagListObject *unusedSem = NULL; (void)cookie; if (semHandle == NULL) { return OS_ERRNO_SEM_PTR_NULL; } intSave = OsIntLock(); if (ListEmpty(&g_unusedSemList)) { OsIntRestore(intSave); return OS_ERRNO_SEM_ALL_BUSY; } unusedSem = OS_LIST_FIRST(&(g_unusedSemList)); ListDelete(unusedSem); semCreated = (GET_SEM_LIST(unusedSem)); semCreated -> semCount = count; semCreated -> semStat = OS_SEM_USED; semCreated -> semMode = semMode; semCreated -> semType = semType; semCreated -> semOwner = OS_INVALID_OWNER_ID; if (GET_SEM_TYPE(semType) == SEM_TYPE_BIN) { INIT_LIST_OBJECT(&semCreated -> semBList); #if defined(OS_OPTION_SEM_RECUR_PV) if (GET_MUTEX_TYPE(semType) == PTHREAD_MUTEX_RECURSIVE) { semCreated -> recurCount = 0 ; } #endif } INIT_LIST_OBJECT(&semCreated -> semList); *semHandle = (SemHandle)semCreated-> semId; OsIntRestore(intSave); return OS_OK; }
[!NOTE] 递归互斥锁递归互斥锁 (Recursive Mutex)是一种特殊的互斥锁(Mutex),允许 同一个线程 多次获取(Lock)同一个锁而不会导致死锁。普通互斥锁如果被同一个线程重复加锁,会导致线程阻塞(死锁),但递归互斥锁会记录加锁次数,必须解锁(Unlock)相同次数才能真正释放锁
在 src/bsp/os_cpu_armv8_external.h 加入 定义
1 #define OS_SEM_ADDR_ALLOC_ALIGN 2U
新建 src/kernel/sem/prt_sem.c 文件。
1 2 3 4 5 6 7 #include "prt_sem_external.h" #include "prt_asm_cpu_external.h" #include "os_attr_armv8_external.h" #include "os_cpu_armv8_external.h" OS_SEC_BSS U16 g_maxSem;
OsSemPostErrorCheck
(释放信号量前检查)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 OS_SEC_ALW_INLINE INLINE U32 OsSemPostErrorCheck (struct TagSemCb *semPosted, SemHandle semHandle) { (void )semHandle; if (semPosted->semStat == OS_SEM_UNUSED) { return OS_ERRNO_SEM_INVALID; } if ((semPosted)->semCount >= OS_SEM_COUNT_MAX) { return OS_ERRNO_SEM_OVERFLOW; } return OS_OK; }
OsSemPendListPut
(挂载阻塞任务到信号量链表)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 OS_SEC_L0_TEXT void OsSemPendListPut(struct TagSemCb *semPended, U32 timeOut) { struct TagTskCb *curTskCb = NULL; struct TagTskCb *runTsk = RUNNING_TASK; struct TagListObject *pendObj = &runTsk-> pendList; OsTskReadyDel((struct TagTskCb *)runTsk); runTsk -> taskSem = (void *)semPended; TSK_STATUS_SET(runTsk, OS_TSK_PEND); if (semPended-> semMode == SEM_MODE_PRIOR) { LIST_FOR_EACH(curTskCb , &semPended-> semList, struct TagTskCb, pendList) { if (curTskCb->priority > runTsk-> priority) { ListTailAdd (pendObj, &curTskCb-> pendList); return; } } } ListTailAdd (pendObj, &semPended-> semList); }
OsSemPendListGet
(从信号量链表唤醒任务)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 OS_SEC_L0_TEXT struct TagTskCb *OsSemPendListGet (struct TagSemCb *semPended) { struct TagTskCb *taskCb = GET_TCB_PEND (LIST_FIRST (&(semPended-> semList))); ListDelete (LIST_FIRST (&(semPended-> semList))); if (TSK_STATUS_TST (taskCb, OS_TSK_TIMEOUT)) { OS_TSK_DELAY_LOCKED_DETACH (taskCb); } TSK_STATUS_CLEAR (taskCb, OS_TSK_TIMEOUT | OS_TSK_PEND); taskCb-> taskSem = NULL; if (!TSK_STATUS_TST (taskCb, OS_TSK_SUSPEND)) { OsTskReadyAddBgd (taskCb); } return taskCb; }
OsSemPendParaCheck
(Pend操作参数检查)
1 2 3 4 5 6 7 8 9 10 11 12 OS_SEC_L0_TEXT U32 OsSemPendParaCheck(U32 timeout) { if (timeout == 0 ) { return OS_ERRNO_SEM_UNAVAILABLE; } if (OS_TASK_LOCK_DATA != 0 ) { return OS_ERRNO_SEM_PEND_IN_LOCK; } return OS_OK; }
OsSemPendNotNeedSche
(快速路径检查)
1 2 3 4 5 6 7 8 9 10 11 OS_SEC_L0_TEXT bool OsSemPendNotNeedSche (struct TagSemCb *semPended, struct TagTskCb *runTsk) { if (semPended-> semCount > 0 ) { semPended-> semCount--; semPended-> semOwner = runTsk-> taskPid; return TRUE; } return FALSE; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 OS_SEC_L0_TEXT U32 PRT_SemPend(SemHandle semHandle, U32 timeout) { uint ptr_t int Save; U32 ret; struct TagTskCb *runTsk = NULL; struct TagSemCb *semPended = NULL; if (semHandle >= (SemHandle)g_maxSem) { return OS_ERRNO_SEM_INVALID; } semPended = GET_SEM(semHandle); int Save = OsIntLock(); if (semPended->semStat == OS_SEM_UNUSED) { OsIntRestore(int Save); return OS_ERRNO_SEM_INVALID; } if (OS_INT_ACTIVE) { OsIntRestore(int Save); return OS_ERRNO_SEM_PEND_INTERR; } runTsk = (struct TagTskCb *)RUNNING_TASK; if (OsSemPendNotNeedSche(semPended, runTsk) == TRUE) { OsIntRestore(int Save); return OS_OK; } ret = OsSemPendParaCheck(timeout); if (ret != OS_OK) { OsIntRestore(int Save); return ret; } OsSemPendListPut(semPended, timeout); if (timeout != OS_WAIT_FOREVER) { OsIntRestore(int Save); return OS_ERRNO_SEM_FUNC_NOT_SUPPORT; } else { OsTskScheduleFastPs(int Save); } OsIntRestore(int Save); return OS_OK; }
OsSemPostSchePre
(Post操作预处理)
1 2 3 4 5 6 7 8 OS_SEC_ALW_INLINE INLINE void OsSemPostSchePre (struct TagSemCb *semPosted) { struct TagTskCb *resumedTask = NULL ; resumedTask = OsSemPendListGet (semPosted); semPosted->semOwner = resumedTask->taskPid; }
OsSemPostIsInvalid
(Post操作无效检查)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 OS_SEC_ALW_INLINE INLINE bool OsSemPostIsInvalid(struct TagSemCb *semPosted) { if (GET_SEM_TYPE(semPosted->semType) == SEM_TYPE_BIN) { if ((semPosted)->semCount == OS_SEM_FULL) { return TRUE ; } } return FALSE ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 OS_SEC_L0_TEXT U32 PRT_SemPost(SemHandle semHandle) { U32 ret; uint ptr_t int Save; struct TagSemCb *semPosted = NULL; if (semHandle >= (SemHandle)g_maxSem) { return OS_ERRNO_SEM_INVALID; } semPosted = GET_SEM(semHandle); int Save = OsIntLock(); ret = OsSemPostErrorCheck(semPosted, semHandle); if (ret != OS_OK) { OsIntRestore(int Save); return ret; } if (OsSemPostIsInvalid(semPosted) == TRUE) { OsIntRestore(int Save); return OS_OK; } if (!ListEmpty(&semPosted->semList)) { OsSemPostSchePre(semPosted); OsTskScheduleFastPs(int Save); } else { semPosted->semCount++; semPosted->semOwner = OS_INVALID_OWNER_ID; } OsIntRestore(int Save); return OS_OK; }
[!NOTE] 关键函数及其操作 (1) OsSemPendListPut
(挂载阻塞任务) void OsSemPendListPut(struct TagSemCb *semPended, U32 timeOut);
- 将当前任务(RUNNING_TASK
)挂到信号量的阻塞队列(semList
)。 - 根据 semMode
决定插入顺序: - SEM_MODE_PRIOR
:按任务优先级插入(高优先级在前)。 - SEM_MODE_FIFO
:插入队尾。
关键操作 :
从就绪队列移除任务(OsTskReadyDel
)。
设置任务状态为 OS_TSK_PEND
(阻塞态)。 (2) OsSemPendListGet
(唤醒阻塞任务) struct TagTskCb *OsSemPendListGet(struct TagSemCb *semPended);
功能 :
从 semList
取出第一个任务,加入就绪队列(OsTskReadyAddBgd
)。
清除任务的阻塞状态(OS_TSK_PEND
)。
返回被唤醒的任务指针。 (3) OsSemPostErrorCheck
(释放前检查) U32 OsSemPostErrorCheck(struct TagSemCb *semPosted, SemHandle semHandle);
检查项 :
信号量是否未初始化(semStat == OS_SEM_UNUSED
)。
计数型信号量是否溢出(semCount >= OS_SEM_COUNT_MAX
)。
src/include/prt_task_external.h 加入 OsTskReadyAddBgd() 将指定任务块加入就绪队列
1 2 3 4 OS_SEC_ALW_INLINE INLINE void OsTskReadyAddBgd (struct TagTskCb *task) { OsTskReadyAdd (task); }
src/kernel/task/prt_task.c 加入 OsTskScheduleFastPs() 触发任务调度`
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 OS_SEC_TEXT void OsTskScheduleFastPs(uint ptr_t int Save) { OsTskHighestSet(); if ((g_highestTask != RUNNING_TASK) && (g_uniTaskLock == )) { UNI_FLAG |= OS_FLG_TSK_REQ; if (OS_INT_INACTIVE) { OsTaskTrapFastPs(int Save); } } }
src/bsp/os_cpu_armv8_external.h 加入 OsTaskTrapFastPs()
1 2 3 4 5 OS_SEC_ALW_INLINE INLINE void OsTaskTrapFastPs (uintptr_t intSave) { (void)intSave; OsTaskTrap (); }
加入 src/include/prt_sem.h [下载 ],该头文件主要是信号量相关的函数声明和宏定义。
验证
Test1Task :
打印消息后释放信号量 (PRT_SemPost
)。
循环打印 5 次 task 1 run ...
。
Test2Task :
尝试获取信号量 (PRT_SemPend
),因初始值为 0
而阻塞。
被 Test1Task
唤醒后,循环打印 5 次 task 2 run ...
。
高优先级任务 (Test2Task
) 优先执行,但通过信号量实现可控阻塞。
task2的优先级>task1的优先级,task2先执行,但是我们对信号量初始为0,task2执行p操作,信号量-1,此时信号量<0,task2被阻塞,task1开始运行,调用v操作,信号量+1,此时信号量=0,唤醒信号量阻塞队列的第一个任务task2,执行task2后在执行task1,所以输出
作业
违反原子性缺陷 我们设置一个share变量,在未加锁的情况下,我们期待看到task1修改了task2输入的2值,使task2输出1 可以看到,task2原本要输出2,但是他执行到一半被中断了,切换到task1,task1把share修改为1,所以task2输出为1
违反顺序缺陷 两个内存访问的预期顺序被打破
死锁 两个进程相互等待
可以看到taskA获取信号量A后等待,此时中断,B获取信号B后等待,两个进程接下来都各自希望获取对方的信号量,陷入了死锁