internet pencereler Android

İşletim sistemi çekirdeği nasıl yazılır. Assembler Okulu: İşletim Sistemi Geliştirme

Son iki yılda Habr'ı okurken, işletim sistemini geliştirmek için yalnızca birkaç girişim gördüm (özellikle: kullanıcılardan ve (süresiz olarak ertelendi) ve (terk edilmedi, ancak şimdilik x86 uyumlu işlemcilerin korumalı modunun bir açıklaması gibi görünüyor) x86 için bir işletim sistemi yazmayı da bilmeniz gerekir); ve bitmiş sistemin bir açıklaması (sıfırdan olmasa da, bunda yanlış bir şey olmamasına rağmen, belki de tam tersi)). Nedense hemen hemen tüm sistem (ve uygulamanın bir parçası) programcılarının en az bir kez olduğunu düşünüyorum, ancak kendi işletim sistemlerini yazmayı düşündüler. Bu bağlamda, bu kaynağın geniş topluluğundan 3 işletim sistemi saçma bir sayı gibi görünüyor. Görünüşe göre, kendi işletim sistemlerini düşünenlerin çoğu fikirlerden öteye gitmiyor, küçük bir kısım bir önyükleyici yazdıktan sonra duruyor, birkaç çekirdek parçası yazıyor ve sadece umutsuzca inatçı bir işletim sistemini belli belirsiz hatırlatan bir şey yaratıyor (karşılaştırıldığında). Windows / Linux gibi bir şeyle) ... Bunun birçok nedeni var, ancak bence en önemlisi, işletim sistemini yazma ve hata ayıklama sürecinin az sayıda açıklaması nedeniyle insanların geliştirmeyi bırakması (bazılarının başlamak için zamanları bile yoktu), Bu, uygulamalı yazılım geliştirme sırasında olanlardan oldukça farklıdır.

Bu küçük notla, doğru bir şekilde başlarsanız, kendi işletim sisteminizi geliştirmenin özellikle zor bir şey olmadığını göstermek isterim. Kesimin altında, sıfırdan bir işletim sistemi yazma eylemi için kısa ve oldukça genel bir kılavuz bulunmaktadır.

Nasıl gerek yok başlamak
Lütfen aşağıdaki metni başka birinin makalelerine veya işletim sistemi yazma kılavuzlarına yönelik açık bir eleştiri olarak algılamayın. Sadece bu tür makalelerde yüksek sesle başlıklar altında çok sık vurgulanır, bir tür minimum hazırlığın uygulanmasına vurgu yapılır ve bu, çekirdeğin bir prototipi olarak sunulur. Aslında, çekirdeğin yapısını ve işletim sisteminin bölümlerinin etkileşimini bir bütün olarak düşünmelisiniz ve bu prototip standart bir "Merhaba, Dünya!" - uygulamalı yazılım dünyasında bir uygulama olarak düşünülmelidir. Bu açıklamalara küçük bir bahane olarak, aşağıda "Merhaba Dünya!" diye bir alt bölümün olduğunu söylemek gerekir.

Bootloader yazmaya gerek yok. Akıllı insanlar, Multiboot Spesifikasyonu ile geldi, uygulandı ve ne olduğunu ve nasıl kullanılacağını ayrıntılı olarak açıkladı. Kendimi tekrar etmek istemiyorum, sadece işe yaradığını, hayatı kolaylaştırdığını ve uygulanması gerektiğini söyleyeceğim. Bu arada, şartnamenin tamamını okumak daha iyidir, kısadır ve hatta örnekler içerir.

İşletim sistemini tamamen assembler'a yazmak gerekli değildir. Bu o kadar da kötü değil, tam tersi - hızlı ve küçük programlar her zaman büyük saygı görecek. Bu dil çok daha fazla geliştirme çabası gerektirdiğinden, montajcı kullanmak yalnızca coşkuda bir azalmaya ve sonuç olarak işletim sistemi kaynak kodunu arka yakıcıya atmaya yol açacaktır.

Video belleğine özel bir yazı tipi yüklemeye ve herhangi bir şeyi Rusça olarak görüntülemeye gerek yoktur. Bunda bir anlam yok. İngilizceyi kullanmak çok daha kolay ve çok yönlüdür ve yazı tipini daha sonra değiştirmek, sabit diskten dosya sistemi sürücüsü aracılığıyla yüklemek (aynı zamanda, sadece başlamaktan daha fazlasını yapmak için ek bir teşvik olacaktır).

Eğitim
Başlangıç ​​olarak, her zaman olduğu gibi, yaklaşmakta olan çalışma hacmi hakkında bir fikir sahibi olmak için genel teoriye aşina olmalısınız. İncelenen konuyla ilgili iyi kaynaklar, Habre'de işletim sistemi yazmakla ilgili diğer makalelerde daha önce bahsedilmiş olan E. Tanenbaum'un kitaplarıdır. Mevcut sistemleri açıklayan makaleler de vardır ve işletim sistemi geliştirme konusunda önyargılı çeşitli kılavuzlar / posta listeleri / makaleler / örnekler / siteler vardır, bazılarına bağlantılar makalenin sonunda verilmiştir.

İlk eğitim programından sonra, ana sorulara karar vermeniz gerekir:

  • hedef mimari - x86 (gerçek / korumalı / uzun mod), PowerPC, ARM, ...
  • çekirdek / işletim sistemi mimarisi - monolit, modüler monolit, mikro çekirdek, ekzokernel, çeşitli melezler
  • dil ve derleyicisi - C, C ++, ...
  • çekirdek dosya formatı - elf, a.out, coff, ikili, ...
  • geliştirme ortamı (evet, bu da önemli bir rol oynar) - IDE, vim, emacs, ...
Ardından, seçilene göre ve aşağıdaki alanlarda bilginizi derinleştirmelisiniz:
  • video belleği ve onunla çalışın - işin kanıtı olarak sonuç, en başından itibaren gereklidir
  • HAL (Donanım Soyutlama katmanı) - birkaç donanım mimarisi için destek olsa ve çekirdeğin en düşük seviyeli kısımlarını süreçler, semaforlar vb. gibi soyut şeylerin uygulanmasından yetkin bir şekilde ayırma planlanmasa bile gereksiz olmayacaktır
  • bellek yönetimi - fiziksel ve sanal
  • yürütme kontrolü - süreçler ve iş parçacıkları, bunların zamanlaması
  • aygıt yönetimi - sürücüler
  • sanal dosya sistemleri - çeşitli dosya sistemlerinin içeriğine tek bir arayüz sağlamak için
  • API (Uygulama Programlama Arayüzü) - uygulamaların çekirdeğe tam olarak nasıl erişeceği
  • IPC (İşlemler Arası İletişim) - er ya da geç süreçler iletişim kurmak zorunda kalacak
Araçlar
Seçilen dil ve geliştirme araçlarını göz önünde bulundurarak, gelecekte komut dosyaları yazarak, montajı, görüntü hazırlamayı ve başlatmayı en üst düzeyde kolaylaştırmayı ve hızlandırmayı mümkün kılacak böyle bir yardımcı program ve bunların ayarlarını seçmelisiniz. proje ile sanal makine. Bu noktaların her biri üzerinde biraz daha ayrıntılı duralım:
  • make, cmake, gibi herhangi bir standart araç oluşturmak için uygundur ... Burada, bir Multiboot üstbilgisi, sağlama toplamları veya başka herhangi bir amaç eklemek için bağlayıcı ve (özel olarak yazılmış) yardımcı programlar için komut dosyaları kullanılabilir.
  • bir görüntü hazırlamak, onu monte etmek ve dosyaları kopyalamak anlamına gelir. Buna göre imaj dosyasının formatı hem mount/copy yardımcı programı hem de sanal makine bunu destekleyecek şekilde seçilmelidir. Doğal olarak, hiç kimse bu noktadan eylemlerin ya montajın son kısmı olarak ya da öykünücüyü başlatmaya hazırlık olarak yapılmasını yasaklamaz. Her şey belirli araçlara ve kullanımları için seçilen seçeneklere bağlıdır.
  • sanal bir iş makinesi başlatmak temsil etmez, ancak önce görüntünün bağlantısını kesmeyi unutmamalısınız (bu noktada sökme, çünkü sanal makineyi başlatmadan önce bu işlemde gerçek bir anlam yoktur). Ayrıca, öykünücüyü hata ayıklama modunda (varsa) başlatmak için bir komut dosyasına sahip olmak gereksiz olmayacaktır.
