linux/arch/i386/kernel/head.S

Sonunda çekirdek imgesi linux/vmlinux yerini aldı. İki girdiye ihtiyaç var:

  • 16 bitlik gerçek kip kodunun yerini göstermek için ESI; INITSEG<<4 durumu;
  • Hangi işlemcinin çalıştığını belirtmek için BX, 0 BSP anlamına gelir, diğer değerler AP için.

ESI, daha sonra empty_zero_page'e kopyalanacak 16 bitlik gerçek kip kodundaki parametre alanını gösterir. ESI sadece BSP için geçerlidir.

BSP (BootStrap Processor) ve APler (Application Processors) Intel terminolojileridir. Çoklu işlemci (MP) ilklendirme işlemleri hakkında bakınız: IA-32 Manual (Vol.3. Ch.7.5. Multiple-Processor (MP) Initialization) ve MultiProcessor Specification.

Yazılım açısından bakıldığında, çok işlemcili bir sistemde, BSP ve APler fiziksel belleği paylaşırlar, fakat kendi yazmaç kümelerini kullanırlar. BSP ilk olarak çekirdek kodunu çalıştırır, işletim sistemi çalıştırma ortamını kurar ve APleri de onun üzerinde çalışması için tetikler. AP BSP onu uyarıncaya kadar uyku kipinde kalır.

Sayfalamayı Etkinleştir

