| |||||||
Sonunda çekirdek imgesi linux/vmlinux yerini aldı. İki girdiye ihtiyaç var:
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.
.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).
#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ü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.
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.
///////////////////////////////////////////////////////////////////////////////
// ö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.
| ||||||||||