Kjetil's Information Center: A Blog About My Projects

Linux 2.6 Distribution for LOADLIN

I have done some experiments and changed the earlier distribution from Linux 2.4 to Linux 2.6. The goal is still to be able to load the kernel and root filesystem with LOADLIN from DOS on a 386 system.

A notable change is that the /dev filesystem is now populated automatically by the kernel using devtmpfs. The VGA/VESA framebuffer console has also been enabled. It seems it is not possible to use the newer "libata" drivers for IDE/ATA hard drive access on a 386 so the older drivers are used. By default the latest 2.6 kernels also try to load the kernel at 16MB address, but this has been changed to 1MB to support systems with less RAM.

The following specific software versions are used:
* linux-2.6.39.4
* gcc-4.9.4
* busybox-1.37.0
* uClibc-ng-1.0.51
* binutils-2.32

Note that I have ran the compilation on a modern Slackware 15.0 system with GCC 11.2 and some adjustments were required to force usage of older C++ when building the GCC 4.9 cross toolchain. I also added the $MAKE_JOBS variable to significantly speed up compilation time for modern multi-core CPUs.

Get the necessary scripts, configuration and patches here to make it yourself. Or just get the completed kernel and root filesystem here.

Once again, for easy reference, here is the script to compile everything:

#!/bin/bash
set -e

TARGET="i386-linux-uclibc"
# PREFIX="${HOME}/opt/gcc-${TARGET}/"
PREFIX="`pwd`/gcc-${TARGET}/"
SYSROOT="${PREFIX}/${TARGET}/sysroot"
MAKE_JOBS="-j32"

GCC_SRC="gcc-4.9.4.tar.bz2"
BINUTILS_SRC="binutils-2.32.tar.xz"
UCLIBC_SRC="uClibc-ng-1.0.51.tar.xz"
LINUX_SRC="linux-2.6.39.4.tar.xz"
BUSYBOX_SRC="busybox-1.37.0.tar.bz2"

export PATH="${PREFIX}bin:$PATH"

# Prepare Prefix and System Root
if [ -d "$SYSROOT" ]; then
  echo "Old system root directory detected, please remove it."
  exit 1
else
  mkdir -p "$SYSROOT/usr"
fi

# Prepare Build Directories:
if [ -d build ]; then
  echo "Old build directory detected, please remove it."
  exit 1
else
  mkdir -p build/binutils
  mkdir -p build/gcc-stage1
  mkdir -p build/gcc-stage2
  mkdir -p build/uclibc
  mkdir -p build/linux
  mkdir -p build/busybox
fi

# Unpack Sources:
if [ -d source ]; then
  cd source
  tar -xvjf "$GCC_SRC"
  tar -xvJf "$BINUTILS_SRC"
  tar -xvJf "$UCLIBC_SRC" -C ../build/uclibc
  tar -xvJf "$LINUX_SRC" -C ../build/linux
  tar -xvjf "$BUSYBOX_SRC" -C ../build/busybox
  cd -
else
  echo "No source directory, please download sources."
  exit 1
fi

# Patch Linux 2.6 timeconst.pl
cd build/linux/linux-*/kernel
if fgrep --silent "defined(@val)" timeconst.pl; then
  patch -p 0 < ../../../../timeconst.pl.patch
fi
cd -

# Patch Linux 2.6 ptrace.h
cd build/linux/linux-*/arch/x86/include/asm
if fgrep --silent "extern long syscall_trace_enter" ptrace.h; then
  patch -p 0 < ../../../../../../../ptrace.h.patch
fi
cd -

# Install Linux 2.6 Headers:
cd build/linux/linux-*
make ARCH=i386 defconfig
make ARCH=i386 INSTALL_HDR_PATH="$SYSROOT/usr" headers_install
cd -

# Build binutils:
cd build/binutils
../../source/binutils-*/configure --target="$TARGET" --prefix="$PREFIX" --with-sysroot="$SYSROOT" --disable-werror --enable-languages=c,c++ --enable-shared --without-newlib --disable-libgomp --enable-fast-install=N/A
make all-{binutils,gas,ld} $MAKE_JOBS
make install-{binutils,ld,gas}
cd -

# Build Stage 1 GCC4 (Without libgcc):
cd build/gcc-stage1
CXXFLAGS="--std=c++03 -g -O2" ../../source/gcc-*/configure --target=$TARGET --prefix=$PREFIX --with-sysroot=$SYSROOT --with-cpu=i386 --disable-fast-install --disable-werror --disable-multilib --enable-languages=c --without-headers --disable-shared --disable-libssp --disable-libmudflap --with-newlib --disable-c99 --disable-libgomp
CXXFLAGS="--std=c++03 -g -O2" make all-gcc $MAKE_JOBS
make install-gcc
cd -

