İçeriğe geç
academia.sh

Ders 15 / 16

Bağlama ve Yükleme

Nesne dosyaları, simge çözümleme, statik ve dinamik bağlama ile programın belleğe yüklenmesi.

İçindekiler

Önceki ders, kod üretiminin her kaynak dosya için ayrı bir çıktı verdiğini söyledi. Gerçek bir program ise birçok dosyadan oluşur ve kullandığı kütüphaneler başka zamanlarda, başka yerlerde derlenmiştir.

Bu dersin sorusu şudur: ayrı ayrı derlenmiş parçalar, tek bir çalışan program hâline nasıl getirilir?

Nesne Dosyası

Derleyicinin bir kaynak dosya için ürettiği çıktı doğrudan çalıştırılamaz; buna nesne dosyası (object file) denir. İçinde makine kodu vardır, ancak eksik bilgiler taşır.

Nesne dosyası bölümlere ayrılmıştır. Adlandırma biçimlere göre değişse de içerik ortaktır:

Bölüm İçerik
Kod Yürütülebilir komutlar
Salt okunur veri Sabitler, dizgi sabitleri
Başlatılmış veri Başlangıç değeri verilmiş küresel değişkenler
Başlatılmamış veri Yalnızca boyutu kayıtlı, sıfırlanacak alan
Simge tablosu Tanımlanan ve gereken adlar
Yeniden konumlandırma kayıtları Adres düzeltmesi gereken yerler

Başlatılmamış veri bölümünün yalnızca boyutu saklanır; içeriği dosyada yer kaplamaz, çünkü tamamı sıfırdır. Bu, büyük dizilerin tanımlandığı dosyaların neden küçük kalabildiğini açıklar.

Simgeler

Simge (symbol), bir ada bağlı bir adrestir: bir fonksiyonun başlangıcı veya bir küresel değişkenin yeri. Nesne dosyası iki tür simge listeler:

  • Tanımlanan simgeler: Bu dosyanın sağladığı adlar.
  • Gereken simgeler: Bu dosyanın kullandığı, ancak başka bir yerde tanımlı adlar.

Derleyici, gereken bir simgeye yapılan çağrıyı üretirken hedef adresi bilmez; yerine boşluk bırakır ve bu boşluğu yeniden konumlandırma kayıtlarına yazar. “Bu adresi, topla adlı simgenin gerçek adresiyle doldur” notu, kayıtta durur.

Bağlama

Bağlayıcı (linker), nesne dosyalarını ve kütüphaneleri alıp tek bir çıktı üretir. Üç iş yapar:

  1. Bölümleri birleştirir. Tüm dosyaların kod bölümleri art arda, veri bölümleri art arda yerleştirilir.
  2. Simgeleri çözer. Her gereken simge için, onu tanımlayan dosya bulunur.
  3. Adresleri düzeltir. Yeniden konumlandırma kayıtlarındaki boşluklar, birleştirme sonrası kesinleşen adreslerle doldurulur.

Bu aşamanın iki tanınmış hatası vardır ve ikisi de derleyici hatası değildir:

  • Tanımlanmamış simge: Bir ad kullanılmış ama hiçbir dosyada tanımlanmamış. Yazım hatası, eksik kütüphane veya yalnızca bildirimi olup gövdesi yazılmamış bir fonksiyon bunu üretir.
  • Yinelenen simge: Aynı ad birden çok dosyada tanımlanmış. Bağlayıcı hangisini seçeceğini bilemez.

Bu hataların derleme değil bağlama aşamasında çıkması, tek bir dosyanın sorunsuz derlenmesinin programın bütününün doğru olduğu anlamına gelmediğini gösterir.

Görünürlük ve Ad Karıştırma

Bir dosyadaki her ad, bağlayıcıya görünmek zorunda değildir. Diller, simgeleri iki görünürlük düzeyine ayırır:

  • Dış görünürlük: Ad, simge tablosuna yazılır ve başka dosyalardan çözülebilir.
  • İç görünürlük: Ad yalnızca kendi dosyasında geçerlidir; bağlayıcı onu görmez.

İç görünürlük iki işe yarar: aynı adın farklı dosyalarda çakışmadan kullanılabilmesi ve bağlayıcının çözmesi gereken simge sayısının azalması. Büyük projelerde bu, bağlama süresini doğrudan etkiler.

İkinci bir ayrıntı, aynı adın birden çok anlam taşıyabildiği dillerde ortaya çıkar. Aynı ada sahip ama farklı parametre türleri alan iki fonksiyon, simge tablosunda aynı adla duramaz. Derleyici bu durumda ada tür bilgisini kodlayarak benzersiz bir simge üretir; buna ad karıştırma (name mangling) denir.

Ad karıştırma düzeni derleyiciye özgüdür ve standartlaşmış değildir. Farklı derleyicilerle üretilmiş nesne dosyalarının birbirine bağlanamamasının başlıca nedeni budur; diller arası çağrılarda ise karıştırmayı kapatan bir bildirim kullanılır.

Statik ve Dinamik Bağlama