Önceki tüm adımları tamamladıysanız, çekirdek olarak yüklenen ve ekranda bir şeyler görüntüleyen minimal bir program yazmalısınız. Seçilen araçların sakıncaları veya eksiklikleri bulunursa, bunları (eksiklikleri) ortadan kaldırmak veya en kötü durumda bunları kabul etmek gerekir.

Bu adımda, gelecekte kullanmayı planladığınız geliştirme araçlarının birçok özelliğini test etmeniz gerekiyor. Örneğin, modülleri GRUB'a yüklemek veya sanal bir makinede görüntü yerine fiziksel disk / bölüm / flash sürücü kullanmak.

Bu aşama başarıyla geçtikten sonra asıl gelişme başlar.

Çalışma zamanı desteği sağlama
Üst düzey dillerde yazılması önerildiğinden, genellikle derleyici paketinin yazarları tarafından uygulanan bazı dil özelliklerinin desteklenmesine özen gösterilmelidir. Örneğin, C ++ için bu şunları içerir:
  • yığında bir veri bloğunu dinamik olarak tahsis etme işlevi
  • yığınla çalışmak
  • veri bloğu kopyalama işlevi (memcpy)
  • program giriş noktası işlevi
  • küresel nesnelerin yapıcılarına ve yıkıcılarına çağrılar
  • istisnalarla çalışmak için bir dizi işlev
  • uygulanmamış saf sanal işlevler için saplama
"Merhaba Dünya!" yazarken bu işlevlerin olmaması hiçbir şekilde kendini hissettirmeyebilir, ancak kod eklendikçe, bağlayıcı tatmin edilmeyen bağımlılıklardan şikayet etmeye başlayacaktır.

Doğal olarak, standart kütüphaneden hemen söz edilmelidir. Tam bir uygulama gerekli değildir, ancak işlevselliğin büyük bir alt kümesi uygulamaya değerdir. O zaman kodlama çok daha tanıdık ve hızlı olacaktır.

hata ayıklama
Bu makalenin sonuna doğru hata ayıklama hakkında ne yazdığına bakma. Aslında bu, işletim sistemi geliştirmede çok ciddi ve zor bir konudur, çünkü olağan araçlar burada uygulanamaz (birkaç istisna dışında).

Aşağıdakileri tavsiye edebilirsiniz:

  • verilen için, hata ayıklama çıktısı
  • "hata ayıklayıcıya" hemen çıkış yaparak onaylayın (sonraki paragrafa bakın)
  • bir konsol hata ayıklayıcısının bir benzeri
  • öykünücünün bir hata ayıklayıcı, sembol tabloları veya başka bir şey bağlamanıza izin verip vermediğini kontrol edin
Çekirdeğe yerleşik bir hata ayıklayıcı olmadan, hataları bulmanın gerçek bir kabus olma şansı vardır. Dolayısıyla, geliştirmenin bir aşamasında onu yazmaktan kaçış yok. Ve bu kaçınılmaz olduğu için, önceden yazmaya başlamak ve böylece gelişiminizi büyük ölçüde kolaylaştırmak ve çok zaman kazandırmak daha iyidir. Hata ayıklamanın sistemin normal çalışması üzerinde minimum etkiye sahip olması için hata ayıklayıcıyı çekirdekten bağımsız bir şekilde uygulayabilmek önemlidir. Yararlı olabilecek birkaç komut türü vardır:
  • standart hata ayıklama işlemlerinden bazıları: kesme noktaları, çağrı yığını, çıktı değerleri, döküm yazdırma, ...
  • zamanlayıcının yürütme kuyruğu veya çeşitli istatistikler gibi çeşitli yararlı bilgileri görüntüleme komutları (ilk bakışta göründüğü kadar yararsız değildir)
  • çeşitli yapıların durumunun tutarlılığını kontrol etmek için komutlar: boş / kullanılmış bellek, yığın veya mesaj kuyruğu listeleri
Gelişim
Ardından, işletim sisteminin şu anda kararlı çalışmasını sağlaması gereken ve gelecekte - kolay genişletilebilirlik ve esneklik sağlayan ana öğelerini yazmanız ve hata ayıklamanız gerekir. Bellek yöneticilerinin/işlemlerin/(ne olursa olsun) yanında, sürücülerin ve dosya sistemlerinin arayüzü çok önemlidir. Tasarımlarına, tüm cihaz / FS türleri çeşitliliği dikkate alınarak özel bir dikkatle yaklaşılmalıdır. Elbette bunları zaman içinde değiştirebilirsiniz, ancak bu çok acı verici ve hataya açık bir süreçtir (ve çekirdeğin hatalarını ayıklamak kolay bir iş değildir), bu yüzden unutmayın - uygulamaya başlamadan önce bu arayüzleri en az on kez düşünün. .
SDK ile benzerlik
Proje geliştikçe, ona yeni sürücüler ve programlar eklenmelidir. Büyük olasılıkla, zaten ikinci sürücüde (muhtemelen belirli bir türde) / programda, bazı ortak özellikler fark edilecektir (dizin yapısı, derleme kontrol dosyaları, modüller arasındaki bağımlılıkların belirtilmesi, ana veya sistem istek işleyicilerinde tekrarlanan kod (örneğin) , sürücülerin kendileri cihazla uyumluluğunu kontrol ederse) )). Eğer öyleyse, bu, işletim sisteminiz için çeşitli program türleri için şablonlar geliştirme ihtiyacının bir işaretidir.

Bu veya bu tür bir programın yazılma sürecini açıklayan belgelere gerek yoktur. Ancak standart öğelerden bir boşluk bırakmaya değer. Bu, yalnızca program eklemeyi kolaylaştırmakla kalmayacak (bu, mevcut programları kopyalayarak ve ardından değiştirerek yapılabilir, ancak bu daha fazla zaman alacaktır), aynı zamanda arabirimlerde, biçimlerde veya başka bir şey. İdeal olarak bu tür değişikliklerin olmaması gerektiği açıktır, ancak işletim sistemi geliştirme atipik bir şey olduğundan, potansiyel olarak yanlış kararlar için birçok yer vardır. Ancak alınan kararların yanlışlığının anlaşılması, her zaman olduğu gibi, uygulanmasından bir süre sonra gelecektir.

Sonraki adımlar
Kısacası, işletim sistemleri hakkında (ve öncelikle cihazları hakkında) okuyun, sisteminizi geliştirin (hız aslında önemli değil, asıl şey hiç durmamak ve zaman zaman yeni güçler ve fikirlerle projeye geri dönmek) ve içindeki hataları düzeltmek doğaldır (hangisinin bazen sistemi başlatmanın ve onunla "oynamanın" gerekli olduğunu bulmak için). Zamanla, geliştirme süreci daha kolay ve kolay hale gelecek, hatalar daha az yaygın olacak ve kendi işletim sistemlerini geliştirme fikrinin saçmalığına rağmen, "umutsuzca inatçı" listesine dahil edileceksiniz. yine de yaptı.

