feature article
Subscribe Now

Create Your Own RTOS in 1 Hour (Part 4)

Let’s Get Coding

“It is our attitude at the beginning of a difficult task which, more than anything else, will affect its successful outcome.” – William James

Time to write some code. The good news is, you won’t need much, and that’s largely the point. We’re going to let the hardware built into every x86 processor since the Reagan administration do the work for us. All we have to do is nudge it in the right direction and let nature take its course. 

First off, we’ll need two tasks. These can be as elaborate as you want, but for demonstration purposes we’ll create two trivially simple and nearly identical tasks. Each task turns on an LED and turns off another LED, and the only difference between them is which LED goes on or off. That way, you can tell which task is running. With any luck, the two LEDs will toggle on and off with every task switch, proving that the tasks are alternating with one another. The code looks something like this: 

task1: MOV DX, green_led

MOV AL, const_on

OUT DX, AL

MOV DX, red_led

MOV AL, const_off

OUT DX, AL

JMP task1

The first three lines of assembly code turn on our green LED, and the next three lines turn off the red LED. The mini-program then loops forever. 

Obviously, you’ll want to replace the four constants with appropriate values for your hardware. The placeholders green_led and red_led should be the I/O addresses of two different LEDs. The other two placeholders, const_on and const_off, are whatever binary values turn your LEDs on and off, respectively. Our other task is nearly identical, but with the on/off values swapped. 

task2: MOV DX, green_led

MOV AL, const_off

OUT DX, AL

MOV DX, red_led

MOV AL, const_on

OUT DX, AL

JMP task2

So… a couple of observations. Both tasks loop forever, hammering on the same I/O addresses over and over. You wouldn’t normally do this, because once you’ve written to the LEDs the first time, you’d expect them to stay that way forever. But, in our multitasking system, you can’t depend on everything staying the way you left it. Another task might swoop in and change things that your first task doesn’t see. In this case, our two tasks battle over the same two LEDs, constantly swapping their condition and undoing each other’s work. 

The second thing we notice is that neither task saves or restores any registers. They’re not written like subroutine calls where you have to save register contents or push and pop arguments off the stack. Each task is blissfully unconcerned with preserving state information because all of that is done for us in the hardware. 

Third, neither task does anything to force a task switch, either by putting itself to sleep or by jumping to the other task. That all has to be done elsewhere. Tasks don’t usually participate in their own task switching. It’s normally forced upon them. 

On one hand, we’ve created tasks that are uninvolved with task switching but, on the other hand, are written with the awareness that they could be task-switched out at any time. A task can be sure that its registers will never change without its knowledge, but that’s not true of memory or I/O devices. You can depend on the registers being exactly as you left them, but not external resources. 

Once our two little programs are set up, make sure each one’s TSS points to the correct code segment (CS) and instruction pointer (EIP). Both tasks might share the same code segment, but they definitely have different starting instructions. Also, make sure that both tasks have a valid stack (SS and ESP) loaded into their respective TSS in case of interrupt. As with the code, they can share a stack segment but not a stack pointer. 

Be sure to also load the processor’s Task Register (TR) with the ID of the third “dummy” TSS we created earlier. This tells the processor where to dump the current state information when it makes the first task switch. We’ll also need to set the Busy bit in the TSS (offset 100) for both tasks and set the Nested Task (NT) bit in the processor’s EFLAGS register. 

Switching Tasks

There are a lot of ways you can trigger a task switch: interrupt, fault, program code, time slice, or any combination of these. You can even have programs voluntarily preempt themselves. Any time you can JMP, CALL, or IRET to another program or subroutine, you can force a task switch. Once all your structures are set up, it’s remarkably easy. The hardest part is deciding which task to switch to. 

In this example, we’ll treat the task switcher as its own task triggered by some sort of interrupt, which is probably how you’d do it in real life. Unlike a typical interrupt service routine (ISR), a self-contained task doesn’t have to worry about saving and restoring registers and preserving other state information. That’s all handled automatically. All we have to do is decide which of our two tasks to tee up to run next.

