实验七 信号量与同步

信号量结构初始化

新建 lab7/src/include/prt_sem_external.h 头文件

 1#ifndef PRT_SEM_EXTERNAL_H
 2#define PRT_SEM_EXTERNAL_H
 3
 4#include "prt_sem.h"
 5#include "prt_task_external.h"
 6#if defined(OS_OPTION_POSIX)
 7#include "bits/semaphore_types.h"
 8#endif
 9
10#define OS_SEM_UNUSED 0
11#define OS_SEM_USED   1
12
13#define SEM_PROTOCOL_PRIO_INHERIT 1
14#define SEM_TYPE_BIT_WIDTH        0x4U
15#define SEM_PROTOCOL_BIT_WIDTH    0x8U
16
17#define OS_SEM_WITH_LOCK_FLAG    1
18#define OS_SEM_WITHOUT_LOCK_FLAG 0
19
20#define MAX_POSIX_SEMAPHORE_NAME_LEN    31
21
22#define GET_SEM_LIST(ptr) LIST_COMPONENT(ptr, struct TagSemCb, semList)
23#define GET_SEM(semid) (((struct TagSemCb *)g_allSem) + (semid))
24#define GET_SEM_TSK(semid) (((SEM_TSK_S *)g_semTsk) + (semid))
25#define GET_TSK_SEM(tskid) (((TSK_SEM_S *)g_tskSem) + (tskid))
26#define GET_SEM_TYPE(semType) (U32)((semType) & ((1U << SEM_TYPE_BIT_WIDTH) - 1))
27#define GET_MUTEX_TYPE(semType) (U32)(((semType) >> SEM_TYPE_BIT_WIDTH) & ((1U << SEM_TYPE_BIT_WIDTH) - 1))
28#define GET_SEM_PROTOCOL(semType) (U32)((semType) >> SEM_PROTOCOL_BIT_WIDTH)
29
30struct TagSemCb {
31    /* 是否使用 OS_SEM_UNUSED/OS_SEM_USED */
32    U16 semStat;
33    /* 核内信号量索引号 */
34    U16 semId;
35#if defined(OS_OPTION_SEM_RECUR_PV)
36    /* 二进制互斥信号量递归P计数,计数型信号量和二进制同步模式信号量无效 */
37    U32 recurCount;
38#endif
39    /* 当该信号量已用时,其信号量计数 */
40    U32 semCount;
41    /* 挂接阻塞于该信号量的任务 */
42    struct TagListObject semList;
43    /* 挂接任务持有的互斥信号量,计数型信号量信号量无效 */
44    struct TagListObject semBList;
45
46    /* Pend到该信号量的线程ID */
47    U32 semOwner;
48    /* 信号量唤醒阻塞任务的方式 */
49    enum SemMode semMode;
50    /* 信号量,计数型或二进制 */
51    U32 semType;
52#if defined(OS_OPTION_POSIX)
53    /* 信号量名称 */
54    char name[MAX_POSIX_SEMAPHORE_NAME_LEN + 1]; // + \0
55    /* sem_open 句柄 */
56    sem_t handle;
57#endif
58};
59
60/* 模块间全局变量声明 */
61extern U16 g_maxSem;
62
63/* 指向核内信号量控制块 */
64extern struct TagSemCb *g_allSem;
65
66extern U32 OsSemCreate(U32 count, U32 semType, enum SemMode semMode, SemHandle *semHandle, U32 cookie);
67extern bool OsSemBusy(SemHandle semHandle);
68
69#endif /* PRT_SEM_EXTERNAL_H */

