Raspberry Pi: linux from scratch

Ano je to tak. Neodolal jsem a konečně jsem si pořídil Raspberry Pi :) Na rozdíl od jiných, kteří s Raspberry tvoří herní konzole a multimediální centra, se s ním hraju trochu na nižší úrovni a objevuji hlubiny hardwaru.

V tomto textu ukážu, jak si pro Raspberry zkompilovat a zprovoznit vlastní Linuxový systém, neboli “cross linux from scratch”. Pokud se nebojíte pracovat v konzoli a zajímá vás, jak fungují vnitřnosti Linuxu, pak bez obav pokračujte ve čtení…

Motivace

Aneb proč se zabývat vytvářením vlastního systému, když jich pro Raspberry existuje víc než dost?

  • Dozvíte se, jak takový Linuxový systém funguje a co všechno obsahuje.
  • Dozvíte se více o samotném Raspberry.
  • Získáte minimalistický systém, který se vejde do necelých 40 MB.
  • Dostanete z Raspberry maximální výkon pro vaši aplikaci, v systému nebude nic, o čem byste nevěděli.

Stále vám to nestačí? No tak co třeba: je to hezká kratochvíle :)

Linux

(Předem se omouvám panu Stallmanovi a jeho fanatickým zástupcům za nesprávné používání pojmů Linux a GNU/Linux ;))

Pokud vlastníte Raspberry Pi, pak pravděpodobně používáte linuxový systém Raspbian, Pidora, RaspBMC, Arch nebo něco podobného. Tyto systémy mají společnou jednu věc – jsou to systémy založené na Linuxu. To znamená, že obsahují jádro (kernel) Linux a nad ním pak běží aplikace. Tomuto celku pak můžeme říkat linuxový systém nebo přesněji GNU/Linux.

Nechci zde zabýhat do teorie okolo operačních systémů. Pokud se chcete pustit do stavby vlasního systému, tyhle věci byste už měli alespoň zhruba znát. Takže jen pro jistotu, abychom si ujasnili pojmy: kernel je speciální aplikace, která zprostředkovává komunikaci mezi hardwarem a aplikacemi. Zároveň umožňuje komunikaci mezi aplikacemi navzájem, rozděluje mezi ně omezené hardwarové prostředky a paměť a řeší spouštění aplikací (multitasking) a hlídá je, aby neprovedly nějakou hloupost (většinou).

Operační systém se skládá z aplikací a jádra (kernelu). (Zdroj: Wikipedia)

Na úrovni aplikací jsou pak standardní Unixové nástroje, většinou implementace z projektu GNU. Tyto aplikace umožňují interakci s uživatelem, mohou spolu skrz jádro komunikovat a zároveň přes jádro komunikovat s hardwarem.

Všechno to v praxi funguje asi takto: po zapnutí Raspberry se aktivuje aplikace zvaná zavaděč. Ta připraví nejnutnější minimum, načte z SD karty jádro do paměti RAM a spustí jej. Jádro provede základní osahání hardware, nastaví jej podle potřeby a poté spustí aplikaci init. Ta většinou spustí sadu nějakých skriptů (říkejme jim init skripty), které připojují další souborové systémy z SD karty, spouští základní aplikace (např. syslog, klogd, fsck, konfiguruje síťové rozhraní a mnoho dalšího) a po jejich dokončení se nejčastěji spustí aplikace getty, která očekává vstup od uživatele (jeho přihlašovací údaje) a poté spustí terminál (bash nebo cokoliv jiného).

Popsaný způsob je ale možné změnit. Pokud si přejeme opravdu minimalistický systém, můžeme aplikaci init úplně vypustit a jádro místo ní spustí rovnou terminál. Případně po dokončení init se nemusí spouštět getty ale cokoliv jiného. Vše je na nás a na tom, co chceme s Raspberry dále dělat. Ostatně, pokud jste dobrodružné povahy a máte spoustu času, nemusíte používat ani Linuxové jádro. Zavaděč může spoutět vaši vlastní aplikaci rovnou a vy tak budete mít celý hardware pod kontrolou.

Výše zmínené systémy jako Raspbian nebo Pidora jsou vyspělé operační systémy, které obsahují desítky nebo i stovky aplikací, propracované init skripty a Linuxové jádro obsahuje podporu pro všemožný hardware od USB klíčenek až po Wifi moduly. Naopak náš budoucí linuxový systém bude minimalistický, pouze s podporou nejnutnějšího hardware a s jednoduchými skripty. Na tomto základu se naučíme jak se všechno skládá dohromady a jak to funguje.

Projekt „linux from scratch“

Svůj první Linuxový systém jsem si skládal asi v roce 2008 pro starou 486ku s 16 MB paměti RAM. Postupoval jsem podle projektu linux from scratch (LFS), který si klade za cíl ukázat čtenáři, jak takový operační systém založený na Linuxovém Jádru funguje a co všechno obsahuje. Tehdy mi trvalo skoro měsíc, než jsem vše dokončil a měl funkční systém, který se vejde do 16 MB paměti a na jednu disketu (no dobře, na disketě bylo jádro a bootloader, rootfs jsem tahal přes TFTP ze sítě).

