İçeriğe geç
academia.sh

Ders 07 / 12

Gömülü Hedefler

Standart kütüphanenin temelindeki işletim sistemi hizmetlerinin (yazdırma, bellek ayırma, panik işleme) hiç bulunmadığı bir hedefin kaynakta nasıl görünür kılındığı; aynı işi bir kez ayırma yaparak, bir kez sabit boyutlu bir tamponla yazan iki gövdenin aynı sonucu verdiği koşturularak gösterilir.

Yayın Güncelleme

Bu derste

  • Yazdırma hizmetinin işletim sistemine dayandığını, bu bağımlılığın koşullu derlemeyle kaynakta nasıl işaretlendiğini gösterir
  • Ayırma yapan bir gövde ile sabit boyutlu bir tamponla çalışan bir gövdenin aynı sonucu ürettiğini karşılaştırarak kanıtlar
  • Sabit boyutlu bir tamponun kapasite sınırını aşan bir girdide neden yetersiz kaldığını örnekle açıklar
İçindekiler

Önceki ders, aynı işletim sisteminin iki ayrı hedefte farklı hizmetler sunduğunu ölçtü: dosya sistemi ve iş parçacığı, tarayıcı hedefinde ya hiç yoktu ya da farklı bir yoldan geliyordu. Bu ders sınırı bir uca taşıyor — işletim sisteminin hiç bulunmadığı bir hedef. Böyle bir hedefte yalnız birkaç hizmet değil, standart kütüphanenin dayandığı temel bile değişir: yazdırma, bellek ayırma ve panik işleme, üçü de bir işletim sisteminin varlığını varsayar.

Bu tür bir hedef, tipik olarak sınırlı belleğe sahip ve kendi başına bir işletim sistemi çalıştırmayan bir donanımı temsil eder: bir sensör kartı, bir motor denetleyicisi, bir kartlı ödeme cihazı. Böyle bir donanımda kod doğrudan işlemcide koşar; altında dosya sistemini yöneten, bellek ayırmasını düzenleyen ya da bir program çöktüğünde iletiyi bir uçbirime basan bir katman yoktur. Rust bu ortam için standart kütüphanenin bütününü değil, yalnız çekirdek dili ve donanımdan bağımsız temel tipleri (tam sayılar, dilimler, seçenek ve sonuç tipleri gibi) sağlayan daha dar bir kütüphane sunar; dosya, iş parçacığı, ağ ve bellek ayırma gibi işletim sistemine dayanan parçalar bu dar kütüphanenin dışında kalır. Bu ayrım kaynakta bir bildirimle işaretlenir: bu ders o bildirimi kullanmıyor, çünkü kullanan kaynak bu makinede sıradan bir program olarak çalıştırılamaz — bildirim, standart kütüphanenin bütününü isteyen hiçbir çağrının kalmadığını, aksi hâlde derlemenin duracağını söyler. Bu derste ölçülen ayrım, o bildirim yazılmasa bile kaynaktan okunabilir: hangi gövdenin hangi hizmete dayandığı, kullandığı çağrıdan bellidir.

Bu derste de gömülü bir hedef için gerçek bir derleme yapılmaz. Hedefe bağlı karar, önceki derste olduğu gibi kaynak üzerinden modellenir: kaynaktaki iki gövdeden hangisinin seçileceğini cfg belirler ve derlemeye yalnız biri girer.

Yazdırmanın Bir İşletim Sistemi Hizmeti Olması

Bir yazdırma çağrısı, arkasında bir işletim sistemi çağrısı çalıştırır: metni bir akışa yazar, akış işletim sistemi tarafından bir uçbirime ya da bir dosyaya yönlendirilir. Bu zincirin ilk halkası — işletim sisteminin kendisi — bulunmayan bir hedefte, aynı çağrı hiçbir yere yazamaz. Önceki derste bir hizmetin (dosya sistemi) yokluğu bir hata değeri olarak geri dönüyordu — program yine de derleniyor, çalışma zamanında bir Result üzerinden anlaşılıyordu. Yazdırma çağrısının burada durumu farklı: standart kütüphanenin dar sürümünde bu çağrının kendisi hiç yoktur, bu yüzden yokluk bir çalışma zamanı sonucu değil, bir derleme zamanı sonucudur — çağrıyı içeren kaynak, o hedef için hiç derlenmez. Aşağıdaki koşum bu bağımlılığı cfg ile işaretliyor.

