Semaphores in DSP/BIOS

OK, this may be a stupid question but I'm going to go ahead and ask it. I seem to be missing something very basic in the use of semaphores.

The SEM module in DSP/BIOS maintains a non-negative count of the number of times it has been "posted". Then when a pend occurs, the process either a) blocks if count = 0, or b) decrements count and resumes.

I have one task T1 that must run to completion before other tasks (T2, ..., TN) run. It *seems* this would be a good use of a semaphore; create a semaphore SEM_T1, then have each task T2, ..., TN pend on SEM_T1. Then when T1 completes, it posts to SEM_T1.

However, this won't work with DSP/BIOS semaphores. What will happen is that the first task that pended, say, T2, will get unblocked when T1 completes, but since there was only one pend by T1, none of the other T3-TN will unblock.

How would you solve this problem in DSP/BIOS?

--
Randy Yates                      % "Watching all the days go by...    
Digital Signal Labs              %  Who are you and who am I?"
mailto://yates@ieee.org          % 'Mission (A World Record)', 
http://www.digitalsignallabs.com % *A New World Record*, ELO
Reply to
Randy Yates
Loading thread data ...

No idea what DSP/BIOS is so no idea of their implementation beyond your comments, here, so...

If you want to use a semaphore for this role, you can:

- have T1 post (V) M to SEM_T1 (M >= number of consumers), or

- have T1 post (V) 1 to SEM_T1 and each subsequent consumer posts (V) 1 as soon as it acquires (P) the semaphore (i.e., propagate it immediately to other consumers pending)

You can also use something less complex like a *sticky* event flag (everyone waits for it to be asserted -- which is only done by T1). [a non-sticky flag would need to be propagated by consumers much the same way that the second solution above works]

Or a condition variable.

Or...

(depends what you have available to you)

A cleaner approach IF T1 IS THE PREREQUISITE FOR [T2..TN] (i.e., maybe T1 is setting up the environments in which the other Ti operate?) is for T1 to explicitly start/create those tasks when it has made the world right for them (i.e., make this dependancy more explicit and visible)

HTH,

--don

Reply to
D Yuniskis

Should have been "...since there was only one POST by T1"...

Reply to
Randy Yates

e days go by... =A0 =A0

m I?"

ttp://

formatting link
*A New World Record*, ELO

eh ... call SEM_post(SEM_T1) N times?

Reply to
wicore

formatting link

It appears that your T1 has the highest priority of all the other tasks, since it must run to completion before the others start. If it may interrupt the others should it gets ready to run while another is running, then it is, indeed, the highest priority task.

Normally you would deal with this by making T1 the highest priority task in the OS, and it pend on whatever event makes it ready (i.e. an ADC read, or a timer tick). That way it'll wake up when it should, do it's job, and go to sleep. Because it's high priority it'll automatically trump the other tasks for processor access, and because it pends when it's done the other tasks will automatically get their chance when it's done. The semaphore that you have T1 pend on could be a regular binary semaphore if they are supported, or it could be a counting semaphore with you taking care about what happens if T1 ever fails to service it often enough.

You may want to look at the book "The Art of Embedded Programming" by Jack Ganssle. It looks like he only has one chapter on using real-time operating systems, so you may have to decide if you want to read that book or if you want to find a whole book on real-time OS usage (not that I can find one in a hurry).

Some background:

formatting link

I started to try write you a "RTOS usage in a nutshell", but realized that it would run over 1000 words and take me most of the day -- so I didn't. You may want to try searching the web, or go straight to Embedded.com and see if they have any articles on this subject. I know that just about all that I have learned on the subject of RTOS usage has come from on-the-job training and from Embedded.com, with just a smattering of lectures at the Embedded Systems Conference thrown in (Michael Barr's talk on Rate Monotonic Scheduling is a must-see after you understand the nuts and bolts of an RTOS; he's got an article on Embedded.com)

