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. 

Leave a Reply

featured blogs
Nov 24, 2020
In our last Knowledge Booster Blog , we introduced you to some tips and tricks for the optimal use of the Virtuoso ADE Product Suite . W e are now happy to present you with some further news from our... [[ Click on the title to access the full blog on the Cadence Community s...
Nov 23, 2020
It'€™s been a long time since I performed Karnaugh map minimizations by hand. As a result, on my first pass, I missed a couple of obvious optimizations....
Nov 23, 2020
Readers of the Samtec blog know we are always talking about next-gen speed. Current channels rates are running at 56 Gbps PAM4. However, system designers are starting to look at 112 Gbps PAM4 data rates. Intuition would say that bleeding edge data rates like 112 Gbps PAM4 onl...
Nov 20, 2020
[From the last episode: We looked at neuromorphic machine learning, which is intended to act more like the brain does.] Our last topic to cover on learning (ML) is about training. We talked about supervised learning, which means we'€™re training a model based on a bunch of ...

featured video

Available DesignWare MIPI D-PHY IP for 22-nm Process

Sponsored by Synopsys

This video describes the advantages of Synopsys' MIPI D-PHY IP for 22-nm process, available in RX, TX, bidirectional mode, 2 and 4 lanes, operating at 10 Gbps. The IP is ideal for IoT, automotive, and AI Edge applications.

Click here for more information about DesignWare MIPI IP Solutions

featured paper

Overcoming PPA and Productivity Challenges of New Age ICs with Mixed Placement Innovation

Sponsored by Cadence Design Systems

With the increase in the number of on-chip storage elements, it has become extremely time consuming to come up with an optimized floorplan using manual methods, directly impacting tapeout schedules and power, performance, and area (PPA). In this white paper, learn how a breakthrough technology addresses design productivity along with design quality improvements for macro-dominated designs. Download white paper.

Click here to download the whitepaper

Featured Chalk Talk

Evaluation and Development Kits

Sponsored by Samtec

With signal integrity becoming increasingly challenging in today’s designs, interconnect is taking on a key role. In order to see how a particular interconnect solution will perform in our design, we really need hands-on evaluation of the technology. In this episode of Chalk Talk, Amelia Dalton chats with Matthew Burns of Samtec about evaluation and development kits for high-speed interconnect solutions.

More information about Samtec Evaluation and Development Kits