DS15. Yazdırılan ileti "olcum tamamlandi" — önceki dersteki sensör kaydının işlendiği varsayılan bir aşamanın bittiğini bildiriyor.

// gomulu1.rs — yazdirma hizmetinin isletim sistemine dayali olmasi
#[cfg(not(target_os = "none"))]
fn bilgi_yaz(mesaj: &str) {
    // Standart cikis akisina yazma: isletim sisteminin sagladigi bir hizmet.
    println!("{}", mesaj);
}

#[cfg(target_os = "none")]
fn bilgi_yaz(mesaj: &str) {
    govde_gomulu_hedefte_baska_bir_yontemle_yazar(mesaj)
}

fn main() {
    bilgi_yaz("olcum tamamlandi");
}
olcum tamamlandi

target_os = "none", işletim sistemi bulunmayan hedeflerin taşıdığı değerdir; bu makine bir işletim sistemi altında koştuğu için bu koşulu sağlamaz ve not(target_os = "none") işaretli gövde derlemeye girer. Karşıt gövde, önceki dersteki wasm örneğinde olduğu gibi, hiç derlenmeden kaynakta duruyor — içindeki tanımsız çağrı bu yüzden hiçbir soruna yol açmıyor. DS16. Gerçek bir işletim sistemi bulunmayan bir hedefte bu ikinci gövde yazdırma işini görebilmesi için bir donanım kaydına doğrudan yazmak ya da bir hata ayıklama arayüzüne veri göndermek gibi tamamen farklı bir yönteme ihtiyaç duyar — bu yöntem işletim sisteminin sağladığı akış soyutlamasından bağımsızdır.

target_os = "none", önceki derste kullanılan target_family = "wasm"dan farklı bir soruyu sorar. target_family, bir hedefin geniş bir ortam sınıfına (wasm ailesi, unix ailesi gibi) ait olup olmadığını sorar; target_os ise daha dar bir soru sorar ve hedefin hangi işletim sistemi üzerinde koştuğunu, ya da hiç koşmadığını adlandırır. "none" değeri, bu sorunun yanıtının “hiçbiri” olduğu tek durumdur — hedefin altında dosya sistemi, süreç yönetimi ya da akış sunan bir katman yoktur. Aynı öznitelik başka değerler de taşıyabilir (bir işletim sisteminin adı gibi), ama bu ders yalnız “hiç işletim sistemi yok” durumuyla ilgileniyor.

Bu iki gövde arasındaki fark, önceki dersteki dosya sistemi örneğiyle aynı biçimde kuruldu, ama dayandığı hizmet farklı. Orada eksik olan tek bir çağrıydı; burada eksik olan, standart kütüphanenin her yazdırma çağrısının temelinde duran bir akış soyutlamasının kendisi. Yazdırma çağrısı, aslında bir dizi katmanın en üstünde duruyor: metni biçimlendirir, sonucu standart çıkış akışına yazan bir işleve devreder, o işlev de altında işletim sisteminin sunduğu bir yazma çağrısını kullanır. Bu zincirin son halkası yoksa, zincirin geri kalanı da anlamsızlaşır — bu yüzden bu yazdırma yolu, işletim sistemi bulunmayan bir hedef için hazırlanan dar kütüphanede doğrudan yer almaz.

Ayırma Yapan ve Ayırma Yapmayan Aynı İş

Bellek ayırma da benzer bir bağımlılık taşır, ama farkı cfg ile değil, iki farklı yazımla göstermek daha açık: her iki yazım da bu makinede derlenip koşuyor, aralarındaki fark yalnızca birinin bellek ayırdığı, ötekinin ayırmadığı. Yazdırma örneğinde iki gövdeden yalnız biri derleniyordu; burada ikisi de derleniyor ve ikisi de çalıştırılıyor, çünkü ölçülen şey hangisinin derlendiği değil, aynı sonucu hangi kaynağa dayanarak ürettikleri. Aşağıdaki koşum aynı sensör kaydını iki ayrı yolla topluyor.

