Bootloader nedir?
Önyükleyici, gömülü bir sistemde güç açıldıktan sonra yürütülen ilk kod parçasıdır. CPU ve ilgili donanımın başlatılmasını tamamladıktan sonra, işletim sistemi görüntüsünü veya gömülü uygulama programını belleğe yükler ve ardından işletim sisteminin bulunduğu alana geçer; böylece işletim sisteminin çalışmasını başlatır. Bu süreç, STM32 gibi belirli mikrodenetleyicilerin programlanmasıyla ilgilenen herkes için temel öneme sahiptir.
Bir uygulama programına benzer şekilde, Bootloader, başlangıç kodu, kesmeler, ana program (Boot_main işlevi) ve isteğe bağlı olarak bir işletim sistemi gibi temel bileşenleri içeren bağımsız bir programdır. Küçük boyutuna rağmen, Bootloader kritik işlevleri kapsar.
Bootloader'lar genellikle donanıma büyük ölçüde bağımlıdır ve özellikle gömülü sistemler alanında önemlidir. Sonuç olarak, gömülü alanda evrensel olarak uygulanabilir bir bootloader oluşturmak neredeyse imkansızdır. Bununla birlikte, kullanıcıların belirli bootloader'ları tasarlamasına ve uygulamasına rehberlik etmek için bootloader'larla ilgili bazı kavramları genelleştirebiliriz.
Bir önyükleyicinin çalışma modları
Çoğu önyükleyici, iki farklı çalışma moduna sahiptir: önyükleme modu ve indirme modu.
Güç verildiğinde, önyükleyici sistemin yazılım ve donanım ortamını başlatır ve mevcut donanım koşullarına göre çalışma modlarından birini seçer. Bu, CPU çalışma modunu yapılandırmayı, belleği başlatmayı, kesmeleri devre dışı bırakmayı ve MMU/Önbelleği kapatma gibi görevleri yerine getirmeyi içerir.
Önyükleme Modu:
"Otonom" modu olarak da bilinen bu modda, önyükleyici hedef makinedeki bir katı hal depolama aygıtından işletim sistemini RAM'e kendi kendine yükler. Bu sürecin tamamı kullanıcı müdahalesi olmaksızın gerçekleşir. Bu mod, önyükleyicinin normal çalışma şeklini temsil eder.
İndirme Modu:
Bu modda, hedef makinedeki önyükleyici, dosyaları indirmek üzere bir ana makineyle seri veya ağ bağlantıları gibi yollarla iletişimi başlatır. Ana makineden alınan dosyalar, genellikle önyükleyici tarafından hedef makinenin RAM’ine kaydedilir; ardından hedef makinedeki Flash belleğe veya benzeri bir katı hal depolama aygıtına yazılır.
Önyükleyici Nasıl Çalışır?
Bir önyükleyicide iki tür başlatma süreci vardır: Tek Aşamalı ve Çok Aşamalı. Genel olarak, çok aşamalı Boot Loader'lar daha karmaşık işlevlere ve gelişmiş taşınabilirliğe sahiptir. Katı hal depolama aygıtlarından başlatılan Boot Loader'lar genellikle 1. aşama ve 2. aşama olarak bölünmüş iki aşamalı bir süreç kullanır: 1. aşama donanım başlatmasını gerçekleştirir, 2. aşama için bellek alanını hazırlar, 2. aşamayı belleğe kopyalar, yığını kurar ve ardından 2. aşamaya geçer.
Önyükleyici Aşama 1
Donanım Aygıtının Başlatılması
- Tüm Kesmeleri Devre Dışı Bırak: Kesmelerin yönetimi genellikle işletim sistemi aygıt sürücülerinin sorumluluğundadır; bu nedenle önyükleyici, çalışması boyunca kesme yanıtlarını göz ardı edebilir. Kesme maskeleme, CPU’nun kesme maskesi kayıtçısını veya durum kayıtçısını (örneğin ARM’nin CPSR kayıtçısı) değiştirerek gerçekleştirilebilir.
- CPU Hızını ve Saat Frekansını Ayarla.
- RAM'i Başlatın: Bu, sistemin bellek denetleyicisinin işlev kayıtlarını ve çeşitli bellek bankası denetim kayıtlarını doğru şekilde yapılandırmayı içerir.
- LED Başlatma: LED'ler genellikle sistemin durumunu (Tamam veya Hata) göstermek için GPIO üzerinden çalıştırılır. LED yoksa, seri iletişim yoluyla Boot Loader'ın logosunu veya karakter bilgilerini yazdırmak için UART'ın başlatılması bu amaca hizmet edebilir.
- CPU Dahili Komut/Veri Önbelleğini devre dışı bırakın.
Bootloader 2. Aşamasını Yüklemek İçin RAM Alanı Hazırlayın
Daha hızlı çalıştırma için, 2. aşama genellikle RAM'e yüklenir. Bu nedenle, önyükleyicinin 2. aşamasını yüklemek üzere kullanılabilir bir bellek aralığı ayrılmalıdır.
2. aşama genellikle C dilinde yazılmış kod içerdiğinden, gerekli alan hem 2. aşamanın yürütülebilir dosya boyutunu hem de yığın alanını dikkate almalıdır. Ayrıca, bu alan tercihen bellek sayfası boyutuna (genellikle 4 KB) hizalanmalıdır. Genellikle 1 MB RAM alanı yeterlidir. Spesifik adres aralığı isteğe bağlı olarak seçilebilir. Örneğin, yaygın bir yaklaşım, 2. aşamanın yürütülebilir görüntüsünü, sistem RAM'inin 0xc0200000 temel adresinden başlayan 1 MB'lik bir alanda çalışacak şekilde ayırmaktır. Ancak, 2. aşamayı tüm RAM alanının en üstteki 1 MB'sine (yani, (RamEnd-1MB) – RamEnd) ayırmak önerilen bir stratejidir.
Ayrılan RAM alanı aralığının boyutunu "stage2_size" (bayt cinsinden) ve başlangıç ve bitiş adreslerini "stage2_start" ve "stage2_end" (her iki adres de 4 bayt sınırlarına hizalanmış) olarak gösterelim. Böylece:
stage2_end = stage2_start + stage2_size
Ayrıca, ayrılan adres aralığının gerçekten yazılabilir ve okunabilir RAM alanı olduğundan emin olmak zorunludur. Bunu sağlamak için, ayrılan adres aralığının test edilmesi gerekir. "blob" tarafından kullanılan gibi uygun bir test yöntemi, her bellek sayfasının ilk iki kelimesinin okuma-yazma yeteneği açısından test edilmesini içerir.
Boot Loader'ın 2. Aşamasını RAM alanına kopyala
Bunu yapmak için lütfen iki hususa dikkat edin:
- 2. aşamanın yürütülebilir görüntüsünün katı hal depolama aygıtındaki konumu.
- RAM alanının başlangıç adresi.
Yığın İşaretçisini (SP) Ayarla
Yığın işaretçisinin (sp) ayarlanması, C dilindeki kodun yürütülmesi için hazırlık sağlar. Genellikle sp'nin değeri, 3.1.2 bölümünde ayrılan 1 MB RAM alanının en üst sınırını temsil eden (stage2_end-4) olarak ayarlanabilir (yığın aşağı doğru genişler).
Ek olarak, yığın işaretçisini ayarlamadan önce, kullanıcılara 2. aşamaya geçişin yaklaştığını belirtmek için LED'i devre dışı bırakmak mümkündür.
Bu yürütme adımlarının ardından, sistemin fiziksel bellek düzeni aşağıdaki şemaya benzemelidir.

