Initramfs İle Erken Kullanıcı Kipi
Önceki bölümlerde Linux sistemlerinin genel açılış sürecini inceledik. Linux çekirdeği açılış sürecinin son önemli adımı olarak, kullanıcı kipinde bir adet uygulamayı çalıştırmak zorundadır. Doğal olarak bu uygulamayı çalıştırmadan önce, uygulamanın bulunduğu dosya sistemi (kök dosya sistemi) mount edilmiş olmalıdır.
Kök dosya sisteminin fiziksel olarak nerede bulunduğu ise kernel tarafından önceden biliniyor olmalıdır. Bu bilgi kernel derleme sürecinde statik olarak kernel obje kodları içerisine gömülebileceği gibi boot yükleyici uygulama üzerinden kernel açılış parametresi olarak da belirtilebilir. Her iki durumda da kernel, kök dosya sisteminin hangi aygıt (NFS dahil) üzerinde bulunduğuna dair tek bir değeri biliyor olacaktır.
Kernel kök dosya sistemi olarak tek bildiği yeri ya başarılı olarak mount edecek ya da VFS: unable to mount root fs şeklindeki bir hata mesajı ile açılış sürecini sonlandıracaktır.
Şimdi bu bilgiler ışığında aşağıdaki sorulara yanıtlar aramaya çalışalım:
Sistemde kullanılan kök dosya sisteminin türüne ait dosya sistemi desteği ve ilgili fiziksel aygıta erişmek için gereken destekler, kök dosya sistemi mount edilmeden önce kernel tarafında hazır olmalıdır. Yani kök dosya sistemi olarak Ext4 kullanılan bir sistemde Ext4 desteği, XFS kullanılan bir sistemde de XFS desteği hazır olmalı, aynı şekilde dosya sistemi fiziksel olarak MMC aygıtı üzerinde ise MMC katmanı, NFS üzerinde ise IP ve NFS katmanı kernel tarafında hazır edilmelidir. Bunlardan herhangi biri eksik olduğunda, kök dosya sistemi mount edilemez ve sistem açılışına devam edemez. Tüm bu destekleri hazır hale getirmenin bir yolu, ilgili destekleri kernel kodu içerisine statik olarak eklemekten (modül yapmamak) geçer. Ancak yüzlerce farklı kombinasyonda çalışmasını beklediğiniz bir Linux dağıtımı için öntanımlı kernel üretmeye çalışıyorsanız, tüm bu destekleri kernel imajı içerisine atmak gibi bir zorunlulukla karşılaşırsınız. Bu da kernel imajını gereksiz bir şekilde büyütecektir. Bu sorunu nasıl çözebiliriz?
Gömülü sistemimizde yedek amaçlı ikinci bir kök dosya sistemi tuttuğunuzu düşünelim. Açılış sürecinde kernel birinci dosya sistemini mount edemez (dosya sistemi bütünlüğü bozulmuş olabilir) veya dosya sistemi içerisinde bir takım anormallikler tespit ederse otomatik olarak yedek olarak tuttuğunuz kök dosya sisteminden açılışa devam etsin istiyorsunuz. Bunu nasıl yapabilirsiniz?
Sistemimizde açılış sırasında özel bir USB stick takmak ve özel bir takım güvenlik kontrolleri yapmak suretiyle, tüm sistem yazılımlarının (kök dosya sistemi ve diğer yazılımlar) güncellenmesi nasıl mümkün olabilir?
Kök dosya sistemini
dm-crypt
veya benzeri bir encrypted dosya sistemi ile birlikte nasıl kullanabiliriz? Bu tarz bir dosya sisteminin kullanılabilmesi için öncesinde gerekencryptsetup
vb. uygulamalarını kök dosya sistemi mount edilmeden nasıl çalıştıracağız?Asıl veya yedek kök dosya sistemlerimizin her ikisinde birden sorun olması veya sistemin bir şekilde kök dosya sistemine erişememesi durumunda kernel panic durumuna düşmek yerine, otomatik olarak recovery amaçlı kullanılabilecek bir ortam sağlanabilir mi? Hatta bu recovery ortamına SSH vb. bir protokolle uzaktan bağlanmak mümkün olur mu?
Soruları daha da artırabiliriz. Dikkat edilirse bu sorulara olumlu yanıtlar verebilmek için, Linux kernel tarafında kök dosya sistemini mount etme işleminden önce, sorumuza bağlı olarak özel bir takım ek işlemler yapılması gerektiği görünmektedir. Sözgelimi dm-crypt
ile encrypted bir kök dosya sistemi kullanıyorsak, kernel tarafından ön hazırlıkların yapılması gerekiyor. Eğer dm-crypt
yerine encfs
kullanmak istersek, bu defa kernel tarafında başka işlemlerin yapılması gerekecekti. Yukarıdaki soruların her biri ve burada listelemediğimiz diğer pek çok soru için Linux kernel tarafında ayrı ayrı ek destek gerekiyor. Böyle bir işlem kernel tarafında mümkün müdür?
Şimdiye kadar öğrendiklerimizi bir kenara atmamıza gerek yok, Linux kernel açılış sürecinin son kısımları oldukça basittir: kök dosya sistemini mount et ve içerisinden bir adet uygulamayı çalıştır.
Yukarıdaki sorunlara çözüm üretebilmek için Linux kernel tarafında karmaşık işlemler yapmak yerine, daha basit bir yaklaşım geliştirilmiştir: 2 aşamalı açılış yöntemi
Bu yöntem başlangıçta genelde sadece Linux dağıtımı geliştiricileri için önemliydi ancak günümüzde pek çok gömülü sistemde de önemli kullanım alanları bulmaktadır.
Initrd - Initial Ramdisk
2 aşamalı açılış sisteminin öncülü initrd
sistemidir. Çekirdeğin çok eski versiyonlarında dahi bu destek bulunmaktadır.
Initrd imajı esasen çalışabilen küçük bir kök dosya sistemidir. Ayrıca hazırlanır ve genellikle ext2 ya da cramfs gibi bir dosya sisteminin hazırlanması ve sıkıştırılmasıyla cpio
arşivi şeklinde oluşturulur.
Örnek olarak kendi kullandığımız Linux bilgisayarımızdaki initrd imajının içeriğine bakmayı deneyelim. Örneğimizde Debian Jessie 64bit versiyonundaki /boot/initrd.img-3.16-2-amd64
dosyasını kullanacağız. Dosyanın boyutunun 15 MB olduğunu görmekteyiz. file
komutu ile dosya hakkında bilgi edinmeye çalışalım:
$ file /boot/initrd.img-3.16-2-amd64
/boot/initrd.img-3.16-2-amd64: gzip compressed data,
last modified: Wed Dec 10 00:23:17 2014, from Unix
.gz
gibi bir uzantı verilmiş olmamakla birlikte dosyanın gzip
ile sıkıştırılmış olduğu görülmektedir. Öncelikle dosyamızı açmalıyız:
$ zcat /boot/initrd.img-3.16-2-amd64 > /tmp/initrd
Dosyayı /tmp/initrd
şeklinde sistemimize açtık ve boyutunun 45 MB'a çıktığını gördük. file
komutuyla bu dosyaya baktığımızda:
$ file /tmp/initrd
initrd: ASCII cpio archive (SVR4 with no CRC)
şeklinde dosyanın bir cpio
arşivi olduğunu öğreniyoruz. Boş bir dizin oluşturup, cpio -id
parametresiyle bu arşivi yeni oluşturduğumuz dizin içerisine açabiliriz:
$ mkdir /tmp/image
$ cd /tmp/image
$ cpio -id < /tmp/initrd
90871 blocks
$ ls
bin conf etc init lib lib64 run sbin scripts
Initrd imajının içerisine görebilmiş olduk. Burada yer alan init
uygulaması initrd açılış yönteminde kullanılan ilk uygulamadır. Çoğunlukla bir kabuk uygulaması olduğundan herhangi bir metin editörü ile içeriğini açıp inceleyebiliriz.
Initrd imajı bu örnekte olduğu gibi kernel imajından ayrı bir dosyada tutulur. Boot yükleyici uygulama initrd açılış sürecinde öncelikle bulunduğu ortamdan kernel imajını belleğe yükler. Sonrasında benzer şekilde initrd imajını da bulunduğu yerden belleğe yükler ve kernel açılışında initrd imajının bellekte bulunduğu adresi kernel tarafına bildirir. Kernel bu şekilde başlatıldığında kök dosya sistemini geleneksel biçimde mount etmeyi denemek yerine, initrd imajının içeriğini RAM Disk
yöntemiyle belleğe açar ve açtığı yeri kök dosya sistemi olarak kullanır. Geleneksel açılışta kök dosya sistemi mount edildikten sonra öntanımlı olarak /sbin/init
çalıştırılıyorken, initrd sistemiyle açılış gerçekleştirildiğinde kök dizindeki /init
uygulaması çalıştırılır.
Özetle Linux kernel tarafında daha önce öğrenmiş olduğumuz açılış sistematiği korunmakta, gene sadece bir adet kök dosya sistemi mount edilmekte ve içerisinden bir adet uygulama çalıştırılmaktadır.
Ancak root dosya sistemi fiziksel bir medya üzerinden değil, boot yükleyici tarafından öncesinde belleğe aktarılmış bir imaj üzerinden gerçekleşmekte ve ilk çalışan uygulama /init
olmaktadır.
Bu yöntem erken kullanıcı kipi (early user-space) olarak da bilinmektedir.
Çift aşamalı açılış sürecinde kernel tarafından hazır edilen dosya sistemi ve /init
uygulamasının çeşitli kontrol vb. işlemleri yaptıktan sonra asıl kök dosya sistemini öncelikle örneğin /mnt
gibi bir dizine mount etmesi, sonra da kök dosya sistemini (/
) ilgili yere (/mnt
) kaydırması beklenmektedir.
Initramfs - Initial Ram FileSystem
Initramfs yöntemi küçük detaylar haricinde initrd süreciyle aynı şekilde gerçekleşir. Initrd için yazdıklarımız genel olarak burada da geçerlidir.
Initramfs sürecindeki temel farklılık, initrd yaklaşımındaki ayrı bir initrd imaj dosyası kullanmak yerine, imajın da kernel içerisine eklemlenmiş olmasıdır.
Bu sayede kernel imajını ayrı initrd imajını ayrı yönetmek yerine, ya hep ya hiç şeklinde boot yükleyici tarafından kernel imajının belleğe yüklenmesi başarıldı ise, her durumda erken kullanıcı kipine ulaşmak mümkün olmakta; initrd imajının bozulmuş olması veya boot yükleyici tarafından diskten yüklenememesi gibi sorunlar ortadan kalkmaktadır.
Linux kernel 2.6 ve sonraki serilerde initrd yerine initramfs sisteminin kullanımı tercih edilmektedir.
Her iki imaj türü de, Kök Dosya Sistemi Oluşturma başlıklı bölümde anlatılan yöntemlerle üretilebilir. Initramfs imajı için bu şekilde küçük bir dosya sistemi hazırladığımızda, derleyeceğimiz kernel içerisine bu imajı eklememiz gereklidir.
Bunun için ya imajımızı tek dosyalık cpio
arşivi haline getirmeli ya da hazırladığımız ana dizini kernel derleme sürecinde belirtmeliyiz. Bu işlem için kernel tarafında CONFIG_INITRAMFS_SOURCE
değişkeni kullanılır.
Açılış Süreci
Her iki yöntemde kernel tarafından çalıştırılan ilk uygulama /init
şeklindedir.
/init
uygulamasındaki yapabileceklerimizin sınırı, initramfs kök dosya sistemine dahil ettiğimiz araçlarla sınırlıdır. Çok küçük bir imaj yapılabileceği gibi çok daha gelişmiş bir dosya sistemi ve uygulamalardan oluşan bir imak da üretilebilir.
Bu noktada konuya başlarken sorduğumuz soruların tümüne cevaplar üretebiliriz zira artık kernel katmanında değil, kullanıcı kipinde çalışmaktayız ve istediğimiz her türlü uygulamayı sistemimize dahil edebilir veya geliştirip kullanabiliriz.
Örnek olarak encrypted kök dosya sistemi kullanacaksak, gerekli araçları initramfs imajımızın içerisine dahil edip, asıl dosya sistemini mount etmeyi denemeden önce sistemimizi uygun şekilde hazırlayabiliriz.
Yedek dosya sistemi senaryosunda, asıl dosya sistemini mount etme işlemimiz başarısız olursa veya mount etmemize rağmen içerisindeki belirli dosyaların md5sum değerleri beklediğimizden farklı ise, unmount işlemiyle vazgeçebilir ve yedek dosya sistemini mount edebiliriz.
USB veriyolunda belirli özelliklere sahip bir disk var ise, içerisinde güvenlik/imza kontrolü de yapıp sistemi içerisindeki dosyalarla güncelleyebilir veya sistemin bir yedeğini ilgili disk üzerine geri kopyalayabiliriz.
Bu şekilde sınırsız sayıda senaryo üretilebilir. Artık kullanıcı kipinde çalıştığımız için senaryo sayısının bizim için (ve de kernel için) bir önemi yoktur. Herhangi bir işlem artık yapılabilir durumdadır.
Bu şekildeki 2 aşamalı açılış süreçlerinde, /init
uygulamasından son olarak asıl kök dosya sistemini belirlemesi ve sistemin initramfs imajını gösteren kök dizinini, asıl kök dosya sistemini gösterecek şekilde değiştirmesi beklenir.
Aşağıda örnek bir /init
betiği görülmektedir:
#!/bin/sh
# get_opt("init=/sbin/init") will return "/sbin/init"
get_opt() {
echo "$@" | cut -d "=" -f 2
}
export PATH=/bin:/sbin:/usr/bin:/usr/sbin
[ -d /dev ] || mkdir -m 0755 /dev
[ -d /root ] || mkdir -m 0700 /root
[ -d /sys ] || mkdir /sys
[ -d /proc ] || mkdir /proc
# devtmpfs does not get automounted for initramfs
mount -t devtmpfs devtmpfs /dev
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t tmpfs tmpfs /tmp
# Sistem spesifik
# USB
modprobe musb_hdrc
modprobe ti81xx
modprobe sd_mod
modprobe usb-storage
modprobe omap_hsmmc
modprobe mmc_block
modprobe ext4
echo "# Checking usb startup disk"
sleep 3
mkdir -p /mnt/usb
mount -t vfat /dev/sda /mnt/usb 2> /dev/null || \
mount -t vfat /dev/sda1 /mnt/usb 2> /dev/null
if [ -e "/mnt/usb/upgrade/run.sh" ]; then
echo "SCRIPT CALISTIRILIYOR..."
sh /mnt/usb/upgrade/run.sh
umount /mnt/usb
fi
# Defaults
init="/sbin/init"
root="/dev/mmcblk0p2"
mnt_point="/mnt/rootfs"
# Process command line options
for i in $(cat /proc/cmdline); do
case $i in
root\=*)
root=$(get_opt $i)
;;
init\=*)
init=$(get_opt $i)
;;
esac
done
# Mount the root device
mount "${root}" $mnt_point
#Check if $init exists and is executable
if [[ -x "$mnt_point/${init}" ]] ; then
mount --move /sys $mnt_point/sys
mount --move /dev $mnt_point/dev
mount --move /tmp $mnt_point/tmp
#Switch to the new root and execute init
exec switch_root $mnt_point "${init}"
fi
#This will only be run if the exec above failed
echo "Failed to switch_root, dropping to a shell"
exec sh
/init
betiğimiz çok daha karmaşık işlemler yapıyor olabilir. Burada fikir vermesi açısından ufak bir implementasyon yapılmış ve özellikle bir kaç ufak iyileştirme yapılabilecek nokta bırakılmıştır. Bunların neler olabileceğini bulmaya çalışmak öğretici olabilir.
Initramfs süreciyle ilgili önemli bir not eklemekte fayda görüyoruz. Bu şekilde açılış gerçekleştirildiğinde, ilerleyen aşamalarda değineceğimiz devtmpfs
dosya sistemi henüz mount edilmiş durumda değildir. Dahası bu mount işlemini bizim yapmamız gerekmektedir. /init
betiğinin ilk satırlarında bu bölümü görmekteyiz. Ancak bir sorun daha var ki, /init
uygulaması çalıştırılırken /dev/console
aygıt dosyasına erişim de yapılmakta ve henüz devtmpfs üzerinden /dev
dizini hazır hale getirilmemiş olduğundan bu dosyaya ulaşılamamakta ve /init
uygulaması çalıştırılamamaktadır. Bu senaryo ile karşılaştığınızda kernel açılış mesajlarında aşağıdaki gibi bir hata görürsünüz:
Warning: unable to open an initial console.
Bu sorunun üstesinden gelebilmek için, initramfs imajını ürettiğininz kök dosya sistemindeki /dev
dizini altına console
özel aygıt dosyasını aşağıdaki gibi oluşturabilir:
$ sudo mknod -m 622 /path/to/initramfs/dev/console c 5 1
veya cp -a
komutuyla kendi sisteminizdeki /dev/console
dosyasını da kopyalayabilirsiniz.
Örnek /init
uygulamamız ilerleyen zamanda daha detaylı açıklanacaktır. Kod üzerinden takip edip ne yapmaya çalıştığımızı anlayabilirsiniz. Bu süreçteki en önemli komutlar switch_root
ve mount --move
ile başlayan satırlarda yer almaktadır.