// gomulu2.rs — ayirma yapan ve ayirma yapmayan govdenin ayni sonucu vermesi
fn ayirarak_topla(kayitlar: &[i32]) -> i32 {
    let mut biriktirici: Vec<i32> = Vec::new();
    for &deger in kayitlar {
        biriktirici.push(deger);
    }
    biriktirici.iter().sum()
}

fn tampon_ile_topla(kayitlar: &[i32]) -> i32 {
    const KAPASITE: usize = 8;
    let mut tampon = [0i32; KAPASITE];
    let mut dolu = 0usize;
    for &deger in kayitlar {
        tampon[dolu] = deger;
        dolu += 1;
    }
    tampon[..dolu].iter().sum()
}

fn main() {
    let kayitlar = [12, 7, 5, 21];
    println!("ayirarak toplam: {}", ayirarak_topla(&kayitlar));
    println!("tampon ile toplam: {}", tampon_ile_topla(&kayitlar));
}
ayirarak toplam: 45
tampon ile toplam: 45

DS17. İki toplam da 45 — aynı dört sayı, aynı sonuç. ayirarak_topla, her push çağrısında büyüyen dizinin kapasitesini gerektiği kadar genişletiyor; bu genişleme bir bellek ayırıcıdan yeni bir blok istemekle sonuçlanır ve ayırıcı, çalışma zamanının bir parçasıdır. tampon_ile_topla ise hiçbir ayırıcıya dokunmuyor: tampon dizisinin boyutu kaynakta KAPASITE sabitiyle önceden belirlenmiş, dizi bir işlev çağrısı kadar kısa ömürlü ve bellekteki yeri derleme zamanında bilinen bir alanda duruyor.

Bir bellek ayırıcı bulunmayan bir hedefte ayirarak_topla derlenemez ya da derlense bile çalışma zamanında ayırma isteği karşılanamadığı için ilerleyemez; tampon_ile_topla ise böyle bir hedefte de aynı sonucu üretir, çünkü hiçbir dış kaynağa ihtiyaç duymaz. Bu fark koşumda görünmüyor — ikisi de bu makinede sorunsuz çalıştı — yalnızca kaynaktaki Vec ile sabit boyutlu dizi arasındaki seçimde görünüyor.

Bu tercih yalnız bir ayırıcının var olup olmamasıyla da sınırlı değil. Bir sensör kartı gibi az bellekli bir donanımda, bir programın en fazla ne kadar bellek kullanacağının önceden bilinmesi ayrı bir değer taşır: tamponın boyutu kaynakta sabit olduğu için, bu işlevin kullanacağı bellek miktarı derleme anında bellidir ve hiçbir çalışma anı kararına bağlı değildir. Vec tabanlı yazımda kullanılan bellek, gelen kayıt sayısına göre değişir; kayıt sayısı önceden bilinmiyorsa, kullanılacak üst sınır da önceden bilinmez. Sabit boyutlu bir tamponun getirdiği kısıt — üst sınırın kaynakta yazılı olması — aynı zamanda bu öngörülebilirliğin kaynağıdır.

Sınırlayıcı Ölçüm

Sabit boyutlu tamponun bu işi görebilmesinin bir koşulu var: KAPASITE, gelen kayıt sayısından büyük ya da eşit olmalı. Yukarıdaki koşumda dört kayıt sekiz kapasitelik bir tampona sığdı. Kayıt sayısı kapasiteyi aşsaydı ve kod bunu denetlemeseydi, tampon[dolu] = deger satırı dizinin sınırlarının dışına yazmaya çalışırdı — bu, Rust’ta sınır denetimi her zaman çalıştığı için sessizce geçmez, program çalışma zamanında dururdu. Bu ders panik tetiklemediği için o durumu koşturmuyor; onun yerine, aynı sınırı panik atmadan ele alan bir yazım koşturuyor.

