// Simple program to exercise the primitive cooperative-multitasking // library Einkornix, a sort of thing commonly called a “real-time // operating system” in embedded systems, inside an ARM Linux process. // The program’s output is “A bA bA b b b b bbbbbbb\n”. // Don’t be intimidated by the length of this file; it’s 40 lines of // code, with the rest being comments. #include #include "einkornix.h" // Our own data. This is what our child tasks will access. int n_live_tasks; // Number of tasks still alive. // Each child task is passed one of these structs at startup to tell // it what to do. typedef struct { int n; // The task writes a character this many times. char c; // Which character? This character! } writer; // This is the code each child task will run. void run_writer(void *userdata) { // Cast from a void pointer to our specific struct type. writer *w = userdata; for (size_t i = 0; i < w->n; i++) { write(1, &w->c, 1); // Unix system call. einyield(); // Einkornix call to permit other tasks to run. } // Now that we’re done writing all our w->n copies of w->c, we // decrement a global variable so that main() will know. We don’t // need any special synchronization here to protect this from // multithreaded access because Einkornix is cooperative. n_live_tasks--; // Einkornix call to terminate this thread. Required! This removes // the thread from the round-robin order. einexit(); } int main() { // Here we set up task data for three different `run_writer` tasks. // Einkornix doesn’t require all the tasks to be running the same // code, as they will be in this example, but if they are, it still // permits passing each task different data at start time as a void // pointer. writer writers[] = { { .n = 3, .c = 'A' }, { .n = 7, .c = ' ' }, { .n = 13, .c = 'b' }, }; enum { n_tasks = sizeof(writers)/sizeof(writers[0]), task_stack_size = 64, // in long-long units, i.e. pairs of registers }; long long stacks[n_tasks * task_stack_size]; // declared as long long in hopes of alignment // Those notes about alignment require some explanation. The ARM // EABI that Einkornix is written for guarantees 8-byte alignment // for the stack at all times. ARM is pretty happy to throw you a // bus error if you fail to observe 4-byte alignment, but 8-byte // alignment only matters, as far as I know, for certain rare atomic // operations. These can still happen to stack items, though, and // depending on your operating environment, I think they can happen // on wherever your stack happens to be at some undefined moment. // In particular, I think Microsoft Windows on ARM does this, and // Linux doesn’t. But Einkornix can also run on Cortex-M // microcontrollers, where at any time you might end up with an // interrupt handler on your stack. // My hope is that by allocating the stacks as an array of long // longs, GCC will ensure that they are properly aligned to be // stacks. // Stack-allocate one task for each writer. Each one is 8 bytes, so // this whole array is only 32 bytes. eintask tasks[n_tasks+1]; // This sets up the current execution stack (the one main() is // running on) as the only existing eintask. einstart(&tasks[0]); // Now we create the other tasks in a loop. for (int i = 0; i < n_tasks; i++) { n_live_tasks++; // This is the global variable the tasks will decrement. // Set up the stack space for the new task. tasks[i+1].stack = &stacks[task_stack_size * (i+1)]; // Spawn tasks in reverse order because the last shall be first. // Literally, the last-spawned task is the first in execution // order. The last argument here is the userdata argument passed // to `run_writer` when it starts. einspawn(&tasks[i+1], run_writer, &writers[n_tasks - i - 1]); } // Now that we have spawned all these other tasks, we need to let // them run. Absent any einjoin() or similar function, we can // simply poll our shared variable to see if they’ve all finished. // In a more complex application we might want to know *which* task // has exited so that we can reuse the eintask struct (or, say, our // writer struct) for other purposes. while (n_live_tasks) einyield(); // Now they have all finished, so we print an end-of-line and exit. write(1, "\n", 1); return 0; }