Nové Raspberry tak byla vynikající záminka k oprášení Linuxových vědomostí. Projekt LFS má totiž i další podprojekty, z nichž nás teď bude nejvíc zajímat cross linux from scratch (CLFS), resp. cross linux from scratch embedded. Cílem CLFS je vytvořit plnohodnotný linuxový systém pro jinou platformu, než na které se kompiluje. Teorie je taková, že si na osobním počítači (platforma x86 nebo x86_64) můžete zkompilovat systém například pro platformu ARM.

Projekt CLFS embedded je ale v začátcích a stále se vyvíjí. Navíc, aby to nebylo tak jednoduché, Raspberry Pi má dost specifický hardware a postupy popsané v CLFS je potřeba upravit. To je také důvod, proč vznikl tento článek – chci zdokumentovat kroky odlišné od CLFS. Pokud máte dobrodružnou povahu, můžete postupovat přesně podle CLFS a všechny problémy si vyřešit sami :)

V dalším textu budu používat pojem hostitelská platforma (host) pro počítač na kterém se kompiluje a sestavuje systém a Raspberry budu říkat cílová platforma (target). V mém konkrétním případě je hostitelská platforma x86 s operačním systémem Debian a cílová platforma ARM.

Hostitelský systém

K vytvoření Linuxového systému je nutné mít… Linuxový systém :) Doporučuji nachystat si virtuální stroj (například VMware) a do něj nainstalovat Debian. Zvolte verzi “Small CD” pro platformu i386. Stažený ISO soubor o velikosti asi 290 MB jednoduše připojte jako CD do virtuálního stroje a proveďte instalaci systému. Nastavte virtuální stroj tak, aby měl Debian přístup k Internetu.

Samotná instalace je jednoduchá a přímočará. Jakmile budete požádáni o výběr nainstalovaného software (software selection), zvolte pouze SSH serverstandard system utilities. Položku desktop environment neinstalujte, budeme totiž pracovat pouze v terminálu a grafické prostředí by jen překáželo.

Jakmile bude instalace hotová, můžete pracovat přímo v okně virtuálního stroje. Osobně ale preferuji práci z terminálu přes SSH. Jednoduše si okno s VMware minimalizuji a pak se v terminálu připojím přes SSH k Debianu. Minimálně na Mac OS je tento způsob pohodlnější.

Teď se přihlaste jako root a nainstalujte tyto aplikace:

$ apt-get install binutils gawk gcc vim make git ncurses-bin fsck-swap

Balíčkový systém se postará o zbytek.

Seznam potřebných aplikací je na webu CLFS projektu v kapitole 1.2. Ve spodní části stránky je jednoduchý skript. Ten zkopírujte a spušťte přímo v terminálu (již jako obyčejný uživatel, ne jako root). Výstupem by mělo být něco takového:

$ ./version-check.sh
bash, version 4.2.37(1)-release
Binutils: (GNU Binutils for Debian) 2.22
bzip2,  Version 1.0.6, 6-Sept-2010.
Coreutils:  8.13
diff (GNU diffutils) 3.2
find (GNU findutils) 4.4.2
GNU Awk 4.0.1
gcc (Debian 4.7.2-5) 4.7.2
GNU C Library (Debian EGLIBC 2.13-38+deb7u1) stable release version 2.13,
grep (GNU grep) 2.12
gzip 1.5
GNU Make 3.81
patch 2.6.1
GNU sed version 4.2.1
Sudo version 1.8.5p2
tar (GNU tar) 1.26
makeinfo (GNU texinfo) 4.13

Pokud vidíte nějaké chybové hlášení, nebo je některý nástroj v nižší než doporučené verzi, proveďte nápravu pomocí apt-get install.

1. Nový uživatel

Nyní si vytvořme nového uživatele, pod kterým budeme celý systém kompilovat. Tento nový uživatel se bude jmenovat například clfs a bude mít nastaveny všechny nutné proměnné a cesty ke cross nástrojům. Díky tomu budeme mít vždy stejně připravené pracovní prostředí, bez ohledu na to, jestli se odhlásíme nebo celý virtuální stroj vypneme.

Pod rootem proveďte následující:

$ groupadd clfs
$ useradd -s /bin/bash -g clfs -m -k /dev/null clfs
$ passwd clfs
... Zadejte heslo uživatele

Nyní se můžete přihlásit pod nově vytvořeným uživatelem. Například:

$ su - clfs

Pod tímto uživatelem si nastavíme terminál…

$ cat > ~/.bash_profile << "EOF"
exec env -i HOME=${HOME} TERM=${TERM} PS1='\u:\w\$ ' /bin/bash
EOF

… a proměnné a cesty ke cross nástrojům:

$ cat > ~/.bashrc << "EOF"
set +h
umask 022
CLFS=$HOME
LC_ALL=POSIX
PATH=${CLFS}/cross-tools/bin:/bin:/usr/bin
export CLFS LC_ALL PATH
unset CFLAGS
unset CXXFLAGS
export CLFS_HOST="i486-cross-linux-gnu"
export CLFS_TARGET="arm-linux-musleabihf"
export CLFS_ARCH="arm"
export CLFS_ARM_ARCH="armv6zk"
export CLFS_FLOAT="hard"
export CLFS_FPU="vfp"
EOF

