Having seen this thread up to this point, I think the key point is summarized in the quote.
You assume that the OP has a very thorough knowledge of what he's doing and why. To me, his questions show that this might not be the case!
Solving his question to the letter will probably not be of much value.
To mention a few points that support this impression of mine:
- What's the behaviour of a function that does not return, when it suddenly returns? To all my knowledge the behaviour will be undefined. You can't complain when undefined behaviour turns out to not be what you expected.
- How should a scheduler detect that the interrupted task is in fact in the spin loop? Should it use the linker map to find the code address (and what about optimizations)? Should it match instruction patterns (and how do you force those to be generated by the compiler)? In my opinion there is no safe way to detect this, unless the function "helps" the scheduler (for example using a Yield() call, global vars, etc).
Since we're talking about a C program, we are constrained by the rules of C. We programmers exhaust those rules to our advantage, and the compiler writers exhaust "their side" of the rules to make better compilers. Therefore we can't break the rules, and C is not the correct tool for what the OP wants (IF it really is what he wants).
The clean way is to use any language or tool to create the desired functionality and encapsulate it into a linkable object. Only then it is compatible to the C portion of the design, and will stay so until the compiler calling conventions change.
I say this as programmer who has written schedulers on various architectures, and coincidentially also a virtual CPU of the architecture used by the OP (involving detailed inspection of instruction set and compiler conventions to create efficient hooks from virtual to physical).
Best regards Marc
Didn't find your answer? Ask the community — no account required.
Hello!I've got Ampro A60702 CPU board from Ebay.The board has CHIPS F8680...
D
David Brown
See and (and also more generally).
It was news to me too that the LTO (link time optimisation) branch of gcc had been merged with the main development line. I've known of its existence for a long time, but for many years it has been (or appeared to be) a bit of a blue-sky project with a lot of ideas and limited working code.
It will still take a while before LTO rolls down to gcc compilers popular in c.a.e. gcc 4.5 is in stage 3 (no new features, bug fix and testing) - it will be a early next year before we can expect a first release. It will take a while from then for Code Sourcery to qualify and verify it thoroughly on their targets, and the 32-bit embedded gcc suppliers will pick it up from there. Smaller ports, such as avr-gcc, will take longer - they have fewer developers and resources. For out-of-tree ports such as the msp430, it depends entirely on what the developers want to prioritise.
At the moment (gcc 4.3), using -combine and -fwhole-program can get you quite a lot of these effects for C programming. Basically, it treats all the files in the program as a single big C file with everything declared "static".
LTO will give several advantages on top of that. Files can be individually compiled - useful for large projects, when files are in different directories, or when you want different compiler options. Libraries can also have LTO information. You can use languages other than C (for example, C++), and mix them together. And gcc 4.5 has a number of new optimisations that are only relevant for whole-program compilation.
It is like a lot of things in C development - finding a general, portable, standards-compliant solution is hard, even though making it work in a real life project is typically very easy. The trick is to find a balance for a solution that is general enough without being overly complicated.
OK.
You can't assume that (otherwise the OP would never have asked the question in the first place...), although we can probably assume this can be forced in some way.
It may make sense for the end-of-time-slice function to do more than just spin. Then it may have to make a separate call to Spin.
That's a big requirement, and totally unnecessary except as a way to implement this bad idea.
That is mostly true (you can't just pop the top of the stack, because the interrupt function must first preserve a register or two - but as you say the msp430 has good stack access instructions), given your assumptions. But as I noted above, the assumptions are not reasonable, IMHO.
Once you have taken into account saving a working register or two (easy enough), then that's a fairly elegant implementation of a very ugly hack.
If this system is for scheduling important or time-critical tasks, and there is no prioritised pre-emption, it is very likely that you need to track when the task's work is done for testing and verification, or for tracking errors.
But you are correct that the kernel might not /need/ that information.
Working around the compiler in this way /is/ fragile. It is a hack, and it is dependent on details of the compiler, the processor, the stack structure, it requires assembly for what should be simple C code, and it hinders the compiler's optimiser.
However, I have to agree that you have come up with a simple implementation of this design (and I am lauding /your/ implementation here, not the OP's bad design, or our guesses about it).
Don't forget the comparisons to check that you are in the spin loop.
A function as simple as Spin case B would often be inlined into the calling function (either explicitly, or via whole-program optimisation). Code like that is smaller as well as faster when inlined.
The code calling Spin can be better optimised if Spin is a proper C function, and the complier knows its definition.
The majority of interrupt handlers in embedded systems are written in C these days. For general interrupts, if you are not happy to trust your compiler to generate good and safe interrupt code, get a better compiler! But thread switching code will almost certainly need some inline assembly at least.
I am referring to the check of the exit flag that you think makes my Spin function too slow compared to your version.
It is, as you say, hard to be sure - especially if Spin contains other code.
Yes, but you don't /need/ to check call or return addresses if you write proper C code...
I think we'll just have to agree to disagree on this one.
Yes, it's against my religion :-) You don't write hacked code based on lying to the compiler, assembly code, and stack manipulation tricks when there are perfectly safe, efficient and reliable ways to do the same job with C. It's about writing legible, maintainable, portable code that is clear in its purpose and easy to verify. Even if this is nothing more than a simple test program, you should maintain a certain level of development quality.
I am not saying that /all/ such hacks are a bad thing - just that you have to have very good reason for using them. Shaving off a few processor cycles (if you are correct and you /do/ save time) is very seldom a good enough reason.
Just because code is accepted by the compiler, and works in practice, does not mean it cannot be *wrong*. And yes, I know I am pontificating
- doesn't that beat preaching?
N
Niklas Holsti
You may of course be right, Marc. My impression is based on two things: the OP seems to have a workable design for the scheduler, and the OP seems able to read and understand the assembly-language code the compiler has generated. The OP's worry was only that a branch instruction would not leave a return address that the scheduler could use to resume the thread. I think that the OP did not know about tail calls implemented as branches, or did not remember this possibility.
The behaviour is defined by the code that the C compiler generates, and which the OP seems to have inspected. Of course, it is risky to rely on future compilations giving the same code. Writing Spin in assembly language would remove that risk.
I do understand that your question is about behaviour as defined in the C standard, but this is not a pure C program.
As I understood it, the Spin function is part of the OP's kernel/scheduler. If the loop in Spin is of the form "lab: jump lab", the scheduler interrupt handler can compare the PC at the interrupt point to the address of the label "lab". If Spin is written in assembly language, "lab" can be defined as a global symbol so its address is accessible to the scheduler as a constant. Or the Spin module can define a globally visible data word that holds the address of "lab".
But I don't know how, or even if, the OP intends to check that the task has reached Spin. Perhaps the OP's Spin sets a flag before entering the loop. Anyway, the check can be done easily and quickly, whether using a flag or using the PC.
We are talking about a program consisting of some C code, divided into several threads/tasks (which is outside C semantics, I believe), plus a thread scheduler (also outside C semantics), probably implemented by a tick interrupt handler. Typical multi-threaded embedded program.
That would certainly be my approach -- for my own purposes, I am not at all an "anything that works is good" programmer; I usually write in Ada, and enjoy it. I would write Spin in assembly language, as well as any coding-sensitive parts of the scheduler.
I agree fully, but I did not want to lecture the OP, only answer the OP's question. I did advise the OP to use separate compilation for Spin. In retrospect, I should have advised the OP to use assembly language for Spin.
The OP was specifically asking how to force the use of a normal calling sequence. The OP thought that a branch instruction did not represent a normal calling sequence, and it doesn't, except when it is implementing a tail call, as I think was the case in the OP's problem.
Agreed. Or pop whatever it has pushed on the stack, before spinning. Or even better, save the context of the thread before spinning, which would really decrease the thread-resumption latency.
Pooh. You have admitted that parts of a thread switch must be written in assembly language. So this code is in that part.
Yep. But if you want a check for time-slice overrun, that has to be done somehow, perhaps with a flag. Both checks are about equally fast, I think. If you don't want to check for time-slice overrun, you need a pre-emptive scheduler.
But if you still want to check for time-slice overrun you have to use flags, and watch out for race conditions. The amount of inlined code is growing...
OK.
Except for the assembly code that you need to switch threads.
The only "lying" that has been done here is writing the eternal Spin loop in C, from which the compiler could deduce that no Spin call returns. I think Spin should be written in assembly language and considered part of the thread-switching code.
In fact (but don't take this too grievously :-), the only non-standard C code for "stack manipulation tricks" was shown by you, when you referred to the special gcc function for getting the return address.
Good one, David! Luckily I'm an atheist... well, perhaps an agnostic for the purposes of this thread.
I 'd like to thank you for your posts because you helped me a lot. And I'm also sorry because I couldn't reply earlier... I would also would like to explain what I wanted with my Spin function. First, it is infinite loop function which increments idle counter only for testing code in simulator. For real work instead of it I'm using low power mode.
The Spin function is part of a kernel API call Task_Suspend_no_sched(). This function should suspend task, change its state, put it in a suspended list, save context and then enter low power mode until next tick, when scheduler should be called. So I needed that PC to save task's context in such way, that when it is scheduled again it could jump outside the Spin, or to instruction after the _bis(LPM1) or somthing like that. So that was idea and finally I realized it like shown below
Task routine should look like this :
void TaskRoutine(void){ while(1){ //do some work Task_Suspend_no_sched(); }
}
Task_Suspend_no_sched should looks like this:
void Task_Suspend_no_sched(void){ change_state(); put_in_suspend_list(); save_context();//it should use PC placed on stack by // Task_Suspend_no_sched call //_bis(LPM1); or loop like below for(;;){ Idlecnt++ }
} So ISR which provide system tick should interrupt loop or low power mode, and then scheduler should be called. When Scheduler schedule this task again it should jump at the beginning of the task.
Thank you for your comments and suggestions, it was very useful. :)
--------------------------------------- This message was sent using the comp.arch.embedded web interface on
formatting link
B
brOS
Hi all.... :)
I 'd like to thank you for your posts because you helped me a lot. And I'm also sorry because I couldn't reply earlier...
I would also would like to explain what I wanted with my Spin function. First, it is infinite loop function which increments idle counter only for testing code in simulator. For real work instead of it I'm using low power mode.
The Spin function is part of a kernel API call Task_Suspend_no_sched(). This function should suspend task, change its state, put it in a suspended list, save context and then enter low power mode until next tick, when scheduler should be called. So I needed that PC to save task's context in such way, that when it is scheduled again it could jump outside the Spin, or to instruction after the _bis(LPM1) or somthing like that.
So that was idea and finally I realized it like shown below
Task routine should look like this :
void TaskRoutine(void){ while(1){ //do some work Task_Suspend_no_sched(); }
}
Task_Suspend_no_sched should looks like this:
void Task_Suspend_no_sched(void){ change_state(); put_in_suspend_list(); save_context();//it should use PC placed on stack by // Task_Suspend_no_sched call //_bis(LPM1); or loop like below for(;;){ Idlecnt++ }
} So ISR which provide system tick should interrupt loop or low power mode, and then scheduler should be called. When Scheduler schedule this task again it should jump at the beginning of the task.
Thank you for your comments and suggestions, it was very useful. :)
--------------------------------------- This message was sent using the comp.arch.embedded web interface on
formatting link
M
Marc Jet
Then all the comments about your approach (being inappropriate) where dead-on.
Your function Task_Suspend_no_sched() should really be named something like WaitForTick(). And it should do just that: wait for the next system tick.
The implementation does not require breaking out of endless loops or other fancy stuff. It is basic task switching theory, descibed in OS literature and all over the internet.
Using such a function, all the rest falls nicely into place.
Good luck with your project! Marc
N
Niklas Holsti
Bogdan's extended description of his design closely matches my guess of his design, as far as I can tell. Therefore I don't agree with Marc's conclusion. I think Bogdan's approach is reasonable, except that it is risky to use C for code such as save_context() and the unconditional spin loop. But perhaps Bogdan is only showing C-like pseudocode?
There is > Task routine should look like this : >
contains just one call of Task_Suspend_no_sched, at the end of the eternal while(1) loop. If this is true of all tasks in Bogdan's application, the design could be simplified by inverting the control, so that instead of TaskRoutine calling the kernel/Scheduler through Task_Suspend_no_sched, and the Scheduler then resuming the TaskRoutine after this call, the Scheduler could always call TaskRoutine at its entry point, and the TaskRoutine could return to the kernel after doing its work. The while(1) in TaskRoutine would be removed as would the call of Task_Suspend_no_sched, and their functions would be taken over by things that the kernel/Scheduler does in between calls of TaskRoutine. The TaskRoutine would be just
void TaskRoutine(void){ //do some work }
A TaskRoutine with a single call of Task_Suspend_no_sched corresponds to a very strict design rule for real-time systems called the single suspension point rule. By this rule, each task shall have a single point where it can be suspended and resumed. The rule is good for schedulability analysis, but can be difficult to use if the task must perform complex timing or interaction sequences, because the task must then use data variables to remember what it is doing -- how far the sequence has advanced -- perhaps by means of a finite-state automaton.
Marc, did you not note that the Scheduler may schedule *another* task, not simply continue the one that is "waiting for the next system tick" in your terms? So the waiting task may be waiting for a longer time, several ticks, and it is necessary to save its context, including the PC. One way to get the PC is to retrieve the return address for Task_Suspend_no_sched, as Bogdan does. Another way is to put the task in a loop until it is interrupted, then retrieve the PC of the interrupt point (in other words, the return address of the interrupt handler).
Any kernel call that can suspend the calling task must retrieve the PC, which seems to be the kind of "fancy stuff" that Marc means.
As for the endless loop, most schedulers can encounter a situation where no real task is ready to do any real work until the next interrupt happens. There are several ways to deal with this:
- Use a special instruction or configuration that halts, idles, or powers-down the processor until an interrupt comes in. This is what Bogdan plans to do in the real system. (As an aside, this can have a nasty drawback: in one project where I was involved and it was tried, the resulting square-wave variation in the processor's power consumption disturbed the sensitive analog electronics on the board, so we had to use an eternal loop instead. That was not an MSP430, however, but a space-qualified 80C32, so it used rather more power.)
- Schedule a lowest-priority null task that contains just an eternal loop that does nothing, or perhaps maintains a processor-load indicator such as Bogdan's Idlecnt. But this is not so easy in a non-preemptive scheduler.
- Use a spin loop in the kernel itself, as Bogdan does. Earlier discussion in this thread shows that making this loop conditional is logically redundant. The only reason for not using an eternal (unconditional) loop would be to avoid confusing the C compiler, which is one reason why this loop should be written in assembly language. I have seen such eternal null loops in more than one kernel, including commercial kernels. I think they are an appropriate solution to this requirement.