Kütüphanelerin programa nasıl katıldığı iki ayrı yaklaşımla çözülür.

Statik bağlamada kütüphane kodu, çıktının içine kopyalanır. Sonuç tek ve büyük bir dosyadır; çalıştığı sistemde başka bir şey aramaz. Kütüphanede sonradan yapılan bir düzeltme, programın yeniden bağlanmasını gerektirir.

Dinamik bağlamada kütüphane ayrı bir dosya olarak kalır; çıktı yalnızca hangi kütüphaneye ihtiyaç duyduğunu kaydeder. Bağlama işinin bir bölümü, programın başlatıldığı ana ertelenir.

Ölçüt Statik Dinamik
Çıktı boyutu Büyük Küçük
Bellek paylaşımı Her süreç kendi kopyasını taşır Aynı kütüphane süreçler arasında paylaşılır
Güncelleme Yeniden bağlama gerekir Kütüphane dosyası değişince tüm programlar etkilenir
Başlatma maliyeti Yok Kütüphane bulma ve simge çözme
Dağıtım Kendi kendine yeter Hedef sistemde uyumlu sürüm bulunmalıdır

Dinamik bağlamanın güncelleme kolaylığı, aynı zamanda kırılganlık kaynağıdır: beklenenden farklı bir sürümün bulunması, çalışma anında simge hatası verir. Uyumluluk kurallarının belgelenmesi ve sürüm işaretlemesi bu nedenle kütüphane geliştirmenin ayrılmaz parçasıdır.

Yükleme

Program çalıştırıldığında yükleyici (loader) devreye girer. Yaptıkları:

  1. Çalıştırılabilir dosyanın bölümlerini sürecin adres uzayına yerleştirir.
  2. Başlatılmamış veri bölümü için yer ayırır ve sıfırlar.
  3. Gerekli dinamik kütüphaneleri bulur, yükler ve kalan simgeleri çözer.
  4. Yığıt için alan hazırlar, başlangıç argümanlarını yerleştirir.
  5. Program sayacını giriş noktasına yazar; artık akış programındır.

Beşinci adım, önceki konudaki getir–çöz–yürüt döngüsünün başlangıcıdır. Yükleyici, o döngünün ilk adresini belirleyen bileşendir.

Yükleyici, kodu önceden bilinmeyen bir adrese yerleştirebilir; bu nedenle üretilen kodun konumdan bağımsız olması istenir. Konumdan bağımsız kod, mutlak adres yerine göreli adres kullanır ve aynı kütüphanenin farklı süreçlerde farklı adreslere yüklenmesine izin verir.

Çalışma Anında Ad Çözme

Dinamik bağlamanın fikri — adın hedefini son ana kadar belirlememek — yorumlanan dillerde daha da ileri götürülür: ad çözümleme her erişimde yapılır.

import math, sys

# İçe aktarma, adı çalışma anında bir modül nesnesine bağlar.
print(type(math))                      # <class 'module'>
print(math.__name__)                   # math

# Modül nesnesi, adları taşıyan bir sözlüktür.
print("sqrt" in vars(math))            # True

# Ad, çağrı anında çözülür: bağ değiştirilebilir.
gercek_sqrt = math.sqrt
math.sqrt = lambda x: "yerine geçen"
print(math.sqrt(9))                    # yerine geçen
math.sqrt = gercek_sqrt                # eski bağ geri yüklendi
print(math.sqrt(9))                    # 3.0

# Yüklü modüller, yükleyicinin tuttuğu tabloya benzer bir kayıtta durur.
print("math" in sys.modules)           # True

Bu esnekliğin bedeli, statik bağlamanın verdiği güvencelerin kaybıdır: yanlış yazılmış bir ad, ilgili satır çalışana kadar hata üretmez. Bağlama zamanı ile hata görme zamanı arasındaki ilişki, önceki derste tip denetimi için kurulan ilişkinin aynısıdır.

Özet

  • Derleyicinin çıktısı olan nesne dosyası eksiktir: tanımladığı ve gereksinim duyduğu simgeleri listeler, dış adresler için boşluk bırakır.
  • Bağlayıcı bölümleri birleştirir, simgeleri çözer ve adresleri düzeltir; tanımlanmamış ve yinelenen simge hataları bu aşamada çıkar.
  • Statik bağlama kütüphane kodunu çıktıya kopyalar; dinamik bağlama onu ayrı dosyada bırakıp çözümü başlatma anına erteler.
  • Dinamik bağlama bellek paylaşımı ve kolay güncelleme sağlar, karşılığında sürüm uyumluluğu sorumluluğu getirir.
  • Yükleyici bölümleri adres uzayına yerleştirir, kütüphaneleri bağlar ve program sayacını giriş noktasına yazar.

Sonraki Adım

Yükleyicinin hazırladığı adres uzayının içi henüz açılmadı: kod nerede durur, küresel değişkenler nerede, fonksiyon çağrılarının yerel değişkenleri nerede tutulur? Kursun son dersi bu bölmeleri ve her birinin yaşam süresi kuralını ele alacak.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat