synchronization between two tasks

2019-04-12 05:24发布

问题:

This is actually a design question for firmware in an embedded system I have two ISRs (of same priority) executed independently . These ISRs are triggered when the h/w generates data. I want a mechanism which must be put in place to synchronise between task1 and task2. task 2 must know about the certain values calculated in task1 which must then be taken into account while computing certain values in task2. I don't have OS primitives to use ie the system does not have any Operating system. Task1 is executed within the context of ISR1 and task2 within the context of ISR2. The processor which we use is a STMicroelectronics 32 controller

EDIT: additional info The processor is connected to certain IPs which trigger interrupts when they are ready with data. These IPs act as type of Accumulators on input streaming frame data.

回答1:

I wouldn't do heavy processing in interrupt context, just read the data and set a flag.

The flags can then be checked in a simple scheduler in the main loop to execute the tasks as necessary. So tasks can't interrupt each other and can't see inconsistent output of the other task.

A task could also set such a flag to activate another task. E.g. Task1 could activate Task2 because Task2 needs values from Task1.

For the data that is read in the ISR you need some buffer. Depending on the timing of your incoming data that might be a ring buffer or a double buffer.



回答2:

  • Disable interrupts before reading or writing to the shared values
  • Re-enable interrupts after reading or writing to the shared values


回答3:

I'm coming at this from a standpoint of no OS, no real framework other than C and registers and such. The way I would do it is to have a state variable for each ISR that can be seen by the other ISR. Then when you enter ISR1 you just check the state of ISR2's task and operate as necessary. Then ISR2 gets called and checks its own state and ISR1's state and operates as it sees fit. I'd use an ENUM in a header file to enumerate the states for clarity (INIT, WAITONDATA, etc) and then a switch in the ISR to handle each state. Then you just have to make sure that the data that needs to be shared can be seen by both ISRs and you're set.

Of course as others have said you don't want to do many calculations in ISRs. You just want to set a flag that an event has occurred or some data is present and then handle the data in the main loop. Then your system isn't blocked for too long handling an ISR. You do this in the same way - a state variable that can be seen in the main loop (or at least the function you call to do the data manipulation FROM the main loop) and the ISR. Change its state in the ISR and check that state in the main loop function. Do chores as needed.

Don't forget - if you are updating variables in ISRs they must be defined as volatile!

volatile uint8 state = INIT;

for example.



回答4:

I'll try to give you an answer based on the limited information, assuming:

  • a simple, homebrew scheduler is used to invoke task1 and task2 based on simple criterea
  • task1 and task2 run to completion (i.e. do not preempt each other)
  • data is a byte-based stream (a slightly different implementation is required if you need packets)

What I usually try when designing embedded systems is to minimize use of semaphores, and model lock-free data flows. I'll illustrate this below:

asynchronous (lock free) communication queues http://www.freeimagehosting.net/uploads/477741db06.gif The ISRs can be decoupled from tasks by use of a thread-safe FIFO queue. An example can be found in http://msmvps.com/blogs/vandooren/archive/2007/01/05/creating-a-thread-safe-producer-consumer-queue-in-c-without-using-locks.aspx

Such an implementation (without locks) has no OS dependencies and should be trivially easy to support. This also gives a clear producer-consumer design which is free of deadlocks.

Assuming that the tasks are triggered by a home-brewn scheduler, you can elect to check for events (non-empty fifo) there. i.e. if (TRUE == fifo_filled(my_read_queue)) { invoke task 1 }

Now on the synchronisation of task1 and task2. If task1 is just producing data, the same mechanism may be used: if you have a queue (fifo) in which task1 can write data, which can be read by task 2 then the tasks are decoupled. Again, this can be checked in your scheduler (if (TRUE==fifo_filled(task1_to_2_queue() ) { invoke task2) }

If you need more (i.e. if tasks do not run to completion, but are preempted) you'll need some mechanism to synchronize. Options include: - use a (free) operating system or simple scheduler anyway - brew your own (my experience: should only be done if it is as simple as a for loop and a few if statements) - use a simple scheduler (much more lightweigth than a full RTOS) - refactor code to integrate task1 and task2 in one. In that case, you'll effectively make the scheduler part of your application code.

Note: the example function I've used in this explanation (fifo_filled() ) is not part of the example code. It should return true if (read != write) Also, the example code uses global variables read and write; you may either google up a function capable of handling multiple queues, or move read/write and buffer variables to structure, and refer to the structure in the parameters.

An alternative way is to create a criticial section by means of interrupt disable. However, I try to limit this as it often creates a strong system coupling, and code becomes harder to re-use.