FreeRTOS Advanced Article 8---freertos Task notification analysis

Source: Internet
Author: User

new features have been introduced in FreeRTOS version V8.2.0: task notifications. In most cases, task notifications can replace binary semaphores, Count semaphores, event groups, and can replace queues of length 1 (which can hold a 32-bit integer or pointer value), and task notifications are faster and use less RAM! I in the "FreeRTOS series 14th---freertos task notice," the article describes how to use the task notification and limitations, today we will analyze the implementation of the task notification source code, see how the task notification is how to achieve efficiency and ram consumption win.
In the article "FreeRTOS Advanced 6---freertos signal analysis" We already know that the FreeRTOS semaphore is implemented using a queue mechanism, and the data structure is exactly the same set of queues. and the task notification is different, its data structure embedded in the task TCB (task control block, see "FreeRTOS Advanced 2---freertos Task Creation analysis"), and the data structure is very simple, involving the task TCB two fields, we will be listed separately:
Volatile uint32_t Ulnotifiedvalue; /* Task Notification value */  volatile uint8_t ucnotifystate;/* task notification status, identifies whether the task is waiting for notification, etc. */
These two fields occupy 5 bytes of RAM (this article is discussed under 32-bit systems), and a queue data structure consumes at least 76 bytes ram! This is not the same order of magnitude, so task notifications triumph over RAM consumption.
In an article that analyzes queues and semaphores, we know that before using queues, semaphores, you must first create queues and semaphores to create queue data structures. For example, use the API function Xqueuecreate () to create a queue, use the API function Xsemaphorecreatebinary () to create a binary semaphore, and so on. Then look at the task notification, because the data structure of the task notification is contained in the task TCB, as long as the task exists, the task notification data structure has been created, can be used directly! In ease of use, the task notification wins again.
To understand the performance of task notifications in the reasons why, it is necessary to analyze the source code.
Only the task can wait for notification, which is not available in the Interrupt service function. If the waiting notification is invalid and the task goes into a blocking state, we can think of the task waiting to be notified as a consumer; other tasks and interrupts can send notifications to tasks waiting to be notified, tasks that send notifications, and interrupt service functions can be considered producers. When a blocked consumer gets notified, it enters the ready state again.
There are two main types of task notification API functions, a class of sending notifications, and a class of waiting notifications. The Send notification API function can be used for task and interrupt service functions, waiting for the notification API function to be used only in the task.
1. Send Notificationslet's look at the Send notification API function first. This kind of function is more, there are 6. But careful analysis will find that they can only complete 3 operations, each with two API functions, with an interrupt-protected version and without an interrupt protection version. FreeRTOS breaks down the API into an interrupt-protected version and without an interrupt-protected version in order to save interrupt service process time and improve performance.
Similar to semaphores, most send notification API interfaces are implemented by macros, as shown in table 1-1.
Table 1-1: Sending a notification API function to the actual calling function list
1.1 xtaskgenericnotify ()send notification API functions without interrupt protection are actually implemented by calling function Xtaskgenericnotify (), let's take a look at this function prototype:
basetype_t xtaskgenericnotify (         taskhandle_t xtasktonotify,         uint32_t ulvalue,         enotifyaction eAction,         uint32_t *pulpreviousnotificationvalue)
    • Xtasktonotify: The task handle being notified.
    • Ulvalue: Updated notification value
    • Eaction: Enumeration Type, indicates the method to update the notification value, enumerates the variable members, and acts as shown in table 1-2.
    • Pulpreviousnotifyvalue: Callback The task notification value that was not updated. This is set to NULL if you do not need to return a task notification value that has not been updated.
