***************************************************************************** * MAKING A KERNEL IMAGE * * * * * * Copyright (c) 2001 Daniel P. Bovet, Marco Cesati, and Cosimo Comella * * Permission is granted to copy, distribute and/or modify this document * * under the terms of the GNU Free Documentation License, Version 1.1, * * published by the Free Software Foundation; with no Invariant Sections, * * with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the * * license is included in the file named LICENSE. * * * * (version 1.1) * ***************************************************************************** The Linux kernel is a huge program. Roughly 95% of it is written in C and the remaining part in Assembly language. Kernel hacking is the activity consisting of looking at the kernel code, changing it, either to add new features or to improve existing ones, and testing the modified kernel version. Even if we change a single line of C or assembly code, the kernel must be recompiled and a new kernel image must be produced. Clearly, the tests will have to be done on the new image and not on the old one. So the first thing to do before attempting any kernel hacking is to learn how to compile the kernel, and thus how to make a kernel image. This process includes five main steps: 1) making a new kernel version 2) configure the set of kernel functionalities we want to include 3) compile the kernel 4) produce a kernel image 5) restart the system using the new kernel image and check whether the modified kernel works properly Kernel hacking has been played for many years by the Linux community and several tools have been built that help considerably in producing kernel images. We'll mention them while expanding on the five basic steps listed above. ***************************************************************************** * 1) MAKING A NEW KERNEL VERSION * ***************************************************************************** Official Linux kernel versions are characterized by three numbers that appear in the first three lines of the main Makefile: Version, Patchlevel, and Sublevel. For instance, Linux 2.4.12 means version 2, patchlevel 4, and sublevel 12. No matter how you plan to change the kernel, you will have to start from some existing official Linux kernel version, for instance Linux 2.4.12. We thus strongly suggest to name your patched version by making use of a fourth identifier called Extraversion (fourth line of the main Makefile). This identifier is a sequence of characters that are concatenated to the sublevel. So these are the steps to create a fresh copy of the kernel code to be modified at will without any risk of corrupting the official version. cd /usr/src (this is where the linux kernel source is located) cp -r linux-2.4.12 linux-2.4.12kh1 (we are using "kh1" as EXTRAVERSION) rm linux (remove previous symbolic link) ln -s linux-2.4.12kh1 linux (make new symbolic link) cd linux use a text editor to edit Makefile and set "EXTRAVERSION = kh1" From now on, the directory containing our own kernel image, that is, linux 2.4.12kh1. If we restart the computer with our patched kernel, the label displayed right before the login prompt will be: "linux 2.4.12kh1". This is handy for remembering which kernel version we are currently using. ***************************************************************************** * 2) CONFIGURE THE KERNEL TO BE COMPILED * ***************************************************************************** Configuring a kernel means selecting the kernel functions and the kernel device drivers according to your hardware configuration and according to your needs. The main Makefile included in the linux 2.4.12kh1 directory will guide you in the selection process. We'll assume that our current working directory is: /usr/src/linux, that is, /usr/src/linux-2.4.12kh1. Then you can either type: make oldconfig or: make menuconfig You should type "make oldconfig" if a .config file already exist in the linux-2.4.12kh1 directory (this is usually the case if the linux-2.4.12 kernel has been compiled at least once and if you didn't run "make distclean" on it). You must type "make menuconfig" if there is no .config file in the linux-2.4.12kh1 directory (this happens if a "make distclean" command was issued on the linux-2.4.12 directory before it was copied into the linux-2.4.12kh1 one). You can start "make menuconfig" either from an xterm window of XWindow or from an alphanumeric console. In the first case, make sure that the size of the xterm window is large enough (80-character lines). Otherwise, the following message will appear: "Your display is too small to run Menuconfig! It must be at least 19 lines by 80 columns." The main configuration options available for linux 2.4.12 are listed below: Code maturity level options ---> Loadable module support ---> Processor type and features ---> General setup ---> Memory Technology Devices (MTD) ---> Parallel port support ---> Plug and Play configuration ---> Block devices ---> Multi-device support (RAID and LVM) ---> Networking options ---> Telephony Support ---> ATA/IDE/MFM/RLL support ---> SCSI support ---> Fusion MPT device support ---> IEEE 1394 (FireWire) support ---> I2O device support ---> Network device support ---> Amateur Radio support ---> IrDA (infrared) support ---> ISDN subsystem ---> Old CD-ROM drivers (not SCSI, not IDE) ---> Input core support ---> Character devices ---> Multimedia devices ---> File systems ---> Console drivers ---> Sound ---> USB support ---> Bluetooth support ---> Kernel hacking ---> Please notice that new patchlevels may introduce further configuration options as new features and drivers are introduced into the kernel. Each configuration option can be answered in three possible ways by typing the character 'Y', 'M' or 'N'. Y: I want the feature to be compiled into the kernel image M: I want the feature to be compiled but as a module, external to the kernel image (see lecture on modules) N: I don't want the feature to be compiled The menuconfig makefile script uses default values for each configuration option. As a rule of thumb, you should carefully check the following options: Processor type and features ---> digit 'N' to Symmetric multi-processing support unless you own a multiprocessor box File systems ---> digit 'Y' to DOS FAT fs support digit 'Y' to MSDOS fs support x x digit 'N' to UMSDOS: Unix-like file system on top of standard MSDOS fs digit 'Y' to VFAT digit 'Y' to ISO 9660 CDROM file system support digit 'Y' to /proc file system support digit 'Y' to Second extended fs support Once you have chosen the configuration options, you leave the the make menuconfig script by answering 'Y' to the following question: "Do you wish to save your new kernel configuration?" As a result, an updated version of the .config file will replace the previous one (if it existed) in the /usr/src/linux directory. ***************************************************************************** * 3) COMPILE THE KERNEL * ***************************************************************************** Thanks to the powerful main Makefile included in the /usr/src/linux directory, compiling the kernel is quite easy. However, before doing that, you should look at the linux/include directory and check for an asm element. Three cases can occur: a) asm exists and it is a symbolic link to asm-i386: that's OK, don't change anything b) asm does not exist: create an asm symbolic link to asm-i386 c) asm exists and it is a directory of *.h files (a copy of asm-i386): remove the whole directory and create an asm symbolic link to asm-i386 Once this is settled, execute successively the following scripts: make clean (removes the *.o object files from a previous compilation) make dep (establishes the logical dependencies among the configured kernel files) make bzImage (creates a compressed compiled kernel image called bzImage in the linux/arch/i386/boot directory) make modules (compiles in the proper directories the kernel files configured as modules; make modules_install (copies the object modules produced by make modules in the lib/modules/2.4.12kh1/ directory) The size of the bzImage kernel image is less than one megabyte and it can thus be stored in a single diskette. On a PC with a 200 MHZ clock rate, "make dep" requires about 3-5 minutes and "make bzImage" requires up to 15 minutes (there are tricks to speed up the compilation process, we shall describe them later) ***************************************************************************** * 4) PRODUCE A KERNEL IMAGE (ON A FLOPPY) * ***************************************************************************** We'll proceed cautiously. At boot time, all BIOS check for devices from which to start the boot in a fixed order. Typically A: (floppy), then C: (hard disk), or: A: (floppy), then C: (hard disk), then E: (CD-ROM). The order can be modified by changing the BIOS setup at boot time. Any way, let us assume that A: is in pole position among the boot devices to be checked by BIOS. If this is the case, the only thing we have to do is to copy bzImage from linux/arch/i386/boot onto a floppy disk (remember to insert one in the driver). This can be done by issuing: dd if=/usr/src/linux/arch/i386/boot/bzImage of=/dev/fd0 bs=1k or, more simply, by issuing: cat /usr/src/linux/arch/i386/boot/bzImage > /dev/fd0 After a couple of minutes the floppy will be ready. ***************************************************************************** * 5) RESTART THE SYSTEM * ***************************************************************************** Now comes the moment of truth. Kernel compilation went smoothly. Check if the boot floppy is still inserted in the driver and restart the computer. You are the superuser so you can issue: shutdown -r now After the BIOS Power On Self Test, you should hear some noise coming from the floppy unit. That means booting from floppy. Otherwise, booting is done from the hard disk. In this case, restart the PC and set the BIOS option to A, C instead of C, A: first check if there is a bootable image in the floppy and then check if there is one in the hard disk. After rougly 60 seconds, the booting is finished. The kernel uncompresses itself, loads the main initial unix processes and the login prompt should appear. Congratulations! OK. You managed to boot Linux from a kernel you compiled yourself. You rush to visit a friend of yours and proudly insert the floppy into his or her PC. Will it work? The answer is: perhaps yes but perhaps no. Let us understand why and see when and how the floppy can be fixed to work on some other PC. The floppy will not boot from an uncompatible PC. The main differences that affect booting refer to: - keyboard type: standard keyboard vs USB keyboard - hard disk type: EIDE disk vs SCSII disk - root device, i.e., the disk partition that contains the root file system (during kernel initialization, it is necessary to mount the ext2 filesystem contained in the root device). Uncompatible mouses and monitors are not a problem because they are handled by the X Window application. Other uncompatible devices such as network cards or sound cards are a minor problem since the kernel (without networking) can run without them. Incompatible root device addresses can be fixed as follows. Suppose that the root partition of your machine is /dev/hda3 while that of your friend's PC is /dev/hda1. If you try to boot with a wrong root partition, you'll get the following message: Warning: unable to open the initial console Kernel panic: No unit found. Try passing init=option to kernel In this case, you can patch the boot floppy so that it will boot from your friend's PC by issuing the following command (with the floppy inserted in the driver): rdev /dev/fd0 /dev/hda1 The rdev program writes the major and minor number of the root device (in the example, /dev/hda1) into bytes 508 and 509 of the kernel image stored in the floppy. ***************************************************************************** * 6) SPEEDING UP THE COMPILATION PROCESS * ***************************************************************************** You are now becoming a kernel hacking addict and you end up compiling the kernel tens of times in a row. Can you speed up somehow the compilation process? Here are a few tricks that might save you considerable time. a) Avoid running make dep as much as possible since every make dep forces a recompilation of the whole kernel. In practice, if: - you didn't include any new directory or any new file to be compiled, i.e, you didn't modify any of the Makefile files - you didn't play with the menuconfig options then you don't need to run make dep. b) Avoid running make clean as much as possible since "make clean" destroys all the object files, thus forcing a recompilation of the whole kernel. If "make dep" were perfect, you should never need to run "make clean" if you don't have to run "make dep". In a few cases, however, "make dep" fails to detect dependencies and old object files that should have been recompiled may cause an erroneous compilation. We suggest to take the risk and run "make clean" as a last resort. c) If you modify a local function inside a source file, only that source file will have to be recompiled, although "make bzImage" will have to check for each source file whether its last modifed timestamp is more recent than the timestamp of the corresponding object file. d) If you modify a file containing functions defined as modules, you don't have to recompile the whole kernel. It is sufficient to run: make modules make install_modules as we shall see in the lecture on modules. e) If you have a multiprocessor box, you can compile the kernel using the -j (or --jobs) make option as follows: make -j n dep make -j n bzImage make -j n modules make -j n modules_install where n is the number of jobs running concurrently; usually n is set so that: n = #processors + 1. Pay attention to specify one target at time when using such option, otherwise the compilation process won't terminate successfully.