Outils pour utilisateurs

Outils du site


issue90:labo_linux_1

In the first part of this series, we saw what the Linux kernel is, and in the second chapter we saw the various ways of obtaining the source code and the other pieces we need to compile it. Now that we have all the bits and required pieces, in this third part we are about ready for the main course: configuring, compiling and installing the kernel.

In this part, I will be using specifically the version of the kernel source code from the Ubuntu repositories. There will be few differences if the reader should choose to use the version downloaded directly from the Kernel.org project. One reason to do so would be to work on the most recent kernel version – or even, if we are feeling really adventurous, on a release candidate for the next version.

THE KERNEL CONFIGURATION SYSTEM

If we take a look at the source code directories and the files contained within, we can find a series of files whose purpose we can quickly understand. Files with extension .c are clearly source code files in the C programming language, and those with extension .h are header files for the same code. In part 2 of this series, we also learned that the Makefile we find in each directory and subdirectory is a file that gives the compiler instructions on how to compile the source code: which source files to compile, how the output files are to be named, and what compiler parameters are to be appended.

When we peruse each Makefile, we can see that the file in each directory refers only to the source code placed in that directory. This means there is a separation between the different parts of the kernel source tree: each directory or subdirectory may be compiled independently. When we come to the concept of kernel modules, we will see this means we will be able to compile just a single module at a time, without having to compile the entire kernel if it is not necessary.

But what about the Kconfig files we can also find in each directory?