A pak už jenom načteme právě nastavené proměné:

$ source ~/.bash_profile

Nyní, pokud si necháte vypsat proměnou $CLFS, měli byste obdržet toto:

$ echo $CLFS
/home/clfs

2. Příprava cross-tools

Nástroje, které slouží ke kompilaci binárek pro platformu ARM budeme nazývat cross-tools. Tyto nástroje se spouští na hostitelské platformě a jejich výstupem jsou binární soubory pro cílovou platformu.

Nejdřív potřebné adresáře:

$ mkdir -p ${CLFS}/source
$ mkdir -p ${CLFS}/tools
$ mkdir -p ${CLFS}/firmware
$ mkdir -p ${CLFS}/cross-tools/${CLFS_TARGET}
$ ln -sfv . ${CLFS}/cross-tools/${CLFS_TARGET}/usr

Teď si stáhneme zdrojové soubory kernelu (tento Git repozitář je obrovský, stažení bude trvat nějakou dobu):

$ cd ${CLFS}/source
$ git clone https://github.com/raspberrypi/linux linux

Budeme používat kernel připravený speciálně pro Raspberry Pi. Tento kernel má oproti vanilkovému (tak se říká čistému jádru přímo od Linuse) moduly a rozšíření pro Raspberry hardware. Především pro SoC Broadcom BCM2835.

Nyní si nainstalujeme jaderné hlavičkové soubory:

$ cd ${CLFS}/source/linux
$ make mrproper
$ make ARCH=${CLFS_ARCH} headers_check
$ make ARCH=${CLFS_ARCH} INSTALL_HDR_PATH=${CLFS}/cross-tools/${CLFS_TARGET} headers_install

To nám v adresáři ${CLFS}/cross-tools/${CLFS_TARGET} vytvoří podadresář include s hlavičkovými soubory jádra.

Teď potřebujeme stáhnout a zkompilovat nástroje z balíku Binutils. Ten obsahuje linker a nástroje pro manipulaci s binárními soubory typu ELF. Zároveň musíme na zdrojové soubory z Binutils aplikovat patch dostupný z projektu CLFS, který nám umožní zkompilovat Binutils s podporou knihovny musl (viz dále).

$ cd ${CLFS}/source
$ wget http://ftp.gnu.org/gnu/binutils/binutils-2.23.2.tar.bz2
$ wget http://patches.cross-lfs.org/embedded-dev/binutils-2.23.2-musl-1.patch
$ tar xjf binutils-2.23.2.tar.bz2
$ cd binutils-2.23.2
$ patch -Np1 -i ../binutils-2.23.2-musl-1.patch
$ mkdir ../binutils-build && cd ../binutils-build
$ ../binutils-2.23.2/configure \
> --prefix=${CLFS}/cross-tools \
> --target=${CLFS_TARGET} \
> --with-sysroot=${CLFS}/cross-tools/${CLFS_TARGET} \
> --disable-nls \
> --disable-multilib
$ make configure-host && make
$ make install

Kompilace chvilku zabere, po skončení byste měli mít s adresáři ${CLFS}/cross-tools/bin binárky ar, as, ld a další s prefixem arm-linux-musleabihf-.

Nyní je na řadě kompilace překladače GCC. Ten budeme kompilovat na dva průchody. Důvod je prostý: máme tady klasický problém slepice/vejce. Vytváříme totiž cross-tools pro platformu ARM. Potřebujeme zkompilovat standardní knihovnu jazyka C ale zatím nemáme překladač. Ale abychom mohli zkompilovat překladač, potřebujeme standardní knihovnu :) Proto budeme GCC kompilovat na dva průchody, nejdříve bez standardní knihovny a pouze staticky, poté již s knihovnou a s podporou dynamických knihoven.

Začneme opět stažením potřebných zdrojových kódů. Kromě samotného GCC budeme potřebovat ještě patch pro knihovnu musl (opět, viz dále) a knihovny GMP (pro přesnou aritmetiku celých a desetiných čísel), MPFR (pro přesnou aritmetiku a zaokrouhlování desetiných čísel) a MPC (pro přesnou aritmetiku a zaokrouhlování komplexních čísel). Tyto tři knihovny umožňují pracovat s libovolně velkými čísly, což se hodí například v kryptografii nebo pokud jste nadšenci do fraktálů a jiných matematických podivností :) V každém případě jsou tyto knihovny nutnou součástí GCC.

$ cd ${CLFS}/source
$ wget ftp://gcc.gnu.org/pub/gcc/releases/gcc-4.7.3/gcc-4.7.3.tar.bz2
$ wget http://ftp.gnu.org/gnu/gmp/gmp-5.1.2.tar.bz2
$ wget http://www.multiprecision.org/mpc/download/mpc-1.0.1.tar.gz
$ wget http://gforge.inria.fr/frs/download.php/32210/mpfr-3.1.2.tar.bz2
$ wget http://patches.cross-lfs.org/embedded-dev/gcc-4.7.3-musl-1.patch
$ patch -Np1 -i ../gcc-4.7.3-musl-1.patch
$ tar xjf gcc-4.7.3.tar.bz2
$ cd gcc-4.7.3
$ patch -Np1 -i ../gcc-4.7.3-musl-1.patch
$ tar xf ../mpfr-3.1.2.tar.bz2
$ mv -v mpfr-3.1.2 mpfr
$ tar xf ../gmp-5.1.2.tar.bz2
$ mv -v gmp-5.1.2 gmp
$ tar xf ../mpc-1.0.1.tar.gz
$ mv -v mpc-1.0.1 mpc

