The essence of UCOS system

Publisher:数字翻飞Latest update time:2015-10-19 Source: eefocusKeywords:thread Reading articles on mobile phones Scan QR code
Read articles on your mobile phone anytime, anywhere
Preface: Threads are not something mysterious. Once you understand them, you will have a sense of sudden enlightenment. In fact, they are very simple.

  Section 1: Program Code Running Conditions
  Recall:
  1. A linked program consists of the following: code segment, read-only data segment, and readable and writable data segment.
  2. Two commonly used resources on microcontrollers: Flash (read-only), RAM (readable and writable)
  3. For microcontrollers, we are accustomed to a mode where the code segment and read-only data are placed on FLASH, readable and writable data are placed at the starting address of RAM, and the stack runs from the highest address in RAM downward.
  4. The processor extracts code from the code segment in three ways: sequence, jump, and call. All code segments must be placed in the correct position.
  5. The data segment on the processor processes data by address, so the data must also be placed in the correct position.
  6. The temporary variable on the processor extracts the variable by the downward offset of the stack pointer, so the address of the temporary variable is not fixed.
  7. As for the stack, that is a skill of C language and is not included in the scope of conditions.
  Summary: 1. The program needs code segment, data segment and stack area to run, and the code segment and data segment must be placed at the corresponding address during editing, and only the stack area can be set. Then, without using the temporary variable address as a calculation factor, the program will still produce the same result even if the top of the stack is changed.

  Section 2: Principles of multi-program operation
  The principle of multi-threading is actually very simple. The system provides a piece of memory as a stack area for each thread. Then select a point in FLASH as the starting address of the thread code, and finally transfer to the starting address of the thread code to start execution. The thread code can freely operate the temporary variables in the allocated stack without interfering with any other threads. Unless your temporary variables are too large and exceed the allocated stack area, this depends on your experience and feeling of using threads. Most people will not carefully calculate how much temporary variable space to use. Threads also have a disadvantage, that is, when threads access addresses outside the stack area, including the data area, there is a possibility that when multiple threads read and modify data in a data area at the same time, a boundary phenomenon will occur, that is, data co-managed by multiple threads. Of course, it is relative to our feelings. Let's take an example: after thread A reads data from address Addr, it happens to be handed over by another thread B, and thread B also reads data at address Addr and modifies one bit of data, then..., when thread A runs again, thread A also modifies data and writes it back to address Addr, so there is a bug, and the bit modified by thread B is overwritten by thread A. We don't actually worry about this, because the system generally provides many mechanisms to avoid this phenomenon.
  If you still don't understand multithreading, I guess you don't have an accurate understanding of the stack, you can refer to relevant materials.


  Section 3: Introduction to ucos system
  I have blocked the advertisement part about ucos, and I will go straight to the point. ucos is a preemptive system. Preemptive means that tasks are run in a preemptive manner. It is inappropriate to understand tasks in Ucos as processes, which will affect our understanding of Windows processes and Linux processes. Tasks in Ucos can only be equivalent to the role of threads. Ucos content includes two major parts, one is the system part: including task operations, time operations, event operations, and memory operations, which do not vary with different processors. The other is the interface part: composed of assembly and C language. It provides task switching functions, timer interfaces, CPU register save and read, and interrupt processing and other hardware-related operation functions.

  Section 4: Create a ucos task
  Use the following function to create a task:
  INT8UOSTaskCreate(void(*task)(void*p_arg),void*p_arg,OS_STK*ptos,INT8Uprio);

  The creation function sets the task function (the first address of the task code) and task parameters, allocates the top of the stack (ptos), and the priority. The way to pass in ptos is to allocate a data OS_STKStk [size] in the readable and writable data area. Then the highest address of stk is transferred to ptos. The stk array is the default stack area allocated to the task. After the task task is running, stk is used to store temporary variables.
  In addition, stk has another reduction that the system needs to subtract a part of the space from the highest bit of stk to store register information. For ARM, it is 16 unsigned long lengths, which are used to store r0-r15 and CPSR of the task. The
  system will also allocate a task control block (TCB) for each created task. The TCB manager manages the status and information of the task. There is also a TCB pointer pointing to the currently running task. TCB controls whether the current process is running, if it is not running, whether it is due to event blocking, when the task is running, where to find the information saved from the last run, etc.
  For each created or running task, there are two important parts, one is TCB, and the other is the stack head (the space reserved at the top of the stack area). The first step in scheduling a task is to find the TCB of the task, then find the stack head address from the TCB, then use the data saved in the stack head to copy to the CPU register and CPSR, and finally jump to the address saved in the last run on the stack head to start execution. When the running task is scheduled, it is also the first to find the stack head pointed to by the TCB, then copy all the contents of the CPU registers, CPSR and the current address, and then find another process that the task should run, and then schedule that process.
  The background of multitasking comes from one point. In fact, most of the programs we write actually have too many delays. For programs without a system, the actual execution efficiency (that is, no invalid loops) may only account for less than 5% of the processor's operation. Insert a sentence, if you are good at processor programming, you should not only see the length of the code when you look at the code, but how long it takes to run this code, and what resources and space it occupies. The introduction of the system will make us re-understand the task running time. We don't want the program to do invalid loops for a long time. We should use this time to do other things to improve the efficiency of the processor. So don't think that the system will occupy your resources. The system will help you work hard to recover more than 95% of the efficiency. It is actually impossible to recover all resources. It depends on how you use the framework and your task level. Each project may be different.


  Section 4: Preemptive Scheduling (UCOS Classic)
  Scheduling means finding the most appropriate task to run from all task queues, and then running the task. The scheduling method determines the performance of the system. The classic of Ucos comes from the fact that it only uses an array to take the simplest behavior to schedule tasks, while also occupying the smallest resources. Therefore, even with an 8-bit processor, many people will use the UCOS system. As
  mentioned above, UCOS is preemptive scheduling. The concept of preemptive scheduling is that there is only one CPU, and all threads occupy the CPU in a preemptive manner, and then run tasks, unless he actively gives up or he is preempted by other tasks, otherwise, he will always occupy the CPU. The preemptive method of UCOS is to compare priorities, and each task needs to be assigned one and only priority. Each scheduling is to compare the priorities of all tasks, find the task with the highest priority (this is actually not complicated, as described in the next paragraph), and then schedule the task and run it. The task with the highest priority needs to exit on its own initiative, otherwise, this one will always be running. When the task runs to the delay or wait event, the system function will block the task from the run queue, and then reschedule and search for the highest priority task again, so that another priority task is found and then run. When the task sleeps or waits for an event, it will also sleep, and then schedule again. At this time, if the highest priority task that was previously sleeping is awakened, it will also be placed in the priority queue. Otherwise, it will enter the next priority.
  The concepts of task queue, sleep, and run are all a kind of understanding concept. In fact, ucos is very simple and classic in this regard. Now let's explain the scheduling queue of ucos. First of all, we need to know what the following array is used for.
  INT8UconstOSUnMapTbl[256]={
  0,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,
  4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,
  5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,
  4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,
  6,0,1,0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
  4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
  5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0,
  3, 0, 1, 0, 2, 0, 1, 0, 7,
  0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4
  , 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
  5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
  4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
  6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4,
  0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5,
  0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
  4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0
  };

  Given an 8-bit number data, the purpose of this array is to find the first 1-bit position (bit0 is 0) in the data from low to high bits by table lookup. That is, it replaces the function below. Usage: prto= OSUnMapTbl[data]
  for (i=0; i<8; i++)
  {
   If ( data & (1<    Break;
  }
  After understanding the array, we introduce two more variables,
  OS_EXTINT8U OSRdyGrp;
  OS_EXTINT8U OSRdyTbl[OS_RDY_TBL_SIZE];
  The priority of each task corresponds to a bit in the OSRdyTbl array. The corresponding relationship is the (prio%8)th bit in OSRdyTbl[prio/8]. (prio/8) and (prio%8) are represented by ->OSTCBX and ->OSTCBY in the task control block TCB. Setting the position corresponding to the task in the OSRdyTbl array to 1 indicates that the task is ready and can participate in preemptive operation. Setting the position corresponding to the task in the OSRdyTbl array to 0 indicates that the task does not exist or is currently blocked and cannot run. The role of the OSRdyGrp variable is to use 8 bits to correspond to the first 8 bytes of the OSRdyTbl array in sequence. If the nth bit is 0, it means that all OSRdyTbl[n] are 0. If the nth bit is 1, it means that at least one of the corresponding OSRdyTbl[n] is 1.
  Then the scheduling tool starts to use the following mechanism to get the highest priority task, that is, the task with the lowest priority number
  y = OSUnMapTbl[OSRdyGrp]; 
  OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]);
  The first line of code finds the lowest non-zero array in the OSRdyTbl array by looking up the table. Then, through the second code OSUnMapTbl[OSRdyTbl[y]]);, we find the position where the lowest one in the corresponding OSRdyTbl[y] is 1. Then add it to (y << 3) to get the position of the lowest 1 in the OSRdyTbl array from low to high. That is the priority of the highest priority task, and then find the corresponding TCB for scheduling according to the priority.
  Each time the previous process is scheduled, the previous process is closed. Therefore, UCOS needs to have at least one executable program in the queue. For this reason, UCOS creates an IDLE task. This task has the lowest priority and is at the highest position. The purpose is to allow the system to still have tasks to adjust when all task processes are asleep, so as not to crash. Another use is to count CPU usage. If the IDLE task has never been called, it means that the task preemption is too high, and the high-priority task needs to release some space to allow the low-priority task to run.

  Section 5: Real-time performance of UCOS
  As I understand it, the real-time performance of UCOS is a conception, where all tasks are in the waiting signal stage. When an interrupt is triggered, the interrupt processing function is executed, and the process is awakened by the signal to complete the task. After completion, it can continue to sleep. Even if there are multiple interrupt responses, as long as the interrupt function can respond in time, the tasks will be lined up to complete the subsequent work. This is my conception of UCOS.
  Therefore, we need to schedule in two places, one is the interrupt, and the scheduling is turned off after each interrupt, but the signal is allowed to wake up the task, and then the scheduling is performed when the last interrupt nest is completed and exited to detect whether there is an executable task that has been awakened. The other is the timer interrupt. After each tick is completed, a rescheduling is performed. The purpose is that when the low priority execution does not release resources, and the high priority task has been awakened. Especially the IDLE task, unless you ask it, it will not release resources for you.

  Section 6: Event Processing
  The events of UCOS mainly include SEMAPHORE, Mutex, and Mbox, Q. They are very simple to use. Generally, only three functions are applicable: creation, suspension, waiting, and release.
  The function of SEMAPHORE is to suspend and wait for the satisfaction of resources when a task must obtain a certain resource. Other tasks or interrupts send SEMAPHORE to indicate that the resource has been established and you can run. If a task finds that a task is suspended when sending SEMAPHORE, it will wake up and schedule it to execute on the task.
   Mutex has a concept of lock. When it gets something, it will lock it immediately. Other tasks can only wait for the task to complete and unlock the lock before running. There is a problem here. Once a low-priority task occupies the lock, the high-priority task must wait, and the low-priority task happens to be snatched by the medium-priority task for execution. The phenomenon of high priority waiting for low priority and low priority waiting for medium priority is called priority flipping. All Mutexes have a mechanism that if the high-priority task wants to get the lock, it will temporarily increase the priority of the low-priority task so that the low-priority task can complete and release the lock as soon as possible.
  The mailbox MBox is basically the same as SEMPAPHORE, except that SEMPAPHORE is sent as a signal flag. Mbox can also be used as SEMPAPHORE, but it will return an address pointer.
  I have never used the Q message queue. It seems that an array is initialized first, and then the array is used to send and receive letters in FIFO mode.
  I usually add some atomic read and write functions atom_read/wirte, mainly for boundary variables. In fact, it is very simple to turn off interrupts before reading and turn on interrupts after reading.

  Section 7: Tick
   Tick is the system time, which is different from the concept of timing, such as OSTimeDly (OS_TICKS_PER_SEC/100), which is not strictly delayed by OS_TICKS_PER_SEC/100 seconds, and there is an error between 0-1/OS_TICKS_PER_SEC. Tick is equivalent to a clock running continuously. The moment when the stopwatch changes is called a tick, and we cannot start timing from the tick. So this is a concept that needs to be distinguished.
   
  Section 8: The defects of UCOS
  After all, UCOS is a small system, and can even run on an 8-bit processor, so it has certain limitations for us to complete more complex tasks and have higher requirements for system efficiency. For example:
  1. The system is closely related to applications, interrupts, etc., and developers need to be familiar with system characteristics, such as. After a task is created, it cannot be exited directly, and it must be destroyed using the API function.
  2. The scheduling method is too single. When there are fewer tasks, a balance can be achieved. When there are more tasks, there will be a serious imbalance in the running time of high-priority and low-priority tasks, and scheduling issues will increase.
  3. Lack of asynchronous reading mechanism. For example, if I want to send data to the serial port, but the serial port buffer is full, we need to give up resources to schedule other tasks. The serial port can make up for it by opening multiple buffers, but for TCP, exiting requires at least waiting for the next tick, which seems a bit long. I have actually been considering this mechanism. 

  Section 9: After Writing
  Another reason why I don't like LPC21xx and Zhou Ligong's UCOS system is that the interrupt mechanism of LPC21xx looks good, but it actually affects the performance of our code. It may also be because I am lazy and have not had time to modify ucos on LPC. Zhou Ligong's interrupt function uses __irq declaration, which is contrary to what is said in Section 5 above.
  In addition, Zhou Ligong's interrupt disable function and interrupt enable function use swi interrupt, which I think is not as good as the original version. The original function is to save the register interrupt disable function and restore the register content. I originally thought that Zhou Ligong might have considered that soft interrupts can directly enter interrupts to avoid interrupt interference, while the original version may still be interrupted in the middle of the interrupt disabling function, as shown below 
  MRSR0, CPSR; //Copy CPSR, which may be interrupted after execution
  ORR R1, R0, #0xC0; //Calculation, which may also be interrupted
  MSRCPSR_c, R1; //This code is completed before the interrupt is truly disabled
  But later, after understanding it, I felt that Zhou Ligong was doing it in vain. Even if it is interrupted before disabling the interrupt, it doesn't matter, because it will return to you intact after the interrupt. I still don't like Zhou Ligong's UCOS and LPC.
  Later, a UCOS was also built on Samsung's s3c2440, and it was matched with TFTP transmission and TCP dialogue. It feels much easier to use than LPC.
  Of course, this is just my personal usage and feeling. As long as the software of each chip is written well, it should be good. Let me briefly talk about my personal usage. I usually define the main function as follows:
  int main(void)
  {
   OSInit();
   OSTaskCreate(MainTask,(void *)1,&MainTaskStk[MainTaskStkLengh-1], MainTaskPrio);
   OSStart(); 
   return 0;
  }
  Directly create a MainTask task, then initialize the hardware and create tasks in MainTask, events
  void MainTask(void *pdata)
  {
   u8 err,iLed=0;
   TargetInit(); 
   env_init();
   PrintMutux=OSMutexCreate(MutexPrintPrior,&err);
   OSTaskCreate (Consoler,(void *)0, &ConsolerStk[ConsolerStkLengh - 1], ConsolerPrio);
   OSTaskCreate(NetConsole,(void *)0, 
   &NetConsoleStk[NetConsoleStkLengh - 1],
   NetConsolePrio);
   while(1){
   iLed++;
   Led_Display(iLed);
   OSTimeDly (400);
   rtcDisplayTime();
   }

  }

Keywords:thread Reference address:The essence of UCOS system

Previous article:2440 Startup Code Analysis Experience
Next article:STM32F10x development process in the environment

Latest Microcontroller Articles
Change More Related Popular Components

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

About Us Customer Service Contact Information Datasheet Sitemap LatestNews


Room 1530, 15th Floor, Building B, No.18 Zhongguancun Street, Haidian District, Beijing, Postal Code: 100190 China Telephone: 008610 8235 0740

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号