Çalıştırılabilir örnekler
Bir işletim sistemi olmadan çalışan bazı küçük metal merhaba dünya programları oluşturalım ve çalıştıralım:
Ayrıca bunları QEMU emülatöründe mümkün olduğunca deneyeceğiz, çünkü bu daha güvenli ve geliştirme için daha elverişlidir. QEMU testleri, önceden paketlenmiş QEMU 2.11.1 içeren bir Ubuntu 18.04 ana bilgisayarında yapılmıştır.
Aşağıdaki x86 örneklerinin ve daha fazlasının kodu bu GitHub deposunda bulunmaktadır .
Örnekler x86 gerçek donanımda nasıl çalıştırılır
Gerçek donanımda çalışan örneklerin tehlikeli olabileceğini unutmayın, örneğin diskinizi silebilirsiniz veya donanımı yanlışlıkla tutabilirsiniz: bunu yalnızca kritik veri içermeyen eski makinelerde yapın! Ya da daha iyisi, Raspberry Pi gibi ucuz yarı atılabilir devboardlar kullanın, aşağıdaki ARM örneğine bakın.
Tipik bir x86 dizüstü bilgisayar için aşağıdaki gibi bir şey yapmanız gerekir:
Görüntüyü bir USB çubuğuna yazın (verilerinizi yok eder!):
sudo dd if=main.img of=/dev/sdX
USB'yi bir bilgisayara takın
aç onu
USB'den önyükleme yapmasını söyleyin.
Bu, bellenimin sabit diskten önce USB almasını sağlamak anlamına gelir.
Makinenizin varsayılan davranışı bu değilse, USB'den önyükleme yapmayı seçebileceğiniz bir önyükleme menüsü alana kadar açılıştan sonra Enter, F12, ESC veya benzeri garip tuşlara basmaya devam edin.
Bu menülerde arama sırasını yapılandırmak çoğu zaman mümkündür.
Örneğin, T430'umda aşağıdakileri görüyorum.
Açtıktan sonra, önyükleme menüsüne girmek için Enter tuşuna basmam gerektiğinde:
Sonra, burada önyükleme aygıtı olarak USB seçmek için F12 tuşuna basmam gerekiyor:
Oradan, böyle bir önyükleme aygıtı olarak USB seçebilirsiniz:
Alternatif olarak, önyükleme sırasını değiştirmek ve daha yüksek önceliğe sahip USB'yi seçmek için, her seferinde manuel olarak seçmek zorunda kalmam, "Başlangıç Kesme Menüsü" ekranında F1'e basar ve daha sonra şuraya giderim:
Önyükleme sektörü
X86'da yapabileceğiniz en basit ve en düşük düzey şey , bir tür önyükleme sektörü olan bir Ana Önyükleme Sektörü (MBR) oluşturmak ve daha sonra bir diske kurmaktır.
Burada tek bir printf
çağrı ile bir tane oluşturuyoruz :
printf '\364%509s\125\252' > main.img
sudo apt-get install qemu-system-x86
qemu-system-x86_64 -hda main.img
Sonuç:
Hiçbir şey yapmadan bile, birkaç karakterin ekranda yazdırıldığını unutmayın. Bunlar bellenim tarafından yazdırılır ve sistemi tanımlamaya yarar.
Ve T430'da yanıp sönen bir imleçle boş bir ekran elde ediyoruz:
main.img
aşağıdakileri içerir:
\364
sekizli == 0xf4
altıgen içinde: hlt
CPU'nun çalışmayı durdurmasını söyleyen bir talimatın kodlaması .
Bu nedenle programımız hiçbir şey yapmaz: sadece başlat ve durdur.
\x
Onaltılık sayılar POSIX tarafından belirtilmediği için sekizli kullanıyoruz .
Bu kodlamayı kolayca elde edebiliriz:
echo hlt > a.S
as -o a.o a.S
objdump -S a.o
hangi çıktılar:
a.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: f4 hlt
ancak elbette Intel kılavuzunda da belgelenmiştir.
%509s
509 alan üretir. Bayt 510'a kadar dosyayı doldurmak gerekiyor.
\125\252
sekizli == 0x55
sonra 0xaa
.
Bunlar 511 ve 512 bayt olması gereken 2 sihirli bayttır.
BIOS, önyüklenebilir olanları arayan tüm disklerimizden geçer ve yalnızca bu iki sihirli bayta sahip olanları önyüklenebilir olarak kabul eder.
Mevcut değilse, donanım bunu önyüklenebilir bir disk olarak işlemez.
Eğer bir printf
usta değilseniz, içeriğini aşağıdakilerle onaylayabilirsiniz main.img
:
hd main.img
bu da beklenenleri gösterir:
00000000 f4 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 |. |
00000010 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | |
*
000001f0 20 20 20 20 20 20 20 20 20 20 20 20 20 20 55 aa | U.|
00000200
20
ASCII'de bir boşluk nerede .
BIOS bellenimi bu 512 baytı diskten okur, belleğe koyar ve bilgisayarı yürütmeye başlamak için ilk bayta ayarlar.
Merhaba dünya çizme sektörü
Şimdi minimal bir program hazırladığımıza, merhaba bir dünyaya geçelim.
Açık soru şu: IO nasıl yapılır? Birkaç seçenek:
firmware'den, örneğin BIOS veya UEFI'den bizim için yapmasını isteyin
VGA: üzerine yazılırsa ekrana yazdırılan özel bellek bölgesi. Korumalı Modda kullanılabilir.
bir sürücü yazın ve doğrudan ekran donanımıyla konuşun. Bunu yapmanın "uygun" yolu: daha güçlü, ama daha karmaşık.
seri bağlantı noktası . Bu, bir ana bilgisayar terminalinden karakter gönderen ve alan çok basit bir standart protokoldür.
Masaüstü bilgisayarlarda şöyle görünür:
Kaynak .
Ne yazık ki çoğu modern dizüstü bilgisayarda gösterilmiyor, ancak geliştirme kartlarına gitmenin ortak yolu, aşağıdaki ARM örneklerine bakın.
Bu tür arayüzler örneğin Linux çekirdeğinde hata ayıklamak için gerçekten yararlı olduğu için bu gerçekten bir utanç .
yongaların hata ayıklama özelliklerini kullanır. ARM, bunlara örneğin yarı gölgeleme diyor . Gerçek donanımda, ekstra donanım ve yazılım desteği gerektirir, ancak emülatörlerde ücretsiz ve kullanışlı bir seçenek olabilir. Örnek .
Burada x86'da daha basit olduğu için bir BIOS örneği yapacağız. Ancak bunun en sağlam yöntem olmadığını unutmayın.
main.S
.code16
mov $msg, %si
mov $0x0e, %ah
loop:
lodsb
or %al, %al
jz halt
int $0x10
jmp loop
halt:
hlt
msg:
.asciz "hello world"
GitHub akış yukarı .
link.ld
SECTIONS
{
/* The BIOS loads the code from the disk to this location.
* We must tell that to the linker so that it can properly
* calculate the addresses of symbols we might jump to.
*/
. = 0x7c00;
.text :
{
__start = .;
*(.text)
/* Place the magic boot bytes at the end of the first 512 sector. */
. = 0x1FE;
SHORT(0xAA55)
}
}
Birleştirin ve bağlantı kurun:
as -g -o main.o main.S
ld --oformat binary -o main.img -T link.ld main.o
qemu-system-x86_64 -hda main.img
Sonuç:
Ve T430'da:
Test tarihi: Lenovo Thinkpad T430, UEFI BIOS 1.16. Bir Ubuntu 18.04 ana bilgisayarında oluşturulan disk.
Standart kullanıcı arazisi montaj talimatlarının yanı sıra:
.code16
: GAS'a 16 bit kod vermesini söyler
cli
: yazılım kesintilerini devre dışı bırak. Bunlar işlemcininhlt
int $0x10
: bir BIOS çağrısı yapar. Karakterleri tek tek basan şey budur.
Önemli bağlantı bayrakları:
--oformat binary
: ham ikili montaj kodu çıktısı, normal kullanıcı alanı yürütülebilir dosyaları için olduğu gibi bir ELF dosyası içine sarmayın.
Bağlayıcı komut dosyası bölümünü daha iyi anlamak için, bağlamanın yer değiştirme adımını tanıyın: Bağlayıcılar ne yapar?
Cooler x86 çıplak metal programları
İşte elde ettiğim birkaç karmaşık çıplak metal kurulumu:
Montaj yerine C kullanın
Özet: Hiç düşünmediğiniz can sıkıcı sorunları çözecek olan GRUB çoklu önyükleme kullanın. Aşağıdaki bölüme bakın.
X86'daki temel zorluk, BIOS'un diskten belleğe yalnızca 512 bayt yüklediği ve C'yi kullanırken bu 512 baytı havaya uçurabilmenizdir!
Bunu çözmek için iki aşamalı bir bootloader kullanabiliriz . Bu, diskten belleğe daha fazla bayt yükleyen daha fazla BIOS çağrısı yapar. İnt 0x13 BIOS çağrılarını kullanarak sıfırdan en az 2. aşama montaj örneği :
Alternatif:
- yalnızca QEMU'da çalışmasına ancak gerçek donanımda çalışmamasına ihtiyacınız varsa
-kernel
, tüm ELF dosyasını belleğe yükleyen seçeneği kullanın . İşte bu yöntemle oluşturduğum bir ARM örneği .
- Raspberry Pi için, varsayılan ürün yazılımı,
kernel7.img
QEMU'nun -kernel
yaptığı gibi bir ELF dosyasından bizim için görüntü yüklemesini halleder .
Yalnızca eğitim amaçlı olarak, burada tek aşamalı minimum C örneği verilmiştir :
main.c
void main(void) {
int i;
char s[] = {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'};
for (i = 0; i < sizeof(s); ++i) {
__asm__ (
"int $0x10" : : "a" ((0x0e << 8) | s[i])
);
}
while (1) {
__asm__ ("hlt");
};
}
entry.s
.code16
.text
.global mystart
mystart:
ljmp $0, $.setcs
.setcs:
xor %ax, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %ss
mov $__stack_top, %esp
cld
call main
linker.ld
ENTRY(mystart)
SECTIONS
{
. = 0x7c00;
.text : {
entry.o(.text)
*(.text)
*(.data)
*(.rodata)
__bss_start = .;
/* COMMON vs BSS: /programming/16835716/bss-vs-common-what-goes-where */
*(.bss)
*(COMMON)
__bss_end = .;
}
/* /programming/53584666/why-does-gnu-ld-include-a-section-that-does-not-appear-in-the-linker-script */
.sig : AT(ADDR(.text) + 512 - 2)
{
SHORT(0xaa55);
}
/DISCARD/ : {
*(.eh_frame)
}
__stack_bottom = .;
. = . + 0x1000;
__stack_top = .;
}
Çalıştırmak
set -eux
as -ggdb3 --32 -o entry.o entry.S
gcc -c -ggdb3 -m16 -ffreestanding -fno-PIE -nostartfiles -nostdlib -o main.o -std=c99 main.c
ld -m elf_i386 -o main.elf -T linker.ld entry.o main.o
objcopy -O binary main.elf main.img
qemu-system-x86_64 -drive file=main.img,format=raw
C standart kütüphane
C standart kitaplığını da kullanmak istiyorsanız işler daha eğlenceli hale gelir, çünkü C standart kitaplık işlevselliğinin çoğunu POSIX aracılığıyla uygulayan Linux çekirdeğimiz yoktur .
Linux gibi tam gelişmiş bir işletim sistemine gitmeden birkaç olasılık şunları içerir:
Kendin yaz. Sonunda sadece bir grup başlık ve C dosyası değil mi? Sağ??
Newlib
Ayrıntılı örnek: /electronics/223929/c-standard-libraries-on-bare-metal/223931
Newlib uygular sizin için tüm sıkıcı olmayan OS belirli şeyler, örneğin memcmp
, memcpy
vb
Daha sonra, kendinize ihtiyacınız olan sistem çağrılarını uygulamanız için bazı taslaklar sağlar.
Örneğin, exit()
ARM ile yarı gölgeleme yoluyla uygulayabiliriz:
void _exit(int status) {
__asm__ __volatile__ ("mov r0, #0x18; ldr r1, =#0x20026; svc 0x00123456");
}
bu örnekte gösterildiği gibi .
Örneğin printf
, UART veya ARM sistemlerine yönlendirebilir veya yarı gölgelemeexit()
ile uygulayabilirsiniz .
FreeRTOS ve Zephyr gibi gömülü işletim sistemleri .
Bu tür işletim sistemleri tipik olarak önleyici zamanlamayı kapatmanıza izin verir, böylece programın çalışma süresi üzerinde tam kontrol sağlar.
Önceden uygulanmış bir tür Newlib olarak görülebilirler.
GNU GRUB Çoklu Önyükleme
Önyükleme sektörleri basittir, ancak çok uygun değildir:
- disk başına yalnızca bir işletim sisteminiz olabilir
- yük kodu gerçekten küçük olmalı ve 512 bayta sığmalıdır
- korumalı moda geçmek gibi kendiniz çok fazla başlangıç yapmanız gerekir
Bu nedenlerden ötürü GNU GRUB , çoklu önyükleme adı verilen daha uygun bir dosya biçimi oluşturmuştur.
Minimum çalışma örneği: https://github.com/cirosantilli/x86-bare-metal-examples/tree/d217b180be4220a0b4a453f31275d38e697a99e0/multiboot/hello-world
Ayrıca GitHub örnekler deposumda da kullanıyorumUSB'yi bir milyon kez yakmadan tüm donanımları gerçek donanımda kolayca çalıştırabilmek için .
QEMU sonucu:
T430:
İşletim sisteminizi bir çoklu önyükleme dosyası olarak hazırlarsanız, GRUB dosyayı normal bir dosya sisteminde bulabilir.
OS görüntülerini altına koyarak çoğu dağıtım bunu yapar /boot
.
Çoklu önyükleme dosyaları temel olarak özel bir başlığa sahip bir ELF dosyasıdır. GRUB tarafından şu adresten belirtilir: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html
Çoklu önyükleme dosyasını ile önyüklenebilir bir diske dönüştürebilirsiniz grub-mkrescue
.
Yazılım
Aslında, önyükleme sektörünüz sistemin CPU'sunda çalışan ilk yazılım değildir.
Aslında ilk önce bir yazılım olan firmware denir :
- donanım üreticileri tarafından yapılan
- tipik olarak kapalı kaynak ama muhtemelen C tabanlı
- salt okunur bellekte saklanır ve bu nedenle satıcının izni olmadan değiştirilmesi zor / imkansızdır.
İyi bilinen yazılımlar şunları içerir:
- BIOS : eski tüm x86 ürün yazılımı. SeaBIOS, QEMU tarafından kullanılan varsayılan açık kaynak uygulamasıdır.
- UEFI : BIOS halefi, daha iyi standartlaştırılmış, ancak daha yetenekli ve inanılmaz derecede şişirilmiş.
- Coreboot : asil çapraz kemer açık kaynak girişimi
Ürün yazılımı aşağıdakileri yapar:
önyüklenebilir bir şey bulana kadar her sabit disk, USB, ağ vb.
QEMU'yu çalıştırdığımızda, -hda
bunun main.img
donanıma bağlı bir sabit disk olduğunu ve denenecek hda
ilk disk olduğunu ve kullanıldığını söylüyor .
ilk 512 baytı RAM bellek adresine yükleyin, 0x7c00
CPU'nun RIP'sini oraya koyun ve çalışmasına izin verin
önyükleme menüsü veya BIOS yazdırma çağrıları gibi şeyleri ekranda göster
Bellenim, çoğu işletim sisteminin bağlı olduğu işletim sistemi benzeri işlevler sunar. Örneğin, bir Python alt kümesi BIOS / UEFI'de çalışacak şekilde taşınmıştır: https://www.youtube.com/watch?v=bYQ_lq5dcvM
Yazılımların işletim sistemlerinden ayırt edilemez olduğu ve yazılımın yapılabilecek tek "gerçek" çıplak metal programlama olduğu iddia edilebilir.
Bu CoreOS geliştiricisinin söylediği gibi :
Zor kısım
Bir PC'ye güç verdiğinizde, yonga setini (kuzeyköprüsü, güneyköprüsü ve SuperIO) oluşturan yongalar henüz doğru şekilde başlatılmamıştır. BIOS ROM'u olabildiğince CPU'dan kaldırılmış olsa da, CPU'ya erişilebilir, çünkü olması gerekir, aksi takdirde CPU'nun yürütme talimatı olmazdı. Bu, BIOS ROM'un tamamen eşlendiği anlamına gelmez, genellikle değil. Ancak, önyükleme işleminin başlaması için yeterince eşlenir. Başka cihazlar, unut gitsin.
Coreboot'u QEMU altında çalıştırdığınızda, daha yüksek Coreboot katmanları ve faydalı yüklerle denemeler yapabilirsiniz, ancak QEMU düşük düzey başlangıç kodunu denemek için çok az fırsat sunar. Birincisi, RAM en başından itibaren çalışır.
Post BIOS başlangıç durumu
Donanım çok şey gibi, standardizasyon zayıf olduğunu ve gereken şeylerden biri değil kodunuzu BIOS sonra çalışan başladığında itimat kayıtları başlangıç halidir.
Kendinize bir iyilik yapın ve aşağıdaki gibi bir başlatma kodu kullanın: https://stackoverflow.com/a/32509555/895245
Kayıtlar önemli yan etkilere sahiptir %ds
ve %es
önemli yan etkilere sahiptir, bu nedenle bunları açıkça kullanmasanız bile bunları sıfırlamanız gerekir.
Bazı emülatörlerin gerçek donanımdan daha hoş olduğunu ve size güzel bir başlangıç durumu verdiğini unutmayın. Sonra gerçek bir donanım üzerinde çalıştığınızda, her şey kırılır.
El Torito
CD'lere yazılabilecek biçim: https://en.wikipedia.org/wiki/El_Torito_%28CD-ROM_standard%29
ISO veya USB üzerinde çalışan hibrit bir görüntü üretmek de mümkündür. Bu grub-mkrescue
( örnek ) ile yapılabilir ve ayrıca Linux çekirdeği tarafından make isoimage
kullanılır isohybrid
.
KOL
ARM'de genel fikirler aynı.
IO için kullanmamız için BIOS gibi yaygın olarak mevcut yarı standartlaştırılmış önceden yüklenmiş ürün yazılımı yoktur, bu nedenle yapabileceğimiz en basit iki IO türü şunlardır:
- devboard'larda yaygın olarak bulunan seri
- LED'i yanıp söner
Yükledim:
X86'dan bazı farklılıklar şunları içerir:
IO doğrudan sihirli adreslerine yazarak yapılıyor, hayır yoktur in
ve out
talimatlar.
Buna bellek eşlemli IO denir .
Raspberry Pi gibi bazı gerçek donanımlar için sabit yazılımı (BIOS) kendiniz disk görüntüsüne ekleyebilirsiniz.
Bu, ürün yazılımını güncellemeyi daha şeffaf hale getirdiği için iyi bir şeydir.
kaynaklar