A samotná kompilace:

$ mkdir -v ../gcc-build
$ cd ../gcc-build
$ ../gcc-4.7.3/configure \
> --prefix=${CLFS}/cross-tools \
> --build=${CLFS_HOST} \
> --host=${CLFS_HOST} \
> --target=${CLFS_TARGET} \
> --with-sysroot=${CLFS}/cross-tools/${CLFS_TARGET} \
> --disable-nls \
> --disable-shared \
> --without-headers \
> --with-newlib \
> --disable-decimal-float \
> --disable-libgomp \
> --disable-libmudflap \
> --disable-libssp \
> --disable-libatomic \
> --disable-libquadmath \
> --disable-threads \
> --enable-languages=c \
> --disable-multilib \
> --with-mpfr-include=$(pwd)/../gcc-4.7.3/mpfr/src \
> --with-mpfr-lib=$(pwd)/mpfr/src/.libs \
> --with-arch=${CLFS_ARM_ARCH} \
> --with-float=${CLFS_FLOAT} \
> --with-fpu=${CLFS_FPU}
$ make all-gcc all-target-libgcc
$ make install-gcc install-target-libgcc

A opět pro kontrolu, v adresáři ${CLFS}/cross-tools/bin bude binárka gcc.

Tak a teď se podívejme na tu knihovnu musl, o které jsem se již dvakrát zmiňoval. Každý Linuxový systém musí obsahovat standardní knihovnu jazyka C. Pokud jste někdy psali v jazyce C, tak znáte takové to #include <stdio.h> nebo #include <stdlib.h>. To jsou hlavičkové soubory standardní knihovny a kromě nich je nutné mít v systému i samotnou zkompilovanou knihovnu.

Většina systémů používá implementace uClibc nebo eglibc. My ale použijeme musl-libc, což je implementace standardní knihovny určená hlavně pro embedded zařízení, kde není tolik paměti a výkonu. Patche, které jsme aplikovali na GCC a Binutils sloužily právě k tomu, abychom mohli tuto knihovnu bez problému použít. Pokud vás zajímají podrobnosti, pak si prostudujte web projektu. My teď musl-libc zkompilujeme naším nově vytvořeným gcc:

$ cd ${CLFS}/source
$ wget http://www.musl-libc.org/releases/musl-0.9.14.tar.gz
$ tar xzf musl-0.9.14.tar.gz
$ cd musl-0.9.14
$ CC=${CLFS_TARGET}-gcc ./configure \
> --prefix=/ \
> --target=${CLFS_TARGET}
$ CC=${CLFS_TARGET}-gcc make
$ DESTDIR=${CLFS}/cross-tools/${CLFS_TARGET} make install

A pro kontrolu: v adresářích ${CLFS}/cross-tools/${CLFS_TARGET}/include a ${CLFS}/cross-tools/${CLFS_TARGET}/lib jsou nyní soubory .h a .a.

Teď provedeme druhou kompilaci GCC, tentokrát již se standardní knihovnou. Zároveň, pokud budete chtít si můžete knihovnu zkompilovat i s podporou jiných jazyků než jen C (například C++).

$ cd ${CLFS}/source
$ rm -rf gcc-4.7.3 gcc-build
$ tar xjf gcc-4.7.3.tar.bz2
$ cd gcc-4.7.3
$ patch -Np1 -i ../gcc-4.7.3-musl-1.patch
$ tar xf ../mpfr-3.1.2.tar.bz2
$ mv -v mpfr-3.1.2 mpfr
$ tar xf ../gmp-5.1.2.tar.bz2
$ mv -v gmp-5.1.2 gmp
$ tar xf ../mpc-1.0.1.tar.gz
$ mv -v mpc-1.0.1 mpc
$ mkdir -v ../gcc-build
$ cd ../gcc-build

Tím jsme si opět vytvořili čistý pracovní adresář a můžeme začít kompilovat:

$ ../gcc-4.7.3/configure \
> --prefix=${CLFS}/cross-tools \
> --build=${CLFS_HOST} \
> --target=${CLFS_TARGET} \
> --host=${CLFS_HOST} \
> --with-sysroot=${CLFS}/cross-tools/${CLFS_TARGET} \
> --disable-nls \
> --enable-languages=c,c++ \
> --enable-c99 \
> --enable-long-long \
> --disable-libmudflap \
> --disable-multilib \
> --with-mpfr-include=$(pwd)/../gcc-4.7.3/mpfr/src \
> --with-mpfr-lib=$(pwd)/mpfr/src/.libs \
> --with-arch=${CLFS_ARM_ARCH} \
> --with-float=${CLFS_FLOAT} \
> --with-fpu=${CLFS_FPU}
$ make
$ make install

