Read the ucosII source code again (Shao Beibei): communication and synchronization between tasks

In µC/OS-II, there are 5 ways to protect shared data between tasks and provide communication between tasks:

  • Use the macros OS_ENTER_CRITICAL() and OS_EXIT_CRITICAL() to disable and enable interrupts. This approach can be used when two tasks or a task and an interrupt service routine share some data
  • Use the functions OSSchedLock() and OSSchekUnlock() to lock and unlock the task scheduling function in µC/OS-II. Data sharing can also be achieved in this way (interrupting data that has no effect, you can use this approach to achieve sharing)
  • signal
  • Mail
  • message queue

The first two have been described in the previous chapters.
Read the ucosII source code again (Shao Beibei): Kernel Structure - Critical Sections (Critical Sections)
Read the ucosII source code again (Shao Beibei): Kernel Structure - Locking and UnLocking the Scheduler

This chapter will introduce three other methods for data sharing and task communication: semaphores, mailboxes, and message queues.
Figure F6.1 describes how the task and the ISR communicate with each other.
write picture description here

A task or interrupt service routine can send signals (semaphores, mailboxes or message queues, etc.) to other tasks through the event control block ECB (Event Control Blocks) [F6.1A(1)].
Here, all signals (semaphores, mailboxes or message queues, etc.) are regarded as events.
This also explains why the data structure used for communication is called an event control block above.
A task can also wait for another task or ISR to signal it [F6.1A(2)].
It should be noted here that only the task can wait for the event to occur, the interrupt service routine cannot do this.
For tasks in the waiting state, you can also specify a maximum waiting time to prevent waiting indefinitely because the waiting event does not occur.
Multiple tasks can simultaneously wait for the same event to occur [F6.1B]. In this case, when the event occurs, among all tasks waiting for the event, the task with the highest priority gets the event and enters the ready state, ready to execute.
The events mentioned above can be semaphores, mailboxes, or message queues.
When the event control block is a semaphore, tasks can wait for it or send messages to it.

Event Control Block ECB

µC/OS-II maintains all the information of an event control block through the OS_EVENT data structure defined in uCOS_II.H [Program Listing L6.1], which is the event control block ECB mentioned at the beginning of this chapter.
In general terms, ECB contains at least the following members:
Event types include: semaphore (counter of semaphore) + message mailbox (pointer to message) + message queue (array of pointers to detailed queue);
event type mark: indicates the current event type
Events correspond to different tasks (task list waiting for tasks of this event type);

In fact, due to the existence of the event waiting task linked list, the operations of inserting, adding, and finding tasks are involved, which can be analogous to the design of the ready list in the system.

typedef struct {
    void   *OSEventPtr;                /* 指向消息或者消息队列的指针 */
    INT8U   OSEventTbl[OS_EVENT_TBL_SIZE]; /* 等待任务列表      */
    INT16U  OSEventCnt;               /* 计数器(当事件是信号量时) */
    INT8U   OSEventType;                   /* 时间类型  */
    INT8U   OSEventGrp;               /* 等待任务所在的组  */
} OS_EVENT;

.OSEventPtr pointer:

Only used if the event being defined is a mailbox or a message queue. When the defined event is a mailbox, it points to a message, and when the defined event is a message queue, it points to a data structure, see Section 6.06 Message Mailbox and Section 6.07 Message Queue.

.OSEventTbl[] 和 .OSEventGrp:

.OSEventTbl[] and .OSEventGrp are very similar to OSRdyTbl[] and OSRdyGrp mentioned earlier, except that the first two contain tasks that wait for an event, while the latter two contain tasks that are in a ready state in the system. (see section 3.04 ready table)

.OSEventCnt

When the event is a semaphore, .OSEventCnt is the counter used for the semaphore, (see Section 6.05 Semaphores).

.OSEventType

Defines the specific type of event. It can be one of a semaphore (OS_EVENT_SEM), a mailbox (OS_EVENT_TYPE_MBOX), or a message queue (OS_EVENT_TYPE_Q). The user needs to call the corresponding system function according to the specific value of this field to ensure the correctness of the operation on it.

Each task waiting for an event to occur is added to the waiting task list in the event control block, which includes two fields, .OSEventGrp and .OSEventTbl[].
Here, the priorities of all tasks are divided into 8 groups (8 priorities in each group), corresponding to the 8 bits in .OSEventGrp. When a task in a group is waiting for the event, the corresponding bit in .OSEventGrp is set. Accordingly, the task's corresponding bit in .OSEventTbl[] is also set. The size of the .OSEventTbl[] array is determined by the lowest priority of the task in the system, which is defined by the OS_LOWEST_PRIO constant in uCOS_II.H. In this way, when the task priority is relatively low, the occupancy of µC/OS-II on the system RAM is reduced.
Reference: Read the ucosII source code again (Shao Beibei): Kernel Structure - Ready List (Ready List)
which explains the ready list in detail. The principle is also applicable in our processing of waiting event linked list, no redundant explanation is given.
write picture description here

Waiting for task column operation:

add, delete,

Put a task on the event's waiting list:

    pevent->OSEventGrp            |= OSMapTbl[prio >> 3];
    pevent->OSEventTbl[prio >> 3] |= OSMapTbl[prio & 0x07];

where prio is the priority of the task and pevent is a pointer to the event control block.
Inserting a task into the waiting list takes the same amount of time, regardless of how many tasks are in the table.

Remove a task from the waiting task list

if ((pevent->OSEventTbl[prio >> 3] &= ~OSMapTbl[prio & 0x07]) == 0) {
    pevent->OSEventGrp &= ~OSMapTbl[prio >> 3];
}

Find the highest priority task in the waiting task list

    y    = OSUnMapTbl[pevent->OSEventGrp];
    x    = OSUnMapTbl[pevent->OSEventTbl[y]];
    prio = (y << 3) + x;

In µC/OS-II, the total number of event control blocks is determined by the total number of semaphores, mailboxes and message queues required by the user. This value is defined by #define OS_MAX_EVENTS in OS_CFG.H.
When OSInit() is called, all event control blocks are linked into a singly linked list - a linked list of idle event control blocks.
Whenever a semaphore, mailbox or message queue is established, an idle event control block is taken from the linked list and initialized.
Since semaphores, mailboxes, and message queues cannot be deleted once established, event control blocks cannot be put back into the free event control block list. This is different from the task control block TCB, which can be deleted == put in OSTCBFreeList.
write picture description here

Event control block general operation encapsulation:

Some common operations performed on event control blocks include:

  • Initialize an event control block
  • make a task ready
  • Puts a task into a state waiting for the event
  • A task entered the ready state due to a timeout of waiting

In order to avoid code duplication and shorten the length of the code, µC/OS-II implements the above operations with four system functions: OS_EventWaitListInit(), OSEventTaskRdy(), OSEventWait() and OSEventTO().

Initialize an event control block, OSEventWaitListInit()

Listing L6.5 is the source code of the function OSEventWaitListInit(). When a semaphore, mailbox or message queue is established, the corresponding establishment function OSSemInit(), OSMboxCreate(), or OSQCreate() initializes the waiting task list in the event control block by calling OSEventWaitListInit(). The function initializes an empty waiting task list with no tasks in it. This function has only one calling parameter, which is the pointer pevent to the event control block that needs to be initialized.
Listing L6.5 Initialize the wait task list for the ECB block

void OSEventWaitListInit (OS_EVENT *pevent)
{
    INT8U i;


    pevent->OSEventGrp = 0x00;
    for (i = 0; i < OS_EVENT_TBL_SIZE; i++) {
        pevent->OSEventTbl[i] = 0x00;
    }
}

Make a task into the ready state, OSEventTaskRdy()

Listing L6.6 brings a task to the ready state

void OSEventTaskRdy (OS_EVENT *pevent, void *msg, INT8U msk)
{
    OS_TCB *ptcb;
    INT8U   x;
    INT8U   y;
    INT8U   bitx;
    INT8U   bity;
    INT8U   prio;


    y    = OSUnMapTbl[pevent->OSEventGrp];                           (1)
    bity = OSMapTbl[y];                                             (2)
    x    = OSUnMapTbl[pevent->OSEventTbl[y]];                         (3)
    bitx = OSMapTbl[x];                                             (4)
    prio = (INT8U)((y << 3) + x);                                     (5)
    if ((pevent->OSEventTbl[y] &= ~bitx) == 0) {                     (6)
        pevent->OSEventGrp &= ~bity;
    }
    ptcb                 =  OSTCBPrioTbl[prio];                     (7)
    ptcb->OSTCBDly       =  0;                                       (8)
    ptcb->OSTCBEventPtr  = (OS_EVENT *)0;                             (9)
#if (OS_Q_EN && (OS_MAX_QS >= 2)) || OS_MBOX_EN
    ptcb->OSTCBMsg       = msg;                                    (10)
#else
    msg                  = msg;
#endif
    ptcb->OSTCBStat     &= ~msk;                                     (11)
    if (ptcb->OSTCBStat == OS_STAT_RDY) {                            (12)
        OSRdyGrp        |=  bity;                                    (13)
        OSRdyTbl[y]     |=  bitx;
    }
}

