summaryrefslogtreecommitdiff
path: root/kernel-devel/kernel-build/handout_kernel-build_de.tex
blob: 0e96858a22d6acc23129f254be69112d1f785d0f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
\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/<Architektur-Name>/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}