📜 ⬆️ ⬇️

On the issue of timers in RTOS

Here are two lines, I'm a genius, away doubts
Give delights, laurels and flowers ...


This post is dedicated to the pretty old task of reading the timer, which I personally read in the book by Jack Gansley (The Art of Designing Embedded Systems (Second Edition), (2008) by Jack Ganssle) which deals with the fight against racing in asynchronous devices. The problem was formulated and 4 ways to solve it were shown (2 incorrect and 2 correct), their shortcomings were considered, in general, good work in the style of Jack (I treat him very well). Unfortunately, in my opinion, even working solutions did not have the proper degree of elegance, but the more beautiful did not occur to me for a long time, but yesterday it dawned on me. So I consider myself entitled to present this problem in its historical context, because I have come up with a very elegant solution (you cannot praise yourself, you go all day as a spat).

We formulate the problem: we have a hardware counter, which is synchronized from a certain system frequency and can be used to measure time. However, due to hardware limitations, its capacity is not enough to form long periods of time, so a software extension of the counter capacity is created. It is implemented as follows: when the counter overflows, an interrupt occurs and the interrupt processing subprogram modifies a global variable, which, together with the counter itself, forms an extended time counter, something like
unsigned int High; interrupt TimerOv(void) { High++; } 
Then we can write the current time as follows
  unsigned long int GetTimer(void) { return (High<<sizeof(int))+ ReadReg(TimerCounter); } 
Everything is simple, understandable, and wrong, as expected. Here the problem lies on the surface and is visible to anyone who wrote similar programs - in the process of reading the two halves of the extended timer, the youngest part may overflow, since it is asynchronous, and then the two parts will not be valid relative to each other. At the same time, we can get both the past time point and the future as the value of the extended timer, and both of these cases are worse.

There is an obvious solution to the problem - to stop the timer hardware at the time of reading, but this solution is unacceptable because of the effect on the counted time.

Let's try another obvious solution - to ban interrupts for the time being read and we get
  unsigned long int GetTimer(void) { DisableInt(); unsigned long Tmp=(High<<sizeof(int))+ ReadReg(TimerCounter); EnableInt(); return Tmp; } 
Of course, when I write about the prohibition of interruption, it is assumed that the current value is saved with its restoration at the end, but these subtleties are omitted, they are not so important now. What is not good in this decision: 1) we prohibit interruptions, which is not encouraging, and 2) this solution will not work. Yes, exactly, although the method is approved, but not for this case. The fact is that the prohibition of interrupts does not allow the timer to work (it is hardware), so if during the interruption request period the counter overflow occurs, we get the low part equal to zero, and the high one is not modified, that is, the data is not valid.
')
Possible modification of this solution leads to the following code.
  unsigned long int GetTimer(void) { DisableInt(); unsigned long TmpH=High; unsigned long TmpL=ReadReg(TimerCounter); if (TimerOvBit()) { TmpH++; TmpL=ReadReg(TimerCounter); }; EnableInt(); TmpH=(TmpH<<sizeof(int))+ TmpL; return Tmp; } 
This is the first correct decision. There are drawbacks to this solution: 1) we still prohibit interrupts, 2) we need an additional hardware bit and we need to take care of the interrupt service, 3) we made certain assumptions about the modification of the older part. Are there any other solutions?

Yes, such a solution exists and was found by Jack and me (honestly, on my own). The code for this solution is as follows.
  unsigned long int GetTimer(void) { unsigned long TmpH,TmpL; do { TmpH=High; TmpL= ReadReg(TimerCounter); while (TmpH!=High); return (TmpH<<sizeof(int))+TmpL; } 
This is the second right decision, pay attention to the fact that the reading of the counter is framed by calls to the older part, otherwise it is wrong. In general, this approach strongly resembles non-blocking algorithms when competing to resources that I like something with, there is some elegance in them. This method has many advantages, but one drawback - if our system is heavily loaded, then we can hang for a long time in the loop, as Jack writes, the execution time becomes unpredictable.

And here a small modification of this algorithm was invented, namely
  unsigned long int GetTimer(void) { unsigned long TmpH,TmpL; TmpH=High; TmpL= ReadReg(TimerCounter); if (TmpH!=(TmpH=High)) TmpL= ReadReg(TimerCounter); return (TmpH<<sizeof(int))+TmpL; } 
This is the third right decision and I, as the author, like it most. We do not prohibit interruptions, do not require anything from the equipment, make no assumptions, always get the right result, that is, 1) never get the value of the extended timer preceding the moment of entering the procedure and 2) never get the value following the time of exit from procedures, we have completely predictable behavior, and all this is practically free. Of course, if we have a highly loaded system, then we can get a time at the output that is significantly less than the time required to exit the procedure, but this is also true of the other two correct methods. If we really need the current value of time with a minimum deviation, then we must carry out all processing with prohibited interrupts, and this is clearly not our case.

As I was right in the comments, the compiler may incorrectly compile the key line of the algorithm, so here’s the corrected version.
  unsigned long int GetTimer(void) { unsigned long TmpHFirst,TmpH,TmpL; TmpHFirst=High; TmpL= ReadReg(TimerCounter); if (TmpHFirst!=(TmpH=High)) TmpL= ReadReg(TimerCounter); return (TmpH<<sizeof(int))+TmpL; } 

Why I didn’t find this solution before (I didn’t see it in the literature either, maybe I just didn’t come across it), it’s completely incomprehensible, because everything is crystal clear and simple, apparently, a certain narrow-mindedness affected. Why it works, I give it to the reader, but it definitely works, I couldn’t find a counter-example, if you manage to do this, I ask in comments.

And finally, another interesting point. We are not interested in time, as such, we usually use the current time in order to set a certain point in relation to it in the future, and to achieve something after it is reached. So, in your opinion, are the next two lines equivalent?
  unsigned long TmpWait; TmpWait=GetTimer()+SomeDelay; while (TmpWait > GetTimer()) ; /*   */ while ((TmpWait - GetTimer()) > 0) ; /*  */ } 
These lines are not equivalent under certain conditions and it is interesting to consider why. I will send interested persons to the Linux manuals, look for the jiffies term.

Source: https://habr.com/ru/post/273885/


All Articles