The Kconfig files are instruction files targeted at the kernel configuration system. The Linux kernel is a very large piece of code. In fact, it hit 15 million lines of code back in 2011 (see http://arstechnica.com/business/2012/04/linux-kernel-in-2011-15-million-total-lines-of-code-and-microsoft-is-a-top-contributor/) and 17 million lines in June 2013 version 3.10 (http://www.extremetech.com/computing/175919-who-actually-develops-linux-the-answer-might-surprise-you). These two references are quite interesting, by the way, since both address the question of who contributes to the kernel source code.

With such a behemoth to compile, we will need some sort of automated configuration system. This is where the Kconfig files come in, giving instructions on which options are available in each directory, and helping create a giant kernel configuration script.

For example, in source directory security/selinux, the Kconfig file contains the stanza:

config SECURITY_SELINUX_BOOTPARAM

      
bool "NSA SELinux boot parameter" 
depends on SECURITY_SELINUX
default n 
help 
  This option adds a kernel parameter 'selinux', which allows SELinux to be disabled at boot. If this option is selected, SELinux functionality can be disabled with selinux=0 on the kernel command line. The purpose of this option is to allow a single kernel image to be distributed with SELinux built in, but not necessarily enabled. 

If you are unsure how to answer this question, answer N.

This should be largely self-explanatory. The stanza indicates the configuration script that the user must be shown a boolean (true/false) checkbox, through which the new kernel can be configured to accept or not the “selinux” boot parameter that allows the Security Enhanced Linux kernel module (SELinux) to be deactivated at boot. Naturally, this is not a very good idea on a production system, which is why the default option is “n” - for “no”.

In file net/ipv6/Kconfig, we find a more complex example:

config INET6_TUNNEL

      tristate 
      default n 

config IPV6_TUNNEL

 tristate "IPv6: IP-in-IPv6 tunnel (RFC2473)" 
 select INET6_TUNNEL 
 ---help--- 
     Support for IPv6-in-IPv6 and IPv4-in-IPv6 tunnels described in 
     RFC 2473. 
     If unsure, say N. 

The first stanza concerns the module that allows the kernel to create tunnels through IPv6 address space. The user will, in this case, be shown a tristate option box, that will give several options: • “Y” to compile the module directly into the kernel. It will be included in the vmlinuz file and loaded at system boot, whether used or not. • “N” to exclude the module from the new kernel. • “M” to compile the module as a loadable file, that will not be loaded into RAM at boot, but only if required during system operation.

The second stanza depends on the presence of the above: if present, the user may configure support for RFC2473 tunnels in either modular or built-in forms.

Now, we need to access the configuration script itself. However, before doing so, it is usually recommended to begin by clearing up any leftover configuration. To do so, issue:

$ make mrproper

As discussed in part 2, we have at least four different configuration scripts we can use. Two are based on text environments: “make config” and “make menuconfig”. Two more are based on graphical toolkits: “make xconfig” on the Qt toolkit, and “make gconfig” on the Gtk libraries. Take your choice – as a last resort, all of these scripts rely on the same Kconfig files. In my case, I will be using

$ make menuconfig

largely because I am comfortable with this lightweight environment that I have been using since way back when (Slackware days, to be precise). This is what you should see something like the image shown below.

CONFIGURING THE KERNEL

Options within brackets are the boolean choice widget that allow us to activate “[*]” or deactivate “[ ]” a feature. Some choices may be forced upon us by other options we have taken previously, in which case the widget will appear as “-*-”. Options within keys correspond to the tristate widget, that allow us to activate a feature directly within the kernel “<*>”, as a loadable module “<M>” or deactivate the feature “< >”. In this latter case, the feature will not be available at all to the new kernel.

Options with the “—>” tail on their description indicate a sub-menu that can be accessed with the ENTER key. Most navigation keys are indicated on-screen, the only prominent exception being the SPACE key that is quite useful to switch between option values.

Most available options will not be immediately of use when compiling our first kernel. I would suggest the reader leave the default options on, they are fine for typical use patterns. Instead, I would like to point out several specific features that may be more of interest.

The first place I would stop at is the very first option presented in the menu, “64-bit kernel”. It does seem likely that one could compile a 64-bit kernel on a 64-bit platform (computer and operating system), since the appropriate C library functions will be available, and likewise for compiling a 32-bit kernel on a 32-bit system. However, it should also be possible in theory to go a little further in the Linux world, as for UNIX from which it is derived. In these systems, it should be quite possible to perform what is called “cross-compiling”, in which a program destined for one platform is compiled upon another. This holds both for compiling a 64-bit kernel on a 32-bit machine, and the opposite. Unfortunately, in practical terms my experience with the Ubuntu 14.04 distribution and kernel source code version 3.13.11.2 leads me to say that things are broken and cross-compiled kernels will actually compile, but will not run on the target computer (the new kernel will not find the init program, even with the appropriate “init=” kernel parameter). So the takeaway for the time being is indeed that we need to compile a 32-bit kernel on a 32-bit operating system, and a 64-bit kernel on a 64-bit computer.

At the second option, “General setup”, we have access to several very basic choices for our new kernel. We shall leave most of these alone, except for “Default hostname” and “Arbitrary version signature”. These two options are the ones that stamp each kernel with the information that can be retrieved in the /proc virtual file system. Try this out on your computer, it can do no harm:

$ cat /proc/version_signature Ubuntu 3.13.0-24.47-generic 3.13.9

In my case, no hostname is indicated on which the kernel has been compiled, so whoever compiled kernel 3.13.0-24 for Linux Mint left the Default hostname option at its default “(none)” value. On the other hand, the “Ubuntu 3.13.0-24.47-generic 3.13.9” character string is what they had in the “Arbitrary version” option.

I have changed these in the screen capture (below), since it is always a Good Idea ™ to give your kernels an identifying string. This can help understand later on what particular purpose you were compiling a particular kernel for. A version number can also come in useful when a series of kernels are compiled to try to solve a particular problem: they can be used to note and keep track of progress.

Let us go back up to the initial menu level, and enter the “Processor type and options” configuration setting. This is where the heavy lifting starts, and we can fine-tune our new kernel to the hardware we wish to run it on. This section also gives us a feeling for the extreme range of different physical architectures the Linux kernel handles: specific microcode for Intel and AMD range processors, software options such as Linux as a guest virtual machine operating system within Linux itself, etc.

If we stop a minute at the “Symmetric multi-processing support” option, this is where we can deactivate multi-processor support within the kernel. Some of us still remember when multi-processor support was a (paid) extra on a Windows system, even the server variants. In any case, it is standard in the Linux kernel since the 2.0 version series. Though it can be deactivated, this really makes little sense at this point in time. Most current processors contain multiple cores, or at the very least HyperThreading which makes a single core appear to the operating system to contain various logical cores (usually two per physical core). SMP is the subsystem that handles all this. On the other hand, when compiling a kernel for a very limited processor on a machine with very little RAM, it is possible to remove this part of the kernel and release a few tens of kBytes of RAM that otherwise would be occupied.

Going down to the “Processor family” sub-menu, we are given the choice of a specific family of processors to compile for. If we have chosen to compile a 64-bit kernel, the choice is between the original Opteron/Athlon family, the older and newer Intel Xeon families, the 64-bit Intel Atom, and finally a default “Generic-x64-64” option. This latter is the most conservative choice, and possibly the best if our new kernel may eventually get executed on more than one computer.

If we have chosen to compile a 32-bit kernel, the range of options is rather more vast, reflecting the evolution of the IA-32 processors over the years. The very early i386 has now been removed, and choices start at the i486, going on up through the various generations of 32-bit Pentium I, II, III and IV processors, several variants by AMD and other brands, ending up with the Intel Core 2 and 32-bit Intel Atom. As a general policy, it is perhaps best to aim lower rather than higher, since more recent processors will usually have backward-compatibility with older offerings. Nowadays, compiling a kernel with the “Pentium-III/Celeron/Pentium-III Xeon” is probably a reasonable choice for most use cases (below left).

As mentioned in the first part of this series, there has been some talk about the recent move by some distributions to include the Physical Address Extension (PAE) feature by default in default kernels. Some versions of the Pentium III had this deactivated within the hardware, so a kernel that has PAE activated cannot work on these processors. In order to compile a kernel with PAE deactivated, in the first place it must be a 32-bit kernel: 64-bit versions always contain a mechanism similar to PAE since these processors are built to handle more than 4GBytes of memory – this is one of the advantages of using numbers with more bits in your architecture.

When you have chosen the 32-bit kernel option, navigate to “Processor type and features”, and towards the last third of the list there is an option called “High Memory Support” (shown below right). This needs to be activated in order to access the full contents of a 4 GByte RAM memory, or to go further up to 64 GBytes. If the 64 GByte option is on, the PAE option will be inserted into the menu a bit lower down. If the High Memory Support is either off (use up to 3 GBytes of RAM) or at the 4 GByte choice, PAE should be automatically deactivated.

Finally, if you wish to examine and/or configure the additional drivers contributed by Canonical to the kernel source, navigate back to the main menu and you will find a separate “Ubuntu Supplied Third Party Drivers” sub-menu (shown below) that contains some of their input. Naturally, this is included only with the version of the kernel code from the Ubuntu repositories.

When you are satisfied with your choices, exit the configuration menu, saving the configuration in the default file .config .

COMPILING THE NEW KERNEL

Compiling the kernel has two different stages: compiling the kernel itself, and compiling the loadable modules – though this second part is performed only if the option for modules has been activated, as it usually is.

To start this quite lengthy process, issue the command:

$ make

and the default Makefile target of compiling the kernel will be executed. Initially, this command compiled only the kernel proper, however in recent versions of the kernel source both the kernel and its modules are compiled and updated.

Be prepared for the processor to work quite hard and for an extended period of time. It is important to make sure ventilation is adequate since the computer will tend to heat up (this is best done on a desktop machine, if at all possible), and will consume power liberally – so plug it in if running off the battery! On a dual-core Intel Core i5, the complete compilation process took about two hours:

real 126m0.103s user 117m35.622s sys 13m31.106s

If we make a change in the kernel configuration, such as changing the Arbitrary version character string as above, executing a new compilation process will need to compile only those parts that have changed. If our alteration affects only the kernel proper, all modules will need to be checked, but not compiled. Many subsystems of the kernel itself will not need to be recompiled – whole directories of the source code will be left unchanged. Compilation time will be significantly reduced, for example:

real 5m51.928s user 2m19.265s sys 0m27.180s

On the other hand, if a modification has been made in one of the modules, we can specify that just the modules need to be checked for alterations and compiled if needed, and not the kernel itself. This is managed with command:

$ make modules

and can considerably shorten compilation time, depending on the number of modules changed and the importance of these changes. For example, on my system:

real 2m42.214s user 1m29.390s sys 0m16.867s

INSTALLING THE KERNEL

Once the kernel and modules have been compiled, they can be found in the very same sub-directories as the source files. For example, in the mm (memory management) subdirectory, you will find both memory pool routines source in mm/mempool.c, and the compiled object file mm/mempool.o .

Once each source file has been compiled into an object, they must be linked together into an executable file for the kernel, and transformed into loadable module files for each module. The kernel itself is file vmlinux in the tree root, and should weigh in at about 158 MBytes. This file will need to be compressed and placed in directory /boot. Once compressed using gzip, bzip or LZMA, the kernel size can go down to the 5-6 MBytes that can be expected of Linux kernel files.

As for the drivers, their compiled and linked loadable module files have extension .ko (“kernel object”), and are distributed around the source tree side-by-side with the .c and .o files. For example, the IPv6 tunnel module will be found in compiled and linked form as net/ipv6/ip6_tunnel.ko.

In order to execute our new kernel, we will need to perform four distinct actions: • The modules must be separated from the source files, and copied into directory /lib/modules/<kernel-name>/kernel. • The kernel itself must be compressed, and the compressed file placed in /boot. • The modules must also be integrated into an initrd (initial file system) compressed file, also placed in /boot. • We must also update the GRUB bootloader configuration so as to include the new kernel in the boot options.

Luckily, there is a specific make target available to do all this automatically. Since we will be making changes in the system configuration, we will need to do it with administrator privileges – thus the “sudo” command. It is also the time when we can seriously break things in our system, so proceed with caution and only when satisfied the previous steps have taken place correctly. Then, to install the modules in /lib (step 1 above), issue:

$ sudo bash

# make modules_install

You will see each .ko file pass by on screen as it is copied over. Now, we are ready to do the kernel itself. Issue:

# make install

and the script will perform steps 2, 3 and 4 all together for you. You will now see the output of the GRUB configuration tool grub-mkconfig on screen, and in directory /boot the new files will make their appearance: • vmlinuz-3.13.11.2 (or similar): the compressed kernel; • System.map-3.13.11.2 (or similar): a table of the symbols in the kernel, and their corresponding positions in memory; • initrd.img-3.13.11.2: the compressed file system (with modules generated from /lib) needed to perform initial system boot.

TRYING OUT YOUR NEW KERNEL

Since the automatic install process has taken care of the GRUB configuration for us, all we have to do now is reboot our computer. In the GRUB menu, the first entry we find is simply labeled “Ubuntu”, and this is the one corresponding to our new kernel. At least one other entry will be found underneath, labeled “Ubuntu 14.04 LTS” or similar. This is the older kernel, still available as a backup just in case the new kernel does not work as expected.

Boot into the new kernel -just hit ENTER- and hopefully the system should come up. In fact, it should be rather difficult to note that the new kernel is being used. However, if we open a terminal and use the uname command, we should see the description and compile date of our new kernel:

$ uname -a

Linux alan-lenovo 3.13.11.2 #5 SMP Sat Jul 19 21:32:47 CEST 2014 x86_64 x86_64 x86_64 GNU/Linux

This information can also be found by listing file /proc/version , while /proc/version_signature contains the free-form “Arbitrary version” character string we entered during configuration:

$ cat /proc/version_signature

Ubuntu 3.13.0-24.47-generic-alan

If you have managed to follow us so far, congratulations! What you have just achieved is quite difficult - or nearly impossible for mortal humans - with most current operating systems. Now, have some fun and try out your new kernel. How does it compare with the old one? What about speed and memory usage?

In the next part of this series, we will be looking into how to make some changes and apply simple tweaks to our kernel, and how they affect system performance.

issue90/labo_linux_1.txt · Dernière modification : 2015/01/02 15:34 de andre_domenech