新建 src/kernel/sem/prt_sem_init.c 文件。

  1#include "prt_sem_external.h"
  2#include "os_attr_armv8_external.h"
  3#include "os_cpu_armv8_external.h"
  4
  5OS_SEC_BSS struct TagListObject g_unusedSemList;
  6OS_SEC_BSS struct TagSemCb *g_allSem;
  7
  8extern void *OsMemAllocAlign(U32 mid, U8 ptNo, U32 size, U8 alignPow);
  9/*
 10* 描述:信号量初始化
 11*/
 12OS_SEC_L4_TEXT U32 OsSemInit(void)
 13{
 14    struct TagSemCb *semNode = NULL;
 15    U32 idx;
 16    U32 ret = OS_OK;
 17
 18    g_allSem = (struct TagSemCb *)OsMemAllocAlign((U32)OS_MID_SEM,
 19                                                0,
 20                                                4096,
 21                                                OS_SEM_ADDR_ALLOC_ALIGN);
 22
 23    if (g_allSem == NULL) {
 24        return OS_ERRNO_SEM_NO_MEMORY;
 25    }
 26
 27    g_maxSem = 4096 / sizeof(struct TagSemCb);
 28
 29    char *cg_allSem = (char *)g_allSem;
 30    for(int i = 0; i < 4096; i++)
 31        cg_allSem[i] = 0;
 32
 33    INIT_LIST_OBJECT(&g_unusedSemList);
 34    for (idx = 0; idx < g_maxSem; idx++) {
 35        semNode = ((struct TagSemCb *)g_allSem) + idx; //指针操作
 36        semNode->semId = (U16)idx;
 37        ListTailAdd(&semNode->semList, &g_unusedSemList);
 38    }
 39
 40    return ret;
 41}
 42
 43/*
 44* 描述:创建一个信号量
 45*/
 46OS_SEC_L4_TEXT U32 OsSemCreate(U32 count, U32 semType, enum SemMode semMode,
 47                            SemHandle *semHandle, U32 cookie)
 48{
 49    uintptr_t intSave;
 50    struct TagSemCb *semCreated = NULL;
 51    struct TagListObject *unusedSem = NULL;
 52    (void)cookie;
 53
 54    if (semHandle == NULL) {
 55        return OS_ERRNO_SEM_PTR_NULL;
 56    }
 57
 58    intSave = OsIntLock();
 59
 60    if (ListEmpty(&g_unusedSemList)) {
 61        OsIntRestore(intSave);
 62        return OS_ERRNO_SEM_ALL_BUSY;
 63    }
 64
 65    /* 在空闲链表中取走一个控制节点 */
 66    unusedSem = OS_LIST_FIRST(&(g_unusedSemList));
 67    ListDelete(unusedSem);
 68
 69    /* 获取到空闲节点对应的信号量控制块,并开始填充控制块 */
 70    semCreated = (GET_SEM_LIST(unusedSem));
 71    semCreated->semCount = count;
 72    semCreated->semStat = OS_SEM_USED;
 73    semCreated->semMode = semMode;
 74    semCreated->semType = semType;
 75    semCreated->semOwner = OS_INVALID_OWNER_ID;
 76    if (GET_SEM_TYPE(semType) == SEM_TYPE_BIN) {
 77        INIT_LIST_OBJECT(&semCreated->semBList);
 78#if defined(OS_OPTION_SEM_RECUR_PV)
 79        if (GET_MUTEX_TYPE(semType) == PTHREAD_MUTEX_RECURSIVE) {
 80            semCreated->recurCount = 0;
 81        }
 82#endif
 83    }
 84
 85    INIT_LIST_OBJECT(&semCreated->semList);
 86    *semHandle = (SemHandle)semCreated->semId;
 87
 88    OsIntRestore(intSave);
 89    return OS_OK;
 90}
 91
 92/*
 93* 描述:创建一个信号量
 94*/
 95OS_SEC_L4_TEXT U32 PRT_SemCreate(U32 count, SemHandle *semHandle)
 96{
 97    U32 ret;
 98
 99    if (count > OS_SEM_COUNT_MAX) {
100        return OS_ERRNO_SEM_OVERFLOW;
101    }
102
103    ret = OsSemCreate(count, SEM_TYPE_COUNT, SEM_MODE_FIFO, semHandle, (U32)(uintptr_t)semHandle);
104    return ret;
105}

在 src/bsp/os_cpu_armv8_external.h 加入 定义

1#define OS_SEM_ADDR_ALLOC_ALIGN 2U //按2的幂对齐,即2^2=4字节