Table 1-2:enotifyaction enumeration members and their role
Compared with the queue operation, the Send notification API function is very simple, the following source code is as follows:
basetype_t xtaskgenericnotify (taskhandle_t xtasktonotify, uint32_t ulvalue, enotifyaction eAction, uint32_t * Pulpreviousnotificationvalue) {tcb_t * PXTCB;    basetype_t Xreturn = pdpass;uint8_t ucoriginalnotifystate;    Configassert (xtasktonotify);    PXTCB = (tcb_t *) xtasktonotify;    Taskenter_critical (); {if (Pulpreviousnotificationvalue! = NULL) {/* callback notification value before update */*pulpreviousnotificationvalue = PXTC        b->ulnotifiedvalue;        } ucoriginalnotifystate = pxtcb->ucnotifystate;        Pxtcb->ucnotifystate = tasknotification_received;                Switch (eaction) {case Esetbits:pxtcb->ulnotifiedvalue |= ulvalue;            Break                Case eincrement: (pxtcb->ulnotifiedvalue) + +;            Break                Case esetvaluewithoverwrite:pxtcb->ulnotifiedvalue = Ulvalue;            Break Case Esetvaluewithoutoverwrite:if (UCoriginalnotifystate! = tasknotification_received) {Pxtcb->ulnotifiedvalue = Ulvalu                E                } else {/* The last notification value has not been taken away, this notification value is discarded */Xreturn = Pdfail;            } break;        Case Enoaction:/* Do not need to update the notification value */break;        } tracetask_notify ();  /* If the notified task is blocked because it waits for notification, it is now unblocked */if (ucoriginalnotifystate = = taskwaiting_notification) {(void            ) Uxlistremove (& (Pxtcb->xstatelistitem));            Prvaddtasktoreadylist (PXTCB); if (Pxtcb->uxpriority > Pxcurrenttcb->uxpriority) {/* If the task being notified has a higher priority than the current task, the PENDSV interrupt is triggered,            The context switch after the critical section t*/taskyield_if_using_preemption ();    }}} taskexit_critical (); return Xreturn;}
function can be summarized as follows: Update the notification value according to the specified method, if the notified task is in a blocked state, then unblock it, the priority of the unblocking task is greater than the priority of the current task, triggering a task switch.
Compared to releasing the semaphore API function, this function is much less expensive to call the sub-function, much less judgmental, less on the list of events, and so on, it is really more concise than the implementation of the release semaphore. This is also for a reason, because the task notification has its own limitations and does not completely replace the semaphore. For example, a task can only be blocked to a notification, such as the need to implement multiple tasks blocking to the same event, only the semaphore is used. It is this limitation that makes task notifications simple and efficient, and in most cases, the method of task notification already solves the problem.
1.2Vtasknotifygivefromisr ()This API function is an interrupt-protected version of Vtasknotifygive () and is specifically designed to replace binary semaphores and count semaphores in some cases. function is also very simple, we directly look at the source code, the source is collated and annotated to facilitate understanding.
void Vtasknotifygivefromisr (taskhandle_t xtasktonotify, basetype_t *pxhigherprioritytaskwoken) {TCB_t * pxTCB;uint8_t Ucoriginalnotifystate;    ubasetype_t Uxsavedinterruptstatus;    PXTCB = (tcb_t *) xtasktonotify;    Uxsavedinterruptstatus = Portset_interrupt_mask_from_isr ();        {ucoriginalnotifystate = pxtcb->ucnotifystate;        Pxtcb->ucnotifystate = tasknotification_received;        /* Notification value plus 1, equivalent to releasing a semaphore */(Pxtcb->ulnotifiedvalue) + +; /* If the target task is blocked because it waits for notification, now unblock it */if (ucoriginalnotifystate = = taskwaiting_notification) {/* If the scheduler is The task is placed in the ready list, otherwise put in the pending Ready list */if (uxschedulersuspended = = (ubasetype_t) pdfalse) {(V                OID) Uxlistremove (& (Pxtcb->xstatelistitem));            Prvaddtasktoreadylist (PXTCB); } else {Vlistinsertend (& (Xpendingreadylist), & (Pxtcb->xeventlistitem            ) ); } if (Pxtcb-> uxpriority > Pxcurrenttcb->uxpriority) {/* If the unblocked task priority is greater than the current task priority, set the context switch identity, and then manually switch on after exiting the function Context, or auto-switch contexts in the System beat Interrupt Service Program */if (pxhigherprioritytaskwoken! = NULL) {*pxh    Igherprioritytaskwoken = Pdtrue;                 /* Set MANUAL Toggle Flag */} else {xyieldpending = pdtrue; /* Set AUTO Toggle flag */}}}} PORTCLEAR_INTERRUPT_MASK_FROM_ISR (uxsavedinterruptst ATUs);}
1.3 Xtaskgenericnotifyfromisr ()as shown in table 1-1, the API functions with the interrupt Protection version Xtasknotifyfromisr () and XTASKNOTIFYANDQUERYFROMISR () are macro definitions, The function that is actually called is XTASKGENERICNOTIFYFROMISR (). This function is used to send notifications in interrupts, very similar to API functions without interrupt protection (xtaskgenericnotify), but only adds some interrupt protection measures, we look directly at the source code. The common source is collated and annotated to facilitate comprehension.
basetype_t Xtaskgenericnotifyfromisr (taskhandle_t xtasktonotify, uint32_t ulvalue, eNotifyAction eAction, uint32_t * Pulpreviousnotificationvalue, basetype_t *pxhigherprioritytaskwoken) {tcb_t * pxtcb;uint8_t ucOriginalNotifyState; basetype_t Xreturn = Pdpass;    ubasetype_t Uxsavedinterruptstatus;    PXTCB = (tcb_t *) xtasktonotify;    Uxsavedinterruptstatus = Portset_interrupt_mask_from_isr (); {if (Pulpreviousnotificationvalue! = NULL) {/* callback notification value before update */*pulpreviousnotificatio        Nvalue = pxtcb->ulnotifiedvalue;        } ucoriginalnotifystate = pxtcb->ucnotifystate;                Pxtcb->ucnotifystate = tasknotification_received;  /* Set notification value by parameter */switch (eaction) {case Esetbits:pxtcb->ulnotifiedvalue |=                Ulvalue;            Break                Case eincrement: (pxtcb->ulnotifiedvalue) + +;            Break          Case Esetvaluewithoverwrite:      Pxtcb->ulnotifiedvalue = Ulvalue;            Break                    Case Esetvaluewithoutoverwrite:if (ucoriginalnotifystate! = tasknotification_received) {                Pxtcb->ulnotifiedvalue = Ulvalue;                } else {/* The last notification value has not been taken away, this notification value is discarded */Xreturn = Pdfail;            } break;        Case Enoaction:/* Do not need to update the notification value */break;        } TRACETASK_NOTIFY_FROM_ISR (); /* If the notified task is blocked by waiting for notification, now unblock it */if (ucoriginalnotifystate = = taskwaiting_notification) {/*                If the scheduler is normal, put the task in the Ready list, otherwise put the pending ready list */if (uxschedulersuspended = = (ubasetype_t) pdfalse) {                (void) Uxlistremove (& (Pxtcb->xstatelistitem));            Prvaddtasktoreadylist (PXTCB); } else {Vlistinsertend (& (XpendinGreadylist), & (Pxtcb->xeventlistitem)); if (Pxtcb->uxpriority > Pxcurrenttcb->uxpriority) {/* If unblocked task priority is greater than current task                Priority, set the context switch identity, switch the context manually after exiting the function, or switch the context automatically in the System beat Interrupt Service Program */if (pxhigherprioritytaskwoken! = NULL)    {*pxhigherprioritytaskwoken = pdtrue;                 /* Set MANUAL Toggle Flag */} else {xyieldpending = pdtrue; /* Set AUTO Toggle flag */}}}} PORTCLEAR_INTERRUPT_MASK_FROM_ISR (uxsavedinterruptst    ATUS); return Xreturn;}
2. Wait for notificationwait for the notification API function to be used only in the task, without an interrupt-protected version, so there are only two API functions: Ultasknotifytake () and xtasknotifywait (). The former is specifically designed to replace the binary semaphore and the Count semaphore, which is used in conjunction with the Send Notification API function xtasknotifygive (), VTASKNOTIFYGIVEFROMISR (), which is a full-featured version of the wait notification. Lightweight binary semaphores, Count semaphores, event groups, and queue lengths of 1 can be implemented according to different parameters.
The Wait notification API function has the maximum blocking time parameter, which is used to specify the maximum blocking time when the task is blocked because it waits for notification.
2.1 ultasknotifytake ()This API function is used to implement a lightweight binary semaphore and count semaphore, as shown in the source code below. It has two parameters, if the first parameter xclearcountonexit is set to Pdfalse, it is used to implement the binary semaphore, the function exits when the notification value is cleared 0, if the first parameter is set to Pdtrue, it is used to implement the count semaphore, when the function exits, the notification value minus one.
uint32_t Ultasknotifytake (basetype_t xclearcountonexit, ticktype_t xtickstowait) {uint32_t ulReturn;    Taskenter_critical (); {/* only if the notification value is 0, the blocking operation is performed */if (Pxcurrenttcb->ulnotifiedvalue = = 0UL) {/* Set flag, indicating that the current task is waiting for a            Know */pxcurrenttcb->ucnotifystate = taskwaiting_notification; if (Xtickstowait > (ticktype_t) 0) {/* Add task to delay list */Prvaddcurrenttaskto                Delayedlist (xtickstowait, pdtrue);                Tracetask_notify_take_block ();            /* Trigger PENDSV Interrupt, wait until exit critical section immediately perform task switch */PORTYIELD_WITHIN_API ();    }}} taskexit_critical ();    /* Here to indicate that other tasks or interrupts have sent notifications to this task, or that the task is blocking timeout, and now continue processing */taskenter_critical ();        {Tracetask_notify_take ();           Ulreturn = pxcurrenttcb->ulnotifiedvalue; if (Ulreturn! = 0UL) {if (xclearcountonexit! = pdfalse) {pxcurrenttcb->    Ulnotifiedvalue = 0UL;        } else {pxcurrenttcb->ulnotifiedvalue = ulReturn-1;    }///Set flag indicating that no wait notification is required */pxcurrenttcb->ucnotifystate = tasknot_waiting_notification;    } taskexit_critical ();    return ulreturn; /* If the return value is 0, the task block timed out */}
Compared with acquiring binary semaphore and acquiring count semaphore function, this function has much less cost of calling sub-function, less judgment, less event list processing, less locking and unlocking of queue, etc., so this function is relatively efficient.
2.2xTaskNotifyWait ()This function is used to implement a full-featured version of the wait notification, depending on the parameters, can be flexibly used to implement lightweight queue, binary semaphore, count Semaphore and event group functions, the function prototype is:
basetype_t xtasknotifywait (uint32_tulbitstoclearonentry,                     uint32_tulbitstoclearonexit,                     uint32_t* Pulnotificationvalue,                     ticktype_txtickstowait);
    • Ulbitstoclearonentry: Before using the notification, the task's notification value is bitwise and operated with the bitwise-Ulbitstoclearonentry of the parameter. Set the parameter ulbitstoclearonentry to 0xFFFFFFFF (Ulong_max), which indicates the zero task notification value.
    • Ulbitstoclearonexit: Bitwise AND operation of the value of the notification of the task and the bitwise inverse of the parameter ulbitstoclearonexit before the function xtasknotifywait () exits. Set the parameter ulbitstoclearonexit to 0xFFFFFFFF (Ulong_max), which indicates the zero task notification value.
    • Pulnotificationvalue: The notification value for the outward postback task. This notification value copies the notification value to *pulnotificationvalue before the parameter ulbitstoclearonexit is in effect. If you do not need to return the task's notification value, this is set to null.
    • Xtickstowait: The maximum time to enter a blocking state due to waiting for notification. The time unit is the system cycle. The macro pdms_to_ticks is used to convert the specified millisecond time to the corresponding number of system beats.
The implementation of this function and Ultasknotifytake () have a lot of similarities, I will the entire process in the form of annotations in the source code, the source code is as follows:
basetype_t xtasknotifywait (uint32_t ulbitstoclearonentry, uint32_t ulbitstoclearonexit, uint32_t *    Pulnotificationvalue, ticktype_t xtickstowait) {basetype_t xreturn;    Taskenter_critical ();            {/* The task will be blocked only if the task is not waiting for notification */if (pxcurrenttcb->ucnotifystate! = tasknotification_received) { /* Before using the task notification value, the parameter ulbitstoclearonentryclear is reversed with the task notification value bit. You can use this method to clear some or all of the notification values before using the task notification value 0 */pxcurrenttcb->            Ulnotifiedvalue &= ~ulbitstoclearonentry;            /* Set task status ID: Wait for notification */pxcurrenttcb->ucnotifystate = taskwaiting_notification; if (Xtickstowait > (ticktype_t) 0) {/* block current task */Prvaddcurrenttasktodelaye                Dlist (xtickstowait, pdtrue);                Tracetask_notify_wait_block ();            /* Trigger PENDSV Interrupt, wait until exit critical section, perform task switch */PORTYIELD_WITHIN_API ();    }}} taskexit_critical (); /* Go here to indicate that another task or interrupt sent a notification to this task, or that the task block timed out, and now continue processing */taskenter_criticAL ();        {tracetask_notify_wait (); if (pulnotificationvalue! = NULL) {/* output current notification value, passed by pointer parameter */*pulnotificationvalue = Pxcurrentt        cb->ulnotifiedvalue; }/* Determines if the task is blocking timeout */if (pxcurrenttcb->ucnotifystate = = taskwaiting_notification) {/        * Do not receive task notification, is blocking timeout */Xreturn = Pdfalse; } else {/* receives the task value, first ulbitstoclearonexit the parameter with the notification value bit, and is used to clear some or all of the notification values to 0 before exiting the function. */Pxcurre            Nttcb->ulnotifiedvalue &= ~ulbitstoclearonexit;        Xreturn = Pdtrue;    }/* Change task notification status, dismiss task notification wait */pxcurrenttcb->ucnotifystate = tasknot_waiting_notification;    } taskexit_critical (); return Xreturn;}
Throughout the implementation of the task notification, it can be found that it is much simpler than the queue, the semaphore. It enables lightweight queues, binary semaphores, Count semaphores, and event groups, and is more convenient, more RAM-efficient, and more effective to use. FreeRTOS's authors have tested, using the GCC compiler,-O2 optimization level in the same platform, using task notifications can be up to 45% faster than using semaphore to unblock tasks! This improvement in performance is huge. We analyzed the source of the semaphore, and today we analyzed the source of the task notification, which makes us know that the reason why there is such a large performance improvement, on the one hand, the task notification data structure is simple, to achieve simplicity; On the other hand, it is related to the freertos of the semaphore mechanism and inefficiency. Because the semaphore implementation is all using the queue mechanism, and does not specifically optimize the signal volume.
In addition, it is emphasized that task notifications do not completely replace queues, binary semaphores, Count semaphores, and event groups, and that task notifications have their limitations, and we end this article with its limitations:
    • Only one task can receive a notification event.
    • A task that receives notifications can go into a blocking state because it waits for a notification, but the task that sends the notification cannot go into a blocking state even if it does not immediately complete the send notification.



FreeRTOS Advanced Article 8---freertos Task notification analysis

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.