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 gereken cryptsetup 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 "[email protected]" | 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.

results matching ""

    No results matching ""