5.7 Building a Custom Kernel: make menuconfig and make
Right, so you’ve decided to build your own kernel. Congratulations and my condolences. This is where we separate the tinkerers from the people who just want their Netflix to work. The payoff is a system tailored precisely to your hardware, potentially faster, more secure, and free of the cruft the distro maintainers threw in because they had to please everyone. The cost is, well, your afternoon and a non-zero chance of building a doorstop. Don’t worry, I’ve bricked more systems than I can count, so you’re in good company.
The entire process hinges on two commands: make menuconfig and make. The first is where you make your fateful choices; the second is where you wait for your CPU to hate you.
The .config File: Your Kernel’s DNA
Before you even type make menuconfig, understand what it’s manipulating: a hidden file in your kernel source directory called .config. This text file is a massive list of CONFIG_ options set to =y (yes, build into the kernel), =m (yes, build as a loadable module), or =n (no, skip it entirely). The menuconfig tool is just a sexy, navigable interface for this otherwise horrifyingly long and complex file. Your first step is always to get a sane starting point. The best way? steal it.
# Navigate to your kernel source directory
cd /usr/src/linux-my-awesome-build
# Copy your current distribution's kernel config as a starting point.
# This is the pro move. It ensures you start with something that actually boots.
zcat /proc/config.gz > .config
# If /proc/config.gz doesn't exist (if your current kernel wasn't built with CONFIG_IKCONFIG_PROC), try looking in /boot:
cp /boot/config-$(uname -r) .config
# Now, prepare the config for your new kernel version. This will ask you about new options.
make olddefconfig
The make olddefconfig command is pure genius. It takes your old .config, figures out what kernel version it was for, and intelligently updates it for the new source code. It sets new options to their safe default values without pestering you. It’s the first thing you run and it saves you from about 500 confusing prompts.
Navigating make menuconfig Without Losing Your Mind
Fire it up: make menuconfig. You’re now in a nostalgic, text-based labyrinth. Use the arrow keys to navigate, Enter to enter submenus, Y to enable, N to disable, M for module, and the spacebar cycles through them. Esc Esc exits a menu or the program. The search function (/) is your best friend. Need to find the option for your specific weird network card? Hit /, type the driver name, and it’ll tell you exactly where to find it.
The hierarchy is actually logical. Device Drivers -> Network device support -> Ethernet driver support -> and so on. The art is in knowing what to strip out. Do you have a single SSD? Why are you enabling SCSI disk support? Do you have an ancient ISA sound card from 1998? No, you don’t. Turn that junk off. The goal is to deselect entire categories of hardware you will never, ever own.
But here’s the critical part: be utterly paranoid about your boot drive. Under Device Drivers -> SCSI device support, you must have support for your SCSI controller (y or m) and SCSI disk support (y). Under Device Drivers -> Block devices, ensure support for your specific drive controller (e.g., NVMe Support). Getting this wrong means the kernel won’t find your root filesystem, and you get a panic. It’s the most common way to screw this up.
The Marathon: Running make and Its Variants
With your .config perfected, it’s time to build. This is a CPU-bound marathon.
# The classic. This will use every single thread your CPU has.
make -j$(nproc)
# If you want to leave some cores for, you know, using your system, specify a lower number.
make -j4
# You can also build specific targets, like just the modules, after the fact.
make modules
The -j flag tells make how many jobs to run in parallel. $(nproc) just spits out your CPU thread count. This will max out your system. Your fans will spin, your system will lag, and you’ll wonder if it’s worth it. It is. Probably.
The build process is a two-part dance: first it builds the core kernel binary (vmlinuz), and then it builds all the modules you marked with =m. The entire thing can take from 20 minutes on a beastly workstation to several hours on a Raspberry Pi. Don’t interrupt it.
Installation: The Moment of Truth
Building is one thing; installing is another. This is where you must pay attention.
# Install the modules to /lib/modules/<kernel-version>/
sudo make modules_install
# Copy the kernel binary and System.map to /boot/
sudo cp arch/x86/boot/bzImage /boot/vmlinuz-my-awesome-kernel
sudo cp System.map /boot/System.map-my-awesome-kernel
# NOW, and this is crucial, update your bootloader.
# For GR2:
sudo update-grub2
The modules_install target is brilliantly simple. It just slams all the freshly built modules into /lib/modules/$(uname -r)/ (but with your new kernel’s version number, of course). The manual copy commands are where you can fatally screw up. You must copy the bzImage file (found in arch/x86/boot/ for most of us) to /boot/ and give it a clear, distinct name. Do NOT overwrite your existing kernel. That’s a recipe for a recovery USB stick session.
Finally, you MUST update your bootloader. GRUB doesn’t just magically know about your new kernel. update-grub2 scans /boot and adds it to the menu. If you skip this step, you’ll reboot and wonder why nothing changed. It’s the equivalent of packing for a trip and then leaving the suitcase in the hallway.
Reboot, cross your fingers, and select your new kernel from the boot menu. If it panics, note the error, reboot into your old kernel, and go back into menuconfig to fix what you broke. It’s a rite of passage. Welcome to the club.