• Aucun résultat trouvé

Critical Regions with Interrupts Enabled: Semaphores the MIPS Waythe MIPS Way

Dans le document 0.1 Style and Limits (Page 139-143)

Exceptions, Interrupts, and Initialization

5.7 An Exception Routine

5.8.4 Critical Regions with Interrupts Enabled: Semaphores the MIPS Waythe MIPS Way

A semaphore is a coding convention for multitasking programs. The semaphore is a shared memory location used by concurrently running processes to ar-range that some resource is only accessed by one of them at once.

118 5.8. Interrupts

Each atomic chunk of code has the following structure:1

wait(sem);

/* do your atomic thing */

signal(sem);

Think of the semaphore as having two values: 1 meaning “in use” and 0 meaning “available”. The signal() is simple; it just sets the semaphore to 0. wait() checks for the variable to have the value 0 and won’t continue until it does. It then sets the variable to 1 and returns. That should be easy, but you can see that it’s essential that the process of checking the value of sem and setting it again is itself atomic. High-level atomicity (at the task level) is dependent on being able to build low-level atomicity, where a test-and-set operation can operate correctly in the face of interrupts (or, on a multiprocessor, in the face of access by other CPUs).

Most mature CPU families have some special instruction set features for this: 680x0 CPUs have an atomic test-and-set instruction; x86 CPUs have an

“exchange register with memory” operation that can be made atomic with a prefer “lock” instruction.

For large multiprocessor systems this kind of test-and-set process be-comes expensive; essentially, all shared memory access must be stopped while the semaphore user obtains the value, completes the test-and-set op-eration, and the set operation percolates through to every cached copy in the system. This doesn’t scale well to large multiprocessors.

It’s much more efficient to allow the test-and-set operation to run without any guarantee of atomicity and then to make the set take effect only if we got away with it. There also needs to be some way to find out whether it was OK;

now unsuccessful test-and-set sequences can be hidden inside the wait() function and retried as necessary.2

This is what MIPS has, using the11(load-linked) andsc(store-conditional) instructions in sequence. scwill only write the addressed location if there has been no competing access since the last 11 and will leave a 1/0 value in a register to indicate success or failure.3

Here’s wait() for the binary semaphore sem:

1Two gurus formulated these ideas. Hoare calls the functionswait()and signal() and that’s what we’ve used. Dijkstra calls equivalent functions (but with a slightly more general concept of semaphore)p()andv()respectively. You can understand why he called them “p” and “v” quite easily if you speak Dutch.

2Of course, you’d better make sure that there are no circumstances where it ends up retrying forever!

3Note that we say “if” and not “if and only if”. Sometimes sc will fail even though the location has not been touched; most uniprocessors will fail the sc when there’s been any exception serviced since thell. It’s only important that thescshould usually succeed when there’s been no competing access and that italwaysfails when there has been one such.

wait:

la t0, sem TryAgaain:

11 t1, 0(t0)

bne t1, zero, WaitForSem 1i t1, 1

sc t1, 0(t0)

beq t1, zero, TryAgain /* got the semaphore... */

Even in a uniprocessor system this can be useful, because it does not involve shutting out interrupts. It avoids the interrupt-disabling problem de-scribed above and can be an important part of a coordinated effort to reduce worst-case interrupt latency, which is often important in embedded systems.

5.9 Starting Up

In terms of its effect on the CPU, reset is almost the same as an exception, though one from which we’re not going to return. In the original MIPS ar-chitecture this is mostly a matter of economy of implementation effort and documentation, but the R4000 offers several different levels of reset from a cold reset through to a nonmaskable interrupt — so reset and exception conditions do shade imperceptibly into each other.

Since we’re recycling mechanisms from regular exceptions, following re-set EPC points to the instruction that was being executed when reset was detected, and most register values are preserved. However, reset disrupts normal operation and a register being loaded or a cache location being stored to or refilled at the moment reset occurred may be trashed.

It is quite possible to use the preservation of state through reset to imple-ment some useful postmortem debugging, but your hardware engineer needs to help; the CPU cannot tell you whether reset occurred to a running system or from power-up. But postmortem debugging is an exercise for the talented reader; we will focus on starting up the system from scratch.

The CPU responds to reset by starting to fetch instructions from 0xBFC0 0000. This is physical address 0x1FC0 0000 in the uncached kseg1 region.

Following reset, enough of the CPU’s control register state is defined so that the CPU can execute uncached instructions. “Enough state” is inter-preted minimally; note the following points:

• Only three things are guaranteed in SR: the CPU is in kernel mode;

interrupts are disabled; and exceptions will vector through the uncached

120 5.9. Starting Up

entry points — that is, SR(BEV) == 1.1

Some implementations may guarantee more: For example, IDT doc-umentation states that the SR(TS) bit is initialized on R3051-family CPUs; it will be set 0 if the CPU has MMU hardware, 1 otherwise. You should not rely on this promise for WIPS CPUs outside the R3051 family.

• In a CPU with R3000- type caches the D-cache may be isolated ifSR(IsC) happens to have come upset, so until you’ve set that bit explicitly you can’t rely on data loads and stores working, even to uncached space. It’s probably best to be pessimistic and assume the same about any MIPS CPU.

• The caches will be in a random, nonsensical state, so a cached load might return rubbish without reading memory.

• The TLB will be in a random state andmust not be accesseduntil initial-ized (the hardware has only minimal protection against the possibility that there are duplicate matches in the TLB, and the result could be a TLB shutdown which Can be amended only by a further reset).

The traditional startup sequence is as follows:

1. Branch to the main ROM code. Why do a branch now?

• The uncached exception entry points start at 0xBFC0 0100, which wouldn’t leave enough space for startup code to get to a “natural break”.

• The branch represents a very simple test to see if the CPU is func-tioning and is successfully reading instructions. If something terri-ble goes wrong with the hardware, the MIPS CPU is most likely to keep fetching instructions in sequence (and next most likely to get permanent exceptions).

If you use test equipment that can track the addresses of CPU reads and writes, it will show the CPU’s uncached instruction fetches from reset; if the CPU starts up and branches to the right place, you have strong evidence that the CPU is getting mostly correct data from the ROM.

By contrast, if your ROM program plows straight in and fiddles with SR, strange and undiagnosable consequences may result from sim-ple faults.

2. Set the status register to some known and sensible state. Now you can load and store reliably in uncached space.

1In R4000-style CPUs, the first two conditions (and more besides) are typicatiy guaran-teed by setting the exception-mode bit SR(EXL), and this is implied by treating reset as an exception.

3. You will probably have to run using registers only until you have initial-ized and (most likely) run a quick check on the integrity of some RAM memory. This will be slow (we’re still running uncached from ROM) so you will probably confine your initialization and check to a chunk of memory big enough for the ROM program’s data.

4. You will probably have to make some contact with the outside world (a console port or diagnostic register) so you can report any problem with the initialization process.

5. You can now assign yourself some stack and set up enough registers to be able to call a standard C routine.

6. Now you can initialize the caches and run in comfort. Some systems can run code from ROM cached and some can’t; on most MIPS CPUs a memory supplying the cache must be able to provide four-word bursts and your ROM subsystem may or may not oblige.

Dans le document 0.1 Style and Limits (Page 139-143)