This page describes the port of mC/OS-II to a protected mode, 32-bit, flat memory model of the 80386, 80486, Pentium and Pentium II CPUs.

mC/OS-II is a portable, ROMable, preemptive, real-time, multitasking kernel for microprocessors. Originally written in C and x86 (real mode) assembly languages, it has been ported to a variety of microprocessors. For more details about mC/OS-II, see the Reference section or visit www.micrium.com.

This port requires the following development tools:

Microsoft Macro Assembler?6.11 (MASM). Note: any other version (including 6.11d) may not work.

Microsoft Visual C++?6.0 (VC++).

DOWNLOAD

The port (ucosx86p.zip) can be downloaded for free, as long as it is not being used commercially.

PORT

The port is loosely based on a mix of the small & large memory models provided on the standard mC/OS-II distribution. The major differences are highlighted below:

It can be loaded from DOS. But switching into protected mode and fully bypassing DOS and the BIOS may create some inconsistencies inside DOS, preventing a normal return to it. Thus, the PC would have to be rebooted in order to get back to DOS. Also, the application would have to be built as a DOS application, requiring a 16-bit compiler.

It can be loaded from a floppy disk upon boot-up. By providing a bootstrap loader, the application would be the first thing loaded and would be in full control. The PC also has to be rebooted back to DOS (or Windows) if required.

It can be burned into PROM, into a stand-alone system.

Since I did not have a 16-bit compiler (I am using Microsoft Visual C++ 6.0) and I already had a bootstrap loader at hand, I decided to choose method #2. I also used a second PC to test the application, in order not to have to reboot my workstation after each test.

The bootstrap loader (BootSctr.img) can easily be installed on the very first sector of a floppy disk (e.g. the boot sector) by using the DEBUG program, distributed with DOS and Windows. The DEBUG’s write command has the following format:

mC/OS-II is normally initialized within the application, by calling OsInit(). This assumes that the application has already been loaded and started. But in the current case, the protected mode must first be activated in order to start the application in a 32-bit, flat memory model mode. Thus, a special application entry point is required, which must be run before main(). Such an entry point is normally provided with a compiler and is operating system-dependent. In our case, since there is no underlying operating system, the entry point must perform itself any initialization tasks required to run the application. Luckily, there are only a few things to care about.

The entry point of the application is coded in Entry.asm. With the interrupts still disabled, the following actions are performed:

The protected mode is activated with a flat memory model that spans 4GB of physical memory.

All segment registers are initialized to default values in order to "forget" about them (hence obtaining a flat, non-segmented memory model).

A temporary 32-bit stack is set to address 8000h (an arbitrary address).

A call is done to main(), which is the application’s entry point.

In case main() returns, an infinite loop is executed. This is not really required, since main() is not expected to return.

The file also contains the system tables required by the processor in protected mode:

Global Descriptor Table (GDT). Since all tasks are given full privileges (CPL 0) and share the same address space (e.g. the entire physical memory), only one code and data descriptors are required.

Entry

Description

00h

Unused (set to 0)

08h

Code segment, with the following attributes:

code segment

executable

read-only

base address of 0

limit of 4GB (FFFFFh, with the G bit set)

D-bit set (to run 32-bit code by default)

10h

Data and Stack segment, with the following attributes:

data segment

writable

base address of 0

limit of 4GB (FFFFFh, with the G bit set)

B-bit set (to execute 32-bit stack instructions by default)

Interrupt Descriptor Table (IDT). Only 64 entries are reserved, among them 16 for mC/OS-II and the application (the number can be increased as needed).

Entry

Description

00h-1Fh

CPU interrupts and exceptions

20h-2Fh

Hardware Interrupt Request Lines (IRQs)

30h-3F

Available for mC/OS and the application

40h-FFh

Unused

There is no Local Descriptor Table (LDT) in use.

The rest of the hardware initialization is performed in the application. Once in main(), a call is done to OsCpuInit(), in os_cpu_c.c, in order to perform the following:

Enable the address line 20, normally disabled for some real mode considerations. The line is enabled by sending a few commands to the Intel 8042 keyboard controller. See InitA20() for details (os_cpu_c.c)

Relocate the IRQ interrupts, since the overlap the CPU interrupts and exceptions (for instance, it is not possible to know if the interrupt 0 has been triggered by the clock or a division by zero). The IRQ are relocated in the range 20h-2Fh by sending a few commands to the Intel 8259 interrupt controllers. See InitPIC() for details (os_cpu_c.c)

The interrupt table is initialized by using SetIntVector() and SetIDTGate(). The 64 entries are set to point to a default interrupt handler (DefIntHandler(), in os_cpu_a.asm), which simply performs an interrupt return

The General Protection Fault (interrupt 13h), triggered by the CPU when a fault is detected (assigning invalid values to segment registers, executing an interrupt above 40h, etc.), is assigned a dump stack handler (DumpStackHandler(), in os_cpu_a.asm). This handler displays the address of the faulty instruction on the upper-left corner of the screen and enters an infinite loop. This is useful to identify the source of a problem.

The clock handler (OsTickISR(), in os_cpu_a.asm) is installed as the interrupt 20h handler.

The mC/OS-II context switch handler (OSCtxSw(), in os_cpu_a.asm), is installed as the interrupt uCOS handler (uCOS is defined as 0x30 in os_cpu.h).

Back in main(), OsInit() is then called to initialize mC/OS. Two tasks are then created by calling OsTaskCreate() twice. Finally, multi-tasking is started by executing OSStart(), which never returns.

Note that the entire initialization takes place with the interrupts disabled; they are re-enabled when the first task is executed.

OSTickISR() (Ix86p_a.asm) makes no longer a call to the previous clock interrupt handler, but it sends an end-of-interrupt to the interrupt controllers (i8259). This would also be required for each hardware interrupt (IRQ) handler.

Some C library functions are added: inportb() and outportb(), implemented in assembly.

Additionally, minor changes have been done in the include files:

In os_cfg.h, the maximum number of tasks (OS_MAX_TASKS) is defined as 2 and the mC/OS interrupt (uCOS) is defined as 0x30.

The object file of mC/OS-II. The source of it is available from the book.

Entry\

Entry.asm

The entry point, in assembly. This is where the protected mode is enabled, and is very similar to Example 2.

Entry.lst

The assembled listing of Entry.asm.

Entry.obj

The assembled application entry point.

Makefile

The Makefile to build Entry.obj.

MyTask\

MyTask.dsw

The Visual C++ (VC++) project file.

MyTask.c

Source file of the test application.

Includes.h Os_cfg.h Ucos.h

mC/OS-II and application header files.

os_cpu_c.c os_cpu.h os_cpu_a.asm

The 80x86 protected mode port of mC/OS-II

Build.bat

As described earlier.

Debug.txt

As described earlier.

Build the project using Visual C++:

Go to the Dev sub-directory.

Double-click on the MyTask.dsw file to open the project in Visual C++ 6.0

Press F7 to build the application. The final image will be in Release\MyTask.img

Insert a formatted 1.44MB floppy disk in your drive A:

Double-click on (or execute) the file Build.bat to build a bootable floppy disk

Reboot with the diskette in drive A: in place, or transfer the diskette to another machine and reboot that machine

WHAT'S NEXT?

The next step would be to add paging to mC/OS-II. This step is significant since it will require many changes in the operating system itself. The objective is to have the ability to run separate tasks in their own address space, which could normally span 4GB on a x86. Paging also offers other advantages, such as isolating the kernel from faulty tasks, shared code and data, etc. Best of all, this port will be available on the 386 and up.