The real trick is that you want to identify your tasks, prioritize them, and let the OS do it's job. For the most part, an event-driven real-time application doesn't have a lot of tasks playing with semaphores: they are posted in the interrupt service routines that respond to the external events and pended on by the _one_ task that services that event, and the "task A must pend on task B" stuff is taken care of with priorities. Only when you are doing something advanced* like granting access to a serial port to more than one task must you use counting semaphores, and then you run into all sorts of potential trouble** that you have to mitigate.

The job of prioritizing the tasks is where Rate Monotonic Scheduling comes in -- once you strip off all the math explaining what's happening under the hood, the actual procedure is easy and gives a pretty concrete yea or nay to whether the underlying assumptions are being met.

I hope this helps.

  • I.e. stupid
** Like priority inversion. Which is why those advanced methods are stupid if you can find a simple way to avoid them, like a _single_ task that talks on that serial port, and handles messages from or to any other tasks that may need to talk.
--
Tim Wescott
Control system and signal processing consulting
www.wescottdesign.com
Reply to
Tim Wescott

I find the following very useful in an RTOS:

Partition those tasks which you wish to initiated from interrupt events into a finite set of priority levels (the fewer the better).

Within each level each task is preceded with common code which implements the following sequence of operations (which must be made uninterruptable):

(1) Is there another task of the same level already running?

(2) If so, place the current task at the end of the queue for this level, and return from interrupt.

(3) If not, lower priority and start running the current task.

And at the end of the task:

(4) Raise priority

(5) If another task for this level is queued, execute it. Otherwise, if the queue is empty, return from interrupt.

Whether you do this with semaphores is an implementation detail.

What you don't want to do is have tasks queueing or executing other tasks which are from a _different_ priority level.

Applying this to your example, T1 is higher priority than T2, T3 etc. which are all at the same (but lower) priority level.

So, when T1 gets to step (3), it lowers priority enough to enough to allow other T1-level tasks to run - but not T2, T3 etc. tasks. Then after T1 gets to step (5), all tasks queued at the T2, T3 level can potentially run.

(Note that if another T1 task does interrupt T1, it only gets queued, it does not pre-empt T1.)

Fundamentally you need a queue of tasks at each priority level, rather than individual tasks reaching across levels to start or stop things.

Steve

Reply to
Steve Pope

This sounds way complicated. Are you doing this within the context of an RTOS? If so, why in heck can't you just use as many fixed priorities as you need to get the job done?

In general, I've found that if you have to do much (or any) changing of priorities as a task executes that's an indication that you're confused, that you're really doing too much with that task, and that you need to split it up or otherwise refactor your code to better match your problem. Only if you're accessing one physical resource from multiple tasks, and you have truly different levels of priority for your interactions with that resource, do you need to play priority games -- and those are well documented under the "priority inheritance" rubric.

--
Tim Wescott
Control system and signal processing consulting
www.wescottdesign.com
Reply to
Tim Wescott

Here's what you wrote earlier:

"The real trick is that you want to identify your tasks, prioritize them, and let the OS do it's job."

I'm saying the same thing, I believe, except perhaps describing what is being done at a lower level.

The problem as stated was that Randy was trying to use a task at one priority level to start a lower priority task; that for me is problematical. Your scheduler (whether part of the OS or not) should be starting that lower priority task.

We may have a slight difference of philosophy in that you may be stating to prioritize all tasks (N tasks, N priorities) whereas I am more in favor of partitioning them into the smallest possible number of priorities.

