DOS 3.x ve 4.x döneminde yazılım geliştirmek, modern geliştiriciler için şaşırtıcı derecede derin bir kontrol anlayışı gerektiriyordu. Programınız, CPU zamanlaması için başka bir süreçle yarışmazdı — çalışmaya başladığında, bellekten klavyeye, hatta ekran kartına kadar her şeyi sahiplenirdi. Bu, çekirdeğin olmadığı, bellek koruma mekanizmalarının bulunmadığı bir ortamdı. Bir dizi talimatı yanlışlıkla bellek sınırlarının dışına yazdığınızda, sistem sessizce çökerdi ve yirmi komut sonra ekran yeşile dönerdi.
Bu ortamda kod yazmanın nasıl bir deneyim olduğunu ilk kez, 2018 yılında hâlâ DOS 4.01 üzerinde çalışan bir endüstriyel kontrolcü kodunu devraldığımda keşfettim. POS sistemlerinden tıbbi cihazlara kadar birçok cihazın arkasında hâlâ gerçek modda çalışan x86 sistemleri bulunuyor. 1988’in kısıtlamaları artık unutulmuş olsa da, on beş yıldır hiç çökmedikleri için kimsenin yeniden yazmak istemediği sistemlerde donmuş durumdalar.
Bu yazıda, o dönemin araç zincirini, bellek karmaşasını ve kesintiye dayalı G/Ç kalıplarını ele alacağız. Ayrıca, POST kartından, hex dökümünden ve kendi kodladığınız bip sesinden başka bir geri bildirim mekanizması olmayan bir ortamda hata ayıklamanın nasıl bir şey olduğunu da anlatacağız.
Kodunuzu Derlemek: .COM ve .EXE Arasındaki Kritik Fark
DOS’un sunduğu en basit ikili format .COM dosyasıydı. Bu formatta, programınız doğrudan 0x100 adresinden başlayan tek bir 64KB’lık bellek bloğuna yüklenirdi. Kod, veri ve yığın aynı bellek alanını paylaşırdı. Eğer programınızın boyutu 64KB’ı aşarsa, .EXE formatına geçmek zorunda kalırdınız — ki bu, anında bellek modeli seçimini zorunlu kılan bir adımdı.
Microsoft’un C 5.1’i veya Borland’ın Turbo C 2.0’ı gibi derleyiciler genellikle .COM yerine .EXE üretiyordu. Bellek modelleri arasındaki ayrım, o dönemdeki en yaygın hatalardan biriydi:
- tiny: Tüm kod, veri ve yığın tek bir segmentte.
.COMformatının eşdeğeri. - small: Bir kod segmenti, bir veri segmenti. En yaygın tercih edilen model.
- compact: Küçük kod, uzak veri göstericileri. Karmaşık olmasıyla biliniyordu.
- large: Tüm göstericiler uzak. Veri 64KB’ı aşarsa kullanılırdı.
- huge: Büyük veri dizilerini destekler, ancak gösterici normalleştirmesi nedeniyle performans kaybına yol açardı.
Yanlış model seçmek, sessizce çalışan ancak farklı donanımlarda bellek bozulmalarına neden olan bir hataya yol açabilirdi. Bugün bile, mikrodenetleyici geliştirmede benzer şekilde fonksiyon imzalarına yerleşen gösterici genişliği varsayımlarını görmeye devam ediyoruz.
Hata Ayıklama: DEBUG.COM’un Altın Çağı
O dönemde DEBUG.COM, sistemle birlikte gelen 20KB’lık bir araçtı ve hem disassembleri hem bellek incelemelerini hem de adım adım hata ayıklamayı tek başına yapabiliyordu. Turbo Debugger gibi üçüncü parti araçlar lüks sayılırdı ve nadiren müşteri makinelerinde bulunurdu.
Gerçek dünyadaki bir sahada, müşterinin makinesinde sadece DEBUG vardı. Hata ayıklama süreci genellikle şu adımlardan oluşurdu:
C:\> debug myprog.com
-u 100 120 ; 0x100 adresinden itibaren 32 baytlık kodu disassemble et
-d ds:0 ff ; Veri segmentini dök
-g =100 ; Programı giriş noktasından çalıştır
-q ; DEBUG’tan çıkDeneyimli geliştiriciler, bellek dökümlerini okuyarak ve bağlayıcı harita dosyasını elle karşılaştırarak yığın çerçevesini zihinlerinde yeniden inşa edebilirdi. Bu beceri, modern IDE’ler kadar otomatik olmasa da, donanım hata ayıklama araçlarının olmadığı ortamlarda paha biçilmezdi. Bugün bile, Cortex-M4 üzerinde çalışan bir firmware’de JTAG adaptörü üç saat dilimi ötede olduğunda, bu eski yöntemler kurtarıcı olabiliyor.
Bellek Yönetimi: 640KB Duvarının Ötesinde
8086’nın 20-bit adres yolu, teorik olarak 1MB adreslenebilir bellek sağlıyordu. Ancak IBM, bu alanı donanım seviyesinde parçalara ayırdı:
- 64KB-128KB: Video RAM (CGA/EGA)
- 128KB-256KB: ROM BIOS
- 256KB-640KB: Konvansiyonel bellek (programlar, DOS, TSR’ler)
- 640KB-1MB: Donanım aygıtları, genişletilmiş bellek (EMS/XMS)
Bu yapı, geliştiricilerin bellek haritalarını ezbere bilmesini gerektiriyordu. Bir fare sürücüsü, ağ yığını ve DOS’un kendisi yüklendiğinde, asıl uygulamanız için sadece 400KB kadar bellek kalabilirdi. Geliştiriciler, bellek kullanımını sürekli izler ve gereksiz TSR’leri sistemden kaldırırdı. Bugün bile, gömülü sistemlerde bellek tüketimini optimize etmek için benzer stratejiler kullanılıyor.
Tekrar Ziyaret Edilen Eski Dönemin Dersleri
DOS döneminde geliştirme yapmanın en önemli mirası, donanıma olan doğrudan erişimin sorumluluğunu anlamaktı. Modern işletim sistemleri, geliştiricileri bu ayrıntılardan korusa da, sistemin nasıl çalıştığına dair derin bir anlayış kaybına yol açtı. Gömülü sistemler, mikrodenetleyiciler ve hatta bulut tabanlı mikro hizmetlerde bile, bellek sınırları ve donanım doğrudanlığıyla karşılaşmaya devam ediyoruz.
Bu dönemdeki araçlar ve teknikler artık nostaljik olabilir, ancak kodunuzun bellek kullanımını ve sistemle etkileşimini anlamak, herhangi bir geliştirici için değerli bir beceridir. Gelecekte, bellek kısıtlamalarının yeniden gündeme geleceği ve geliştiricilerin bu eski ancak gerekli bilgiyi hatırlamasının gerekeceği bir dönem olabilir.
Yapay zeka özeti
DOS döneminde geliştirme yaparken karşılaşılan 640KB bellek sınırı, `.COM` ve `.EXE` farkları ve DEBUG.COM ile hata ayıklama tekniklerini keşfedin.