4. Building systems: the manual way¶
This chapter explains how to compile and build a full system from scratch
manually. Using the haskus-system-build tool presented in
Building systems: the automated way is much easier as it performs many of the
required steps presented in this chapter automatically.
We will follow these steps:
- Building your own systems : these are user-space Linux
initprograms (written in Haskell and using thehaskus-systemframework) which are compiled statically. - Putting your systems into ramdisks.
- Configuring and building the Linux kernel.
- Testing with QEMU.
- Configuring a boot-loader. Building ISO images and bootable devices. Testing the ISO image with QEMU.
4.1. Building systems¶
Suppose we have the following HelloWorld system code and that we
want to build it.
import Haskus.System
main :: IO ()
main = runSys' <| do
term <- defaultTerminal
writeStrLn term "Hello World!"
waitForKey term
powerOff
You can use Cabal as for any other Haskell program. However, we want to
build a static executable, hence the .cabal file must contain an
executable section such as:
executable HelloWorld
main-is: HelloWorld.hs
build-depends:
base,
haskus-system
default-language: Haskell2010
ghc-options: -Wall -static -threaded
cc-options: -static
ld-options: -static -pthread
#extra-lib-dirs: /path/to/static/libs
If static versions of the libgmp, libffi and glibc libraries (used
by GHC’s runtime system) are not available on your system, you have to compile
them and to indicate to the linker where to find them: uncomment the last line
in the previous extract of the .cabal file (the extra-lib-dirs entry)
and modify it so that the given path points to a directory containing the static
libraries.
Finally, we recommend using stack to ensure that you are using appropriate
versions of GHC and of haskus-system. Example of stack.yaml contents:
resolver: lts-8.15
packages:
- '.'
- location:
git: git@github.com:haskus/haskus-system
commit: 33ba0413f2adae33f66f78e51f7e9e52e63758f1
extra-dep: true
ghc-options:
"haskus-system": -freduction-depth=0 -fobject-code
extra-deps:
- haskus-binary-0.6.0.0
- haskus-utils-0.6.0.0
Finally use stack build to compile the program.
Examples
The haskus-system-examples repository contains
several examples of such systems (including this HelloWorld program).
Refer to its .cabal file and to its stack.yaml file.
4.2. Creating ramdisks¶
To execute a system with the Linux kernel, the easiest way is to create a
ramdisk: an image of the file-system containing your system (basically a zip
file).
To do that, put your system files into a directory /path/to/my/system. Then
execute:
(cd /path/to/my/system ; find . | cpio -o -H newc | gzip) > myimage.img
You need to have the cpio and gzip programs installed. It builds a
ramdisk file named myimage.img in the current directory.
4.3. Building the Linux kernel¶
The Linux kernel is required to execute systems using haskus-system. Leaving
aside modules and firmwares, a compiled Linux kernel is a single binary file
that you can use with QEMU to execute your own systems.
To build Linux, you first need to download it from http://kernel.org and to unpack it:
wget https://www.kernel.org/pub/linux/kernel/v4.x/linux-4.9.8.tar.xz
tar xf linux-4.9.8.tar.xz
Then you need to configure it. We recommend at least the following:
cd linux-4.9.8
# default configuration for the X86-64 target
make x86_64_defconfig
# enable some DRM (graphics) drivers
./scripts/config -e CONFIG_DRM_BOCHS
./scripts/config -e CONFIG_DRM_RADEON
./scripts/config -e CONFIG_DRM_NOUVEAU
./scripts/config -e CONFIG_DRM_VIRTIO_GPU
# fixup configuration (use default values)
make olddefconfig
If you know what you are doing, you can configure it further with:
make xconfig
Finally, build the kernel with:
make -j8
Copy the resulting kernel binary that you can use with QEMU for instance:
cp arch/x86/boot/bzImage linux-4.9.8.bin
You can also copy built modules and firmwares with:
make modules_install INSTALL_MOD_PATH=/path/where/to/copy/modules
make firmware_install INSTALL_FW_PATH=/path/where/to/copy/firmwares
4.4. Testing with QEMU¶
To test a system with QEMU, we recommend that you first build a ramdisk
containing it, say myimage.img. We suppose your system (i.e., the user-space
program) is stored in /my/system in the ramdisk.
You also need to build a recent Linux kernel, say linux.bin.
To launch QEMU, use the following command line:
qemu-system-x86_64
-kernel linux.bin
-initrd myimage.img
-append "rdinit=/my/system"
We recommend the following options for QEMU:
# make QEMU faster by using KVM
-enable-kvm
# use newer simulated hardware
-machine q35
# make pointer handling better by simulating a tablet
-usbdevice "tablet"
# redirect the guest Linux console on the host terminal
-serial stdio
-append "console=ttyS0"
# enable better sound device
-soundhw "hda"
# make the guest Linux output more quiet
-append "quiet"
4.5. Distributing systems¶
To distribute your systems, we will create a directory /my/disk containing:
- your system (in a ramdisk)
- the Linux kernel
- the boot-loader files (including its configuration)
A boot-loader is needed as it loads Linux and the ramdisk containing your system. We use Syslinux boot-loader but you can use others such as GRUB. Note that you don’t need a boot-loader when you test your system with QEMU because QEMU acts as a boot-loader itself.
To distribute your systems, you can install the boot-loader on a device (e.g.,
USB stick) and copy the files in the /my/disk directory on it. Or you can
also create a .iso image to burn on a CD-ROM (or to distribute online).
Downloading Syslinux
You first need to download and unpack the Syslinux boot-loader:
wget http://www.kernel.org/pub/linux/utils/boot/syslinux/syslinux-6.03.tar.xz
tar xf syslinux-6.03.tar.xz
Creating the disk directory
You need to execute the following steps to create your disk directory:
Create some directories:
mkdir -p /my/disk/boot/syslinux
Copy Syslinux:
find syslinux-6.03/bios *.c32 -exec cp {} /my/disk/boot/syslinux ;
cp syslinux-6.03/bios/core/isolinux.bin /my/disk/boot/syslinux/
Copy the Linux kernel:
cp linux-4.9.8.bin /my/disk/boot/
Copy the system ramdisk:
cp myimage.img /my/disk/boot/
Finally, we need to configure the boot-loader by creating a file
/my/disk/boot/syslinux/syslinux.cfg containing:
DEFAULT main
PROMPT 0
TIMEOUT 50
UI vesamenu.c32
LABEL main
MENU LABEL MyOS
LINUX /boot/linux-4.9.8.bin
INITRD /boot/myimage.img
APPEND rdinit="/my/system"
Replace /my/system with the path of your system in the myimage.img
ramdisk.
Creating a bootable device
To create a bootable device (e.g., bootable USB stick), you have to know its
device path (e.g., /dev/XXX) and the partition that will contain the boot
files (e.g., /dev/XXX_N).
You can use fdisk and mkfs.ext3 to create an appropriate partition.
You have to install Syslinux MBR:
sudo dd bs=440 if=syslinux-6.03/bios/mbr/mbr.bin of=/dev/XXX
Then you have to copy the contents of the disk directory on the partition and configure it to be bootable:
sudo mount /dev/XXX_N /mnt/SOMEWHERE
sudo cp -rf /my/disk/* /mnt/SOMEWHERE
sudo syslinux-6.03/bios/extlinux/extlinux --install /mnt/SOMEWHERE/boot/syslinux
sudo umount /mnt/SOMEWHERE
Now your device should be bootable with your system!
Creating a bootable CD-ROM
To create a bootable CD-ROM, you first need to create a .iso disk image with the xorriso utility:
xorriso -as mkisofs
-R -J # use Rock-Ridge/Joliet extensions
-o mydisk.iso # output ISO file
-c boot/syslinux/boot.cat # create boot catalog
-b boot/syslinux/isolinux.bin # bootable binary file
-no-emul-boot # does not use legacy floppy emulation
-boot-info-table # write additional Boot Info Table (required by SysLinux)
-boot-load-size 4
-isohybrid-mbr syslinux-6.03/bios/mbr/isohdpfx_c.bin # hybrid ISO
/my/disk
It should create a mydisk.iso file that you can burn on a CD or distribute
online.