A to je vše. Tímto krokem jsme dokončili přípravu cross-tools. Ještě malá rekapitulace: v adresáři ${CLFS}/cross-tools nyní máme hlavičkové soubory jádra a standardní knihovny, nástroje binutils a překladač GCC, to vše pro platformu ARM s hardwarovou podporou desetinných čísel. V tomto okamžiku se můžeme pustit do tvorby vlastního systému pro Raspberry.

3. Tvorba CLFS systému

Nyní začneme se samotnou tvorbou systému pro Raspberry. Nejdříve vytvoříme adresářovou strukturu podle doporučení Filesystem Hierarchy Standard (FHS). To je takové to známé rozdělení na bin boot dev etc home lib usr proc sbin sys, které nejspíš znáte z jiných Linuxových distribucí (a nejenom těch, podobně to má i Mac OS nebo BSD systémy).

$ mkdir -pv ${CLFS}/targetfs/{bin,boot,dev,etc,home,lib/{firmware,modules}}
$ mkdir -pv ${CLFS}/targetfs/{mnt,opt,proc,sbin,srv,sys}
$ mkdir -pv ${CLFS}/targetfs/var/{cache,lib,local,lock,log,opt,run,spool}
$ install -dv -m 0750 ${CLFS}/targetfs/root
$ install -dv -m 1777 ${CLFS}/targetfs/tmp
$ mkdir -pv ${CLFS}/targetfs/usr/{,local/}{bin,include,lib,sbin,share,src}

Toto nám v adresáři ${CLFS}/targetfs vytvoří základní adresáře, které začneme pomalu plnit.

Nejdřív vytvoříme základní konfigurační soubory v adreáři etc. To jsou passwd, group, fstab, mtab a mdev, profile a inittab:

$ cat > ${CLFS}/targetfs/etc/fstab << "EOF"
# Begin /etc/fstab

# file system  mount-point  type   options          dump  fsck
#                                                         order
/dev/mmcblk0p1 /boot        vfat   defaults         1     2
/dev/mmcblk0p2 /            ext4   defaults         0     1
/dev/mmcblk0p3 swap         swap   sw               0     0
proc           /proc        proc   defaults         0     0
sysfs          /sys         sysfs  defaults         0     0
devpts         /dev/pts     devpts gid=4,mode=620   0     0
shm            /dev/shm     tmpfs  defaults         0     0
# End /etc/fstab
EOF
$ ln -svf ../proc/mounts ${CLFS}/targetfs/etc/mtab
$ cat > ${CLFS}/targetfs/etc/passwd << "EOF"
root::0:0:root:/root:/bin/ash
EOF
$ cat > ${CLFS}/targetfs/etc/group << "EOF"
root:x:0:
bin:x:1:
sys:x:2:
kmem:x:3:
tty:x:4:
tape:x:5:
daemon:x:6:
floppy:x:7:
disk:x:8:
lp:x:9:
dialout:x:10:
audio:x:11:
video:x:12:
utmp:x:13:
usb:x:14:
cdrom:x:15:
EOF
$ touch ${CLFS}/targetfs/var/run/utmp ${CLFS}/targetfs/var/log/{btmp,lastlog,wtmp}
$ chmod -v 664 ${CLFS}/targetfs/var/run/utmp ${CLFS}/targetfs/var/log/lastlog

Teď si exportuje proměnné odkazující na náš cross-tools překladač, který budeme dále používat:

$ echo export CC=\""${CLFS_TARGET}-gcc\"" >> ~/.bashrc
$ echo export CXX=\""${CLFS_TARGET}-g++\"" >> ~/.bashrc
$ echo export AR=\""${CLFS_TARGET}-ar\"" >> ~/.bashrc
$ echo export AS=\""${CLFS_TARGET}-as\"" >> ~/.bashrc
$ echo export LD=\""${CLFS_TARGET}-ld\"" >> ~/.bashrc
$ echo export RANLIB=\""${CLFS_TARGET}-ranlib\"" >> ~/.bashrc
$ echo export READELF=\""${CLFS_TARGET}-readelf\"" >> ~/.bashrc
$ echo export STRIP=\""${CLFS_TARGET}-strip\"" >> ~/.bashrc
$ source ~/.bashrc

Soubor profile:

$ cat > ${CLFS}/targetfs/etc/profile<< "EOF"
# /etc/profile

# Set the initial path
export PATH=/bin:/usr/bin

if [ `id -u` -eq 0 ] ; then
        PATH=/bin:/sbin:/usr/bin:/usr/sbin
        unset HISTFILE
fi

# Setup some environment variables.
export USER=`id -un`
export LOGNAME=$USER
export HOSTNAME=`/bin/hostname`
export HISTSIZE=1000
export HISTFILESIZE=1000
export PAGER='/bin/more '
export EDITOR='/bin/vi'

# End /etc/profile
EOF

A inittab. Zde si všimněte hlavně řádku začínajícího na ::respawn, který přesměrovává standardní výstup na konzoli ttyAMA0, což je UART rozhraní na pinech GPIO. Tam si později připojíme USB-UART převodník abychom nemuseli Raspberry připojovat k monitoru nebo televizi.

$ cat > ${CLFS}/targetfs/etc/inittab<< "EOF"
# /etc/inittab