Bu makale dizisi, düşük seviyeli programlamaya, yani bilgisayar mimarisine, işletim sistemi tasarımına, montaj dili programlamasına ve ilgili alanlara ayrılmıştır. Şimdiye kadar iki habrauser yazıyla uğraşıyor - iley ve pehat. Birçok lise öğrencisi, öğrenci ve profesyonel programcı için bu konuları öğrenmenin çok zor olduğu ortaya çıkıyor. Düşük seviyeli programlama üzerine çok sayıda literatür ve kurs var, ancak bunlardan tam ve kapsamlı bir resim elde etmek zor. Montajcı ve işletim sistemleri üzerine bir veya iki kitap okuduktan sonra, en azından genel anlamda, bu karmaşık demir, silikon ve birçok programın - bir bilgisayarın - gerçekte nasıl çalıştığını hayal etmek zordur.

Herkes öğrenme problemini kendi yöntemiyle çözer. Birisi çok fazla literatür okur, biri yol boyunca çabucak uygulamaya ve anlamaya çalışır, biri çalıştığı her şeyi arkadaşlarına açıklamaya çalışır. Biz de bu yaklaşımları birleştirmeye karar verdik. Bu nedenle, bu makale kursunda, basit bir işletim sisteminin nasıl yazılacağını adım adım göstereceğiz. Makaleler genel bir nitelikte olacak, yani kapsamlı teorik bilgiler içermeyecekler, ancak her zaman iyi teorik materyallere bağlantılar sağlamaya ve ortaya çıkan tüm soruları cevaplamaya çalışacağız. Net bir planımız yok, bu süreçte geri bildirimleriniz dikkate alınarak birçok önemli karar verilecek.

Belki de size ve kendimize yanlış bir kararın tüm sonuçlarını tam olarak anlamamıza ve bu konuda bazı teknik beceriler geliştirmemize izin vermek için geliştirme sürecini kasıtlı olarak durma noktasına getireceğiz. O yüzden bizim kararlarımızı tek doğru olarak görmemeli ve bize körü körüne güvenmelisin. Bir kez daha, okuyucuların makaleleri tartışırken aktif olmalarını beklediğimizi ve bunun sonraki makalelerin genel gelişimini ve yazılmasını güçlü bir şekilde etkilemesini beklediğimizi vurguluyoruz. İdeal olarak, bazı okuyucuların zaman içinde sistemin gelişimine katıldığını görmek isteriz.

Okuyucunun, bilgisayar mimarisinin temel kavramlarının yanı sıra, montaj ve C dillerinin temellerine zaten aşina olduğunu varsayacağız. Yani, register veya diyelim ki rastgele erişimli hafızanın ne olduğunu açıklamayacağız. Yeterli bilgiye sahip değilseniz, her zaman ek literatüre başvurabilirsiniz. Kısa bir referans listesi ve iyi makalelere sahip sitelere bağlantılar makalenin sonundadır. Ayrıca, tüm derleme talimatları bu sistem için özel olarak verileceğinden, Linux kullanabilmek de istenmektedir.

Ve şimdi - konuya daha fazla. Yazının devamında klasik bir "Merhaba Dünya" programı yazacağız. Merhaba Dünyamız biraz spesifik olacak. Herhangi bir işletim sisteminden değil, doğrudan "çıplak metal üzerinde" çalışacak. Doğrudan kodu yazmaya geçmeden önce, bunu tam olarak nasıl yapmaya çalıştığımızı bulalım. Ve bunun için bilgisayarınızı başlatma sürecini düşünmeniz gerekir.

Bu yüzden en sevdiğiniz bilgisayarı alın ve sistem birimindeki en büyük düğmeye basın. Neşeli bir açılış ekranı görüyoruz, sistem ünitesi bir hoparlörle mutlu bir şekilde bip sesi çıkarıyor ve bir süre sonra işletim sistemi yükleniyor. Anladığınız gibi, işletim sistemi sabit diskte depolanır ve burada soru ortaya çıkar: işletim sistemi sihirli bir şekilde RAM'e nasıl önyükleme yaptı ve çalışmaya başladı?

Şunu bilin: Bundan herhangi bir bilgisayardaki sistem sorumludur ve adı - hayır, Windows değil, dilinizi tırmıklıyor - buna BIOS denir. Adı, Temel Giriş-Çıkış Sistemi, yani temel giriş-çıkış sistemi anlamına gelir. BIOS, anakart üzerindeki küçük bir mikro devre üzerinde bulunur ve büyük ON düğmesine bastıktan hemen sonra başlar. BIOS'un üç ana görevi vardır:

  1. Bağlı tüm cihazları (işlemci, klavye, monitör, RAM, ekran kartı, kafa, kollar, kanatlar, bacaklar ve kuyruklar ...) tespit edin ve çalışabilirliklerini kontrol edin. POST programı (Power On Self Test) bundan sorumludur. Hayati bir donanım bulunamazsa, o zaman hiçbir yazılım yardımcı olamaz ve bu noktada sistem hoparlörü uğursuz bir şey gıcırdatacak ve işletim sistemi işletim sistemine hiç ulaşmayacaktır. Üzücü hakkında konuşmayalım, tamamen çalışan bir bilgisayarımız olduğunu varsayalım, sevinin ve ikinci BIOS işlevini incelemeye devam edin:
  2. İşletim sistemine donanımla çalışmak için temel bir dizi işlev sağlamak. Örneğin, BIOS işlevleri aracılığıyla ekranda metin görüntüleyebilir veya klavyeden veri okuyabilirsiniz. Bu nedenle temel G/Ç sistemi olarak adlandırılır. Tipik olarak, işletim sistemi bu işlevlere kesintiler yoluyla erişir.
  3. İşletim sistemi yükleyicisini başlatma. Bu durumda, kural olarak, önyükleme sektörü okunur - bilgi taşıyıcısının ilk sektörü (disket, sabit disk, CD, flash sürücü). Yoklama ortamının sırası BIOS KURULUMU'nda ayarlanabilir. Önyükleme sektörü, bazen birincil önyükleyici olarak adlandırılan bir program içerir. Kabaca söylemek gerekirse, önyükleyicinin işi işletim sistemini başlatmaktır. Bir işletim sisteminin önyükleme işlemi çok özel olabilir ve özelliklerine büyük ölçüde bağımlı olabilir. Bu nedenle, birincil önyükleyici doğrudan işletim sistemi geliştiricileri tarafından yazılır ve yükleme sırasında önyükleme sektörüne yazılır. Önyükleyici başladığında, işlemci gerçek moddadır.
Üzücü haber: Önyükleyicinin boyutu yalnızca 512 bayt olmalıdır. Neden bu kadar az? Bunu yapmak için, disketin cihazını tanımamız gerekir. İşte bilgilendirici bir resim:

Resim bir disk sürücüsünün yüzeyini göstermektedir. Disketin 2 yüzeyi vardır. Her yüzeyde halka şeklinde izler (izler) bulunur. Her parkur, sektör adı verilen küçük, kemerli parçalara bölünmüştür. Bu nedenle, tarihsel olarak, bir disket sektörünün boyutu 512 bayttır. Diskteki ilk sektör olan önyükleme sektörü, BIOS tarafından 0x7C00 ofsetinde sıfır bellek segmentine okunur ve ardından kontrol bu adrese aktarılır.Önyükleyici genellikle belleğe işletim sisteminin kendisini değil, başka bir yükleyiciyi yükler. program diskte depolanır, ancak bir nedenden dolayı (büyük olasılıkla, bu neden boyuttur) bir sektöre sığmaz.Ve şimdiye kadar işletim sistemimizin rolü banal bir Merhaba Dünya tarafından gerçekleştirildiğinden, asıl amacımız bilgisayarı tek bir sektörde bile olsa işletim sistemimizin varlığına inandırın ve çalıştırın.

Önyükleme sektörü nasıl çalışır? Bir PC'de, önyükleme sektörü için tek gereksinim, son iki baytının 0x55 ve 0xAA - önyükleme sektörü imzası değerlerini içermesidir. Yani, ne yapmamız gerektiği zaten az çok açık. Hadi kodu yazalım! Yukarıdaki kod yasm assembler için yazılmıştır.