# Install uClibc headers:
cd build/uclibc/uClibc-*
cp -v ../../../config-uclibc .config
sed -i -e "s%KERNEL_HEADERS=.*%KERNEL_HEADERS=\"$SYSROOT/usr/include/\"%" .config
make ARCH=i386 PREFIX="$SYSROOT" install_headers
cd -

# Build Stage 1 GCC4 (libgcc):
cd build/gcc-stage1
CXXFLAGS="--std=c++03 -g -O2" make all-target-libgcc $MAKE_JOBS
make install-target-libgcc
cd -

# Build uClibc:
cd build/uclibc/uClibc-*
make ARCH=i386 PREFIX="$SYSROOT" $MAKE_JOBS
make ARCH=i386 PREFIX="$SYSROOT" install
cd -

# Build Stage 2 GCC4:
cd build/gcc-stage2
CXXFLAGS="--std=c++11 -g -O2" ../../source/gcc-*/configure --target=$TARGET --prefix=$PREFIX --with-sysroot=$SYSROOT --with-cpu=i386 --enable-fast-install=N/A --disable-werror --enable-languages=c,c++ --enable-shared --without-newlib --disable-libgomp
CXXFLAGS="--std=c++11 -g -O2" make all-{gcc,target-libgcc,target-libstdc++-v3} $MAKE_JOBS
make install-{gcc,target-libgcc,target-libstdc++-v3}
cd -

# Build Linux 2.6:
cd build/linux/linux-*
cp -v ../../../config-linux .config
make ARCH=i386 CROSS_COMPILE=i386-linux-uclibc- bzImage $MAKE_JOBS
cp -v ./arch/x86/boot/bzImage ../../../bzImage
cd -

# Build Busybox:
cd build/busybox/busybox-*
cp -v ../../../config-busybox .config
make CROSS_COMPILE=i386-linux-uclibc- $MAKE_JOBS
cd -
          


Once again, here is the script to make the root filesystem:

#!/bin/bash
set -e

ROOTFS="`pwd`/rootfs/"

TARGET="i386-linux-uclibc"
# PREFIX="${HOME}/opt/gcc-${TARGET}/"
PREFIX="`pwd`/gcc-${TARGET}/"
SYSROOT="${PREFIX}/${TARGET}/sysroot"

export PATH="${PREFIX}bin:$PATH"

if [ -d "$ROOTFS" ]; then
  echo "Old root FS directory detected, please remove it."
  exit 1
fi
mkdir -p "$ROOTFS"

# Install Busybox:
cd build/busybox/busybox-*
make CROSS_COMPILE=i386-linux-uclibc- CONFIG_PREFIX="$ROOTFS" install
cd -

# Create some essential directories:
cd "$ROOTFS"
mkdir -v etc
mkdir -v etc/init.d
mkdir -v lib
mkdir -v proc
mkdir -v sys
mkdir -v tmp
mkdir -v root
mkdir -v dev
cd -

# Initial rc.S:
cat > rcS <<EOF
#!/bin/sh
mount -t proc /proc /proc
mount -t tmpfs /tmp /tmp
loadkmap < /etc/no-latin1.bmap
hostname busybox
EOF
mv -v rcS "$ROOTFS/etc/init.d/"

# Initial inittab:
cat > inittab <<EOF
::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
::restart:/sbin/init
EOF
mv -v inittab "$ROOTFS/etc/"

# Copy this system's keymap:
loadkeys -b /usr/share/kbd/keymaps/i386/qwerty/no-latin1.map.gz > "$ROOTFS/etc/no-latin1.bmap"

# Make everything root user:
sudo chown -v -R root:root "$ROOTFS"

# SetUID on busybox binary:
sudo chmod +s "$ROOTFS/bin/busybox"

# Make rcS executable:
sudo chmod +x "$ROOTFS/etc/init.d/rcS"

# Make Compressed ROM archive:
mkfs.cramfs rootfs rootfs.cramfs
          


LOADLIN is typically used like this, after renaming the files to DOS format:

loadlin.exe bzimage initrd=rootfs
          


QEMU also works, in graphical mode:

qemu-system-i386 -kernel bzImage -initrd rootfs.cramfs
          


Or QEMU using the serial console directly to the terminal:

qemu-system-i386 -kernel bzImage -initrd rootfs.cramfs -append "earlycon=uart8250,io,0x3f8,115200n8 console=ttyS0" -serial mon:stdio -nographic
          


Here is also a demo video.

Topic: Configuration, by Kjetil @ 02/02-2025, Article Link