The MemOS Memory Reporting System
By Rich West, Boston University.
**** THIS PROJECT CAN BE PERFORMED IN GROUPS OF TWO ****
If working in a group of two, only one person is required to submit a solution via gradescope. In a README file, please include the name of the other person.
Background
In this primer, you are going to write a very simple OS. Okay, it’s not a particularly useful OS but it will provide a way to understand how systems are booted, and how system information is displayed on the screen. The idea is to bootstrap a program that probes the system BIOS and reports the amount of physical memory available in your machine. This is then displayed as a message, in the form:
“MemOS: Welcome *** System Memory is: XXXMB”
The value XXX is replaced by the actual memory your system has available.
You can use any of the BIOS functions available for detecting memory except BIOS call E801 (shown in class). Bonus points will be given for detecting memory using BIOS INT 0x15, EAX=0xE820 and reporting only the usable RAM (known as Type 1 memory) as opposed to all memory. You can find more on the osdev page for the five types of memory reported using BIOS INT 0x15, EAX=0xE820.
Step 1: Building a Virtual Disk
The first step is to build a virtual disk for MemOS. To do this you should follow the general guidelines I provided for creating a simple disk image.
Tis disk image can be used with any virtual machine PC emulation tools such as QEMU, BOCHS, virtualbox, etc. QEMU comes pre-installed in Puppy Linux, and the steps for creating your virtual disk are recommended to be performed in the provided Puppy Linux virtual machine.
An example using dd to create a virtual disk file is as follows:
$dd if=/dev/zero of=disk.img bs=1k count=32760
A similar command is shown in the guidelines for creating a virtual disk, except that example assumes a block size of 512 bytes. It does not matter in this case as all we are doing is allocating a number of bytes to our file that equals the size of a hypothetical disk partition.
In this example, we simply use the pseudo-device /dev/zero to fill an output file, disk.img, with zero’d bytes, with a block size of 1024 and a count of blocks equal to 32760. This will create a file whose size is 32760*1024 bytes. For larger or smaller files, you can choose different count values. Similarly, you can change the block size as it’s not particularly important unless we’re dealing with a real disk device.
If you have qemu-img, you can create a raw disk image using the following command as an example:
$qemu-img create -f raw disk.img 32760K
In choosing the size of your virtual disk, you should be aware of disk geometries. In a real disk, at least older ones based on CHS geometries rather than logical block addressing (LBA), the size is calculated as:
cylinders * heads * sectors * sector-size
This is equivalent to:
cylinders * (tracks / cylinder) * (sectors / track) * sector-size
Let’s assume that we’re going to adopt the default sectors-per-track value for DOS compatibility. This is 63. Also, let’s assume we have a complete geometry as follows:
Cylinders = 65
Heads = 16 (same as tracks / cylinder) Sectors = 63 (actually, sectors / track) Sector-size = 512 bytes
This gives us a disk size of:
65 * 16 * 63 * 512 = 32760KB (where 1KB is 1024 bytes)
Once we have a raw virtual disk partition file, we can start to properly configure its geometry and its filesystem. Then, we can install a bootloader.
You should follow the guidelines to create your formatted disk image. You only need to install GRUB if you use it to boot your OS, which is necessary for memos-2, described below. It is not needed for memos-1.
If installing GRUB, we will assume the bootloader is based on version 1 (GRUB legacy) rather than GRUB2. You will need to copy stage1, stage2, and e2fs_stage1_5 to a /boot/grub directory on your virtual disk, as described in the HOWTO step 5. Then you will need to install stage1 in the master boot record (MBR) region of your disk image, using the interactive grub shell. Once successfully installed, you are ready for Step 2…
Step 2: Writing the MemOS Code
Here, you will have to write an x86 assembly program, called memos-X.s, where X is replaced with “1” or “2” depending on the version (described later). To help, I have provided a test program called vga16.s, written for use with the GNU assembler, gas. You should study vga16.s to see how it works. Intel’s Software Developers Manual Volume 2 (Instruction Set) is helpful here.
Notice how the size of vga16.s is limited to 512 bytes. It’s actually possible to load this code, after assembly and linkage into the MBR of your disk partition and treat it as a bootable program. This is because it has a valid boot signature 0xAA55 in the last two bytes of the 512 byte sector.
To assemble and link your memos-X.s program, you will need to follow the instructions at the bottom of vga16.s as a guideline. What is missing is the linker script to complete the linkage of your program. Here, I provide the linker script for vga16.s. You should read the info or man pages on GNU ld, which is part of the binutils package, to understand the format of linker scripts. Notice how, at the bottom of vga16.s we use dd again, to create a sector image of 512 bytes that will fit in an MBR if desired. Here, we skip the first 4096 bytes to bypass the object file program header, generated by ld. This is because the assembler (as) and linker (ld) produce an output file in ELF binary format and what we really want are just the program sections if we’re to map this code into an MBR.
Now, let’s backtrack a moment. It is possible using a PC emulator such as QEMU to boot the resultant binary, memos-X, without mapping it to a virtual disk. To do this, we can assume it is written just like vga16_test (created from vga16.s using dd). We can execute memos-X as follows:
$qemu -hda memos-X (depending on your version of qemu, you might need to use qemu-system-i386 or something similar for the binary name) The -hda flag, above, is actually optional here and won’t really affect anything as it stands.
First Deliverable
For the first solution to the problem of detecting memory, you should produce a file called memos-1.s which when assembled and linked into a 512-byte file called memos-1 will run as standalone boot code. Your solution will use BIOS interrupts to detect memory and write it to the screen. For this you will need to understand:
the BIOS
ways to use the BIOS to detect memory
how to use the BIOS to write to the VGA display. Here, you should use INT 0x10 interrupts, similar to how printing is done in vga16.s.
The next test is to show that you can boot your memos-1 code assembled and linked into the MBR of a virtual disk. The first deliverable only requires that you submit the source code memos-1.s, a linker script called memos-1.ld, a Makefile to assemble and link memos-1.s into a binary image called memos-1, and a README-1 file. README-1 should document exactly the steps taken to get your memos-1 binary into the virtual disk’s MBR, so that it can execute to produce the output strings at the top of this document.
Specifically, the message:
“MemOS: Welcome *** System Memory is: XXXMB”
You should use gradescope to submit all your files (discounting the virtual disk image) to a subdirectory called MEMOS. Include the linker script, memos-1.s files, a Makefile and README-1.
Second Deliverable
Here, you will create a file called memos-2.s and appropriate linker scripts to generate a binary that is booted by GRUB. Here, we will assume legacy GRUB rather than GRUB version 2. You will need to go back to the virtual disk creation described above and install GRUB in your MBR (or, alternatively, create a second virtual disk and install GRUB on that). This is documented in step 5 of the BOCHS virtual disk HOWTO. Then you will need to add a file called menu.lst to /boot/grub on your virtual disk. To do this, make sure you have mounted your virtual disk as a loopback device. For example, you can do this as follows:
$mount -t ext2 disk.img /mnt -o loop,offset=32256
The offset, above, is because the ext2 filesystem begins after the first track on your virtual disk. If you have formatted your disk to have a sectors-per-track value of 63 (the DOS compatibility value) then the first track has a size of 63*512 bytes, which is where the 32256 value comes from. To make sure you use 63 sectors/track when creating disk.img, be sure that your version of fdisk is running in DOS compatibility mode. If it’s an older version, it will automatically assume 63 sectors/track. If you get a value such as 2048 as your default, you’ll need to force DOS compatibility by using fdisk -c=dos … when creating your virtual disk.
The menu.lst file will look something like the following:
title MemOS
root (hd0,0)
kernel /boot/memos-2
This file is interpreted by GRUB to determine which OS should actually be loaded into memory. GRUB is intelligent enough to be able to read the filesystem of your bootable disk partition. This is where e2fs_stage1_5 is used by GRUB. Finally, the second stage of bootloading copies your bootable system into memory. However, your OS image, memos-2, will not load because it does not have a valid GRUB-compliant multiboot header. Since GRUB is based on the multiboot spec, it requires the OS to have a special magic number in the first 4096 bytes of memory. So, to get your memos-2 binary to be booted by GRUB you’ll need to add something like the following at the beginning of memos-2.s:
_start:
jmp real_start
/* Multiboot header — Safe to place this header in 1st page of memory for GRUB */
.align 4
.long 0x1BADB002 /* Multiboot magic number */
.long 0x00000003 /* Align modules to 4KB, req. mem size */
/* See ‘info multiboot’ for further info */
.long 0xE4524FFB /* Checksum */
real_start:
# This is where the rest of your program goes
Once you have successfully generated your memos-2 binary image, you should copy it to /boot within your virtual disk. To do this, you’ll need your disk.img mounted as a loopback device again, as described earlier.
But it Still Doesn’t Work!
If you’ve got this far with memos-2, congratulations! However, in all likelihood nothing is working. This is because GRUB boots your OS code into a protected mode environment. In protected mode, you no longer have access to the BIOS, so all your real-mode software interrupts will not work. At this stage you can either write some code to force your memos-2 code back into real-mode, or you can “poke” directly into VGA video memory, the bytes that you want displayed. To do this, you need to write to video memory, which is mapped at address 0xB8000 for text mode graphics. Further details can be found on the OSDEV wiki:
How to print a character to the screen How to work with VGA in text mode
Note that GRUB boots your system in VGA Text Mode so you will not have to set this yourself. You can also use GRUB itself to report how much memory your system has. If you get this far you should be able to use a PC emulator to boot your disk image and display text on the screen. For QEMU, type something like:
$qemu -hda disk.img
Submission
You should use gradescope to submit files to a MEMOS directory, as stated earlier.
As stated above (but repeated here), the first deliverable for memos-1 requires you to submit memos-1.s, memos-1.ld, Makefile, and README-1. README-1 documents the steps
to copy your memos-1 image to the MBR of your virtual disk image. Be sure to test your virtual disk image works with memos-1 so that you can verify your instructions in README-1.
The second deliverable requires you to submit: memos-2.s, memos-2.ld, Makefile, README-2, memos-2.img.
Put in README-2 the names and BUIDs of everyone in the same team (up to two people), and the byte size and configuration (cylinders, heads, sectors) of your virtual disk memos- 2.img.
memos-2.img will be a virtual disk image that includes all GRUB files, menu.lst and memos-2.
You should create memos-2.img to have the following configuration (cylinders=10+last 2 digits of your BUID, heads=16, sectors=63). If you work in a group of two, put in README-2 which person’s BUID you chose to use for your virtual disk image.
Puppy Linux
You can use the Puppy Linux image already discussed in labs (which is the same as used for CS552, available here).
Puppy Linux provides support for legacy GRUB. You can run the GRUB shell, and access stage1, stage2 and e2fs_stage1_5 files in /boot/grub within the Puppy Linux virtual disk image.
Using QEMU to Test memos-1 and memos-2
QEMU is included in Puppy Linux, along with vncviewer, so you can test your OS development inside another guest OS!
You can type: qemu-system-i386 -hda memos-1 -vnc :screen_num (where screen_num is replaced by an integer).
Then, if you type: vncviewer :screen_num you will be able to see the output.
For memos-2, you can do exactly the same as above, replacing memos-1 with the name of your virtual disk image. Alternatively, if you don’t want to copy memos-2 into your disk image you can quickly test it using QEMU’s very useful built-in grub support. You can do the following for testing and development purposes: qemu-system-i386 -kernel memos-2
You can vary the amount of memory available to your memos-1 or memos-2 systems by adding “-m XX” to your qemu-system-i386 command, where XX is the amount of memory you want to restrict to your OS. Note, you cannot allocate more memory than is available to the system running QEMU. If using QEMU in Puppy Linux, you will be restricted by the amount of memory your Puppy virtual machine has. Here, we will likely not test above 128MB.
Happy Programming!