DS18. On kayıt gönderiliyor, kapasite sekiz; ilk sekizi kabul edilip toplanıyor, son ikisi reddediliyor.

// gomulu3.rs — kapasiteyi asan girdiyi panik atmadan reddeden tampon
fn tampon_ile_topla_guvenli(kayitlar: &[i32]) -> (i32, usize) {
    const KAPASITE: usize = 8;
    let mut tampon = [0i32; KAPASITE];
    let mut dolu = 0usize;
    let mut reddedilen = 0usize;
    for &deger in kayitlar {
        if dolu < KAPASITE {
            tampon[dolu] = deger;
            dolu += 1;
        } else {
            reddedilen += 1;
        }
    }
    (tampon[..dolu].iter().sum(), reddedilen)
}

fn main() {
    let cok_kayit = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    let (toplam, reddedilen) = tampon_ile_topla_guvenli(&cok_kayit);
    println!("kabul edilen toplam: {} reddedilen kayit: {}", toplam, reddedilen);
}
kabul edilen toplam: 36 reddedilen kayit: 2

Bu yazım, dolu < KAPASITE denetimini yazmayla aynı işlemi birleştirerek sınırı asla aşmıyor — dokuzuncu ve onuncu kayıt, tampona hiç yazılmadan sayılıp bir kenara bırakılıyor. Sonuç, panik yerine bir bilgi: kaç kaydın kabul edildiği, kaç kaydın reddedildiği. DS19. Bu, gömülü bir hedefte yaygın bir alışkanlığı gösteriyor: bir işletim sistemi çökeni yakalayıp iletiyi yazdıramadığı için, sınır denetimini önceden elle yapıp durumu bir sonuç değeriyle bildirmek, panikle durmaktan daha güvenilir bir yazım kabul edilir — panik sonrası davranış hedefe göre değiştiği hâlde, bir sonuç değeri her hedefte aynı biçimde ele alınabilir.

ayirarak_topla böyle bir sınır taşımıyor — Vec, kapasitesi dolduğunda kendini büyütüyor, kayıt sayısı önceden bilinmese de çalışıyor. Bu, iki yazım arasındaki gerçek değiş tokuşu gösteriyor: sabit boyutlu tampon, hedefin bir ayırıcı sunmasını gerektirmiyor, ama üst sınırı kaynakta yazılı olmak zorunda ve o sınır aşıldığında ya durur ya da (yukarıdaki gibi elle yazılmışsa) fazlasını reddeder. Büyüyen dizi ise üst sınır taşımıyor, ama bir ayırıcı bulunmasını şart koşuyor. Ayırmasız bir yazım her zaman mümkün değildir; kayıt sayısı çalışma zamanında öğrenilen ve önceden sınırlanamayan bir işte, sabit boyutlu bir tampon fazlasını ya reddetmek ya da durmak zorunda kalır.

Gerçek bir gömülü sistemde KAPASITE genellikle keyfi seçilmez; donanımın kendisinden gelen bir sayıya bağlanır — bir kartta kaç sensör kanalı olduğu, bir iletişim çerçevesinin en fazla kaç bayt taşıdığı, bir arabelleğin sığdığı bellek bölgesinin boyutu gibi. Bu ders için seçilen 8 böyle bir donanım kısıtını temsil ediyor; on kayıtlık girdi, o kısıtın aşıldığı bir durumu göstermek için seçildi. Kod, aştığı girdiyi sessizce yok saymak yerine kaçının reddedildiğini sayarak bildiriyor — bu da gömülü yazılımda sık görülen bir başka alışkanlık: bir sınırın aşıldığını fark etmeden geçmek, sınırın aşıldığını fark edip bunu bir yerde kaydetmekten daha kötü bir sonuçtur.