::sysinit:/etc/rc.d/startup

tty1::respawn:/sbin/getty 38400 tty1

# Raspberry's UART
::respawn:/sbin/getty -L ttyAMA0 115200 vt100

::shutdown:/etc/rc.d/shutdown
::ctrlaltdel:/sbin/reboot
EOF

Soubor HOSTNAME definuje jméno našeho systému. Sem si můžete vložit téměř cokoliv.

$ echo "[CLFSRaspberry]" > ${CLFS}/targetfs/etc/HOSTNAME

Aby vše správně fungovalo, musíme mít soubor hosts, který obsahuje FQDN záznamy. V našem případě to bude jen jeden řádek definující IP adresu localhostu. Tento soubor je potřeba i v případě, že nechcete Raspberry připojit k síti.

$ cat > ${CLFS}/targetfs/etc/hosts << "EOF"
# Begin /etc/hosts

127.0.0.1 localhost

# End /etc/hosts
EOF

Ještě nám zbývá soubor /etc/mdev. BusyBox (viz dále) používá tento soubor jako náhradu za udev. Jelikož tento soubor obsahuje více jak 100 řádku, nebudu jej uvádět celý. Celý příkaz, který tento soubor vygeneruje si můžete zkopírovat v kapitole 7.3 CLFS Embedded.

BusyBox: A konečně se pustíme do překladu první aplikace. Tou je BusyBox, což je projekt, který se snaží implementovat všechny základní Unixové nástroje do jediné binárky. To se hodí právě pro embedded systémy.

Začneme opět stažením a rozbalením BusyBox balíku:

$ cd ${CLFS}/source
$ wget http://busybox.net/downloads/busybox-1.21.1.tar.bz2
$ wget http://patches.cross-lfs.org/embedded-dev/busybox-1.21.1-musl-1.patch
$ tar xjf busybox-1.21.1.tar.bz2
$ cd busybox-1.21.1

Nejdříve aplikujeme patch pro lib-musl a použijeme defaultní konfiguraci BusyBoxu, která je pro naše potřeby dostatečně dobrá.

$ make distclean
$ patch -Np1 -i ../busybox-1.21.1-musl-1.patch
$ ARCH="${CLFS_ARCH}" make defconfig

V konfiguraci změníme jenom komponenty ifplugd a inetd, které dělají při překladu s knihovnou musl potíže. Dále nastavíme BusyBox tak, aby se zkompiloval do jediné statické binárky, tedy vypneme používání shared libraries. S nimi jsem měl na Raspberry problémy, zatím jsem neodhalil příčinu a netuším proč se mi nechtějí dynamické knihovny načítat. Bez této úpravy by nám systém vůbec nechtěl nabootovat.

$ sed -i 's/\(CONFIG_\)\(.*\)\(INETD\)\(.*\)=y/# \1\2\3\4 is not set/g' .config
$ sed -i 's/\(CONFIG_IFPLUGD\)=y/# \1 is not set/' .config
$ sed -i 's/\(CONFIG_FEATURE_HAVE_RPC\)=y/# \1 is not set/' .config
$ sed -i 's/# \(CONFIG_STATIC\) is not set/\1=y/' .config

A samotný překlad.

$ ARCH="${CLFS_ARCH}" CROSS_COMPILE="${CLFS_TARGET}-" make
$ ARCH="${CLFS_ARCH}" CROSS_COMPILE="${CLFS_TARGET}-" make  \
>  CONFIG_PREFIX="${CLFS}/targetfs" install
$ cp examples/depmod.pl ${CLFS}/cross-tools/bin
$ chmod 755 ${CLFS}/cross-tools/bin/depmod.pl

E2fsprogs: Obsahem tohoto balíku jsou aplikace pro práci se souborovými systémy. Jsou to aplikace jako fsck, mkfs a mnoho dalších. Obecně se E2fsprogs považují za důležité součásti systému, během bootu se například používá fsck a v případě problémů se systém odmítne spustit. Tyto aplikace se budou nacházet v adresářích /bin a /sbin. Opět je budeme kompilovat jako statické binárky.

$ cd ${CLFS}/source
$ wget http://downloads.sourceforge.net/e2fsprogs/e2fsprogs-1.42.7.tar.gz
$ tar xzf e2fsprogs-1.42.7.tar.gz
$ cd e2fsprogs-1.42.7
$ mkdir -v build && cd build
$ LDFLAGS=-static CC="${CC} -Os" ../configure --prefix=/usr \
>  --with-root-prefix="" --host=${CLFS_TARGET} --disable-tls \
>  --disable-debugfs --disable-e2initrd-helper --disable-nls
$ make
$ make DESTDIR=${CLFS}/targetfs install
$ make DESTDIR=${CLFS}/targetfs install-libs

IANA-etc: Tento balíček obsahuje dva soubory /etc/protocols a services, které popisují síťové porty a aplikace. Samotný balík IANA-etc je ale zastaralý, a proto je nutné nejdříve aplikovat patch.