Bölüm .text use16 org 0x7C00; programımız 0x7C00 başlangıcında yüklenir: mov ax, cs mov ds, ax; veri segmentini seçin mov si, mesaj cld; dizi komutları için yön mov ah, 0x0E; BIOS işlev numarası mov bh, 0x00; video bellek sayfası puts_loop: lodsb; sonraki sembolü al test al, al'a yükleyin; boş karakter, dizenin sonu anlamına gelir jz puts_loop_exit int 0x10; BIOS işlevini çağırın jmp puts_loop puts_loop_exit: jmp $; sonsuz döngü mesajı: db "Merhaba Dünya!", 0 bitiş: çarpı 0x1FE-bitiş + başlangıç ​​db 0 db 0x55, 0xAA; önyükleme sektörü imzası

Bu kısa program bir dizi önemli açıklama gerektirir. org 0x7C00 satırı, derleyicinin (yani programı kastediyorum, dili değil) etiketler ve değişkenler için adresleri (puts_loop, puts_loop_exit, mesaj) doğru şekilde hesaplaması için gereklidir. Bu yüzden programın 0x7C00 adresindeki belleğe yükleneceğini kendisine bildiriyoruz.
Hatta
mov balta, cs mov ds, balta
veri segmenti (ds) kod segmentine (cs) eşit olarak ayarlanır, çünkü programımızda hem veri hem de kod bir segmentte depolanır.

Ardından döngüde karakter karakter “Merhaba Dünya!” mesajı görüntülenir. Bunun için kesme 0x10'un 0x0E işlevi kullanılır. Aşağıdaki parametrelere sahiptir:
AH = 0x0E (işlev numarası)
BH = video sayfa numarası (henüz zahmet etmeyin, 0 belirtin)
AL = ASCII karakter kodu

"jmp $" satırında program kilitleniyor. Ve haklı olarak, onun fazladan kod yürütmesine gerek yok. Ancak, bilgisayarın tekrar çalışması için yeniden başlatmanız gerekecek.

"times 0x1FE-finish + start db 0" satırında, program kodunun geri kalanı (son iki bayt hariç) sıfırlarla doldurulur. Bu, derlemeden sonra programın son iki baytında önyükleme kesimi imzası görünecek şekilde yapılır.

Programın kodunu bir nebze çözdük, şimdi bu mutluluğu derlemeye çalışalım. Derleme için aslında bir montajcıya ihtiyacımız var - yukarıda belirtilen yasm. Çoğu Linux deposunda bulunur. Program aşağıdaki gibi derlenebilir:

$ yasm -f bin -o merhaba.bin merhaba.asm

Ortaya çıkan merhaba.bin dosyası disketin önyükleme sektörüne yazılmalıdır. Bu böyle bir şey yapılır (elbette fd yerine sürücünüzün adını değiştirmeniz gerekir).

$ dd if = merhaba.bin of = / dev / fd

Herkesin disket sürücüleri ve disketleri kalmadığından, örneğin qemu veya VirtualBox gibi bir sanal makine kullanabilirsiniz. Bunu yapmak için, önyükleyicimizle bir disket görüntüsü oluşturmanız ve onu "sanal disket sürücüsüne" yerleştirmeniz gerekir.
Bir disk görüntüsü oluşturun ve sıfırlarla doldurun:

$ dd if = / dev / sıfır = disk.img bs = 1024 sayı = 1440

Programımızı görüntünün en başına yazıyoruz:
$ dd if = merhaba.bin of = disk.img dönş = notrunc

Ortaya çıkan görüntüyü qemu'da çalıştırın:
$ qemu -fda disk.img -önyükleme a

Başlattıktan sonra, "Merhaba Dünya!" Neşeli bir satıra sahip bir qemu penceresi görmelisiniz. Bu, ilk makaleyi tamamlar. Geri bildiriminizi ve dileklerinizi görmekten memnuniyet duyarız.

Bir çekirdek geliştirmek, haklı olarak göz korkutucu bir görev olarak kabul edilir, ancak herkes en basit çekirdeği yazabilir. Çekirdek hacklemenin büyüsüne dokunmak için bazı kuralları gözlemlemeniz ve montajcıda ustalaşmanız yeterlidir. Bu yazımızda size parmaklarımızda nasıl yapılacağını göstereceğiz.


Selam Dünya!

x86 uyumlu sistemlerde GRUB üzerinden boot edecek bir kernel yazalım. İlk çekirdeğimiz ekranda bir mesaj gösterecek ve orada duracaktır.

x86 makineleri nasıl açılır?

Bir çekirdeğin nasıl yazılacağını düşünmeden önce, bilgisayarın nasıl önyüklendiğini ve kontrolü çekirdeğe nasıl aktardığını görelim. x86 işlemci kayıtlarının çoğu, önyüklemeden sonra belirli anlamlara sahiptir. Talimat İşaretçi Kaydı (EIP), işlemci tarafından yürütülecek talimatın adresini içerir. Sabit kodlanmış değeri 0xFFFFFFF0'dır. Yani, x86 işlemcisi her zaman 0xFFFFFFF0 fiziksel adresinden yürütmeye başlayacaktır. Bu, 32 bitlik adres alanının son 16 baytıdır. Bu adrese "sıfırlama vektörü" denir.

Yonga setinde bulunan bellek kartı, 0xFFFFFFF0 adresinin RAM'e değil, BIOS'un belirli bir bölümüne atıfta bulunduğunu söylüyor. Ancak BIOS, daha hızlı erişim için kendisini RAM'e kopyalar - bu, gölgeleme veya gölge kopya oluşturma adı verilen bir işlemdir. Bu nedenle 0xFFFFFFF0 adresi yalnızca BIOS'un kendisini kopyaladığı bellek konumuna gitme talimatını içerecektir.

Böylece BIOS çalışmaya başlar. İlk olarak, ayarlarda belirtilen sırayla önyükleme yapabileceğiniz aygıtları arar. Ortamda, önyüklenebilir diskleri normal disklerden ayıran bir "sihirli sayı" olup olmadığını kontrol eder: ilk sektördeki 511 ve 512 baytları 0xAA55 ise, disk önyüklenebilirdir.

BIOS, önyükleme aygıtını bulur bulmaz, 0x7C00 adresinden başlayarak ilk sektörün içeriğini RAM'e kopyalayacak ve ardından yürütmeyi bu adrese aktaracak ve az önce yüklediği kodu yürütmeye başlayacaktır. Bu koda önyükleyici denir.

Yükleyici, çekirdeği 0x100000 fiziksel adresinde yükler. Popüler çekirdeklerin çoğu tarafından x86 için kullanılan kişidir.

Tüm x86 uyumlu işlemciler, "gerçek mod" adı verilen ilkel 16 bit modda başlar. GRUB önyükleyici, CR0 kaydının alt bitini bire ayarlayarak işlemciyi 32 bit korumalı moda yerleştirir. Bu nedenle, çekirdek 32 bit korumalı modda yüklenmeye başlar.

Linux çekirdeklerinde GRUB'un uygun önyükleme protokolünü seçtiğini ve çekirdeği gerçek modda yüklediğini unutmayın. Linux çekirdeklerinin kendileri korumalı moda geçer.

Neye ihtiyacımız var

  • X86 uyumlu bilgisayar (tabii ki)
  • Linux,
  • NASM montajcısı,
  • ld (GNU Bağlayıcı),
  • GRUB.

Assembly dili giriş noktası

Elbette her şeyi C ile yazmak isteriz ama assembler kullanmaktan tamamen vazgeçemeyiz. Çekirdeğimizin başlangıç ​​noktası olacak küçük bir dosyayı x86 assembler'da yazacağız. Derleme kodunun tek yapacağı, C'ye yazacağımız harici bir işlevi çağırmak ve ardından programın yürütülmesini durdurmak.

Montaj kodunu çekirdeğimiz için nasıl başlangıç ​​noktası yaparız? Nesne dosyalarını birbirine bağlayan ve son çekirdek yürütülebilir dosyasını oluşturan bir bağlayıcı komut dosyası kullanıyoruz (aşağıda daha ayrıntılı olarak açıklanmıştır). Bu komut dosyasında, ikili dosyamızın 0x100000'de yüklenmesini istediğimizi doğrudan belirteceğiz. Bu, daha önce yazdığım gibi, önyükleyicinin çekirdeğe giriş noktasını görmeyi beklediği adres.

İşte montajcı kodu.

kernel.asm
bit 32 bölüm .metin global başlangıç ​​dış kmain başlangıcı: cli mov esp, stack_space çağrı kmain hlt bölümü .bss resb 8192 stack_space:

İlk bit 32 komutu bir x86 derleyicisi değil, 32 bit modunda çalışacak bir işlemci için kod üretmek için bir NASM yönergesidir. Örneğimiz için bu gerekli değildir, ancak bunu açıkça belirtmek iyi bir uygulamadır.

İkinci satır, kod bölümü olarak da bilinen bir metin bölümünü başlatır. Tüm kodlarımız buraya gelecek.

global başka bir NASM yönergesidir, kodumuzdaki sembollerin global olduğunu bildirir. Bu, bağlayıcının bizim giriş noktamız olan başlangıç ​​sembolünü bulmasını sağlayacaktır.

kmain, kernel.c dosyamızda tanımlanacak bir fonksiyondur. extern, işlevin başka bir yerde bildirildiğini bildirir.

Ardından kmain'i çağıran ve hlt komutuyla işlemciyi durduran başlatma işlevi gelir. Kesintiler, hlt'den sonra işlemciyi uyandırabilir, bu yüzden önce cli (clear interrupts) talimatı ile kesmeleri devre dışı bırakırız.

İdeal olarak, yığın için bir miktar bellek ayırmalı ve yığın işaretçisini (esp) ona yönlendirmeliyiz. GRUB bunu bizim için zaten yapıyor gibi görünüyor ve bu noktada yığın işaretçisi zaten ayarlanmış. Ancak, her ihtimale karşı, BSS bölümünde bir miktar bellek ayıralım ve yığın işaretçisini başlangıcına yönlendirelim. Resb komutunu kullanıyoruz - bayt cinsinden belirtilen belleği saklıyor. Ardından, ayrılmış bellek yığınının kenarına işaret eden bir etiket bırakılır. kmain çağrısından hemen önce yığın işaretçisi (esp) mov komutuyla bu alana yönlendirilir.

C'de Çekirdek

kernel.asm dosyasında kmain() fonksiyonunu çağırdık. Yani C kodunda yürütme oradan başlayacak.

kernel.c
void kmain (void) (const char * str = "ilk çekirdeğim"; char * vidptr = (char *) 0xb8000; unsigned int i = 0; unsigned int j = 0; while (j)< 80 * 25 * 2) { vidptr[j] = " "; vidptr = 0x07; j = j + 2; } j = 0; while(str[j] != "\0") { vidptr[i] = str[j]; vidptr = 0x07; ++j; i = i + 2; } return; }

Çekirdeğimizin tek yapacağı, ekranı temizlemek ve ilk çekirdeğimin satırını çıkarmak.

Yaptığımız ilk şey, 0xb8000'e işaret eden bir vidptr işaretçisi oluşturmak. Korumalı modda bu, video belleğinin başlangıcıdır. Metinsel ekran belleği, adres alanının yalnızca bir parçasıdır. 0xb8000 adresinde başlayan ekran G / Ç için bir bellek bölümü ayrılmıştır - içine 25 satır 80 ASCII karakteri yerleştirilir.

Metin belleğindeki her karakter, alıştığımız 8 bit (1 bayt) ile değil, 16 bit (2 bayt) ile temsil edilir. İlk bayt, ASCII karakter kodudur ve ikinci bayt, nitelik baytıdır. Bu, rengi de dahil olmak üzere sembolün biçiminin tanımıdır.

s yeşilini siyah üzerine yazdırmak için, video belleğinin ilk baytına s ve ikinci bayta 0x02 değerini koymamız gerekir. Burada 0 siyah arka plan anlamına gelir ve 2 yeşil anlamına gelir. Açık gri bir renk kullanacağız, kodu 0x07.

İlk while döngüsünde, program 80 karakterlik 25 satırın tamamını 0x07 özniteliği ile boş karakterlerle doldurur. Bu ekranı temizleyecektir.

İkinci while döngüsünde, ilk çekirdeğim boş sonlandırılmış dizedeki karakterler video belleğine yazılır ve her karaktere 0x07'ye eşit bir nitelik baytı atanır. Bu, dizeyi çıkarmalıdır.

Düzen

Şimdi kernel.asm'yi NASM kullanarak bir nesne dosyasında derlememiz ve ardından kernel.c'yi başka bir nesne dosyasında derlemek için GCC'yi kullanmamız gerekiyor. Görevimiz, bu nesneleri önyüklenebilir yürütülebilir bir çekirdeğe bağlamaktır. Bunu yapmak için, argüman olarak ileteceğimiz linker (ld) için bir komut dosyası yazmanız gerekir.

link.ld
OUTPUT_FORMAT (elf32-i386) GİRİŞ (başlangıç) BÖLÜMLERİ (. = 0x100000; .text: (* (. Metin)) .data: (* (. Veri)) .bss: (* (. Bss)))

Burada ilk önce yürütülebilir dosyamızın biçimini (OUTPUT_FORMAT) x86 mimarisi için Unix sistemleri için standart ikili biçim olan 32-bit ELF (Yürütülebilir ve Bağlanabilir Biçim) olarak belirledik.

ENTRY bir argüman alır. Yürütülebilir dosya için giriş noktası görevi görecek sembolün adını belirtir.

BÖLÜMLER bizim için en önemli kısımdır. Yürütülebilir dosyamızın düzenini burada tanımlıyoruz. Farklı bölümlerin nasıl birleştirileceğini ve her birinin nereye yerleştirileceğini tanımlayabiliriz.

SECTIONS ifadesini izleyen küme parantezleri içinde nokta, konum sayacını gösterir. SECTIONS bloğunun başında otomatik olarak 0x0 olarak başlatılır, ancak yeni bir değer atanarak değiştirilebilir.

Daha önce çekirdek kodunun 0x100000'de başlaması gerektiğini yazmıştım. Bu nedenle konum sayacını 0x100000 olarak ayarladık.

.text satırına bir göz atın: (* (. Metin)). Yıldız işareti, burada herhangi bir dosya adıyla eşleşen bir maske ayarlar. Buna göre * (. Metin) ifadesi, tüm girdi dosyalarındaki tüm girdi .text bölümleri anlamına gelir.

Sonuç olarak, bağlayıcı, tüm nesne dosyalarının tüm metin bölümlerini yürütülebilir dosyanın metin bölümünde birleştirecek ve bunları konum sayacında belirtilen adrese yerleştirecektir. Yürütülebilir dosyamızın kod bölümü 0x100000'de başlayacaktır.

Bağlayıcı metin bölümünü oluşturduktan sonra, konum sayacı 0x100000 artı metin bölümünün boyutudur. Aynı şekilde data ve bss bölümleri birleştirilerek pozisyon sayacının verdiği adrese yerleştirilecektir.

GRUB ve çoklu önyükleme

Artık tüm dosyalarımız çekirdeği oluşturmaya hazır. Ancak çekirdeği GRUB kullanarak yükleyeceğimiz için bir adım daha kaldı.

Bir önyükleyici kullanarak farklı x86 çekirdeklerini başlatmak için bir standart vardır. Buna "çoklu önyükleme belirtimi" denir. GRUB, yalnızca kendisiyle eşleşen çekirdekleri yükler.

