// Definitive preemptive-concurrency test via fork() ping-pong. A forked GNO // thread (child) and the parent alternate ownership of a shared "turn" word in // bank 2: the parent advances it on EVEN turns, the child on ODD. Each must // WAIT for the other to hand over the turn, ROUNDS times. This completes ONLY // if GNO preemptively time-slices the two threads -- if they ran sequentially, // whichever went first would spin forever at round 2 waiting for a turn the // other can't give. No program-load latency and no gsh job control to muddy it // (unlike the two-separate-programs path): fork is in-process and immediate. // // The child stays within the leaf-only fork contract: a register loop + absolute // bank-2 loads/stores + _exit (no calls, libcalls, fn-pointers, or &global). // // 0x025050 ROUNDS*2 (0x0014) final turn value if the ping-pong completed // 0x025052 5050 parent finished all ROUNDS turns (else DEAD = stuck/no sched) // 0x025054 CCCC child finished all ROUNDS turns (else DEAD = never scheduled) #include extern int fork(void *subr); extern int wait(int *status); extern __attribute__((noreturn)) void _exit(int status); #define M(a) (*(volatile uint16_t *)(a)) #define TURN 0x025050UL #define RES_P 0x025052UL #define RES_C 0x025054UL #define ROUNDS 10 // Child (leaf): take the ODD turns. Wait until TURN is odd, then bump it even. static void child(void) { uint16_t k; for (k = 0; k < ROUNDS; k++) { while ((M(TURN) & 1) == 0) { } M(TURN) = M(TURN) + 1; } M(RES_C) = 0xCCCC; _exit(0); } int main(void) { uint16_t k; int pid; int st; M(TURN) = 0x0000; // even -> parent goes first M(RES_P) = 0xDEAD; M(RES_C) = 0xDEAD; pid = fork((void *)child); if (pid <= 0) { M(RES_P) = 0xF00D; // fork failed outright } else { for (k = 0; k < ROUNDS; k++) { while ((M(TURN) & 1) == 1) { } M(TURN) = M(TURN) + 1; } M(RES_P) = 0x5050; // parent completed its turns wait(&st); } for (volatile unsigned long j = 0; j < 300000UL; j++) { } return 0; }