By all means use the features of your RTOS to do what I described above. Assuming those features are there. (They were not, back when I was working in this area, but I assume things have vastly improved... a modern RTOS will queue up equal-priority event-driven tasks and run them sequentially...right?

Steve

Reply to
Steve Pope

e days go by... =A0 =A0

m I?"

ttp://

formatting link
*A New World Record*, ELO

If it's of any help, elaborate RTOS-style synchronization used to confuse me too. At some point, I found that all this malarkey can be reduced to a combination of 2/4-phase asynchronous handshaking protocol. Then only thing you have left to do is hook these up with boolean expressions.

-Momo

Reply to
Manny

I didn't read that in the OP's initial query at all! There was no mention of "priority". Rather, there was a timing dependency of T2..TN on the actions that T1 performed. T1 can have *any* priority relative to the other tasks and there can still be this dependency -- which can be managed with mutexes, semaphores, event flags, etc.

I.e., if you claim T2 has lower priority than T1, then what happens if T1 *blocks* for some reason (e.g., waiting on a timer). Now, tasks of lower priorities

*can* run (T2, etc.). Yet, the OP explicitly claimed:

"I have one task T1 that must run to completion before other tasks (T2, ..., TN) run."

Merely rearranging priorities is not going to give the OP this "guarantee".

See my post re: how to do this with semaphores, event flags, condition variables, etc.

Reply to
D Yuniskis

Yes, I'm saying don't do this. If you want T1 to complete before T2, then T1 should be higher priority -- if that is workable.

That's a very interesting example. A blocked task normally caused the next task of the same priority to be scheduled. Not a task of a lower priority. But it does get interesting when you have tasks other than the lowest priority subject to blocking...

Yes, I agree it's needed in the case you describe, but I am not at all sure the OP was describing this case. And if so it's the "messier" case.

Steve

Reply to
Steve Pope

It wasn't clear to me from his original post if that was the case -- I was hoping that he would clarify in that case.

Yes and no. Yes, they should be used to describe what activities are more important, but when you do the scheduling calculations per the RMS algorithm you may find that the algorithm's results change your impression of the 'importance' ordering from what you had originally thought.

--
Tim Wescott
Control system and signal processing consulting
www.wescottdesign.com
Reply to
Tim Wescott

Because it isn't an issue of "priorities". He wants an explicit interlock between the execution of T1 and T2..TN.

Either let T1 *start* T2..TN, *resume* them, *or* use a counting semaphore, event flag, condition variable, mutexes, etc. as I described in my other post.

E.g., my Init() task runs at the absolute lowest priority in the system. When it is done setting things up, it becomes the "idle" task.

It sets up all of the other tasks in the system, allocates their stacks, heaps, etc. But, each of the other tasks has an interlock that causes them to NOT proceed until I have set up *everything*. I.e., if any task starts to run, it will preempt init() -- since init runs at the lowest priority level -- so, if I let the other tasks run, then init() will never get a chance to finish setting up all of the tasks!

I do this in a variety of different ways.

- leave the scheduler disabled until after everything is set up (i.e., last thing init() does is start the scheduler). This makes things easy for init() *if* it never needs any feedback from the tasks that it is starting.

- let each task immediately preempt init() as soon as it is made ready. Then, after doing whatever housekeeping is required, ensure the task blocks on something that init() ultimately has control over (e.g., a semaphore or event). This lets init() know that the tasks it is creating are starting properly -- else it can take corrective action. When init() has built all of the tasks, it can then signal them all to begin.

- create tasks in a dormant state and explicitly enable them from init(). Then, use one of the above scenarios.

- subtle/implicit variations on the above (i.e., let each task wait for whatever it needs while init() effectively controls those occurences (e.g., if each task is waiting on different events signalled from IRQs, then init() can just keep interrupts disabled and *know* that all of the tasks will eventually block waiting for the IRQ-driven events that *can't* yet materialize

Priorities should be used to decide what activities are "more important" than what *other* activites.

Reply to
D Yuniskis

Well, both Tim and I inferred from Randy's post that T1 is, or perhaps should be, higher priority than T2, T3... you're assuming equal priorities with need for an explicit interlock.

I believe in the general rule of not using an explicit semaphore or message if your scheduler can handle it.

What I don't know is whether DSP/BIOS has a good enough scheduler.

Steve

Reply to
Steve Pope

You certainly shouldn't hijack the scheduler from a task, or attempt to micromanage it. But there are situations where you want to start unpend a lower-priority task from a higher-priority one. The case in point that I can think of is if you have two relatively independent tasks that work on short queues -- say ADC collection and serial port servicing -- that will break if you don't do a little bit of work really quickly. If those tasks feed larger tasks that work on a larger time scale -- say, respectively, implementing a control rule and parsing what's coming over the serial link -- then that indicates four tasks to me: hardware service routines for the ADC and serial (possibly in an ISR, but an ISR is effectively a really high priority task if you think about it), and the motor control and the communications stack.

My suspicion is that you would want to have (say) the ADC collection and motor control in one task with a priority change in the middle, where I would want to have separate tasks (truly, in many cases I'd probably want the ADC collection to be in an ISR, but that's a quibble and a matter for another thread).

I'm for whatever works, but yes that is what I'm propounding.

Yes, a modern RTOS will do so. Some RTOS's are more modern than others

-- I know that MicroC/OS-II has a "one task, one priority" rule, because in effect the task ID is the priority. But that particular RTOS is intended to be small and simple.

If you're designing things correctly (and using a decent RTOS) then your bunch of equal-priority tasks should have enough time to run and having them all the same priority shouldn't starve any one of them.

--
Tim Wescott
Control system and signal processing consulting
www.wescottdesign.com
Reply to
Tim Wescott

But priority isn't the issue here. What the OP appears to be claiming is there is something that needs to be done

*by* T1 before T2..TN can run. E.g., maybe T1 sets up the interrupt system that the others will rely on. Or etc.

What you need is a mechanism that forces T2..TN to stop and wait until they are *told* to proceed. E.g., create a sticky event flag called "T1_HAS_DONE_HIS_JOB". Then, have T2..TN each do: await(T1_HAS_DONE_HIS_JOB); this causes T2..TN to *block* when/if they get to this line of code (in their respective "programs") and T1 has NOT YET done his job. (I.e., T2..TN can execute all of their own code leading up to this line of code but can't continue past it).

If T2..TN *literally* can't run until T1 is done, then T1 should be the thiing that *starts* T2..TN -- directly or indirectly (i.e., T1 can signal T0 that it has done it's job and T0 can then "start" T2..TN)

But, you don't *know* that you have another task of the same priority *available*. None might exist. Or, the other(s) might, coincidentally, be blocking on other things at the same time, etc.

If you want a genuine interlock between tasks, install one. This makes the dependency very visible in the code. Otherwise, if you rely on other aspects of the code to *effectively* give you this behavior, then when someone comes along and changes one of those aspects, things suddenly *break* ("Gee, all I did was change the algorithm so T1b would finish it's chores a little quicker. Why did T2 start running before T1a finished??")

Reply to
D Yuniskis

If that's the case then Randy does, indeed, want to use semaphores. Having a bunch of stuff pend on one thing can be done with binary events of certain styles (basically ones that you can explicitly pend on without changing their state, then explicitly change their state). Or you can get this behavior from a counting semaphore with a "pend-and-post" operation, or you can have one semaphore for each of the subsidiary tasks (ick).

I.e. if the OS supports it have T1 actually call the start_task routine for the other tasks? Kinda kludgy, but it may work.

(other comments snipped)

--
Tim Wescott
Control system and signal processing consulting
www.wescottdesign.com
Reply to
Tim Wescott

It depends on what situation the OP is trying to address. When I read his post, what ran through my mind was system startup (or, SUBsystem startup). I.e., T1 sets up things that T2..TN *need* BEFORE they can run. As if T1 was the "init()" routine for this system/subsystem.

In which case, *someone* has to "start_task()" so why not put that where it makes sense (if, indeed, T1 is setting up this system/subsystem)? Or, it T0 is really the init routine, then let T0 start T1 and *wait* for T1's completion -- before T0 proceeds to start T2..TN.

Again, depends on what the OP intends. My point is to make hard dependencies very obvious in the code lest they get optimized away at some future date...

Reply to
D Yuniskis

the days go by... =A0 =A0

am I?"

,

formatting link
*A New World Record*, ELO

Right, and so modify the task everytime you add a new task? Talk about inelegant...

Reply to
Randy Yates

the days go by... =A0 =A0

am I?"

,

formatting link
*A New World Record*, ELO

Momo, sounds like an elegant solution (I like simple solutions - they are usually the best). Can you expound more on what 2/4-phase asynchronous handshaking is and how you would determine the boolean expressions?

Reply to
Randy Yates

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.