Bu spesifikasyona göre, çekirdek ilk 8 kilobaytta bir başlık (Multiboot başlık) içerebilir. Bu başlık üç alan içermelidir:

  • büyü- başlığın tanımlandığı 0x1BADB002 "sihirli" numarasını içerir;
  • bayraklar- Bu alan bizim için önemli değil, sıfır bırakabilirsiniz;
  • sağlama toplamı- sağlama toplamı, büyü ve bayrak alanlarına eklenirse sıfır vermelidir.

Şimdi kernel.asm dosyamız bu şekilde görünecek.

kernel.asm
bit 32 bölüm .text; çoklu önyükleme belirtimi hizalama 4 dd 0x1BADB002; sihirli dd 0x00; bayraklar dd - (0x1BADB002 + 0x00); sağlama toplamı global başlangıç ​​dış kmain başlangıcı: cli mov esp, yığın_uzayı çağrısı kmain hlt bölümü .bss resb 8192 yığın_uzayı:

dd komutu 4 baytlık bir çift kelime belirtir.

Çekirdeği bir araya getirmek

Böylece, kernel.asm ve kernel.c'den bir nesne dosyası oluşturmak ve betiğimizi kullanarak bunları bağlamak için her şey hazır. Konsola yazıyoruz:

$ nasm -f elf32 kernel.asm -o kasm.o

Bu komut ile montajcı, ELF-32 bit formatında bir kasm.o dosyası oluşturacaktır. Şimdi GCC'nin sırası:

$ gcc -m32 -c kernel.c -o kc.o

-c seçeneği, dosyanın derlemeden sonra bağlanması gerekmediğini belirtir. Kendimiz yapacağız:

$ ld -m elf_i386 -T link.ld -o çekirdek kasm.o kc.o

Bu komut, bağlayıcıyı betiğimizle başlatacak ve çekirdek adlı bir yürütülebilir dosya oluşturacaktır.

UYARI

Çekirdek hackleme en iyi şekilde sanal bir makinede yapılır. Çekirdeği GRUB yerine QEMU'da çalıştırmak için qemu-system-i386 -kernel kernel komutunu kullanın.

GRUB'u yapılandırma ve çekirdeği başlatma

GRUB, çekirdeği takip etmek için çekirdek dosya adını gerektirir.<версия>... Öyleyse dosyayı yeniden adlandıralım - benimkine kernel-701 adını vereceğim.

Şimdi çekirdeği / boot dizinine koyuyoruz. Bu, süper kullanıcı ayrıcalıkları gerektirecektir.

GRUB yapılandırma dosyasında grub.cfg, şunun gibi bir şey eklemeniz gerekecek:

Başlık myKernel root (hd0,0) kernel / boot / kernel-701 ro

Eğer varsa gizli menü yönergesini kaldırmayı unutmayınız.

GRUB 2

Yeni dağıtımlarda varsayılan olarak gönderilen GRUB 2'de oluşturduğumuz çekirdeği çalıştırmak için yapılandırmanız şöyle görünmelidir:

Menü girişi "çekirdek 701" (kök = "hd0, msdos1" çoklu önyükleme / önyükleme / çekirdek-701 ro ayarla)

Bu ekleme için Ruben Laguana'ya teşekkürler.

Bilgisayarınızı yeniden başlatın ve çekirdeğinizin listelendiğini görmelisiniz! Ve onu seçtikten sonra, o çizgiyi göreceksiniz.



Bu senin çekirdeğin!

Klavye ve ekran destekli kernel yazma

GRUB üzerinden önyüklenen, korumalı modda çalışan ve ekrana tek bir satır yazdıran minimal bir çekirdek üzerinde çalışmayı bitirdik. Şimdi onu genişletmenin ve klavyeden karakterleri okuyacak ve ekranda gösterecek bir klavye sürücüsü eklemenin zamanı geldi.

I/O cihazları ile I/O portları üzerinden haberleşeceğiz. Özünde, bunlar sadece G / Ç veri yolundaki adreslerdir. Okuma ve yazma işlemleri için özel işlemci talimatları vardır.

Bağlantı noktalarıyla çalışma: okuma ve çıktı

read_port: mov edx, al, dx ret write_port: mov edx, mov al, dx çıkışı, al ret

G / Ç bağlantı noktalarına, x86 paketinde bulunan giriş ve çıkış talimatları kullanılarak erişilir.

read_port'ta port numarası argüman olarak iletilir. Derleyici bir işlevi çağırdığında, tüm argümanları yığına iter. Argüman, yığın işaretçisi kullanılarak edx kaydına kopyalanır. Dx kaydı, edx kaydının alt 16 bitidir. Buradaki talimat, dx'de verilen port numarasını okur ve sonucu al'a koyar. Al kaydı, eax kaydının alt 8 bitidir. Fonksiyonların döndürdüğü değerlerin eax registerından geçtiğini kolej dersinden hatırlarsınız. Böylece read_port I/O portlarından okuma yapmamızı sağlar.

write_port işlevi benzer şekilde çalışır. İki argüman alıyoruz: port numarası ve yazılacak veri. Out komutu veriyi porta yazar.

kesintiler

Şimdi, sürücüyü yazmaya dönmeden önce, işlemcinin bir aygıtın bir işlem gerçekleştirdiğini nasıl bildiğini anlamamız gerekiyor.

En basit çözüm, cihazları yoklamaktır - durumlarını bir döngüde sürekli olarak kontrol edin. Bu, bariz nedenlerden dolayı etkisiz ve pratik değildir. İşte bu noktada kesintiler devreye giriyor. Kesinti, bir olayın meydana geldiğini belirten bir aygıt veya program tarafından işlemciye gönderilen bir sinyaldir. Kesintileri kullanarak, cihazları yoklama ihtiyacını ortadan kaldırabiliriz ve yalnızca bizi ilgilendiren olaylara yanıt veririz.

Programmable Interrupt Controller (PIC) adlı bir çip, x86 mimarisindeki kesintilerden sorumludur. Donanım kesintilerini yönetir ve yönlendirir ve bunları uygun sistem kesintilerine dönüştürür.

Kullanıcı cihaza bir şey yaptığında, PIC'ye Kesinti İsteği (IRQ) adı verilen bir darbe gönderilir. PIC, alınan kesmeyi bir sistem kesmesine çevirir ve işlemciye yaptığı şeyi durdurma zamanının geldiğine dair bir mesaj gönderir. Daha fazla kesme işlemi, çekirdeğin sorumluluğundadır.

PIC olmadan, herhangi birinin dahil olduğu bir olayın meydana gelip gelmediğini görmek için sistemde bulunan tüm cihazları yoklamamız gerekirdi.

Bunun klavye durumunda nasıl çalıştığına bir göz atalım. Klavye, 0x60 ve 0x64 bağlantı noktalarında kilitleniyor. 0x60 bağlantı noktası veri gönderir (bir düğmeye basıldığında) ve bağlantı noktası 0x64 durum gönderir. Ancak, bu portları tam olarak ne zaman okuyacağımızı bilmemiz gerekiyor.

Kesintiler burada işe yarar. Düğmeye basıldığında, klavye IRQ1 kesme hattına bir PIC sinyali gönderir. PIС, başlatma sırasında saklanan ofset değerini saklar. Kesme vektörünü oluşturmak için bu girintiye giriş satır numarasını ekler. İşlemci daha sonra kesme işleyici işlevine numarasına karşılık gelen bir adres vermek için Kesme Tanımlayıcı Tablosu (IDT) adı verilen bir veri yapısı arar.

Daha sonra bu adresteki kod yürütülür ve kesmeyi işler.

IDT'yi Ayarlama

