Path: utzoo!utgpu!water!watmath!clyde!rutgers!gatech!hubcap!ncrcae!ncr-sd!crash!jeh From: jeh@crash.cts.com (Jamie Hanrahan) Newsgroups: comp.os.vms Subject: Re: AST interrupts and process state Summary: ASTs can be delivered to LEF state, sometimes. Read on. Keywords: AST wait state Message-ID: <2364@crash.cts.com> Date: 13 Jan 88 10:30:14 GMT References: <8801121623.AA23046@ucbvax.Berkeley.EDU> Reply-To: jeh@crash.CTS.COM (Jamie Hanrahan) Organization: CMKRNL Press, San Diego, CA Lines: 213 In article <8801121623.AA23046@ucbvax.Berkeley.EDU> RSD1901@TAMSIGMA.BITNET (Shane Davis) writes: > [questions re. AST deliverability in LEF state, nested HIBERs, etc.] I'm a little brain-dead at the moment, and I didn't completely follow your description of your application design. I'm particularly puzzled about what you meant by "nested $HIBERs". (More on this later.) However, are the rules concerning AST delivery, HIBER/WAKE, etc. An AST queued to a process will be delivered to that process (i.e. the AST procedure will be called) unless one or more of the following conditions are true: 1. The process already has an AST active at the same access mode (user, supervisor, executive, kernel), or at a more privileged access mode, than the access mode of the AST. 2. The process is running or waiting at a more privileged access mode than that of the AST. For instance, if you're looping in kernel mode, or even if you're in HIB state as a result of a $HIBER call that you did while in kernel mode, no user-, supervisor-, or executive-mode ASTs can be delivered. For processes that stay in user mode except for brief periods when they call system services or RMS -- which includes most processes we deal with -- the above rules boil down to: The process must not already be executing (or waiting) at AST level. If it is, the next AST is queued, as are all subsequent ASTs. Each gets delivered when the previous one RETs. (More on the "or waiting" part in a few moments.) 3. The process is in a wait state that precludes AST delivery. Of the wait states which programmers voluntarily put processes into, only SUSPend precludes AST delivery; ASTs are deliverable in LEF, CEF, and HIB states. They are not deliverable in most (if not all; I don't have my source fiche at hand) of the wait states into which the system "automatically" places processes, such as page fault, the infamous MWAIT state, etc., as in most such states the process is sitting at IPL 2, which inhibits the AST delivery mechanism. (The rules for the SUSPend state change in V5. You'll be able to say "suspend, but leave kernel-mode ASTs deliverable"; this allows a process suspended in this way to be deleted while blocking user-mode ASTs (presumably the application's ASTs). For most user-generated code this will have little significance, except that they'll have a way to kill off processes that get stuck in suspend.) 4. The process has explicitly blocked AST delivery at the access mode of the AST. (It can do this by calling the $SETAST service from the mode to be controlled. Of course, it can later enable ASTs with the same service, at which time any ASTs that got queued up in the interim will be delivered, one at a time. This is a good way to hold off AST delivery while you process data gathered by the AST, or to avoid problems with using non-AST-reentrant code when an AST might be delivered, etc.) 5. The process is executing at IPL 2 or above. (Many system services, $QIO for example, raise IPL to 2 temporarily to block AST delivery, even special kernel ASTs. They only do so for very short times. As with $SETAST, as soon as IPL is lowered, any ASTs that were queued while IPL was raised will be delivered, unless something else inhibits them.) So, assuming that you are talking about all user-mode code, and you're not playing with $SETAST, yes, ASTs are deliverable in LEF state, and also in HIB... with one important exception. It's not really an exception, but it's not obvious: IF YOU ENTER THE WAIT STATE (LEF, CEF, HIB) FROM CODE INVOKED VIA AST -- that is, if an AST was active when you entered the wait state; doesn't matter whether the AST procedure itself, or an inner procedure which the AST procedure called, calls the `wait' system service -- ASTs CANNOT BE DELIVERED WHILE YOU ARE IN THAT WAIT STATE. So, for instance, if you: (in non-AST code:) do something that will eventually cause an AST to be queued $HIBER (whatever it is that you did causes the AST to be queued) (the AST is delivered) (in your AST procedure:) do something that will eventually cause an AST to be queued $HIBER RETURN You're stuck. You called $HIBER at AST level; therefore ASTs can't make you run. Some other process will have to $WAKE you. It doesn't matter if you use two $HIBERs as shown here, or a $HIBER in the non-AST and a $WAITFR in the AST... what matters is that you entered a wait state, ANY wait state, from AST level. This blocks ASTs at your current access mode. There might be some applications where you want this behavior. As a general rule, don't call "wait" services from AST level. -------------------- Now, about nested HIBERs and all that... I think a brief explanation of how wait states interact with ASTs is in order. When you use $SUSPND, $HIBER, or $WAITFR (or any of its variants, including $SYNCH), there are three things to keep in mind. First, there's a bit (or bits) somewhere which, if set, will allow you to run. For $WAITFR and etc. the bits are event flags; for $SUSPND and $HIBER they are the "resume pending" and "wake pending" bits, set by the $RESUME and $WAKE services, respectively. Second, when you call any of these services, they *first* check the appropriate bit(s) to see if you really should go into the wait state. For instance, if someone tries to $WAKE you and you aren't hibernating, your wake pending bit is set. If you try to hibernate after that, $HIBER checks (and clears) the wake pending bit, and you keep right on going. A subsequent $HIBER will succeed (unless another $WAKE occurred after the first $HIBER). Note that no count of outstanding wake requests is kept-- only the fact that there's at least one. $SUSPND works the same way, only it uses a different bit. $WAITFR, et al, don't clear the event flags after checking them, but otherwise are the same. Third, and here's the fun part: When you call any of these services, if you do in fact go into the desired wait state, THE STORED PROGRAM COUNTER (PC) FOR YOUR PROCESS POINTS (effectively) TO THE CALL TO THE SYSTEM SERVICE, NOT THE NEXT THING AFTER IT. What does this mean? It means that whenever your process comes out of a wait state, you call the system service that put you in the wait state AGAIN. The reason for the wait is re-evaluated, if you will. You might go right back into the wait state, depending on what else has happened. Let's see how this all works to make ASTs and wait services act the way they do: (non-AST code) start an I/O, specifying an AST procedure $HIBER (AST procedure) have we done enough IOs? if yes $WAKE (path a) else start the next one (path b) endif RETURN We've started the first I/O (from the non-AST code) and entered $HIBER. (We'll assume that the "wake pending" bit was originally clear.) The IO completes. The delivery of the AST makes your process computable; that is, it takes it out of HIB state. Completely. The only record that we were ever in HIB state is that stored PC that's pointing to the call to the $HIBER service. So the process is made current. Whups! There's an AST to be delivered, so the saved PC is ignored for now; instead the system climbs down to user mode and CALLs your AST procedure. The AST runs. The first time through it takes path b; it starts another IO, then RETs to its caller (the AST delivery mechanism). There are no more ASTs queued, so the saved PC gets used to restart execution of your non-AST code... and the $HIBER service is called, and the "wake pending" bit is found to be clear, so back to hibernate state you go. Repeat until the AST procedure decides "Hold, enough!" It calls $WAKE and RETurns. Now when you re-execute $HIBER it finds the "wake pending" bit set (and clears it), so it doesn't go into $HIBER state. It just returns to its caller and you keep executing. Same thing if you'd used $WAITFR in the main program and $SETEF in the AST. Now, a related question to see if you understand the material... :-) Suppose you: (non-AST code) $QIO, specifying an event flag and an AST $WAITFR the event flag (in the AST procedure) clear the event flag RETURN What happens? Well, when the I/O completes, the event flag gets set (and your process is made computable as a result, but we're not done yet), and the IOSB is filled in, and the AST gets delivered. The AST procedure, nasty thing that it is, clears the event flag, then returns to the system. The process is now free to execute non-AST code. Since the stored PC points to the $WAITFR call, and since the AST cleared the event flag, back into the LEF (or CEF) state you go. I hope this helps. Sorry for the length, but better to rattle something off that's too long than decide that I don't have time to do it up right. Perhaps I'll clean this up in the next few weeks and submit it to the Pageswapper (newsletter of the DECUS VAX SIG); any comments or suggestions for improvements will be welcomed. Oh... there *is* an optimization in the system for $WAKE out of the $HIBER state. The $WAKE service checks your process and, if it's in the $HIBER state, takes it out thereof, leaves wake pending clear, and prevents you from re-executing the $HIBER call, in order to save a little bit of time (hiber/wake is supposed to be the fastest inter- process synchronization technique, although in my tests waitfr/setef was not noticeably slower. Note that this optimization doesn't work when $WAKE is called from within an AST executing in the context of the process being awakened, as the process being "awakened" is current, not hibernating, at the time; $WAKE has no way to determine that you "used to be" hibernating.