linux/arch/i386/boot/compressed/head.S

Artık bvmlinux içindeyiz! misc.c:decompress_kernel()'in yardımıyla, çekirdek imgemiz linux/vmlinux'u elde etmek için piggy.o'yu açacağız.

Bu dosya saf 32 bitlik açılış kodudur. Önceki iki dosyadan farklı olarak kaynak kodun içinde hiç .code16 deyimi yoktur. Ayrıntılar için Using as: Writing 16-bit Code belgesine başvurun.

Sıkıştırılmış Çekirdeğin Açılması

Bölüt tanımlayıcılarındaki (bölüt seçicisi __KERNEL_CS ve __KERNEL_DS'ye tekabül eden) bölüt taban adresleri 0'a eşittir; bu yüzden, eğer bu iki adresten her ikisi de kullanıldıysa, mantıksal konum adresi (bölüt:konum biçiminde) kendi doğrusal adresine eşit olacaktır. zImage için, CS:EIP şimdi 10:1000 mantıksal adresinde (doğrusal 0x1000 adresi), bzImage ise 10:100000 (doğrusal 0x100000) adresindedir.

Sayfalama etkinleştirilmediği için doğrusal adres fiziksel adres ile özdeştir. Adres konuları için IA-32 Manual (Vol.1. Ch.3.3. Memory Organization, and Vol.3. Ch.3. Protected-Mode Memory Management) ve Linux Device Drivers: Memory Management in Linux belgelerine bakınız.

BX=0 ve ESI=INITSEG<<4 olması setup.S'den ileri gelir.

.text
///////////////////////////////////////////////////////////////////////////////
startup_32()
{
  cld;
  cli;
  DS = ES = FS = GS = __KERNEL_DS;
  SS:ESP = *stack_start;  // user_stack[] sonu, misc.c içinde tanımlı
  // korumalı kip etkinleştirildikten sonra tüm bölüt kaydedicileri yeniden yüklnir

  // A20'nin gerçekten etkin olup olmadığını kontrol et
  EAX = 0;
  do {
1:  DS:[0] = ++EAX;
  } while (DS:[0x100000]==EAX);

  EFLAGS = 0;
  clear BSS;                              // _edata'dan _end'e

  struct moveparams mp;                   // subl $16,%esp
  if (!decompress_kernel(&mp, ESI)) {     // AX'deki değeri döndür
    ESI'yi yığıttan geri yükle;
    EBX = 0;
    goto __KERNEL_CS:100000;
    // bkz. linux/arch/i386/kernel/head.S:startup_32
  }

  /*
  * Yüksek yüklediysek buraya geliriz.
  * move-in-place rutinini aşağı 0x1000'e taşımamız gerekir
  * ve sonra yığıttan aldığımız yazmaçlardaki
  * tampon adresleri ile başlatırız.
  */
3:   move_rountine_start..move_routine_end 0x1000'e taşı;
  // move_routine_start & move_routine_end aşağıda tanımlanmıştır

  // move_routine_start() parametrelerini hazırla
  EBX = real mode pointer;        // ESI değeri setup.S'den geçer
  ESI = mp.low_buffer_start;
  ECX = mp.lcount;
  EDX = mp.high_buffer_star;
  EAX = mp.hcount;
  EDI = 0x100000;
  cli;                    // kesme almadığımızdan emin ol.
  goto __KERNEL_CS:1000;  // move_routine_start();
}

/* Eğer yüksek yüklediysek, yerinde çözülmüş çekirdeği taşımak için
 * yordam (şablon). Bu PIC kodu olmalı! */
///////////////////////////////////////////////////////////////////////////////
move_routine_start()
{
  mp.low_buffer_start'ı 0x100000'a taşı, mp.lcount bayt,
    iki adımda: (lcount >> 2) kelime + (lcount & 3) bayt;
  move/append mp.high_buffer_start, ((mp.hcount + 3) >> 2) kelime
  // 1 kelime == 4 bayt, 32 bitlik kod/veri anlamında.

  ESI = EBX;              // gerçek kip gösterici, setup.S'deki gibi
  EBX = 0;
  goto __KERNEL_CS:100000;
  // bkz. linux/arch/i386/kernel/head.S:startup_32()
move_routine_end:
}

je 1b ve jnz 3f'nin anlamları için Using as: Local Symbol Names belgesine başvurunuz.

_edata ve _end tanımlamalarını bulamadınız mı? Sorun değil, onlar "dahili ilintileme betiği" içinde tanımlanmıştır. -T (--script=) seçeneği belirtilmeksizin kullanılırsa, ld bu yerleşik betiği compressed/bvmlinux'u ilintilemek için kullanır. Bu betiği görüntülemek için "ld --verbose" komutunu kullanınız ya da Dahili İlintileme Betiği bölümüne bakınız.

-T (--script=), -L (--library-path=) ve --verbose seçimlerinin tarifi için Using LD, the GNU linker: Command Line Options belgesine başvurunuz. Ayrıca "man ld" ve "info ld" de yardımcı olabilir.

piggy.o çözüldü ve kontrol __KERNEL_CS:100000'ye geçirildi, örn. linux/arch/i386/kernel/head.S:startup_32(). Bakınız linux/arch/i386/kernel/head.S.

#define LOW_BUFFER_START      0x2000
#define LOW_BUFFER_MAX       0x90000
#define HEAP_SIZE             0x3000
///////////////////////////////////////////////////////////////////////////////
asmlinkage int decompress_kernel(struct moveparams *mv, void *rmode)
|-- setup real_mode(=rmode), vidmem, vidport, lines and cols;
|-- if (is_zImage) setup_normal_output_buffer() {
|       output_data      = 0x100000;
|       free_mem_end_ptr = real_mode;
|   } else (is_bzImage) setup_output_buffer_if_we_run_high(mv) {
|       output_data      = LOW_BUFFER_START;
|       low_buffer_end   = MIN(real_mode, LOW_BUFFER_MAX) & ~0xfff;
|       low_buffer_size  = low_buffer_end - LOW_BUFFER_START;
|       free_mem_end_ptr = &end + HEAP_SIZE;
|       // get mv->low_buffer_start and mv->high_buffer_start
|       mv->low_buffer_start = LOW_BUFFER_START;
|       /* To make this program work, we must have
|        *   high_buffer_start > &end+HEAP_SIZE;
|        * As we will move low_buffer from LOW_BUFFER_START to 0x100000
|        *   (max low_buffer_size bytes) finally, we should have
|        *   high_buffer_start > 0x100000+low_buffer_size; */
|       mv->high_buffer_start = high_buffer_start
|           = MAX(&end+HEAP_SIZE, 0x100000+low_buffer_size);
|       mv->hcount =  0 if (0x100000+low_buffer_size >  &end+HEAP_SIZE);
|                  = -1 if (0x100000+low_buffer_size <= &end+HEAP_SIZE);
|       /* mv->hcount==0 : we need not move high_buffer later,
|        *   as it is already at 0x100000+low_buffer_size.
|        * Used by close_output_buffer_if_we_run_high() below. */
|   }
|-- makecrc();          // create crc_32_tab[]
|   puts("Uncompressing Linux... ");
|-- gunzip();
|   puts("Ok, booting the kernel.\n");
|-- if (is_bzImage) close_output_buffer_if_we_run_high(mv) {
|       // get mv->lcount and mv->hcount
|       if (bytes_out > low_buffer_size) {
|           mv->lcount = low_buffer_size;
|           if (mv->hcount)
|               mv->hcount = bytes_out - low_buffer_size;
|       } else {
|           mv->lcount = bytes_out;
|           mv->hcount = 0;
|       }
|   }
`-- return is_bzImage;  // return value in AX

end "dahili ilintileme betiği" içinde de tanımlanmıştır.

decompress_kernel() bir asmlinkage değiştiricisine sahiptir. linux/include/linux/linkage.h dosyasında:

#ifdef __cplusplus
#define CPP_ASMLINKAGE extern "C"
#else
#define CPP_ASMLINKAGE
#endif

#if defined __i386__
#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))
#elif defined __ia64__
#define asmlinkage CPP_ASMLINKAGE __attribute__((syscall_linkage))
#else
#define asmlinkage CPP_ASMLINKAGE
#endif

asmlinkage makrosu derleyiciyi yığıttaki tüm işlev argümanlarını aktarması için (bazı eniyileştirme yöntemleri bunu değiştirmeye çalışsa bile) zorlayacaktır. Ayrıntılar için Using the GNU Compiler Collection (GCC): Declaring Attributes of Functions (regparm) ve Kernelnewbies FAQ: What is asmlinkage belgelerine bakınız.

gunzip()

decompress_kernel() sadece bzImage için sıkıştırılmış çekirdek imgesini düşük (output_data ile gösterilen) ve yüksek (high_buffer_start ile gösterilen) tamponlara açmak için linux/lib/inflate.c dosyasında tanımlı gunzip() -> inflate() çağrısını yapar.

gzip dosya biçimi RFC 1952 içinde belirtilmiştir.

Tablo 1.6. gzip dosya biçimi

BileşenAçılımıBayt sayısıYorumu
ID1IDentification 1 (1. belirteç)131 (0x1f, \037)
ID2IDentification 2 (2. belirteç)1139 (0x8b, \213)[a]
CMCompression Method (Sıkıştırm Yöntemi)18 - "deflate" sıkıştırma yöntemini gösterir
FLGFLaGs (Seçenekler)1çoğu durumda 0
MTIMEModification TIME (Değişiklik zamanı)4özgün dosyanın değişiklik zamanı
XFLeXtra FLags (ek seçenekler)12 - sıkıştırıcı en yavaş algoritmayı[b] kullanır, azami sıkıştırma yapar
OSOperating System (İşletim Sistemi)13 - Unix
ek alanlar--değişken uzunluk, alan FLG ile belirtilir[c]
sıkıştırılmış bloklar--değişken uzunluk
CRC32-4sıkıştırılmamış verinin CRC değeri
ISIZEInput SIZE (Girdi uzunluğu))4sıkıştırılmamış girdi verisi boyunun 2^32 ile bölümünden kalan

[a] ID2 değeri gzip 0.5 için 158 (0x9e, \236) olabilir;

[b] XFL değeri 4 olduğunda ise sıkıştırıcı en hızlı algoritmayı kullanacaktır.

[c] FLG biti 0 olduğunda FTEXT, herhangi bir ek alan belirtmez.

Bu dosya biçimi bilgisini gzipli linux/vmlinux'un başlangıcını bulmak için kullanabiliriz.

[root@localhost boot]# hexdump -C /boot/vmlinuz-2.4.20-28.9 | grep '1f 8b 08 00'
00004c50  1f 8b 08 00 01 f6 e1 3f  02 03 ec 5d 7d 74 14 55  |.......?...]}t.U|
[root@localhost boot]# hexdump -C /boot/vmlinuz-2.4.20-28.9 -s 0x4c40 -n 64
00004c40  00 80 0b 00 00 fc 21 00  68 00 00 00 1e 01 11 00  |......!.h.......|
00004c50  1f 8b 08 00 01 f6 e1 3f  02 03 ec 5d 7d 74 14 55  |.......?...]}t.U|
00004c60  96 7f d5 a9 d0 1d 4d ac  56 93 35 ac 01 3a 9c 6a  |......M.V.5..:.j|
00004c70  4d 46 5c d3 7b f8 48 36  c9 6c 84 f0 25 88 20 9f  |MF\.{.H6.l..%. .|
00004c80
[root@localhost boot]# hexdump -C /boot/vmlinuz-2.4.20-28.9 | tail -n 4
00114d40  bd 77 66 da ce 6f 3d d6  33 5c 14 a2 9f 7e fa e9  |.wf..o=.3\...~..|
00114d50  a7 9f 7e fa ff 57 3f 00  00 00 00 00 d8 bc ab ea  |..~..W?.........|
00114d60  44 5d 76 d1 fd 03 33 58  c2 f0 00 51 27 00        |D]v...3X...Q'.|
00114d6e

Yukarıdaki örnekte gzipli dosyanın 0x4c50 adresinde başladığını görebiliriz. "1f 8b 08 00"den önceki dört byte input_len'dir (küçük sonlu olarak 0x0011011e) ve 0x4c50+0x0011011e=0x114d6e değeri bzImage (/boot/vmlinuz-2.4.20-28.9) dosyasının boyuna eşittir.

static uch *inbuf;           /* girdi tamponu */
static unsigned insize = 0;  /* inbuf içindeki geçerli baytlar*/
static unsigned inptr = 0;   /* inbuf içinde işlenecek sonraki baytın indisi */
///////////////////////////////////////////////////////////////////////////////
static int gunzip(void)
{
  Girdi tamponunu {ID1, ID2, CM} için kontrol et, şöyle olmalı:
          {0x1f, 0x8b, 0x08} (normal durum), veya
          {0x1f, 0x9e, 0x08} (gzip 0.5 için);
  FLG'yi (seçenek baytı) kontrol et, 1, 5, 6 ve 7. bitler atanmamalı;
  Ignore {MTIME, XFL, OS};
  FLG biti 2,3 ve 4'e karşılık gelen seçimlik yapıları yönet;
  inflate();              // sıkıştırılmış blokları yönet
  Validate {CRC32, ISIZE};
}

linux/arch/i386/boot/compressed/misc.c içinde tanımlı get_byte() ilk defa çağırıldığında, girdi tamponunu inbuf=input_data ve insize=input_len olacak şekilde ayarlamak için fill_inbuf() işlevini çağırır. input_data ve input_len sembolleri piggy.o ilintileme betiğinde tanımlanmıştır. Bakınız linux/arch/i386/boot/compressed/Makefile.

inflate()

// misc.c içindeki bazı önemli tanımlamalar
#define WSIZE 0x8000            /* Pencere boyutu en azından 32k olmalı,
                                 * ve ikinin üssü olmalı */
static uch window[WSIZE];       /* Kayan pencere tamponu */
static unsigned outcnt = 0;     /* çıktı tamponundaki bayt sayısı */

// linux/lib/inflate.c
#define wp outcnt
#define flush_output(w) (wp=(w),flush_window())
STATIC unsigned long bb;        /* bit tamponu */
STATIC unsigned bk;             /* bit tamponundaki bit sayısı */
STATIC unsigned hufts;          /* belleği kullanımı izlemek*/
static long free_mem_ptr = (long)&end;
///////////////////////////////////////////////////////////////////////////////
STATIC int inflate()
{
  int e;                  /* son blok seçeneği */
  int r;                  /* sonuç kod */
  unsigned h;             /* struct huft'un azami belleği */
  void *ptr;

  wp = bb = bk = 0;

  // sıkıştırılmış blokları birer birer şişir (inflate)
  do {
          hufts = 0;
          gzip_mark() { ptr = free_mem_ptr; };
          if ((r = inflate_block(&e)) != 0) {
                  gzip_release() { free_mem_ptr = ptr; };
                  return r;
          }
          gzip_release() { free_mem_ptr = ptr; };
          if (hufts > h)
          h = hufts;
  } while (!e);

  /* Çok fazla ileri bakmayı (lookahead) geri al. Sonraki okuma bayt hizalı
   * olacak böylece son anlamlı bayttaki kullanılmayan bitleri çıkarabileceğiz.
   */
  while (bk >= 8) {
          bk -= 8;
          inptr--;
  }

  /* çıktı penceresini (window[0..outcnt-1]) çıktı verisine (output_data) yaz,
    * output_ptr/output_data, crc ve bytes_out'u da buna bağlı olarak güncelle
    * ve outcnt'yi 0'a ayarla. */
  flush_output(wp);

  /* başarılı olduğunu döndür */
  return 0;
}

free_mem_ptr dinamik bellek tahsisi için misc.c:malloc() içinde kullanılır. Sıkıştırılmış her bir bloğu şişirmeden önce, gzip_mark() free_mem_ptr değerini saklar. Şişirmeden sonra gzip_release() bu değeri geri yükleyecektir. Bu inflate_block() içinde ayırılan belleğin serbest bırakılma işlemidir.

Gzip dosyaları sıkıştırmak için Lempel-Ziv (LZ77) kodlamasını kullanır. Sıkıştırılmış veri biçimi RFC 1951 içinde belirtilmiştir. inflate_block() bit düzeni olarak ele alınabilen sıkıştırılmış blokları şişirir.

Sıkıştırılmış her bir bloğun veri yapısı anahatlarıyla şöyledir:

BFINAL (1 bit)
    0  - son blok değil
    1  - son blok
BTYPE  (2 bit)
    00 - sıkıştırma yok
        bayt sınırına kadar kalan bitler;
        LEN      (2 bayt);
        NLEN     (2 bayt, LEN'in tamamlayıcısı);
        data     (LEN bayt);
    01 - düzeltilmiş Huffman kodu ile sıkıştırılmış
        {
        literal  (7-9 bitleri, 256 hariç 0..287 kodunu temsil eder);
                     // Bakınız RFC 1951, 3.2.6 paragrafındaki tablo.
        length   (0-5 bitleri, literal > 256 ise 3..258 arasında bir uzunluk gösterir);
                     // Bkz. RFC 1951, 3.2.5 paragrafındaki 1. alfabe tablosu.
        data     (literal < 256 ise literal baytlarının verileri);
        distance (literal == 257..285 ise 5 artı 0-13 ek bit,
                         1..32768 arasında bir mesafe belirtir;
                     /* Bakınız RFC 1951, 3.2.5 paragrafındaki 2. alfabe tablosu,
                      * 3.2.6 paragrafındaki deyim değil*/
                     /* Çıktı akımında "distance" bayt geri git
                      * ve "length" baytı kopyala. */
        }*           // çok sayıda örnek olabilir
        literal  (7 bit, tümü 0, literal == 256, blok sonu belirtir);
    10 - Dinamik Huffman koduyla sıkıştırılmış
        HLIT     (5 bit, Literal/Length kodlarının sayısı - 257, 257-286);
        HDIST    (5 bit, Distance kodlarının sayısı       - 1, 1-32);
        HCLEN    (4 bit, Code Length kodlarının sayısı    - 4, 4 - 19);
        Code Length dizisi    ((HCLEN+4)*3 bit)
        /* Aşağıdaki 2 alfabe tablosu, önceki Code Length dizisinden üretilen
         * Huffman kod çözme tablosu kullanılarık çözülecektir. */
        Literal/Length alfabesi (HLIT+257 kod)
        Distance alfabesi       (HDIST+1 kod)
        // Kod çözme tabloları bu alfabe tablolarından oluşturur.
        /* Aşağıdaki, farklı kod çözme tabloları kullanmak dışında düzeltilmiş
         * Huffman kodları kısmı ile benzerlik gösterir. */
        {
        literal/length
                 (değişken uzunluk, Literal/Length alfabesine bağımlı);
        data     (literal < 256 ise literal baytlarının verisi);
        distance (literal == 257..285 ise değişken uzunlukta,
                         Distance alfabesine bağımlı);
        }*           // çok sayıda örnek olabilir
        literal  (literal değeri 256, blok sonu anlamında);
    11 - reserved (hata)

Dikkat ederseniz Huffman kodları MSB'den başlarken, veri elemanları En-Değersiz-Bit'ten (Least-Significant Bit - LSB) başlayıp En-Değerli-Bit'e (Most-Significant Bit - MSB) kadar byte'ları paketler. Ayrıca literal değerleri 286-287 ve distance kodları 30-31'in asla oluşmayacağına dikkat edin.

RFC 1951 ve yukarıdaki veri yapısı elinizdeyken inflate_block() işlevini anlamak çok da zor olmayacaktır. Huffman kodlaması ve alfabe tablosu üretimi için RFC 1951 içindeki ilgili paragraflara başvurunuz.

Daha fazla ayrıntı için linux/lib/inflate.c belgesine, gzip kaynak koduna (bir çok yorum satırı var) ve ilgili başvuru materyallerine bakınız.