struct IDT_entry (imzasız kısa int offset_lowerbits; işaretsiz kısa int seçici; işaretsiz karakter sıfır; işaretsiz karakter type_attr; imzasız kısa int offset_higherbits;); struct IDT_entry IDT; void idt_init (void) (imzasız uzun klavye_adresi; işaretsiz uzun idt_adresi; işaretsiz uzun idt_ptr; klavye_adresi = (imzasız uzun) klavye_işleyicisi; IDT.offset_lowerbits = klavye_adresi & 0xffff; IDT.selector = 0x08;DT_* KERNEL; 0x8e; / * INTERRUPT_GATE * / IDT.offset_higherbits = (keyboard_address & 0xffff0000) >> 16; write_port (0x20, 0x11); write_port (0xA0, 0x11); write_port (0x21, 0x20xA1, 0x20); write_port 0x28); write_port (0x21) , 0x00); write_port (0xA1, 0x00); write_port (0x21, 0x01); write_port (0xA1, 0x01); write_port (0x21, 0xff); write_port (0xA1, 0xff); idt_address = (işaretsiz uzun) IDT; idt_ptr = ( sizeof (struct IDT_entry) * IDT_SIZE) + ((idt_address & 0xffff)<< 16); idt_ptr = idt_address >> 16; load_idt (idt_ptr); )

IDT, bir IDT_entry yapısı dizisidir. Ayrıca bir işleyiciye bir klavye kesmesi bağlamayı tartışacağız, ancak şimdilik PIC'nin nasıl çalıştığını görelim.

Modern x86 sistemlerinde, her biri sekiz giriş hattına sahip iki PIC bulunur. Onlara PIC1 ve PIC2 diyeceğiz. PIC1, IRQ0'dan IRQ7'ye ve PIC2, ​​IRQ8'den IRQ15'e kadar alır. PIC1, komutlar için 0x20 ve veriler için 0x21 bağlantı noktasını kullanırken, PIC2 komutlar için 0xA0 ve veriler için 0xA1 bağlantı noktasını kullanır.

Her iki PIC, Başlatma komut sözcükleri (ICW) adı verilen sekiz bitlik sözcüklerle başlatılır.

Korumalı modda, her iki PIC'nin de önce ICW1 başlatma komutunu (0x11) vermesi gerekir. PIC'ye veri portuna gelecek üç başlatma kelimesi daha beklemesini söyler.

Bu komutlar PIC'yi gönderir:

  • dolgu vektörü (ICW2),
  • PIC'ler (ICW3) arasındaki master/slave ilişkisi nedir,
  • çevre hakkında ek bilgi (ICW4).

İkinci başlatma komutu (ICW2) ayrıca her bir PIC'nin girişine gönderilir. Kesme numarasını almak için satır numarasını eklediğimiz değer olan ofseti atar.

PIC'ler, pinlerinin birbirlerinin girişlerine basamaklandırılmasına izin verir. Bu, ICW3 kullanılarak yapılır ve her bit, karşılık gelen IRQ için basamaklı durumu temsil eder. Şimdilik, basamaklı yeniden yönlendirmeyi kullanmayacağız ve sıfıra ayarlayacağız.

ICW4, ek ortam parametrelerini ayarlar. PIC'lerin 80x86 modunda çalıştığımızı bilmesi için sadece alt biti tanımlamamız gerekiyor.

Ta-dam! PIC'ler şimdi başlatıldı.

Her PIC, Interrupt Mask Register (IMR) adı verilen dahili bir sekiz bitlik kayıt defterine sahiptir. PIC'ye giden IRQ satırlarının bir bitmap'ini saklar. Bit ayarlanmışsa, PIC isteği yok sayar. Bu, karşılık gelen değeri 0 veya 1 olarak ayarlayarak belirli bir IRQ hattını açıp kapatabileceğimiz anlamına gelir.

Veri bağlantı noktasından okuma, IMR kaydındaki değeri döndürür ve yazma işlemi, kaydı değiştirir. Kodumuzda, PIC'yi başlattıktan sonra, tüm bitleri bire ayarlıyoruz, böylece tüm IRQ satırlarını devre dışı bırakıyoruz. Daha sonra klavye kesmelerine karşılık gelen satırları aktif ediyoruz. Ama önce, onu kapatalım!

IRQ hatları çalışıyorsa, PIC'lerimiz IRQ üzerindeki sinyalleri alabilir ve ofset ekleyerek bunları bir kesme numarasına dönüştürebilir. IDT'yi klavyeden gelen kesme sayısı yazacağımız işleyici fonksiyonunun adresine denk gelecek şekilde doldurmamız gerekiyor.

IDT'de klavye işleyicisini hangi kesme numarasına bağlamamız gerekiyor?

Klavye IRQ1 kullanır. Bu giriş satırı 1'dir ve PIC1 tarafından işlenir. PIC1'i 0x20 offset ile başlattık (bkz. ICW2). Kesinti numarasını almak için 1 ve 0x20 eklemeniz gerekir, 0x21 alırsınız. Bu, 0x21'i kesmek için klavye işleyicisinin adresinin IDT'ye bağlanacağı anlamına gelir.

Görev, 0x21 kesintisi için IDT'yi doldurmaktır. Bu kesmeyi assembler dosyasına yazacağımız keyboard_handler fonksiyonuna eşleyeceğiz.

Her IDT girişi 64 bit uzunluğundadır. Kesmeye karşılık gelen kayıtta, işleyici işlevinin tüm adresini saklamayız. Bunun yerine, onu 16 bitlik iki parçaya böldük. Alt bitler, IDT kaydının ilk 16 bitinde depolanır ve üst 16 bit, kaydın son 16 bitinde saklanır. Bütün bunlar 286 işlemci ile uyumluluk için yapılır. Gördüğünüz gibi, Intel bu tür sayıları düzenli olarak ve birçok yerde yayınlıyor!

IDT kaydında, tüm bunların kesintiyi yakalamak için yapıldığını belirten türü kaydetmemiz bize kalır. Ayrıca çekirdek kod segmentinin ofsetini de ayarlamamız gerekiyor. GRUB bizim için GDT'yi belirler. Her GDT girişi 8 bayt uzunluğundadır, burada çekirdek kodu tanımlayıcısı ikinci segmenttir, dolayısıyla ofseti 0x08'dir (ayrıntılar bu makaleye girmeyecektir). Kesme kapısı 0x8e olarak temsil edilir. Ortada kalan 8 biti sıfırlarla doldurun. Bu, klavye kesmesine karşılık gelen IDT girişini dolduracaktır.

IDT eşlemesi yapıldığında işlemciye IDT'nin nerede olduğunu söylememiz gerekecek. Bunun için bir montajcı talimatı kapağı vardır, bir işlenen alır. IDT'yi tanımlayan yapıya yönelik bir tanıtıcı işaretçisidir.

Tanımlayıcı ile ilgili herhangi bir zorluk yoktur. IDT'nin bayt cinsinden boyutunu ve adresini içerir. Daha kompakt hale getirmek için bir dizi kullandım. Benzer şekilde, bir yapı kullanarak bir tanımlayıcı doldurabilirsiniz.

idr_ptr değişkeninde load_idt() fonksiyonunda lidt komutuna ilettiğimiz bir işaretçimiz var.

Load_idt: mov edx, lidt sti ret

Ek olarak, load_idt () işlevi, sti komutunu kullanırken bir kesme döndürür.

IDT'yi doldurup yükledikten sonra, daha önce bahsettiğimiz kesme maskesini kullanarak klavye IRQ'suna erişebiliriz.

Void kb_init (void) (write_port (0x21, 0xFD);)

0xFD 11111101'dir - yalnızca IRQ1'i (klavye) etkinleştirin.

İşlev - klavye kesme işleyicisi

Bu nedenle, 0x21 kesmesi için bir IDT girişi oluşturarak klavye kesmelerini klavye_işleyici işlevine başarıyla bağladık. Bu işlev, herhangi bir düğmeye her bastığınızda çağrılacaktır.