To do that, this program examines its own TSS to see which task ran last. Was it Task #1 or Task #2? The processor always stores that information into the Back Link field of the incoming task’s TSS (at offset 0) every time there’s a task switch, so you can always learn which task called you or which task was interrupted (assuming you have the ability to read your own TSS). All our program does is examine its Back Link field and then modify it so that we’ll “return” to whichever task wasn’t running before. 

sched: MOV AX,WORD PTR DS:[back_link]

CMP AX, task_1

JE switch_2




switch_1: MOV AX, task_1

JMP switch




switch_2: MOV AX, task_2

JMP switch




switch: MOV WORD PTR DS:[back_link], AX

IRET

JMP sched

The first instruction simply reads 16 bits from the TSS. (This assumes that data segment DS encompasses the current TSS and that back_link is the offset to its Back Link field.) The second instruction compares that value to the selector for Task #1. Does it match? If so, we need to tee up Task #2. Otherwise, we’ve just come from Task #2, so the fourth instruction gets ready to stuff the selector for Task #1 into our own Back Link field. Instructions eight and nine (at label switch) do the real work. The MOV pokes the 16-bit selector for the desired task into the Back Link field of the TSS, and the interrupt return (IRET) completes this task and forces a task switch. Done! 

Why is there a JMP at the very end? The almost-final IRET will cause a task switch, which also dumps the state of this task into its TSS. The next time it’s called, it will pick up right where it left off, so without a JMP to create an infinite loop, we’d fall off the end of the code. 

And there you have it. Nothing but a few dozen bytes of code, plus a few hundred bytes of data. All the rest is automatic. Our example task switcher was trivially simple, but you can replace it with something more elaborate to implement round-robin scheduling, or a priority scheme, or whatever you like. The mechanics are the same: modify the current Back Link field and execute an IRET to “return” to whatever task you choose. There are plenty of other ways to trigger a task switch, too, but this is enough to get you started on your own system. 

One thought on “Create Your Own RTOS in 1 Hour (Part 4)”

Leave a Reply

featured blogs
May 18, 2021
Since I was a kid, I’ve always been a fan of technology making life better. When I was 8, I remember programming the VCR to record the morning cartoons so I wouldn’t miss the good ones after the bus picked me up from school. When I was 10, I made mixtapes of my fa...
May 18, 2021
原文出è•: Please Excuse the Mesh: CFD and Pointwise ä½è…: Paul McLellan Cadence於今年四æˆæ”¶è³¼äº†æµé«”動力學公司Pointwiseã‚å¨æˆ‘的前ä¸ç¯‡æ–‡ç« æŽ¢è¨Ž PointwiseãPCIeã...
May 13, 2021
Our new IC design tool, PrimeSim Continuum, enables the next generation of hyper-convergent IC designs. Learn more from eeNews, Electronic Design & EE Times. The post Synopsys Makes Headlines with PrimeSim Continuum, an Innovative Circuit Simulation Solution appeared fi...
May 13, 2021
By Calibre Design Staff Prior to the availability of extreme ultraviolet (EUV) lithography, multi-patterning provided… The post A SAMPle of what you need to know about SAMP technology appeared first on Design with Calibre....

featured video

Super Resolution with ARC EV Processor IP

Sponsored by Synopsys

Interested in upscaling images with AI? Join Gordon Cooper for an update on SR-GAN with ARC EV Processors.

Click here for more information about DesignWare ARC EV Processors for Embedded Vision

featured paper

Smile, You're on My Security Camera!

Sponsored by Maxim Integrated

Advances in wireless and IoT technologies are fueling market growth for security camera systems. Outdoor security cameras need to operate for a long time on small disposable batteries. This design solution shows how a high-performance power management system can power an outdoor security camera several months longer than an ordinary solution.

Click to read more

featured chalk talk

Using the Graphical PMSM FOC Component in Harmony3

Sponsored by Mouser Electronics and Microchip

Developing embedded software, and particularly configuring your embedded system can be a major pain for development engineers. Getting all the drivers, middleware, and libraries you need set up and in the right place and working is a constant source of frustration. In this episode of Chak Talk, Amelia Dalton chats with Brett Novak of Microchip about Microchip’s MPLAB Harmony 3, with the MPLAB Harmony Configurator - an embedded development framework with a drag-and-drop GUI that makes configuration a snap.

Click here for more information about Microchip Technology MPLAB® X Integrated Development Environment (IDE)