$ cd ${CLFS}/source
$ wget http://sethwklein.net/iana-etc-2.30.tar.bz2
$ wget http://patches.cross-lfs.org/embedded-dev/iana-etc-2.30-update-2.patch
$ tar xjf iana-etc-2.30.tar.bz2
$ cd iana-etc-2.30
$ patch -Np1 -i ../iana-etc-2.30-update-2.patch
$ make get && make STRIP=yes
$ make DESTDIR=${CLFS}/targetfs install

Kernel: Konečně přichází na řadu to nejdůležitější – Linuxové jádro. Jak už jsem říkal na začátku, nebudeme používat vanilkový kernel ale upravenou verzi přímo pro Raspberry Pi.

Jelikož už jsme jádro jednou stahovali, nebudeme jej stahovat podruhé. Zároveň použijeme defaultní konfiguraci bcmrpi_cutdown_defconfig, která jádro nastaví pro Raspberry. K tomuto kroku se pravděpodobně budete chtít později vrátit. Jakmile budete mít plně funkční systém, můžete si jádro vyladit podle svých potřeb (například vypnout vlastnosti které nepotřebujete).

$ cd {CLFS}/source/linux
$ make mrproper
$ make ARCH=${CLFS_ARCH} CROSS_COMPILE=${CLFS_TARGET}- bcmrpi_cutdown_defconfig
$ make ARCH=${CLFS_ARCH} CROSS_COMPILE=${CLFS_TARGET}- oldconfig
$ make ARCH=${CLFS_ARCH} CROSS_COMPILE=${CLFS_TARGET}- 
$ make ARCH=${CLFS_ARCH} CROSS_COMPILE=${CLFS_TARGET}- \
>  INSTALL_MOD_PATH=${CLFS}/targetfs modules_install

Až budete chtít své jádro vyladit do nejmenšího detailu, stačí se k tomtuto kroku vrátit a místo předchozí sady příkazů spustit toto:

$ cd ${CLFS}/source/linux
$ make mrproper
$ make ARCH=${CLFS_ARCH} CROSS_COMPILE=${CLFS_TARGET}- menuconfig
$ make ARCH=${CLFS_ARCH} CROSS_COMPILE=${CLFS_TARGET}-
$ make ARCH=${CLFS_ARCH} CROSS_COMPILE=${CLFS_TARGET}- \
>  INSTALL_MOD_PATH=${CLFS}/targetfs modules_install

Ať už jste spustili první nebo druhou sadu příkazů, nyní zkopírujeme jadernou binárku včetně konfiguračního souboru do našeho souborového systému:

$ cp arch/arm/boot/Image ${CLFS}/targetfs/boot/kernel.img
$ cp .config ${CLFS}/targetfs/boot/kernel.conf

Dříve bylo ještě samotnou jadernou binárku nutné upravit speciálním skriptem. Nový Raspberry bootloader, který budeme používat již ale umí pracovat i s neupraveným jádrem a tím pádem je tento skript zbytečný.

Bootloader: V embedded systémech se většinou používají bootloadery jako Barebox nebo U-boot. Pro Raspberry ale existuje speciální zavaděč, jelikož jeho bootovací sekvence je dost specifická.

$ wget -o firmware.tar.gz https://api.github.com/repos/raspberrypi/firmware/tarball
$ tar xzf firmware.tar.gz
$ cd raspberrypi-firmware-*
$ cp -v boot/{bootcode.bin,fixup.dat,fixup_cd.dat,start.elf,start_cd.elf} $CLFS/targetfs/boot/

Když už jsme u bootloaderu, nastavíme i parametry které bude předávat jádru. To se dělá textovým souborem cmdline.txt umístěným v adresáři /boot.

$ echo "dwc_otg.lpm_enable=0 rpitestmode=1 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait" > targetfs/boot/cmdline.txt

Toto jsou parametry, které zavaděč předá jádru. V našem případě přesměrováváme standardní výstup na konzoli ttyAMA0, což je UART na GPIO pinech, viz dále. Dále říkáme jádru, že root filesystem je umístěn na SD kartě /dev/mmcblk0p2 a je zformátovaný na typ EXT4.

Init skripty: Jakmile zavaděč spustí jádro a to dokončí boot, spouští se program nazývaný init. Ten je v našem případě součástí BusyBoxu a spouští skript /etc/init.d/rcS. My ale umístíme naše init skripty do /etc/rc.d, a proto musíme ještě udělat symbolický link.

$ wget http://cross-lfs.org/files/packages/embedded-0.0.1/clfs-embedded-bootscripts-1.0-pre5.tar.bz2
$ tar xjf clfs-embedded-bootscripts-1.0-pre5.tar.bz2
$ cd clfs-embedded-bootscripts-1.0-pre5
$ make DESTDIR=${CLFS}/targetfs install-bootscripts
$ install -dv ${CLFS}/targetfs/etc/init.d
$ ln -sv ../rc.d/startup ${CLFS}/targetfs/etc/init.d/rcS

Nastavení sítě: Předpokládám, že budete chtít svoje Raspberry připojit k síti. V takovém případě je nutné nastavit skripty v adresáři /etc/network.

$ mkdir -pv ${CLFS}/targetfs/etc/network/if-{post-{up,down},pre-{up,down},up,down}.d
$ mkdir -pv ${CLFS}/targetfs/usr/share/udhcpc
$ cat > ${CLFS}/targetfs/etc/network/interfaces << "EOF"
auto eth0
iface eth0 inet dhcp
EOF