Listing L6.6 is the source code for the function OSEventTaskRdy(). When an event occurs, and the event waits for the highest priority task (Highest Priority Task - HPT) in the task list to be placed in the ready state, the corresponding OSSemPost(), OSMboxPost(), OSQPost(), and OSQPostFront corresponding to the event () function calls OSEventTaskRdy() to implement this operation.
In other words, the function removes the HPT task (Highest Priority Task) from the queue of waiting tasks and puts the task in the ready state. Figure F6.4 shows the first four actions of the OSEventTaskRdy() function.
The function first calculates the byte index [L6.6/F6.4(1)] of the HPT task in .OSEventTbl[], the result is a number from 0 to OS_LOWEST_PRIO/8+1, and uses the index Get the bitmask [L6.6/F6.4(2)] of this priority task in .OSEventGrp (the value can be obtained from Table T6.1). Then, the OSEventTaskRdy() function determines the position of the corresponding bit of the HPT task in .OSEventTbl[] [L6.6/F6.4(3)], the result is a number from 0 to OS_LOWEST_PRIO/8+1, and Corresponding bitmask [L6.6/F6.4(4)]. Based on the above results, the OSEventTaskRdy() function calculates the priority of the HPT task [L6.6(5)], and then the task can be deleted from the waiting task list [L6.6(6)].
A task's task control block contains information that needs to be changed.
Knowing the priority of an HPT task, a pointer to the task control block for that task can be obtained [L6.6(7)]. Because the operating conditions of the highest priority task have been satisfied, the decrementing operation of the .OSTCBDly field by the OSTimeTick() function must be stopped, so OSEventTaskRdy() directly clears this field to 0 [L6.6(8)]. Because the task is no longer waiting for the event to occur, the OSEventTaskRdy() function points the pointer to the event control block in its task control block to NULL [L6.6(9)].
If OSEventTaskRdy() is called by OSMboxPost() or OSQPost(), this function also passes the corresponding message to the HPT in its task control block [L6.6(10)].
Also, when OSEventTaskRdy() is called, the bitmask msk is passed to it as a parameter. This parameter is a bitmask used to clear bits in the task control block, corresponding to the type of event that occurred [L6.6(11)]. Finally, according to .OSTCBStat to determine whether the task is in the ready state [L6.6(12)]. If so, insert HPT into µC/OS-II's ready task list [L6.6(13)]. Note that the HPT task does not necessarily enter the ready state after getting this event, maybe the task has been suspended for other reasons. [See Section 4.07, Suspend a task, OSTaskSuspend(), and Section 4.08, Resume a task, OSTaskResume()].
In addition, the .OSEventTaskRdy() function should be called with interrupts disabled.

Make a task enter the state of waiting for an event, OSEventTaskWait()

Listing L6.7 is the source code for the OSEventTaskWait() function. When a task waits for an event to occur, the OSSemPend(), OSMboxPend() or OSQPend() function of the corresponding event will call this function to delete the current task from the ready task list and place it in the event control block of the corresponding event in the waiting list.
Listing L6.7 puts a task into a wait state

void OSEventTaskWait (OS_EVENT *pevent)
{
    OSTCBCur->OSTCBEventPtr = pevent;                                      (1)
    if ((OSRdyTbl[OSTCBCur->OSTCBY] &= ~OSTCBCur->OSTCBBitX) == 0) {       (2)
        OSRdyGrp &= ~OSTCBCur->OSTCBBitY;
    }
    pevent->OSEventTbl[OSTCBCur->OSTCBY] |= OSTCBCur->OSTCBBitX;            (3)
    pevent->OSEventGrp                   |= OSTCBCur->OSTCBBitY;
}

The task is placed in the ready state due to a timeout of waiting, OSEventTO()

Listing L6.8 is the source code of the OSEventTO() function. When the event that the task is waiting for does not occur within the pre-specified time, the OSTimeTick() function will set the task's state to ready due to the waiting timeout. In this case, the event's OSSemPend(), OSMboxPend() or OSQPend() function calls OSEventTO() to do the job. This function is responsible for removing the task from the waiting task list in the event control block [L6.8(1)] and placing it in the ready state [L6.8(2)]. Finally, delete the pointer to the event control block from the task control block [L6.8(3)]. Users should note that calling OSEventTO() should also disable interrupts first.
Listing L6.8 puts the task in the ready state because of the wait timeout:

void  OSEventTO (OS_EVENT *pevent)
{
    if ((pevent->OSEventTbl[OSTCBCur->OSTCBY] &= ~OSTCBCur->OSTCBBitX) == 0) {  (1)
        pevent->OSEventGrp &= ~OSTCBCur->OSTCBBitY;
    }
    OSTCBCur->OSTCBStat     = OS_STAT_RDY;  (2)
    OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0;    (3)
}

signal

See also: Read the ucosII source code again (Shao Beibei): Communication and synchronization between tasks – semaphore

Mail

See also: Read the ucosII source code again (Shao Beibei): Communication and synchronization between tasks – mailbox

message queue

See also: Read the ucosII source code again (Shao Beibei): Communication and synchronization between tasks - message queue

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325419845&siteId=291194637