.text
///////////////////////////////////////////////////////////////////////////////
startup_32()
{
  /* bölütleri bilinen değerlere ata */
  cld;
  DS = ES = FS = GS = __KERNEL_DS;

#ifdef CONFIG_SMP
#define cr4_bits mmu_cr4_features-__PAGE_OFFSET
  /* long mmu_cr4_features linux/arch/i386/kernel/setup.c içinde tanımlı
    * __PAGE_OFFSET = 0xC0000000, örn. 3G */

  // CR4 desteği ile ApP (> Intel 486) CR'ü BSP'den kopyalayacak
  if (BX && cr4_bits) {
    // sayfalama seçeneklerini aç (turn on) (PSE, PAE, ...)
    CR4 |= cr4_bits;
  } else
#endif
  {
    /* sayfa tablolarını (pg0..empty_zero_page-1) sadece BSP ilklendirir
      *   .org 0x2000'de pg0
      *   .org 0x4000'de empty_zero_page
      *   toplam (0x4000-0x2000)/4 = 0x0800 girdi */
    pg0 = {
      0x00000007,             // 7 = PRESENT + RW + USER
      0x00001007,             // 0x1000 = 4096 = 4K
      0x00002007,
      ...
    pg1:    0x00400007,
      ...
      0x007FF007              // toplam 8M
    empty_zero_page:
    };
  }

Bir çekirdek sembolüne başvuracağımız zaman neden -__PAGE_OFFSET eklemek zorundayız, örneğin pg0 gibi?

linux/arch/i386/vmlinux.lds içinde şunlar bulunur:

  . = 0xC0000000 + 0x100000;
  _text = .;                    /* Metin ve salt-okunur veri */
  .text : {
        *(.text)
...

linux/vmlinux için bağlanacak ilk dosya olan linux/arch/i386/kernel/head.o içinde, pg0 .text bölümü 0x2000 konumunda olduğu için, çıktı bölümü .text içinde 0x2000 konumunda olacaktır. Böylece ilintilendikten sonra 0xC0000000+0x100000+0x2000 adresinde olacaktır.

[root@localhost boot]# nm --defined /boot/vmlinux-2.4.20-28.9 | \
grep 'startup_32\|mmu_cr4_features\|pg0\|\<empty_zero_page\>' | sort
c0100000 t startup_32
c0102000 T pg0
c0104000 T empty_zero_page
c0376404 B mmu_cr4_features

Korumalı kipte sayfalama etkinleştirilmeden, doğrusal adres doğrudan fiziksel adrese eşlenecektir. "movl $pg0-__PAGE_OFFSET,%edi" pg0'ın fiziksel adresine eşit olan EDI=0x102000 değerini atayacaktır (linux/vmlinux 0x100000 adresine yerleştirildiği için). -PAGE_OFFSET şeması olmadan, yanlış ve muhtemelen RAM alanının ötesinde olacak 0xC0102000 fiziksel adresine erişecektir.

mmu_cr4_features .bss bölümü içindedir ve yukarıdaki örnekte 0x376404 fiziksel adresine yerleştirilmiştir.

Sayfa tablosu ilklendirildikten sonra sayfalama etkinleştirilebilir.

  // sayfa dizini temel göstericisini ata, fiziksel adres
  CR3 = swapper_pg_dir - __PAGE_OFFSET;
  // sayfalama etkin!
  CR0 |= 0x80000000;      // PG bitini ayarla
  goto 1f;                // flush prefetch-queue
1:
  EAX = &1f;          // sonraki komutu takip eden adres
  goto *(EAX);            // EIP'yi yeniden konumla
1:
  SS:ESP = *stack_start;

Sayfa dizini swapper_pg_dir ( Muhtelif bölümündeki tanımlamalara bakınız), sayfa tabloları pg0 ve pg1 ile birlikte, doğrusal 0..8M-1 ve 3G..3G+8M-1 adreslerinin her ikisinin de 0..8M-1 fiziksel adresine eşlendiğini belirtir. Artık çekirdek sembollerine "-__PAGE_OFFSET" olmadan erişebiliriz. Çünkü çekirdek alanı (>=3G doğrusal adresinde bulunur) sayfalama etkinleştirildikten sonra doğru bir şekilde kendi fiziksel adresine eşlenecektir.

"lss stack_start,%esp" (SS:ESP = *stack_start) yeni bir yığıt kuran "-PAGE_OFFSET" olmadan bir sembole başvuran ilk örnektir. BSP için, yığıt init_task_union'ın sonundadır. AP için, stack_start.esp linux/arch/i386/kernel/smpboot.c:do_boot_cpu() tarafından smp_init() içinde "(void *) (1024 + PAGE_SIZE + (char *)idle)" olacak şekilde yeniden tanımlanmıştır.

Sayfalama mekanizmaları ve veri yapıları için bakınız: IA-32 Manual Vol.3. (Ch.3.7. Page Translation Using 32-Bit Physical Addressing, Ch.9.8.3. Initializing Paging, Ch.9.9.1. Switching to Protected Mode ve Ch.18.26.3. Enabling and Disabling Paging).

Çekirdek Parametrelerini Al

#define OLD_CL_MAGIC_ADDR       0x90020
#define OLD_CL_MAGIC            0xA33F
#define OLD_CL_BASE_ADDR        0x90000
#define OLD_CL_OFFSET           0x90022
#define NEW_CL_POINTER          0x228   /* Gerçek kip veriye göreli */

#ifdef CONFIG_SMP
  if (BX) {
    EFLAGS = 0;             // AP EFLAGS'leri temizler
  } else
#endif
  {
    // İlk CPU BSS'yi temizler
    clear BSS;              // örn. __bss_start .. _end
    setup_idt() {
      /* idt_table[256]; arch/i386/kernel/traps.c içinde tanımlı
        *   .data.idt bölümüne yerleştirilmiş
      EAX = __KERNEL_CS << 16 + ignore_int;
      DX = 0x8E00;    // kesme kapısı, dpl = 0, mevcut
      idt_table[0..255] = {EAX, EDX};
    }
    EFLAGS = 0;
    /*
      * Önyükleme parametrelerini yolun dışına kopyala (ayak altından al).
      * _empty_zero_page'in ilk 2kB'lık bölümü önyükleme parametreleri için,
      * ikinci 2kB'lık bölümü komut satırı içindir.
      */
    taşı *ESI (gerçek kip başlık)'dan empty_zero_page'e, 2KB;
    temizle empty_zero_page+2K, 2KB;
    ESI = empty_zero_page[NEW_CL_POINTER];
    if (!ESI) {             // 32 bitlik komut satırı göstericisi
      if (OLD_CL_MAGIC==(uint16)[OLD_CL_MAGIC_ADDR]) {
        ESI = [OLD_CL_BASE_ADDR]
              + (uint16)[OLD_CL_OFFSET];
        taşı *ESI'dan empty_zero_page+2K'ya, 2KB;
      }
    } else {                // 2.02+'da geçerli
      taşı *ESI'dan empty_zero_page'e, 2KB;
    }
  }
}

BSP için çekirdek parametreleri ESI tarafından gösterilen bellekten empty_zero_page'e kopyalanır. Eğer uygulanabilir ise çekirdek komut satırı empty_zero_page+2K'ya kopyalanacaktır.

İşlemci Türünü Kontrol Et

İşlemci türünün ve işlemci içeriğinin nasıl belirlendiği konusunda bakınız: IA-32 Manual Vol.1. (Ch.13. Processor Identification and Feature Determination).

struct cpuinfo_x86;                  // bakınız: include/asm-i386/processor.h
struct cpuinfo_x86 boot_cpu_data;    // bakınız: arch/i386/kernel/setup.c

#define CPU_PARAMS      SYMBOL_NAME(boot_cpu_data)
#define X86             CPU_PARAMS+0
#define X86_VENDOR      CPU_PARAMS+1
#define X86_MODEL       CPU_PARAMS+2
#define X86_MASK        CPU_PARAMS+3
#define X86_HARD_MATH   CPU_PARAMS+6
#define X86_CPUID       CPU_PARAMS+8
#define X86_CAPABILITY  CPU_PARAMS+12
#define X86_VENDOR_ID   CPU_PARAMS+28

checkCPUtype:
{
  X86_CPUID = -1;                 // CPUID yok

  X86 = 3;                        // en azından 386
  save original EFLAGS to ECX;
  flip AC bit (0x40000) in EFLAGS;
  if (AC bit not changed) goto is386;

  X86 = 4;                        // en azından 486
  flip ID bit (0X200000) in EFLAGS;
  restore original EFLAGS;        //  AC ve ID seçenekleri için
  if (ID bit değişemez) goto is486;

  // işlemci bilgilerini al
  CPUID(EAX=0);
  X86_CPUID = EAX;
  X86_VENDOR_ID = {EBX, EDX, ECX};
  if (!EAX) goto is486;

  CPUID(EAX=1);
  CL = AL;
  X86 = AH & 0x0f;                // aile
  X86_MODEL = (AL & 0xf0) >> 4;   // model
  X86_MASK = CL & 0x0f;           // adımlama kimliği (stepping id)
  X86_CAPABILITY = EDX;               // özellik

x87 aritmetik işlemcisinin ayarlanışı için bakınız: IA-32 Manual Vol.3. (Ch.9.2. x87 FPU Initialization, and Ch.18.14. x87 FPU).

is486:
  // PG, PE, ET'yi kaydet AM, WP, NE, MP'yi ise ayarla
  EAX = (CR0 & 0x80000011) | 0x50022;
  goto 2f;                   // "is386:" işlemeyi atla
is386:
  orjinal EFLAGS'ları ECX'den yeniden al;
  // PG, PE, ET'yi kaydet MP'yi ayarla
  EAX = (CR0 & 0x80000011) | 0x02;

  /* ET: Eklenti Türü (Extension Type) (CR0'ın 4 biti).
    * Intel 386 ve Intel 486 işlemcilerde bu seçenek atandığı (set) zaman
    * Intel 387 DX aritmetik işlemcisi komutlarının desteklendiğini gösterir.
    * Pentium 4, Intel Xeon ve P6 ailesi işlemcilerde ise
    * bu seçenek sabit 1 olur.
    *     -- IA-32 Manual Vol.3. Ch.2.5. Control Registers (p.2-14) */

2:
  CR0 = EAX;
  check_x87() {
    /* Doğru olması için ET'ye bağımlıyız.
     * Bu 287/387 için sınar. */
    X86_HARD_MATH = 0;
    clts;                   // CR0.TS = 0;
    fninit;                 // Init FPU;
    fstsw AX;               // AX = ST(0);
    if (AL) {
      CR0 ^= 0x04;    // yardımcı işlemci (coprocessor) yok, EM'i ata
    } else {
      ALIGN
1:    X86_HARD_MATH = 1;
      /* IA-32 Manual Vol.3. Ch.18.14.7.14. FSETPM komutu
        * 287 işlemcisinin korumalı kipte olduğunu söyler
        * 387 tarafından dikkate alınmaz*/
      fsetpm;
    }
  }
}

linux/include/linux/linkage.h içinde tanımlanan ALIGN makrosu, 16-byte hizalama ve 0x90 değeri doldurma (NOP için opcode) belirtir. .align talimatının anlamı için ayrıca Using as: Assembler Directives belgesine bakınız.

Çekirdeği Başlat

  ready:  .byte 0;        // global değişken
{
  ready++;                // kaç işlemci hazır
  lgdt gdt_descr;         // yeni betimleme tablosunu güvenli yerde kullan
  lidt idt_descr;
  goto __KERNEL_CS:$1f;   // "lgdt"den sonra bölüt yazmaçlarını yeniden yükle
1:
  DS = ES = FS = GS = __KERNEL_DS;
#ifdef CONFIG_SMP
  SS = __KERNEL_DS;       // sadece bölütü yeniden yükle
#else
  SS:ESP = *stack_start;  /* init_task_union'ın sonu,
                           * linux/arch/i386/kernel/init_task.c'de tanımlı */
#endif
  EAX = 0;
  lldt AX;
  cld;

#ifdef CONFIG_SMP
  if (1!=ready) {         // ilk işlemci değil
    initialize_secondary();
    // bakınız: linux/arch/i386/kernel/smpboot.c
  } else
#endif
  {
    start_kernel(); // bakınız: linux/init/main.c
  }
L6:
  goto L6;
}

İlk işlemci (BSP) linux/init/main.c:start_kernel()'i çağıracak ve diğerleri (AP) linux/arch/i386/kernel/smpboot.c:initialize_secondary()'yi çağıracak. linux/init/main.c içinde start_kernel()'e ve initialize_secondary() içindeki initialize_secondary()'e bakınız.

init_task_union ilk süreç olan "idle" süreci (pid=0) için görev yapısı olmak üzere oluşur. "idle" sürecinin yığıtı init_task_union'ın sonundan itibaren gelişir. Aşağıdaki kod init_task_union ile ilgilidir.

ENTRY(stack_start)
  .long init_task_union+8192;
  .long __KERNEL_DS;

#ifndef INIT_TASK_SIZE
# define INIT_TASK_SIZE 2048*sizeof(long)
#endif

union task_union {
  struct task_struct task;
  unsigned long stack[INIT_TASK_SIZE/sizeof(long)];
};

/* INIT_TASK ilk görev tablosunu kurmak için kullanıldı,
 * riski göze alarak kullanın! Base=0, limit=0x1fffff (=2MB) */
union task_union init_task_union
  __attribute__((__section__(".data.init_task"))) =
    { INIT_TASK(init_task_union.task) };

init_task_union BSP "idle" süreci içindir. init() bölümünde değinilen "init" süreciyle karıştırmayın.

Muhtelif

///////////////////////////////////////////////////////////////////////////////
// öntanımlı kesme yöneticisi ("handler")
ignore_int() { printk("Unknown interrupt\n"); iret; }

/*
 * Kesme belirtici tablosu 256 idt için odaya (room) sahiptir,
 * global belirtici tablosu sahip olabileceğimiz
 * görev sayısına bağımlıdır...
 */
#define IDT_ENTRIES     256
#define GDT_ENTRIES     (__TSS(NR_CPUS))

.globl SYMBOL_NAME(idt)
.globl SYMBOL_NAME(gdt)

  ALIGN
  .word 0
idt_descr:
  .word IDT_ENTRIES*8-1           # idt 256 girdi içerir
SYMBOL_NAME(idt):
  .long SYMBOL_NAME(idt_table)

  .word 0
gdt_descr:
  .word GDT_ENTRIES*8-1
SYMBOL_NAME(gdt):
  .long SYMBOL_NAME(gdt_table)

/*
 * Bu, 0-8M'de (önyükleme amaçları için) bir kimlik eşleşmesi ve
 * PAGE_OFFSET sanal adresinde başka bir 0-8M eşleşmesi oluşturmak
 * üzere ilklendirilir.
 */
.org 0x1000
ENTRY(swapper_pg_dir)   // "ENTRY" linux/include/linux/linkage.h'da tanımlı
  .long 0x00102007
  .long 0x00103007
  .fill BOOT_USER_PGD_PTRS-2,4,0
  /* öntanımlı: 766 girdi */
  .long 0x00102007
  .long 0x00103007
  /* öntanımlı: 254 girdi */
  .fill BOOT_KERNEL_PGD_PTRS-2,4,0

/*
 * Sayfa tablolarının burada sadece 8MB'ı ilklendirilir
 * - sonuncu sayfa tabloları bellek boyutuna bağlı
 * olarak daha sonra ayarlanır.
 */
.org 0x2000
ENTRY(pg0)

.org 0x3000
ENTRY(pg1)

/*
 * empty_zero_page hemen sayfa tablosunu takip etmelidir !
 * (İlklendirme döngüsü empty_zero_page'e kadar sayar)
 */
.org 0x4000
ENTRY(empty_zero_page)

/*
 * normal "text" bölütünün gerçek başlangıcı
 */
.org 0x5000
ENTRY(stext)
ENTRY(_stext)

///////////////////////////////////////////////////////////////////////////////
/*
 * Bu veri bölümünü başlatır. Dikkat ederseniz yukarıda tümü
 * text bölümündedir çünkü bu bizim başka bir şekilde
 * gideremeyeceğimiz hizalama gereksinimidir.
 */
.data

ALIGN
/*
 * Tipik olarak 140 "quadwords" içerir; NR_CPUS'a bağlı olarak.
 *
 * DİKKAT! Herhangi bir şeyi değiştirirseniz, bunun head.S'deki
 * gdt belirticisiyle eşleştiğinden emin olun.
 */
ENTRY(gdt_table)
  .quad 0x0000000000000000        /* NULL belirtici */
  .quad 0x0000000000000000        /* kullanılmadı */
  .quad 0x00cf9a000000ffff        /* 0x10 kernel 4GB code at 0x00000000 */
  .quad 0x00cf92000000ffff        /* 0x18 kernel 4GB data at 0x00000000 */
  .quad 0x00cffa000000ffff        /* 0x23 user   4GB code at 0x00000000 */
  .quad 0x00cff2000000ffff        /* 0x2b user   4GB data at 0x00000000 */
  .quad 0x0000000000000000        /* kullanılmadı */
  .quad 0x0000000000000000        /* kullanılmadı */
  /*
    * APM bölütleri bayt taneciklilik özelliğine sahiptir ve
    * tabanları ile sınırları çalışma zamanında atanır.
    */
  .quad 0x0040920000000000        /* 0x40 kötü BIOS'lar için APM ataması */
  .quad 0x00409a0000000000        /* 0x48 APM CS    kod */
  .quad 0x00009a0000000000        /* 0x50 APM CS 16 kod (16 bit) */
  .quad 0x0040920000000000        /* 0x58 APM DS    veri */
  .fill NR_CPUS*4,8,0             /* TSS'ler ve LDT'ler için boşluk */

idt_descr ve gdt_table'dan önce olan ALIGN makrosu performans ile ilgilidir.