这个是apue中10.5习题参考解答
Title: Implementing Software Timers
By: Don LibesOriginally appeared in the Nov. 1990 "C User's Journal" and is alsoreprinted as Chapter 35 of "Obfuscated C and Other Mysteries",John Wiley & Sons, 1993, ISBN 0-471-57805-3.---------------------------------------------------------------------This column will describe a set of functions to implement softwaretimers. What are software timers and why do you need them? Softwaretimers make up for inherent limitations in hardware timers. Forexample, while most computers have clock hardware, you can typicallyonly have the clock trigger an interrupt for one time in the future.When running multiple tasks, you will want to have the clock keeptrack of multiple timers concurrently so that interrupts can begenerated correctly even if the time periods overlap. Operatingsystems do this all the time.Robert Ward discussed the related problem of building a generalpurpose scheduler ("Practical Schedulers for Real-Time Applications")in the April 1990 CUJ. In the "Additional Ideas" section, Robertdescribed the usefulness of a timer scheduling queue. "Events canspecify the timing of other events by putting a timer programmingrequest in a special queue." That is exactly what the software inthis column will do. (Thanks for the lead in, Robert.) You may wantto reread at least the beginning of his column right now, although itisn't really necessary.The code in this column has other uses as well. For example, you canuse it to simulate multiple timers in environments such as a UNIXprocess which only allows the user one software timer. Even if youaren't interested in software timers, I think you will find this anintriguing column. Using simple techniques and data structures, thisC code produces very powerful results. The code was very tricky toget right, and my commentary should be interesting if only as somemore practice in reading and writing C code.TimersBy implementing the timers as a separate piece of software, we canreduce the complexity of the scheduler. Some people like this kind ofmodularization, and some don't. Similarly some operating systems dothis, and some don't. I like it. It makes the code easier to write,to read, and to correct (oops).The basic idea of a timer is that they allow tasks to be run at sometime in the future. When their time arrives, they are scheduled to berun. The responsibility of actually running them is then turned overto someone else, such as the scheduler. In order to communicate withthe scheduler, we'll set up a common data structure called a timer(listing 1). I've also included a few other miscellaneous definitionsthat will be needed later on. For instance, the TIME typedef is usedto declare all relative time variables. You can complete thisdefinition based on what your needs are.#include #define TRUE 1#define FALSE 0#define MAX_TIMERS ... /* number of timers */typedef ... TIME; /* how time is actually stored */#define VERY_LONG_TIME ... /* longest time possible */struct timer {int inuse; /* TRUE if in use */TIME time; /* relative time to wait */char *event; /* set to TRUE at timeout */} timers[MAX_TIMERS]; /* set of timers */listing 1Each timer will be represented by a timer struct. The set of timerswill be maintained in an array, timers. The first element of eachtimer declares whether the timer is in use. The second element of atimer is the amount of time being waited for. As time passes, thiswill be periodically updated. event is a pointer to a value that isinitially set to 0. When it is time to run the task, *event is set to1. We can imagine that the scheduler also keeps an event pointer.Every so often, it reexamines it. When it finds it has been set to 1,it knows that the timer has expired and the associated task can berun.[Notice how simple this is. Other schedulers or other scheduler datastructures could enable runnability, without worrying or even knowingabout timers.]The code in listing 2 initializes the timers. It runs through thearray setting each inuse flag to FALSE. This for loop will becomeidiomatic to you by the end of this column.voidtimers_init() {struct timer *t;for (t=timers;t<&timers[MAX_TIMERS];t++)t->inuse = FALSE;}listing 2Now we can write the routines to schedule the timers. First, I'llshow timer_undeclare, which is a little simpler than its counterpart,timer_declare.There are a variety of ways to keep track of the timers. Machineswhich don't have sophisticated clock hardware usually call aninterrupt handler at every clock tick. The software then maintainsthe system time in a register, as well as checking for timer entriesthat have expired.More intelligent machines can maintain the clock in hardware, onlyinterrupting the CPU after a given time period has expired. By havingthe clock interrupt for when an event is waiting, you can get atremendous speedup. This technique is also common in softwaresimulations and thread implementation.Reading the clock may require an operating system call, but for ourpurposes we will assume the variable time_now to be automaticallyupdated by the hardware for just this purpose. volatile indicatesthat the variable should not be cached in a register but read fromstorage each time.volatile TIME time_now;We will define several variables for shorthands. timer_next willpoint to the timer entry that we next expect to expire.time_timer_set will contain the system time when the hardware timerwas last set.struct timer *timer_next = NULL;/* timer we expect to run down next */TIME time_timer_set; /* time when physical timer was set */void timers_update(); /* see discussion below */voidtimer_undeclare(t)struct timer *t;{disable_interrupts();if (!t->inuse) {enable_interrupts();return;}t->inuse = FALSE;/* check if we were waiting on this one */if (t == timer_next) {timers_update(time_now - time_timer_set);if (timer_next) {start_physical_timer(timer_next->time);time_timer_set = time_now;}}enable_interrupts();}Listing 3Undeclaring Timers - Why and How?timer_undeclare does just what its name implies, it undeclares atimer. Undeclaring timers is actually an important operation in someapplications. For example, network code sets timers like crazy. Insome protocols, each packet sent generates a timer. If the senderdoesn't receive an acknowledgement after a given interval, the timerforces it to resend a packet. If the sender does receive anacknowledgement, it undeclares the timer. If things are going well,every single timer declared is later undeclared.timer_undeclare (listing 3) is performed with interrupts disabled.This is necessary because we are going to have an interrupt handlerthat can access the same data. Because this data is shared, accessmust be strictly controlled. I've shown the interrupt manipulation asa function call, but you must use whatever is appropriate to yoursystem. This is very system dependent.timer_undeclare starts by checking the validity of the argument as atimer entry. We will see later that the system clock can implicitlyundeclare timer entries. Thus we must make a reasonable attempt toassure ourselves that a timer to be undeclared is still declared.Once assured the timer is valid, timer_undeclare marks the entryinvalid. If the timer happens to be the very one next expected toexpire, the physical timer must be restarted for the next shortertimer. Before doing that, all the timer entries have to be updated bythe amount of time that has elapsed since the timer was last set.This is done by timers_update which also calculates the next shortesttimer. Looking for the shortest timer in that function is a littleobscure but happens to be very convenient since timers_update has tolook at every timer anyway.timers_update (listing 4) goes through the timers, subtracting thegiven time from each. If any reach 0 this way, they are triggered bysetting the event flag. Any lag in the difference between when atimer was requested and timers_update is called, is accounted for bybasing the latency against time_now and also collecting timers thathave "gone negative" in timers_update. (Why might a timer gonegative?) Lastly, we also remember the lowest nonzero timer to waitfor as timer_next.timer_last is just a temporary. It is a permanently non-schedulabletimer that will only show up when all the other timers have beenscheduled./* subtract time from all timers, enabling any that run out along the way */voidtimers_update(time)TIME time;{static struct timer timer_last = {FALSE /* in use */,VERY_LONG_TIME /* time */,NULL /* event pointer */};struct timer *t;timer_next = &timer_last;for (t=timers;t<&timers[MAX_TIMERS];t++) {if (t->inuse) {if (time < t->time) { /* unexpired */t->time -= time;if (t->time < timer_next->time)timer_next = t;} else { /* expired *//* tell scheduler */*t->event = TRUE;t->inuse = 0; /* remove timer */}}}/* reset timer_next if no timers found */if (!timer_next->inuse) timer_next = 0;}listing 4Declaring Timerstimer_declare (listing 5) takes a time and an event address asarguments. When the time expires, the value that event points to willbe set. (This occurs in timers_update under the comment /* tellscheduler */.) timer_declare returns a pointer to a timer. Thispointer is the same one that timer_undeclare takes as an argument.struct timer *timer_declare(time,event)unsigned int time; /* time to wait in 10msec ticks */char *event;{struct timer *t;disable_interrupts();for (t=timers;t<&timers[MAX_TIMERS];t++) {if (!t->inuse) break;}/* out of timers? */if (t == &timers[MAX_TIMERS]) {enable_interrupts();return(0);}/* install new timer */t->event = event;t->time = time;if (!timer_next) {/* no timers set at all, so this is shortest */time_timer_set = time_now;start_physical_timer((timer_next = t)->time);} else if ((time + time_now) < (timer_next->time + time_timer_set)) {/* new timer is shorter than current one, so */timers_update(time_now - time_timer_set);time_timer_set = time_now;start_physical_timer((timer_next = t)->time);} else {/* new timer is longer, than current one */}t->inuse = TRUE;enable_interrupts();return(t);}listing 5As with its counterpart, interrupts are disabled in timer_declare toprevent concurrent access to the shared data structure.The first thing timer_declare does is to allocate a timer. If noneare available, a NULL is returned so that the caller can fail or retrylater.Once a timer is allocated and initialized, we must check if thephysical timer must be changed. There are three cases:1) There are no other timers;In this case, we go ahead and start the physical timer with thetime of this timer.2) There are other timers, but this new one is the shortest of all the others;In this case, we must restart the physical timer to the new time.But before we do that, we must update all the other timers by theamount of time that has elapsed since the physical timer was lastset.3) There are other timers, and this new one is not the shortest.There is nothing to do in this case. However, for legibility it isbroken into its own case which contains only a comment. That wayit is clear what is going on when the previous else-if test fails.Before enabling interrupts and returning, the timer's inuse flag isset. The reason it is done afterwards rather than with the earliertimer settings is that this prevents timers_update from updating itwith a time period that occurred before it was even declared.Handling Timer InterruptsThe only remaining routine is the interrupt handler (listing 6)actually called when the physical clock expires. When the interrupthandler is called, we are guaranteed that the time described bytimer_next has elapsed.voidtimer_interrupt_handler() {timers_update(time_now - time_timer_set);/* start physical timer for next shortest time if one exists */if (timer_next) {time_timer_set = time_now;start_physical_timer(timer_next->time);}}listing 6Each time the interrupt handler is called, a timer has expired. Bycalling timers_update, all the timers will be decremented and anytimers that have expired will have their event flags enabled. Thiswill also set up timer_next so that the physical timer can berestarted for the next timer we expect to occur.Let's examine one special case. Suppose we have only one timer setup. Now imagine that we have called timer_undeclare and just asinterrupts are disabled, the physical clock ticks down all the way.Since interrupts are disabled, the interrupt will be deliveredimmediately after interrupts are enabled. But they will be enabledafter the timer has been deleted. So we see a situation where aninterrupt will be delivered for a timer that no longer exists. Whatoccurs in the interrupt handler?timers_update is called. It finds nothing to update. As aconsequence of this, timer_next is set to 0. The remainder of theinterrupt handler already handles the case of no remaining timers, andthe handler returns normally.This is an example of the kind of special casing you have to keep inmind when writing the code. (In fact, my first implementation didn'thandle this right, and it was painful to debug. Debuggers don't workvery well when fooling around with interrupts!)ConclusionI have presented an implementation of timers. The code is carefullydesigned so that it is relatively free of special demands it places ona scheduler. For example, it doesn't close off the scheduler fromusing a different kind of timer at the same time.One thought that may have occurred to you while reading this, is whythe timers are maintained as an array rather than say, a linked list.Using a linked list would avoid the overhead of stepping througharrays (which can be almost entirely empty). Keeping the list sortedby time would make the timers_update function much simpler.On the other hand, it would complicate the other functions. Forexample, timer_undeclare, would either require you to use adoubly-linked list, or to search the entire list from the beginningeach time. Another point is that real-time systems typically avoiddynamic structures to begin with. For example, using malloc/free froma process-wide heap can cause an indeterminate amount of time that isdifficult to estimate. If I was to recode this using linked lists, Iwould use a malloc implementation from a small pool of timer-onlybuffers, which in effect is very similar to what I've done here witharrays. There would be a tradeoff in space and time, which you mightprefer or not depending upon your application.If you decide to recode or just modify my implementation, be verycareful. Always imagine the worst thing that can happen when twoprocesses attempt to access the same data structure at the same time.Happy interruptions!ThanksDebugging timing routines is very different than other code, sinceunrelated events in the computer can make your programs behavedifferently. Even putting in printf statements can change criticalexecution paths. It is extremely aggravating when problems disappearonly when you are debugging. Furthermore, most debuggers do not workwell when interrupts are disabled. Ed Barkmeyer was of great helpdebugging the timer code and teaching me to persevere when I saw codebehaving in ways that had to be impossible. Thanks to Sarah Wallaceand Randy Miller who debugged this column and also forced me to makeall the explanations much clearer.
阅读(1391) | 评论(0) | 转发(0) |