新建 src/kernel/sem/prt_sem.c 文件。

  1#include "prt_sem_external.h"
  2#include "prt_asm_cpu_external.h"
  3#include "os_attr_armv8_external.h"
  4#include "os_cpu_armv8_external.h"
  5
  6/* 核内信号量最大个数 */
  7OS_SEC_BSS U16 g_maxSem;
  8
  9
 10OS_SEC_ALW_INLINE INLINE U32 OsSemPostErrorCheck(struct TagSemCb *semPosted, SemHandle semHandle)
 11{
 12    (void)semHandle;
 13    /* 检查信号量控制块是否UNUSED,排除大部分错误场景 */
 14    if (semPosted->semStat == OS_SEM_UNUSED) {
 15        return OS_ERRNO_SEM_INVALID;
 16    }
 17
 18    /* post计数型信号量的错误场景, 释放计数型信号量且信号量计数大于最大计数 */
 19    if ((semPosted)->semCount >= OS_SEM_COUNT_MAX) {
 20        return OS_ERRNO_SEM_OVERFLOW;
 21    }
 22
 23    return OS_OK;
 24}
 25
 26
 27/*
 28* 描述:把当前运行任务挂接到信号量链表上
 29*/
 30OS_SEC_L0_TEXT void OsSemPendListPut(struct TagSemCb *semPended, U32 timeOut)
 31{
 32    struct TagTskCb *curTskCb = NULL;
 33    struct TagTskCb *runTsk = RUNNING_TASK;
 34    struct TagListObject *pendObj = &runTsk->pendList;
 35
 36    OsTskReadyDel((struct TagTskCb *)runTsk);
 37
 38    runTsk->taskSem = (void *)semPended;
 39
 40    TSK_STATUS_SET(runTsk, OS_TSK_PEND);
 41    /* 根据唤醒方式挂接此链表,同优先级再按FIFO子顺序插入 */
 42    if (semPended->semMode == SEM_MODE_PRIOR) {
 43        LIST_FOR_EACH(curTskCb, &semPended->semList, struct TagTskCb, pendList) {
 44            if (curTskCb->priority > runTsk->priority) {
 45                ListTailAdd(pendObj, &curTskCb->pendList);
 46                // goto TIMER_ADD;
 47                return;
 48            }
 49        }
 50    }
 51    /* 如果到这里,说明是FIFO方式;或者是优先级方式且挂接首个节点或者挂接尾节点 */
 52    ListTailAdd(pendObj, &semPended->semList);
 53
 54}
 55
 56/*
 57* 描述:从非空信号量链表上摘首个任务放入到ready队列
 58*/
 59OS_SEC_L0_TEXT struct TagTskCb *OsSemPendListGet(struct TagSemCb *semPended)
 60{
 61    struct TagTskCb *taskCb = GET_TCB_PEND(LIST_FIRST(&(semPended->semList)));
 62
 63    ListDelete(LIST_FIRST(&(semPended->semList)));
 64    /* 如果阻塞的任务属于定时等待的任务时候,去掉其定时等待标志位,并将其从去除 */
 65    if (TSK_STATUS_TST(taskCb, OS_TSK_TIMEOUT)) {
 66        OS_TSK_DELAY_LOCKED_DETACH(taskCb);
 67    }
 68
 69    /* 必须先去除 OS_TSK_TIMEOUT 态,再入队[睡眠时是先出ready队,再置OS_TSK_TIMEOUT态] */
 70    TSK_STATUS_CLEAR(taskCb, OS_TSK_TIMEOUT | OS_TSK_PEND);
 71    taskCb->taskSem = NULL;
 72    /* 如果去除信号量阻塞位后,该任务不处于阻塞态则将该任务挂入就绪队列并触发任务调度 */
 73    if (!TSK_STATUS_TST(taskCb, OS_TSK_SUSPEND)) {
 74        OsTskReadyAddBgd(taskCb);
 75    }
 76
 77    return taskCb;
 78}
 79
 80OS_SEC_L0_TEXT U32 OsSemPendParaCheck(U32 timeout)
 81{
 82    if (timeout == 0) {
 83        return OS_ERRNO_SEM_UNAVAILABLE;
 84    }
 85
 86    if (OS_TASK_LOCK_DATA != 0) {
 87        return OS_ERRNO_SEM_PEND_IN_LOCK;
 88    }
 89    return OS_OK;
 90}
 91
 92OS_SEC_L0_TEXT bool OsSemPendNotNeedSche(struct TagSemCb *semPended, struct TagTskCb *runTsk)
 93{
 94    if (semPended->semCount > 0) {
 95        semPended->semCount--;
 96        semPended->semOwner = runTsk->taskPid;
 97
 98        return TRUE;
 99    }
100    return FALSE;
101}
102
103/*
104* 描述:指定信号量的P操作
105*/
106OS_SEC_L0_TEXT U32 PRT_SemPend(SemHandle semHandle, U32 timeout)
107{
108    uintptr_t intSave;
109    U32 ret;
110    struct TagTskCb *runTsk = NULL;
111    struct TagSemCb *semPended = NULL;
112
113    if (semHandle >= (SemHandle)g_maxSem) {
114        return OS_ERRNO_SEM_INVALID;
115    }
116
117    semPended = GET_SEM(semHandle);
118
119    intSave = OsIntLock();
120    if (semPended->semStat == OS_SEM_UNUSED) {
121        OsIntRestore(intSave);
122        return OS_ERRNO_SEM_INVALID;
123    }
124
125    if (OS_INT_ACTIVE) {
126        OsIntRestore(intSave);
127        return OS_ERRNO_SEM_PEND_INTERR;
128    }
129
130    runTsk = (struct TagTskCb *)RUNNING_TASK;
131
132    if (OsSemPendNotNeedSche(semPended, runTsk) == TRUE) {
133        OsIntRestore(intSave);
134        return OS_OK;
135    }
136
137    ret = OsSemPendParaCheck(timeout);
138    if (ret != OS_OK) {
139        OsIntRestore(intSave);
140        return ret;
141    }
142    /* 把当前任务挂接在信号量链表上 */
143    OsSemPendListPut(semPended, timeout);
144    if (timeout != OS_WAIT_FOREVER) {
145        OsIntRestore(intSave);
146        return OS_ERRNO_SEM_FUNC_NOT_SUPPORT;
147    } else {
148        /* 恢复ps的快速切换 */
149        OsTskScheduleFastPs(intSave);
150
151    }
152
153    OsIntRestore(intSave);
154    return OS_OK;
155}
156
157OS_SEC_ALW_INLINE INLINE void OsSemPostSchePre(struct TagSemCb *semPosted)
158{
159    struct TagTskCb *resumedTask = NULL;
160
161    resumedTask = OsSemPendListGet(semPosted);
162    semPosted->semOwner = resumedTask->taskPid;
163}
164
165/*
166* 描述:判断信号量post是否有效
167* 备注:以下情况表示post无效,返回TRUE: post互斥二进制信号量,若该信号量被嵌套pend或者已处于空闲状态
168*/
169OS_SEC_ALW_INLINE INLINE bool OsSemPostIsInvalid(struct TagSemCb *semPosted)
170{
171    if (GET_SEM_TYPE(semPosted->semType) == SEM_TYPE_BIN) {
172        /* 释放互斥二进制信号量且信号量已处于空闲状态 */
173        if ((semPosted)->semCount == OS_SEM_FULL) {
174            return TRUE;
175        }
176    }
177    return FALSE;
178}
179
180/*
181* 描述:指定信号量的V操作
182*/
183OS_SEC_L0_TEXT U32 PRT_SemPost(SemHandle semHandle)
184{
185    U32 ret;
186    uintptr_t intSave;
187    struct TagSemCb *semPosted = NULL;
188
189    if (semHandle >= (SemHandle)g_maxSem) {
190        return OS_ERRNO_SEM_INVALID;
191    }
192
193    semPosted = GET_SEM(semHandle);
194    intSave = OsIntLock();
195
196    ret = OsSemPostErrorCheck(semPosted, semHandle);
197    if (ret != OS_OK) {
198        OsIntRestore(intSave);
199        return ret;
200    }
201
202    /* 信号量post无效,不需要调度 */
203    if (OsSemPostIsInvalid(semPosted) == TRUE) {
204        OsIntRestore(intSave);
205        return OS_OK;
206    }
207
208    /* 如果有任务阻塞在信号量上,就激活信号量阻塞队列上的首个任务 */
209    if (!ListEmpty(&semPosted->semList)) {
210        OsSemPostSchePre(semPosted);
211        /* 相当于快速切换+中断恢复 */
212        OsTskScheduleFastPs(intSave);
213    } else {
214        semPosted->semCount++;
215        semPosted->semOwner = OS_INVALID_OWNER_ID;
216
217    }
218
219    OsIntRestore(intSave);
220    return OS_OK;
221}