Dále musíme upravit jeden init skript, který v současné podobě nefunguje. Skript maximálně zjednodušíme, vypustíme vpodstatě většinu částí a necháme jen to nejpodstatnější.

$ cat > ${CLFS}/targetfs/etc/rc.d/init.d/network << "EOF"
#!/bin/ash
# 
# Network interface(s) init script
#
# config: /etc/network.conf 
#         /etc/network.d/interface.[devname]

. /etc/rc.d/init.d/functions
. /etc/network.conf

if [ "$NETWORKING" != "yes" ]; then
        echo "Networking is disabled in /etc/network.conf"
        exit 0
fi

case "$1" in
        start)
        echo -n "Starting network interface eth0: "
        ifup eth0
        check_status
        ;;
stop)
        echo -n "Turning off network interface eth0: "
        ifdown eth0
        check_status
        ;;
restart)
        $0 stop
        $0 start
        ;;
status)
        ifconfig
        route
        ;;
*)
        echo "Usage: $0 {start|stop|restart|status}"
        exit 1
esac
EOF

Ještě je potřeba skript, který převezme IP adresu od DHCP klienta a aktualizuje soubor /etc/resolv.conf.

$ cat > ${CLFS}/targetfs/usr/share/udhcpc/default.script << "EOF"
#!/bin/sh
# udhcpc Interface Configuration
# Based on http://lists.debian.org/debian-boot/2002/11/msg00500.html
# udhcpc script edited by Tim Riker <Tim@Rikers.org>

[ -z "$1" ] && echo "Error: should be called from udhcpc" && exit 1

RESOLV_CONF="/etc/resolv.conf"
[ -n "$broadcast" ] && BROADCAST="broadcast $broadcast"
[ -n "$subnet" ] && NETMASK="netmask $subnet"

case "$1" in
        deconfig)
                /sbin/ifconfig $interface 0.0.0.0
                ;;

        renew|bound)
                /sbin/ifconfig $interface $ip $BROADCAST $NETMASK

                if [ -n "$router" ] ; then
                        while route del default gw 0.0.0.0 dev $interface ; do
                                true
                        done

                        for i in $router ; do
                                route add default gw $i dev $interface
                        done
                fi

                echo -n > $RESOLV_CONF
                [ -n "$domain" ] && echo search $domain >> $RESOLV_CONF
                for i in $dns ; do
                        echo nameserver $i >> $RESOLV_CONF
                done
                ;;
esac

exit 0
EOF

$ chmod +x ${CLFS}/targetfs/usr/share/udhcpc/default.script

A nakonec soubor, který globálně zapne nebo vypne síťové skripty během initu.

$ echo "NETWORKING=yes" > targetfs/etc/network.conf

Celou tuto část jsem oproti CLFS upravil, protože původní návod neodpovídá obsahu init skriptů. Jelikož je ale celý projekt CLFS Embedded teprve ve fázi vývoje, můžeme to autorům odpustit :)

4. Dokončení

V tomto okamžiku nám již zbývá posledních pár kroků od plně funkčního systému. V této části zkopírujeme zbývající knihovny to adresáře ${CLFS}/targetfs, změníme vlastníka souborů a vše zabalíme do TARu.

$ cp -vP ${CLFS}/cross-tools/${CLFS_TARGET}/lib/*.so* ${CLFS}/targetfs/lib/
$ ${CLFS_TARGET}-strip ${CLFS}/targetfs/lib/*

Teď se přihlaste jako root a proveďte následující příkazy.

$ echo ${CLFS}
/home/clfs

Tím jsme ověřili, že i uživatel root má správně nastavenu proměnou ${CLFS}. Pokud příkaz echo vypsal něco jiného nebo vůbec nic, je nutné ji znovu vytvořit příkazem export CLFS=/home/clfs.

$ chown -Rv root:root ${CLFS}/targetfs
$ chgrp -v 13 ${CLFS}/targetfs/var/run/utmp ${CLFS}/targetfs/var/log/lastlog
$ install -dv ${CLFS}/build
$ cd ${CLFS}/targetfs
$ tar jcfv ${CLFS}/build/clfs-embedded.tar.bz2 *

Konec! Tímto jsme dokončili náš Linuxový systém pro Raspberry Pi. Zkomprimovaný balík se nachází v adresáři ${CLFS}/build/. Teď už jenom stačí připravit SD kartu a vše na ni překopírovat.

Závěr

V tomto článku jsme si podrobně prošli celým procesem tvorby Linuxového systému pro Raspberry Pi. Tím ale naše práce nekončí. Ještě je nutné správně naformátovat SD kartu a celý systém na ni překopírovat. Poté k Raspberry připojit USB-UART převodník a poprvé nabootovat.

Abych tento, již tak dlouhý text dále neprodlužoval, rozhodl jsem se zbylou část dát do dalšího článku.

Pokud se pustíte do tvorby vlastního systému podle mého textu, budu rád za jakoukoliv zpětnou vazbu. Jestli budete dělat úpravy v některých krocích nebo přijdete na to, proč nefungují dynamické knihovny, budu rád když napíšete do komentáře nebo mně na email.