stage2'nin C giriş noktasına atla
Yukarıdaki tüm hazırlıklar tamamlandıktan sonra, önyükleyicinin stage2 aşamasına geçerek çalıştırma işlemini gerçekleştirebilirsiniz. Örneğin, ARM sistemlerinde bu işlem PC kaydını uygun adrese ayarlayarak yapılabilir. Önyükleyicinin stage2 yürütülebilir görüntüsünün RAM alanına yeni kopyalandığı andaki sistem bellek düzeni yukarıdaki şekilde gösterilmiştir.
Önyükleyici 2. Aşama
Donanım Aygıtının Başlatılması
- Terminal kullanıcılarıyla G/Ç iletişim kurmak için en az bir seri bağlantı noktasını başlatın.
- Zamanlayıcıları ve diğer donanım bileşenlerini başlatın.
Bu aygıtları başlatmadan önce, main() işlevinin yürütülmeye başladığını belirtmek için LED'i yakmak da mümkündür. Aygıt başlatma işleminden sonra, program adı dizeleri ve sürüm numaraları gibi belirli bilgiler çıktılanabilir.
Sistem Bellek Eşlemesi Algılama
Bellek eşlemesi, sistem RAM birimlerine adres atamak için 4 GB'lık fiziksel adres alanının tamamı içindeki adres aralıklarının tahsis edilmesini ifade eder. Örneğin, SA-1100 CPU'da 0xC000,0000 adresinden başlayan 512 MB'lık bir adres alanı, sistemin RAM adres alanı olarak işlev görür. Samsung S3C44B0X CPU'da ise, 0x0c00,0000 ile 0x1000,0000 arasındaki 64 MB'lık adres alanı, sistemin RAM adres alanı olarak kullanılır. CPU'lar genellikle adres alanının önemli bir bölümünü sistem RAM'i için ayırsa da, belirli gömülü sistemler oluşturulurken ayrılan RAM adres alanının tamamı kullanılmayabilir. Bu nedenle, gömülü sistemler genellikle CPU'nun ayrılmış RAM adres alanının yalnızca bir kısmını RAM birimlerine eşler ve ayrılmış RAM adres alanının bir kısmını kullanılmadan bırakır. Bu durum göz önüne alındığında, Boot Loader'ın 2. aşaması, herhangi bir işlem yapmadan önce (örneğin, flash bellekte depolanan bir çekirdek görüntüsünü RAM alanına okumak gibi) sistemin tüm bellek eşlemesini incelemelidir. CPU'nun ayrılmış RAM adres alanının hangi kısımlarının gerçekten RAM adres birimlerine eşlendiğini ve hangilerinin "kullanılmayan" durumda olduğunu bilmesi gerekir.
Bellek Eşlemesinin Tanımı
RAM adres alanında kesintisiz bir adres aralığını tanımlamak için aşağıdaki veri yapısı kullanılabilir:
typedef struct memory_area_struct {
u32 start; /* the base address of the memory region */
u32 size; /* the byte number of the memory region */
int used;
} memory_area_t;
RAM adres alanı içindeki bu tür bitişik adres aralıkları iki durumdan birinde olabilir:
- used=1, sürekli adres aralığının uygulandığını ve RAM birimlerine gerçekten eşlendiğini gösterir.
- used=0, sürekli adres aralığının sistemde uygulanmadığını ve kullanılmamış olarak kaldığını gösterir.
Yukarıda açıklanan memory_area_t veri yapısına göre, CPU'nun ayrılmış RAM adres alanının tamamı, aşağıda gösterildiği gibi memory_area_t türünde bir dizi ile temsil edilebilir:
memory_area_t memory_map[NUM_MEM_AREAS] = {
[0 ... (NUM_MEM_AREAS - 1)] = {
.start = 0,
.size = 0,
.used = 0
},
};
Bellek Eşlemesi Algılama
İşte tüm RAM adres alanı içindeki bellek eşleme durumunu tespit etmek için basit ama etkili bir algoritma:
/* Initialize the array */
for(i = 0; i < NUM_MEM_AREAS; i++)
memory_map[i].used = 0;
/* Write 0 to all memory locations */
for(addr = MEM_START; addr < MEM_END; addr += PAGE_SIZE)
*(u32 *)addr = 0;
for(i = 0, addr = MEM_START; addr < MEM_END; addr += PAGE_SIZE) {
/*
* Check whether the address space starting from base address
* MEM_START + i * PAGE_SIZE, with a size of PAGE_SIZE, is a valid RAM address space.
*/
Call the algorithm test_mempage() from section 3.1.2;
if (current memory page is not a valid RAM page) {
/* no RAM here */
if (memory_map[i].used )
i++;
continue;
}
/*
* The current page is a valid address range mapped to RAM.
* However, we need to determine if it's an alias of some address page within the 4GB address space.
*/
if (*(u32 *)addr != 0) { /* alias? */
/* This memory page is an alias of an address page within the 4GB address space. */
if (memory_map[i].used )
i++;
continue;
}
/*
* The current page is a valid address range mapped to RAM,
* and it's not an alias of an address page within the 4GB address space.
*/
if (memory_map[i].used == 0) {
memory_map[i].start = addr;
memory_map[i].size = PAGE_SIZE;
memory_map[i].used = 1;
} else {
memory_map[i].size += PAGE_SIZE;
}
} /* end of for (…) */
Sistemin bellek eşleme durumunu tespit etmek için yukarıdaki algoritma uygulandığında, önyükleyici ayrıntılı bellek eşleme bilgilerini seri bağlantı noktasına da yazdırabilir.
Çekirdek ve Kök Dosya Sistemi Görüntülerinin Yüklenmesi
Bellek Düzeni Planlaması
Bu, iki unsuru içerir:
- çekirdek görüntüsünün kapladığı bellek aralığı;
- kök dosya sistemi tarafından işgal edilen bellek aralığı. Bellek düzenini planlarken, görüntülerin başlangıç adresini ve boyutunu göz önünde bulundurun.
Çekirdek görüntüsü için, genellikle (MEM_START + 0x8000) adresinden başlayan ve yaklaşık 1 MB boyutunda bir bellek aralığına kopyalanır (gömülü Linux çekirdekleri genellikle 1 MB'nin altındadır). Neden MEM_START ile MEM_START + 0x8000 arasında 32 KB'lik bir boşluk bırakılır? Bunun nedeni, Linux çekirdeğinin bu bellek segmentine önyükleme parametreleri ve çekirdek sayfa tabloları gibi belirli küresel veri yapılarını yerleştirmesidir.
Kök dosya sistemi görüntüsü genellikle MEM_START + 0x0010,0000 adresinden başlayan konuma kopyalanır. Kök dosya sistemi görüntüsü olarak bir Ramdisk kullanılıyorsa, sıkıştırılmamış boyut genellikle 1 MB civarındadır.
Flash'tan kopyalama
ARM gibi gömülü işlemciler genellikle Flash ve diğer katı hal depolama aygıtlarına tek bir bellek adres alanı içinde eriştiği için, Flash'tan veri okumak RAM birimlerinden veri okumaya benzer. Flash aygıtından görüntüyü kopyalamak için basit bir döngü yeterlidir:
while (count) {
*dest++ = *src++; /* they are all aligned with word boundary */
count -= 4; /* byte number */
};
Çekirdek Önyükleme Parametrelerini Ayarlama
Çekirdek görüntüsü ve kök dosya sistemi görüntüsü RAM alanına kopyalandıktan sonra, Linux çekirdeğinin başlatılması için hazırlıklar yapılabilir. Ancak çekirdeği çalıştırmadan önce bir hazırlık adımı gereklidir: Linux çekirdeğinin önyükleme parametrelerinin ayarlanması.
2.4.x sürümünden itibaren Linux çekirdekleri, önyükleme parametrelerinin etiketli bir liste biçiminde aktarılmasını bekler. Önyükleme parametresi etiketli listesi, ATAG_CORE etiketiyle başlar ve ATAG_NONE etiketiyle biter. Her etiket, parametreyi tanımlayan bir tag_header yapısından ve ardından parametre değerlerini içeren veri yapılarından oluşur. tag ve tag_header veri yapıları, Linux çekirdek kaynak kodunun include/asm/setup.h başlık dosyasında tanımlanmıştır.
Gömülü Linux sistemlerinde, önyükleyici tarafından ayarlanması gereken yaygın önyükleme parametreleri arasında ATAG_CORE, ATAG_MEM, ATAG_CMDLINE, ATAG_RAMDISK ve ATAG_INITRD bulunur.
Örneğin, ATAG_CORE'u şu şekilde ayarlayabilirsiniz:
params = (struct tag *)BOOT_PARAMS;
params->hdr.tag = ATAG_CORE;
params->hdr.size = tag_size(tag_core);
params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;
params = tag_next(params);
Burada BOOT_PARAMS, bellekteki çekirdek önyükleme parametrelerinin başlangıç adresini temsil eder ve params işaretçisi struct tag türündedir. tag_next() makrosu, geçerli etiketin hemen ardından gelen bir sonraki etiketin başlangıç adresini hesaplar. Çekirdeğin kök dosya sistemi için aygıt kimliğinin burada ayarlandığını belirtmek önemlidir.
Aşağıda, bellek eşleme bilgilerini ayarlamak için bir örnek kod bulunmaktadır:
for (i = 0; i < NUM_MEM_AREAS; i++) {
if (memory_map[i].used) {
params->hdr.tag = ATAG_MEM;
params->hdr.size = tag_size(tag_mem32);
params->u.mem.start = memory_map[i].start;
params->u.mem.size = memory_map[i].size;
params = tag_next(params);
}
}
memory_map[] dizisinde, her bir geçerli bellek segmenti bir ATAG_MEM parametre etiketine karşılık gelir.
Linux çekirdeği, başlatma sırasında komut satırı parametreleri olarak bilgi alabilir. Bu, çekirdeğin kendi başına algılayamadığı donanım parametre bilgilerini sağlamamıza veya çekirdeğin algıladığı bilgileri geçersiz kılmamıza olanak tanır. Örneğin, "console=ttyS0,115200n8" komut satırı parametre dizesini kullanarak çekirdeğe ttyS0'ı "115200bps, eşlik yok, 8 veri biti" ayarlarıyla konsol olarak kullanmasını söyleriz. İşte çekirdeğin komut satırı parametre dizesini ayarlamak için bir örnek kod:
char *p;
for (p = commandline; *p == ' '; p++)
;
if (*p == '\0')
return;
params->hdr.tag = ATAG_CMDLINE;
params->hdr.size = (sizeof(struct tag_header) + strlen(p) + 1 + 4) >> 2;
strcpy(params->u.cmdline.cmdline, p);
params = tag_next(params);
Yukarıdaki kodda, tag_header yapısının boyutunu belirlerken, dizgede sonlandırma karakteri olan '\0' karakterini de içermesi ve en yakın 4 bayt katına yuvarlanması gerektiğine dikkat edin; zira tag_header yapısının size üyesi, kelime sayısını temsil eder.
Aşağıda, initrd görüntüsünün (sıkıştırılmış biçimde) RAM'de bulunduğu konumu ve boyutunu belirten ATAG_INITRD ayarı için bir örnek kod bulunmaktadır:
params->hdr.tag = ATAG_INITRD2;
params->hdr.size = tag_size(tag_initrd);
params->u.initrd.start = RAMDISK_RAM_BASE;
params->u.initrd.size = INITRD_LEN;
params = tag_next(params);
Son olarak, ATAG_NONE etiketini ayarlayarak başlangıç parametrelerinin tüm listesini tamamlayın:
static void setup_end_tag(void) {
params->hdr.tag = ATAG_NONE;
params->hdr.size = 0;
}
Çekirdeği Çalıştırma
Önyükleyici, çekirdeğin ilk komutuna doğrudan atlayarak, yani MEM_START + 0x8000 adresine doğrudan atlayarak Linux çekirdeğini çağırır. Atlama işlemi sırasında aşağıdaki koşulların karşılanması gerekir:
- CPU kayıt ayarları:
R0 = 0
R1 = Makine türü kimliği (Makine Türü Numarası için linux/arch/arm/tools/mach-types dosyasına bakın)
R2 = RAM'deki önyükleme parametresi etiketli listesinin başlangıç adresi - CPU modu: Kesmeleri
devre dışı bırakın (IRQ'lar ve FIQ'lar)
CPU, SVC modunda olmalıdır - Önbellek ve MMU ayarları:
MMU kapalı olmalıdır
Komut önbelleği açık veya kapalı olabilir
Veri önbelleği kapalı olmalıdır
C kullanılıyorsa, çekirdeğin çağrılması şu şekilde yapılabilir:
void (*theKernel)(int zero, int arch, u32 params_addr) =
(void (*)(int, int, u32))KERNEL_RAM_BASE;
theKernel(0, ARCH_NUMBER, (u32)kernel_params_start);
theKernel() işlevinin çağrısının asla geri dönmemesi gerektiğine dikkat edin. Geri dönerse, bir hata meydana gelmiştir.
Gömülü Sistem Önyükleyicisi ve PC Önyükleyicisi
PC mimarilerinde önyükleyici, BIOS (temelde bir donanım yazılımı) ve sabit diskin MBR'sinde bulunan işletim sistemi önyükleyicisinden (ör. LILO, GRUB) oluşur. BIOS, donanım algılama ve kaynak tahsisini tamamladıktan sonra, önyükleyiciyi sabit diskin MBR'sinden sistemin RAM'ine yükler ve kontrolü işletim sistemi önyükleyicisine devreder. Önyükleyicinin birincil görevi, çekirdek görüntüsünü sabit diskten RAM'e okumak ve ardından işletim sistemini başlatmak için çekirdeğin giriş noktasına atlamaktır.
Gömülü sistemlerde genellikle BIOS gibi bir ürün yazılımı programı yoktur (ancak bazı gömülü CPU'lar küçük bir gömülü önyükleme programı içerebilir). Sonuç olarak, tüm sistemin yükleme ve başlatma görevi önyükleyici tarafından gerçekleştirilir. Örneğin, ARM7TDMI çekirdeğine dayalı bir gömülü sistemde, sistem genellikle güç açıldığında veya sıfırlandığında 0x00000000 adresinden çalışmaya başlar ve bu adres genellikle sistemin önyükleyici programını içerir.
Boot Loader tarafından desteklenen CPU ve Gömülü Kart
Her farklı CPU mimarisi, kendine özgü bir önyükleyiciye sahiptir. Bazı önyükleyiciler, birden fazla mimariye sahip CPU’ları da destekler. Örneğin, U-Boot hem ARM mimarisini hem de MIPS mimarisini destekler. Önyükleyici, CPU’nun mimarisine bağlı olmasının yanı sıra, aslında belirli gömülü kart düzeyindeki cihazların yapılandırmasına da bağlıdır. Yani, aynı CPU’ya dayalı olarak üretilmiş olsalar bile, önyükleyicinin bu iki farklı gömülü kart için her zaman uygun olduğu söylenemez.
Önyükleyici Kurulum Ortamı
Bir sistem açıldığında veya sıfırlandığında, CPU'lar genellikle CPU üreticisi tarafından belirlenen önceden belirlenmiş bir adresten komutları alır. Örneğin, ARM7TDMI çekirdeğine dayalı CPU'lar, sıfırlamadan sonra ilk komutlarını genellikle 0x00000000 adresinden alır. CPU mimarileri üzerine kurulu gömülü sistemler, genellikle bir tür katı hal depolama aygıtını (ROM, EPROM veya FLASH gibi) bu önceden belirlenmiş adrese eşler. Bu nedenle, sistem açıldıktan sonra CPU ilk olarak önyükleyici programını çalıştırır.



