\input{confighandout} \subsection{Kernel konfigurieren und kompilieren} \subsubsection{Vorarbeiten} Bevor man einen Kernel konfigurieren kann, sollte man wissen, was man will. Das hört sich zunächst nach einer trivialen Binsenweisheit an, kann aber manchmal tatsächlich nicht ganz einfach sein. Zunächst sollte man möglichst viele Informationen über die fragliche Hardware sammeln. Dazu gehört die genaue Prozessorversion sowie Typen und Versionen der angeschlossenen Chips. Insbesondere die Chips, die bereits beim Booten benötigt werden, sollten klar sein. Dazu gehören auf üblichen PCs der, Chipsatz mit ATA/SATA-Controller oder, je nach gewünschter Bootquelle auch der USB- oder Netzwerk-Chip. Bei Embedded Devices kommen oft noch NAND-Controller hinzu. In diesem Fall sollte man auch über die genauen Daten der angeschlossenen NAND-Chips informiert sein. Weiterhin muss man sich überlegen, von welchen Dateisystemen man booten möchte. Gängige Varianten sind ext2, ext3, NFS, oder NAND-Dateisysteme wie jffs2 oder ubifs. FAT eignet sich nicht für ein Linux-Rootfilesystem, da es beispielsweise keine Gerätedateien unterstützt. Will man Linux von einem USB-Stick booten, so wird man diesen dazu beispielsweise mit einem ext2-Dateisystem versehen. \subsubsection{Kernel-Konfiguration} Mit diesen Informationen versehen, kann man jetzt die Konfiguration beginnen. Dies erfolgt durch Eingabe von \cmd{make menuconfig} Abbildung \ref{img:menuconfig} zeigt die oberste Ebene des Menüs, das dann erscheint. \begin{figure}[h] \centering \includegraphics[width=0.8\textwidth]{images/menu_rt_001.png} \caption{Menü zur Kernel-Konfiguration} \label{img:menuconfig} \end{figure} Am oberen Rand gibt das Menü eine stichwortartige Erklärung zur Bedienung. Man sollte bei der Konfiguration zuerst grundlegende Dinge wie den Prozessortyp angeben, da untergeordnete Eigenschaften oder Treiber davon abhängig sein können. \subsubsection{Module, ja oder nein?} Bei üblichen Kerneln für Desktop-Systeme wird man immer einen Großteil der Treiber als Module kompilieren. Solche Rechner besitzen viele Schnittstellen, über die man später die Hardware erweitern kann, und kann den Kernel bereits mit den möglicherweise vorkommenden Treiber-Modulen ausstatten. Diese werden dann nur bei Bedarf nachgeladen. Bei Embedded Systems ist die Situation anders. Hier ist die Hardware meist fest vorgegeben, so dass man schon beim Kompilieren genau weiss, welche Treiber benötigt werden. Ausserdem ist der im Rootfs zur Verfügung stehende Platz oft beschränkt, man wird hier ungern Speicher für unnötige Treiber-Module verschwenden. Andererseits ist es manchmal praktisch, einen Treiber als Modul vorliegen zu haben. Speziell während der Entwicklung eines eigenen Treibers ist es vorteilhaft, dass man einfach durch Austausch des Moduls eine neue Version ausprobieren kann. Unter Linux muss man dabei nicht einmal neu booten, sondern kann einen Treiber mit \cmd{modprobe mein\_modul} installieren und mit \cmd{rmmod mein\_modul} wieder deinstallieren. Die Entscheidung, ob man einen Kernel ganz ohne Module baut, hängt vom Einzelfall ab. Bei Embedded Systems kommt dies aber durchaus öfter vor. Aus verständlichen Gründen dürfen Treiber, die zum Booten und Mounten des Rootfs benötigt werden, niemals als Module gebaut werden! \subsubsection{initrd} Die Kernel von Distributionen haben das Problem, dass sie auf möglichst vielen unterschiedlichen Rechnern booten müssen. Würde man die Treiber für alle denkbaren Kombinationen fest in den Kernel einkompilieren, so hätte man einen riesigen Kernel, von dem der Großteil des Codes niemals benutzt würde. Die statt dessen verwendete Lösung besteht in einem Verfahren, bei dem der Kernel beim Booten eine RAM-Disk anlegt und diese mit einem vorbereiteten Image füllt. Das Image enthält sämtliche in Frage kommenden Treiber als Module. Nach dem Booten und dem Mounten des Root-Filesystems kann die RAM-Disk wieder aus dem Speicher entfernt werden. Dieses Verfahren nennt man \emph{initrd} (von \emph{Initial RAM Disk}). Wenn man einen Kernel für ein bestimmtes Board selbst kompiliert, braucht man nie eine initrd, da man ja genau die richtigen Treiber fest einkompilieren kann. \subsubsection{initramfs} Ein anderes Verfahren, bei dem der Kernel zunächst eine RAM-Disk anlegt, ist \emph{initramfs}. Im Unterschied zu initrd wird diese aber nicht aus einem externen Image geladen, sondern das Image wird bereits beim Kompilieren zum Kernel dazugelinkt. Auch hier wird der von der RAM-Disk belegte Speicher am Ende wieder freigegeben. Das initramfs wird vom Kernel wie ein normales Root-Filesystem gemountet. Üblicherweise verwendet man ein minimales System, etwa basierend auf uclibc und einer busybox. Man kann in den Startskripten dieses Minimalsystems jetzt Aktionen ausführen, bevor das eigentliche Rootfs gemountet wird. Eine Anwendung dieses Verfahrens ist ein kleiner Bootkernel, der von einem IPL gestartet wird. In seinem initramfs befinden sich Startskripte, die den eigentlichen Produktiv-Kernel aus einer beliebigen Quelle nachladen und dann per \cmd{kexec} starten. Der Vorteil bei diesem Verfahren ist, dass der Bootkernel bereits jeden beliebigen Treiber (beispielsweise einen kompletten USB-Stack) enthalten kann, während Bootloader meist nur unvollständig mit den gängigsten Treibern versehen sind. Eine weitere Anwendung sind Firmware-Updates. So könnten die Startskripte des initramfs beispielsweise auf einem Server nachschauen, ob Updates vorhanden sind, und diese dann anwenden. \subsubsection{Kernel kompilieren} Nach abgeschlossener Kernel-Konfiguration kompiliert man den Kernel einfach durch Eingabe von \begin{lstlisting} make \end{lstlisting} Falls der zum Kompilieren verwendete Rechner mehrere CPUs besitzt, kann man den Vorgang beschleunigen, indem man \cmd{make} anweist, mehrere Jobs gleichzeitig zu starten. Für die Zahl der Jobs ist eine häufig genannte Empfehlung die doppelte Anzahl der CPUs plus eins. Auf einem Dual-Core würde man also folgenden Befehl verwenden: \begin{lstlisting} make -j3 \end{lstlisting} Will man den Kernel für eine andere Architektur als die des Compile-Rechners crosskompilieren, so muss man \cmd{make} mitteilen, mit welcher Toolchain und für welche Architektur gebaut werden soll. Damit man die doch etwas länglichen Optionen nicht immer wieder von Hand eintippen muss, schreibt man sie am Besten in ein kleines Skript, das man dann anstelle von \cmd{make} aufruft. Ein Skript für die ARM-Architektur unter Verwendung der Codesourcery-Toolchain 2007q3 könnte etwa so aussehen: \begin{lstlisting} #!/bin/sh ARCH="arm" CROSS="/opt/arm-2007q3/bin/arm-none-linux-gnueabi-" export INSTALL_MOD_PATH="/rfs" make ARCH=$ARCH CROSS_COMPILE=$CROSS $@ \end{lstlisting} Speichert man dies beispielsweise unter dem Namen \cmd{makearm} im Hauptverzeichnis der Kernelquellen, so ergibt sich folgender Aufruf: \begin{lstlisting} ./makearm -j3 \end{lstlisting} \subsubsection{Kernel installieren} Einen Kernel für den lokalen Rechner (also den, auf dem man auch kompiliert hat) installiert man einfach durch Eingabe von \begin{lstlisting} make install \end{lstlisting} Dies führt folgende Aktionen durch (Beispiel für einen Kernel 2.6.30): \begin{itemize} \item Falls es im Verzeichnis \cmd{/boot} bereits ein \cmd{vmlinuz-2.6.30} gibt, wird es in \cmd{vmlinuz-2.6.30.old} umbenannt \item Kopieren von \cmd{arch//boot/zImage} nach \cmd{/boot/vmlinuz-2.6.30} \item Anlegen eines symbolischen Link \cmd{vmlinuz -> vmlinuz-2.6.30} in \cmd{/boot} \end{itemize} Anschliessend darf man nicht vergessen, auch die Module des neuen Kernels zu installieren: \begin{lstlisting} make modules_install \end{lstlisting} Dabei werden alle Module (Dateien mit der Endung \cmd{*.ko} in Verzeichnisse unter \cmd{/lib/modules/2.6.30/...} kopiert. Ausserdem werden dort Zusatzinformationen zu den Modulen und ihren Abhängigkeiten generiert. \emph{VORSICHT!} Wenn man einen Kernel für eine andere Architektur crosskompiliert hat, darf man \emph{NIEMALS} leichtfertig \cmd{make modules\_install} aufrufen, da dies ohne weitere Warnung die eigenen Module mit den natürlich nicht verwendbaren Modulen der fremden Architektur überschreiben würde! Das oben aufgeführte Skript zeigt die korrekte Vorgehensweise. In der Variablen \cmd{INSTALL\_MOD\_PATH} kann man ein anderes Root-Filesystem angeben, in das die Module installiert werden sollen. Im Beispiel oben würde der Befehl \begin{lstlisting} ./makearm modules_install \end{lstlisting} die Module nach \cmd{/rfs/lib/modules/2.6.30/...} installieren. Hat man sein Root-Filesystem per NFS aus diesem Verzeichnis gemountet, so stehen die Module sofort und ohne Reboot zur Verfügung. Diese Vorgehensweise hat sich bei der Entwicklung von Treibern für Embedded Systems sehr bewährt. \subsubsection{Kontrollfragen} \begin{enumerate} \item Beschreiben Sie Unterschiede zwischen initrd und initramfs. \item Warum braucht man bei selbst kompilierten Kerneln in der Regel keine initrd? \item Nennen Sie eine Anwendung von initramfs. \end{enumerate} \input{tailhandout}