• Please review our updated Terms and Rules here

Atomic read of 32-bit integer from memory on x86

neilobremski

Experienced Member
Joined
Oct 9, 2016
Messages
55
Location
Seattle, USA
Is it possible to perform a 32-bit atomic read from memory on an 8086/8088 without disabling interrupts?

I noticed the LDS and LES instructions read a full DWORD for the DS:SI or ES:DI far pointer respectively so I plopped together the following code which abuses the former a bit ...

Code:
PUSH DS
XOR AX, AX
MOV DS, AX
MOV SI, 046C
LDS DI, [SI]
MOV DX, DS
MOV AX, DI
POP DS

That reads the BIOS timer tick count into DX:AX.

Would or could a timer interrupt occur in the middle of LDS executing?
 
Last edited:
That's an interesting question. An interrupt can't (as far as I know) be acknowledged in the middle of an instruction on the 8086 (is this true for LOOP constructs?) so in effect, yes. But it wouldn't be purely atomic, since (I imagine...?) DMA could potentially change a value in between memory reads.
 
REPx constructs can be interrupted--you just resume the instruction at the REPx prefix.

Lseg reg,type operations are atomic, since the CPU doesn't carry enough information to restart in mid-instruction. (cf. the MC68000 original CPU and why the instruction set isn't fully amenable to virtual memory--although you can restrict the set of instructions you do use).

In any case, while a 32-bit atomic fetch is possible, the CPU doesn't provide for a 32-bit atomic store, so a fat lot of good it does you.
 
In any case, while a 32-bit atomic fetch is possible, the CPU doesn't provide for a 32-bit atomic store, so a fat lot of good it does you.

Awesome. It does seem moderately useful for reading the BIOS tick count directly from memory without disabling/enabling interrupts. Then again I'm not sure why one would care about that when the CLI/STI instructions are so short.
 
Yup. Atomic operations are essential to multi-processor applications accessing the same shared memory, but otherwise, not so much, as you said, you can disable interrupts on a single-CPU system.
 
Is it possible to perform a 32-bit atomic read from memory on an 8086/8088 without disabling interrupts?

LDS/LES is likely the best way, as you discovered. Although, cli/sti are 1-byte opcodes so it's not that bad to use them. Just don't use them if you don't know if your code will be called from inside an interrupt handler; use PUSHF; CLI;...POPF instead.

You can also use LDS AX in your code, which would shorten it by one instruction.

There is also a way to do this without explicitly disabling interrupts, but rather have the CPU do it for you: Any 808x CPU is supposed to disable interrupts for any instruction that modifies a segment register and also for the instruction that comes after it. From the 8086 family manual:

A MOY (move) to segment register instruction and a POP segment register instruction are treated similarly: no interrupt is recognized until after the following instruction. This mechanism protects a program that is changing to a new stack (by updating SS and SP). If an interrupt were recognized after SS had been changed, but before SP had been altered, the processor would push the flags, CS and IP into the wrong area of memory. It follows from this that whenever a segment register and another value must be updated together, the segment register should be changed first, followed immediately by the instruction that changes the other value.

On the 80186 and later, this behavior changed from "any segment register" to "only the SS register". So you can't really rely on this behavior unless you're targeting only 808x.

REPx constructs can be interrupted--you just resume the instruction at the REPx prefix.

With exceptions, yes. The 8086/80186/80286 only recognize the last prefix after resuming from an interrupt, so if you did ES: REP MOVSW and it was interrupted, it would continue without the ES:. Which means before the interrupt it would be copying ES:SI to ES:DI, and after the interrupt it would now be copying from DS:SI to ES:DI. I was bitten by this.
 
Well, a REP/MOVS with a segment override can be made to work with interrupts, but it's a little tricky.

First off, the order matters. You want REP ES: MOVS ordering, not ES: REP MOVS. So when the interrupt returns, it will begin with the ES: MOVS part and move one word or byte--and CX will not be decremented. So a test for CX nonzero after the REP MOVS instruction indicates that an interrupt happened during the operation and you can correct CX and proceed back to the REP ES: MOVS instruction to complete the operation.

A pain in the neck, but it's a way to deal with the issue--it's simpler to simply set the segment registers match the default values and eliminate the segment override completely.
 
Back
Top