Panik davranışının kendisi de bu sınırın bir parçası. Bir işletim sistemi altında panik, bir ileti yazdırıp programı durdurur; bazı yapılandırmalarda program bu noktadan geri sarılmayı da dener, bazılarında doğrudan durur — ama iki durumda da bu davranış bir işletim sisteminin sağladığı süreç yönetimine dayanır. İleti bir akışa yazılır, süreç işletim sistemine bir çıkış koduyla döner, işletim sistemi bu süreci kapatır. İşletim sistemi bulunmayan bir hedefte bu zincirin hiçbir halkası yoktur; standart kütüphanenin dar sürümü, panik anında hangi davranışın izleneceğini belirtmez, yalnızca bu davranışı kaynağın ayrıca tanımlamasını zorunlu kılar. Yazan taraf bu tanımda bir donanım kaydına sabit bir değer yazmayı, işlemciyi düşük güç kipinde sonsuz bir döngüye sokmayı ya da bir hata ayıklama arayüzüne bir işaret göndermeyi seçebilir; hangisinin seçildiği, donanımın kendisine ve o donanımın hangi geri kazanım yollarını sunduğuna bağlıdır. Bu ders böyle bir tanım koşturmuyor, yalnızca varlığını anıyor — yazdırma ve ayırma ile aynı sınıfa ait bir bağımlılık olduğu için.

Üç bağımlılığın (yazdırma, ayırma, panik işleme) ortak noktası, hiçbirinin dilin kendisinden eksik olmamasıdır. Rust bu üçünü de dil düzeyinde tanır; yazdırma sözdizimi, Vec tipi, panik sözcüğü, hepsi çekirdek dilin ya da standart kütüphanenin bir parçasıdır. Eksik olan, bu üçünün çalışabilmesi için gereken ortamdır — bir akış, bir ayırıcı, bir süreç yönetimi. Dil aynı kalıyor, hedefin sunduğu ortam değişiyor; bu yüzden bu üç bağımlılığın sınıfı dilin dışında, derleyicinin ya da standart kütüphanenin bir eksikliği değil.

Özet

  • Yazdırma, bellek ayırma ve panik işleme, standart kütüphanenin işletim sistemine dayanan üç temel hizmeti; işletim sistemi bulunmayan (target_os = "none") bir hedefte üçü de kaynakta ayrıca ele alınmak zorunda
  • Gömülü bir hedef için gerçek bir derleme yapılmadı; hedefe bağlı karar cfg ile kaynak üzerinden modellendi
  • Ayırma yapan gövde (Vec) ile sabit boyutlu bir tamponla çalışan gövde aynı toplamı (45) verdi; fark sonuçta değil, birinin bir ayırıcı gerektirmesinde
  • Bir hedefin bellek ayırıcı, işletim sistemi hizmeti ya da panik işleyicisi sunmaması, bu hedefin verdiği güvencenin dilin kendisinden değil hedeften geldiğini gösterir — sınıf dilin dışında
  • Sabit boyutlu bir tampon ayırıcı gerektirmez ama üst sınırı kaynakta yazılı olmak zorundadır; bu sınır aşıldığında ayırmasız yazım ya durur ya da (sınır elle denetlenmişse) fazlasını panik atmadan reddeder

Sonraki Adım

Bu konu boyunca güvence hep aynı yerde bitti: derleyicinin denetimi değil, dilin dışındaki bir düzenek belirleyiciydi. unsafe Rust dersinde bu düzenek yazana devredilen bir sözleşmeydi; Yabancı Fonksiyon Arayüzü dersinde derleyicinin hiç görmediği bir dilin imzasıydı; WebAssembly Hedefi dersinde tarayıcının yalıttığı bir ortamdı; bu derste ise işletim sisteminin kendisinin yokluğuydu. Dördünde de ortak olan, güvencenin sınırının kaynaktan okunabilir olması — unsafe bloğu, extern bildirimi ve cfg etiketi, üçü de bu sınırı kaynakta işaretleyen birer iz; hiçbiri koşum sırasında keşfedilmesi gereken gizli bir davranış değil. Sıradaki konu bu eksenden ayrılıyor: orada sorulan, bir bağımlılığın ya da bir sınamanın hangi güvenceyi verdiği, bir hedefin neyi elden aldığı değil.

İ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