src/include/prt_task_external.h 加入 OsTskReadyAddBgd()

1OS_SEC_ALW_INLINE INLINE void OsTskReadyAddBgd(struct TagTskCb *task)
2{
3    OsTskReadyAdd(task);
4}

src/kernel/task/prt_task.c 加入 OsTskScheduleFastPs()

 1// src/core/kernel/task/prt_amp_task.c
 2/*
 3* 描述:如果快速切换后只有中断恢复,使用此接口
 4*/
 5OS_SEC_TEXT void OsTskScheduleFastPs(uintptr_t intSave)
 6{
 7    /* Find the highest task */
 8    OsTskHighestSet();
 9
10    /* In case that running is not highest then reschedule */
11    if ((g_highestTask != RUNNING_TASK) && (g_uniTaskLock == 0)) {
12        UNI_FLAG |= OS_FLG_TSK_REQ;
13
14        /* only if there is not HWI or TICK the trap */
15        if (OS_INT_INACTIVE) {
16            OsTaskTrapFastPs(intSave);
17        }
18    }
19}

src/bsp/os_cpu_armv8_external.h 加入 OsTaskTrapFastPs()

1OS_SEC_ALW_INLINE INLINE void OsTaskTrapFastPs(uintptr_t intSave)
2{
3    (void)intSave;
4    OsTaskTrap();
5}