Keyboard_handler: keyboard_handler_main irett'i çağırın

Bu işlev, C ile yazılmış başka bir işlevi çağırır ve iret sınıfının talimatlarını kullanarak denetimi döndürür. Tüm işleyicimizi buraya yazabiliriz, ancak C'de kodlamak çok daha kolay, o yüzden oraya dönelim. Kontrol, kesme işleme işlevinden kestiği programa döndüğünde, ret yerine iret / iretd komutları kullanılmalıdır. Bu komut sınıfı, bir kesme çağrıldığında yığına itilen bir bayrak kaydı oluşturur.

Void keyboard_handler_main (void) (unsigned char status; char keycode; / * EOI yazarız * / write_port (0x20, 0x20); status = read_port (KEYBOARD_STATUS_PORT); / * Eğer arabellek boş değilse alt durum biti ayarlanır * / if (durum ve 0x01) (anahtar kodu = read_port (KEYBOARD_DATA_PORT); if (anahtar kodu< 0) return; vidptr = keyboard_map; vidptr = 0x07; } }

Burada önce PIC komut portuna yazarak EOI (End Of Interrupt) sinyalini veriyoruz. Ancak o zaman PIC daha fazla kesme isteklerine izin verecektir. İki bağlantı noktası okumamız gerekiyor: veri bağlantı noktası 0x60 ve komut bağlantı noktası (aka durum bağlantı noktası) 0x64.

Öncelikle durumu almak için 0x64 portunu okuyoruz. Alt durum biti sıfır ise, arabellek boştur ve okunacak veri yoktur. Diğer durumlarda, 0x60 veri portunu okuyabiliriz. Bize basılan tuşun kodunu verecektir. Her kod bir düğmeye karşılık gelir. Kodları karşılık gelen karakterlerle eşleştirmek için keyboard_map.h dosyasında tanımlanan basit karakter dizisini kullanırız. Sembol daha sonra, çekirdeğin ilk versiyonunda kullandığımız aynı teknik kullanılarak ekranda görüntülenir.

Kodu karmaşıklaştırmamak için burada sadece a'dan z'ye küçük harfleri ve 0'dan 9'a kadar sayıları işliyorum. Alt, Shift ve Caps Lock gibi özel karakterleri kolayca ekleyebilirsiniz. Komut portunun çıktısından bir tuşa basıldığını veya bırakıldığını öğrenebilir ve uygun işlemi yapabilirsiniz. Aynı şekilde, herhangi bir klavye kısayolunu kapatma gibi özel işlevlere bağlayabilirsiniz.

Artık çekirdeği oluşturabilir, gerçek bir makinede veya ilk bölümdeki gibi bir öykünücüde (QEMU) çalıştırabilirsiniz.

0'dan 1'e İşletim Sistemi GitHub'da yayınlanır ve 2.000'den fazla yıldıza ve 100 çatala sahiptir. Adından da anlaşılacağı gibi, okuduktan sonra kendi işletim sisteminizi oluşturabilirsiniz - ve belki de programcıların dünyasındaki birkaç şey daha havalı olabilir.

Bu kitap sayesinde şunları öğreneceksiniz:

  • Donanım teknik belgelerine dayalı bir işletim sisteminin nasıl oluşturulacağını öğrenin. Gerçek dünyada böyle çalışır, hızlı cevaplar için Google'ı kullanamazsınız.
  • Yazılımdan donanıma kadar bilgisayar bileşenlerinin birbirleriyle nasıl etkileşime girdiğini anlayın.
  • Kendiniz kod yazmayı öğrenin. Kodun kör olarak kopyalanması bir öğrenme eğrisi değildir; sorunları nasıl çözeceğinizi gerçekten öğreneceksiniz. Bu arada, kör kopyalama da tehlikelidir.
  • Düşük seviyeli geliştirme için tanıdık araçlarda ustalaşın.
  • Assembly diline aşina olun.
  • Hangi programlardan yapıldığını ve işletim sisteminin bunları nasıl çalıştırdığını öğrenin. Merak edenler için bu konuya küçük bir genel bakış sunduk.
  • GDB ve QEMU ile bir programın hatalarını doğrudan donanım üzerinde nasıl ayıklayacağınızı öğrenin.
  • Programlama dili C. Takip ederek hızlıca ustalaşabilirsiniz.
  • Temel Linux bilgisi. Sitemizde okumanız yeterlidir.
  • Fizikte temel bilgiler: atomlar, elektronlar, protonlar, nötronlar, voltaj.

İşletim sistemi yazmak için bilmeniz gerekenler

Bir işletim sistemi oluşturmak, bir bilgisayarın çalışması hakkında kapsamlı ve karmaşık bilgi gerektirdiğinden, programlamadaki en zor görevlerden biridir. Hangileri? Aşağıda ona bakalım.

işletim sistemi nedir

İşletim sistemi (OS), bilgisayar donanımı ve kaynakları ile çalışan ve bir bilgisayarın donanımı ile yazılımı arasındaki köprü olan yazılımdır.

Birinci nesil bilgisayarların işletim sistemleri yoktu. İlk bilgisayarlardaki programlar, sistemin doğrudan çalışması için kod, çevresel cihazlarla iletişim ve bu programın yazıldığı yürütülmesi için hesaplamaları içeriyordu. Bu durumdan dolayı mantık açısından basit olan programların bile yazılımda uygulanması zor olmuştur.

Bilgisayarlar daha çeşitli ve karmaşık hale geldikçe, hem işletim sistemi hem de uygulama olarak çalışan programlar yazmak uygunsuz hale geldi. Bu nedenle, programların yazılmasını kolaylaştırmak için bilgisayar sahipleri yazılım geliştirmeye başladılar. İşletim sistemleri böyle ortaya çıktı.

İşletim sistemi, özel programları çalıştırmak için ihtiyacınız olan her şeyi sağlar. Görünüşleri, artık programların bilgisayarın çalışmasının tüm hacmini kontrol etmesine gerek olmadığı anlamına geliyordu (bu, kapsüllemenin harika bir örneğidir). Artık programların işletim sistemiyle çalışması gerekiyordu ve sistemin kendisi kaynaklarla ilgilendi ve çevre birimleriyle (klavye, yazıcı) çalıştı.

Kısaca işletim sistemlerinin tarihi hakkında

C dili

Yukarıda belirtildiği gibi, bir işletim sistemi yazmak için birkaç üst düzey programlama dili vardır. Ancak bunların en popüleri C.

Bu dili öğrenmeye buradan başlayabilirsiniz. Bu kaynak sizi temel kavramlarla tanıştıracak ve sizi daha karmaşık görevlere hazırlayacaktır.

C'yi Zor Yoldan Öğrenin, başka bir kitabın adıdır. Alışılmış teoriye ek olarak, birçok pratik çözüm içerir. Bu eğitim, dilin tüm yönlerini kapsayacaktır.

Veya şu kitaplardan birini seçebilirsiniz:

  • Kernighan ve Ritchie'nin The C Programlama Dili;
  • Perry ve Miller tarafından "C Programlama Mutlak Başlangıç ​​Kılavuzu".

işletim sistemi geliştirme

Bilgisayar bilimi, montaj dili ve C hakkında bilmeniz gereken her şeye hakim olduktan sonra, doğrudan işletim sistemi geliştirme hakkında en az bir veya iki kitap okumalısınız. İşte bunu yapmak için bazı kaynaklar:

Sıfırdan Linux. Burada Linux işletim sistemini oluşturma süreci ele alınmaktadır (eğitici, Rusça dahil birçok dile çevrilmiştir). Burada, ders kitaplarının geri kalanında olduğu gibi, gerekli tüm temel bilgiler size sağlanacaktır. Onlara güvenerek, bir işletim sistemi oluşturmayı deneyebilirsiniz. İşletim sisteminin yazılım bölümünü daha profesyonel hale getirmek için ders kitabına eklemeler var: “