This is a tutorial on how to build linux and test it using qemu on a M series Mac.
You will need brew, git ,docker, qemu and a Mac with a M series chip (M1, M2, M3 ...)
Install brew from https://brew.sh/ and then install qemu and docker using brew:
brew install qemu docker git
Test docker using the following command:
docker run hello-world
If it says cant connect to docker daemon, then you need to start docker. I used colima to start docker.
brew install colima
colima start
Now re-run the docker hello world command and it should work.
We need to use docker to build the linux from scratch because the build tools that come with Mac are not great for building linux. It also containerises the build so that it doesn't mess up your Mac.
First we need to build the docker image. Go to your documents and make a folder called docker-lfs
in here make another folder called Dockerfile
and copy the following into it:
FROM debian:latest
RUN apt-get update
RUN apt-get install -y git fakeroot build-essential ncurses-dev xz-utils libssl-dev bc flex libelf-dev bison cpio
RUN apt-get install -y qemu-system-aarch64 vim nano
CMD ["/bin/bash"]
This has exactly the right requirements to build linux from scratch. Now we need to build the docker image. Inside the docker-lfs
folder run the following command:
docker build --platform linux/arm64 -t linux-dev .
This will now take a while to build the docker image depending on your internet speed and the chip you have.
We need to download the sources including the linux kernel and busybox.
cd ~/Documents
git clone https://www.github.com/torvalds/linux
git clone https://www.github.com/mirror/busybox
Tip
In future if the builds fail you can always re-download a stable version of the linux kernel from https://www.kernel.org/ and busybox from https://www.busybox.net/downloads/
99% of the time the builds fail because of a new version of the linux kernel which might have a bug in it. Always check with the stable version of the linux kernel and busybox when encountering a build error.
Now we need to start the docker container. Go to the docker-lfs
folder and make a run.sh
and copy the following into it:
sudo docker run -it --privileged --cap-add SYS_ADMIN --cpus=2 --platform linux/arm64 -v ~/Documents/linux:/linux -v ~/Documents/busybox:/busybox linux-dev
Now run the container
sh run.sh
You are now in the docker container. Please CHECK the following before continuing:
Important
CHECK if you are in arm64 mode by running uname -m
or arch
it should say arm64
or aarch64
if it doesn't then you are not in arm64 mode and you need to restart the docker container and check if linux/arm64
is on the start.sh
.
Important
CHECK if the linux and busybox folders are mounted correctly by running ls /linux
and ls /busybox
it should list the contents of the folders. If it doesn't then you need to restart the docker container and check if the paths are correct on the start.sh
.
Now we need to build the linux kernel. First we need to configure the kernel. Run the following commands:
cd /linux
make menuconfig
You can now configure the linux kernel to your needs. To save and exit press esc
twice and press save.
make -j$(nproc)
Important
CHECK Has there been a file created called arch/arm64/boot/Image
if not then the build has failed and you need to check the error messages and fix them.
Now we need to build busybox. First we need to configure busybox. Run the following commands:
cd /busybox
make defconfig
make menuconfig
IMPORTANT STEP In the busybox configuration we need to change the Build static binary (no shared libs)
to y
and then save the configuration. This configuration is stored in settings>build options>build static binary (no shared libs)
.
Now we can build busybox:
make -j$(nproc)
Now we need to install busybox to our source directory:
make install
Important
CHECK Has there been a folder created called /busybox/_install
. This should contain the busybox binary's and the base initramfs. If not then the build has failed and you need to check the error messages and retry this step.
This might sound odd but becuase our /busybox
folder is shared with our Mac we need to copy the busybox sources to a new folder name so we dont have the following error when using mknod
later on:
Operation not permitted
To do this run the following commands:
cd /
cp -r /busybox /busybox-2
Now we need to create the initramfs. Run the following commands:
cd /busybox-2/_install
mkdir -p dev
mknod dev/console c 5 1
mknod dev/ram b 1 0
We now need to make a init
script which will be the first thing that runs when we boot linux. Run the following command:
nano init
Or if you prefer vim:
vim init
This should put you in a text editor. Copy the following into it:
#!/bin/sh
mkdir /proc /sys /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t tmpfs none /tmp
echo "Welcome to LSFMac"
exec /bin/sh
Now we need to make the init script executable:
chmod a+x init
Now we need to make the initramfs:
find -print0 | cpio -0oH newc | gzip -9 > /initramfs.cpio.gz
In order for us to run it we need to move this initramfs to our mac:
mv /initramfs.cpio.gz /busybox/initramfs.cpio.gz
Now open finder and go to your Documents/busybox
folder and you should see a file called initramfs.cpio.gz
. Drag it to your user folder and rename it to initramfs.cpio.gz
. This will be important later on.
Now we have everything we need to run the linux kernel. Run the following command on your mac not in the docker container:
qemu-system-aarch64 -kernel ~/Documents/linux/arch/arm64/boot/Image -initrd ~/initramfs.cpio.gz \
--append "root=/dev/ram rw init=/init.sh" -nographic \
-machine virt \
-cpu cortex-a57 \
-m 2G \
This should boot linux and you should see the following:
Welcome to LSFMac
~ #
Congratulations you have now successfully built linux from scratch on a M series Mac.