加入 src/include/prt_sem.h [下载],该头文件主要是信号量相关的函数声明和宏定义。

提示

将新增文件加入构建系统

验证

  1#include "prt_typedef.h"
  2#include "prt_tick.h"
  3#include "prt_task.h"
  4#include "prt_sem.h"
  5
  6extern U32 PRT_Printf(const char *format, ...);
  7extern void PRT_UartInit(void);
  8extern U32 OsActivate(void);
  9extern U32 OsTskInit(void);
 10extern U32 OsSemInit(void);
 11
 12
 13static SemHandle sem_sync;
 14
 15
 16void Test1TaskEntry()
 17{
 18    PRT_Printf("task 1 run ...\n");
 19    PRT_SemPost(sem_sync);
 20    U32 cnt = 5;
 21    while (cnt > 0) {
 22        // PRT_TaskDelay(200);
 23        PRT_Printf("task 1 run ...\n");
 24        cnt--;
 25    }
 26}
 27
 28void Test2TaskEntry()
 29{
 30    PRT_Printf("task 2 run ...\n");
 31
 32    PRT_SemPend(sem_sync, OS_WAIT_FOREVER);
 33    U32 cnt = 5;
 34    while (cnt > 0) {
 35        // PRT_TaskDelay(100);
 36        PRT_Printf("task 2 run ...\n");
 37        cnt--;
 38    }
 39}
 40
 41S32 main(void)
 42{
 43    // 任务模块初始化
 44    OsTskInit();
 45    OsSemInit(); // 参见demos/ascend310b/config/prt_config.c 系统初始化注册表
 46
 47    PRT_UartInit();
 48
 49    PRT_Printf("            _       _ _____      _             _             _   _ _   _ _   _           \n");
 50    PRT_Printf("  _ __ ___ (_)_ __ (_) ____|   _| | ___ _ __  | |__  _   _  | | | | \\ | | | | | ___ _ __ \n");
 51    PRT_Printf(" | '_ ` _ \\| | '_ \\| |  _|| | | | |/ _ \\ '__| | '_ \\| | | | | |_| |  \\| | | | |/ _ \\ '__|\n");
 52    PRT_Printf(" | | | | | | | | | | | |__| |_| | |  __/ |    | |_) | |_| | |  _  | |\\  | |_| |  __/ |   \n");
 53    PRT_Printf(" |_| |_| |_|_|_| |_|_|_____\\__,_|_|\\___|_|    |_.__/ \\__, | |_| |_|_| \\_|\\___/ \\___|_|   \n");
 54    PRT_Printf("                                                     |___/                               \n");
 55
 56    PRT_Printf("ctr-a h: print help of qemu emulator. ctr-a x: quit emulator.\n\n");
 57
 58    U32 ret;
 59    ret = PRT_SemCreate(0, &sem_sync);
 60    if (ret != OS_OK) {
 61        PRT_Printf("failed to create synchronization sem\n");
 62        return 1;
 63    }
 64
 65    struct TskInitParam param = {0};
 66
 67    // task 1
 68    // param.stackAddr = 0;
 69    param.taskEntry = (TskEntryFunc)Test1TaskEntry;
 70    param.taskPrio = 35;
 71    // param.name = "Test1Task";
 72    param.stackSize = 0x1000; //固定4096,参见prt_task_init.c的OsMemAllocAlign
 73
 74    TskHandle tskHandle1;
 75    ret = PRT_TaskCreate(&tskHandle1, &param);
 76    if (ret) {
 77        return ret;
 78    }
 79
 80    ret = PRT_TaskResume(tskHandle1);
 81    if (ret) {
 82        return ret;
 83    }
 84
 85    // task 2
 86    // param.stackAddr = 0;
 87    param.taskEntry = (TskEntryFunc)Test2TaskEntry;
 88    param.taskPrio = 30;
 89    // param.name = "Test2Task";
 90    param.stackSize = 0x1000; //固定4096,参见prt_task_init.c的OsMemAllocAlign
 91
 92    TskHandle tskHandle2;
 93    ret = PRT_TaskCreate(&tskHandle2, &param);
 94    if (ret) {
 95        return ret;
 96    }
 97
 98    ret = PRT_TaskResume(tskHandle2);
 99    if (ret) {
100        return ret;
101    }
102
103    // 启动调度
104    OsActivate();
105
106    // while(1);
107    return 0;
108
109}

lab7 作业

作业1

各种并发问题模拟,至少3种。