Bir OTA güncellemesinin mutlu yolu üç satırda anlatılır: indir, yaz, yeniden başlat. Mühendislik işi mutlu yolda değil, o yolun kesildiği yerlerdedir. Sahaya çıkmış bir cihaz için asıl soru şudur: akışın tam olarak hangi anında elektrik kesilirse cihaz kurtarılamaz hâle gelir? Bu yazı OTA'yı bir özellik listesi olarak değil bir başarısızlık ağacı olarak ele alıyor — A/B yerleşiminin hangi kesintileri kapattığını, hangilerini kapatmadığını ve atomik geçiş noktasının nereye konması gerektiğini. Araç seçimi ayrı bir tartışma; onu SWUpdate ve Mender karşılaştırmasında ele aldık. Burada araçtan bağımsız tasarım kararları var.
Başarısızlık ağacı: kesinti nereye düşerse ne olur
Bir güncelleme akışını zaman ekseninde kesip her noktada "şimdi güç giderse ne olur?" diye sormak, tasarımın tamamını ortaya döker.
| Kesintinin düştüğü an | Cihazın durumu | Kurtarma yolu |
|---|---|---|
| Manifest indirilirken | Hiçbir şey değişmedi | Baştan dene |
| Yük pasif yuvaya yazılırken | Aktif yuva sağlam, pasif yarım | Pasif yuvayı baştan yaz |
| Yazma bitti, geçiş kaydı yazılmadan | Aktif yuva sağlam | Yazmayı veya geçişi tekrarla |
| Geçiş kaydı yazılırken | Tasarıma bağlı — tanımsız olabilir | Yedekli kayıt + sağlama |
| Yeni yuvadan ilk boot sırasında | Yeni yuva şüpheli | Boot sayacı eski yuvaya döndürür |
| Sağlık kontrolü sırasında | Yeni yuva boot ediyor ama sağlıksız | Onay verilmez, geri dönülür |
| Onay verildikten sonra | Yeni yuva kalıcı | Geri dönüş artık yeni bir güncelleme işidir |
Tablonun tek bir okuması var: yalnızca bir satır tanımsız durum üretebiliyor. İyi bir OTA tasarımının işi, o satırı tek bir atomik yazmaya indirmektir. Geri kalan her adım ya tekrarlanabilir ya da geri alınabilir olmalıdır. Bu ilke, IETF'in gömülü cihazlar için yazdığı firmware güncelleme mimarisinde de (RFC 9019) aynı biçimde durur: kurulum akışının kendisi kesintiye dayanıklı olmak zorundadır, çünkü kesinti istisna değil normal işletme koşuludur.
Atomik geçiş noktası nerede duruyor?
"Atomik" burada felsefi bir sıfat değil, depolama ortamının verdiği somut bir garantidir: güç kesintisinden sonra kayıt ya tamamen eski hâlindedir ya tamamen yeni hâlinde; arada bir şey yoktur. Pratikte kullanılan mekanizmalar sınırlıdır.
- U-Boot yedekli ortam alanı (
CONFIG_ENV_OFFSET_REDUND). Ortamın iki kopyası tutulur; her kopyanın kendi CRC'si ve hangisinin geçerli olduğunu söyleyen bir bayrak baytı vardır. U-Boot pasif kopyayı yazar, sonra bayrağı çevirir. Yazma yarıda kalırsa eski kopya hâlâ geçerlidir. - eMMC boot alanı seçimi. JEDEC JESD84-B51'in tanımladığı
EXT_CSDiçindekiPART_CONFIGyazmacı, hangi boot alanından (boot0/boot1) açılacağını tek bir kayıt yazımıyla belirler. Bootloader'ı A/B yapmanın standart yolu budur. - GPT bölüm öznitelik bitleri (öncelik / kalan deneme / başarılı). Burada gizli bir tuzak var: GPT'nin birincil kopyası diskin başında, yedek kopyası sonundadır. Tanım gereği iki ayrı yazma demektir; aracın iki kopya arasındaki uyuşmazlığı tolere eden bir okuma sırası olmalıdır.
- Boot girdisi dosya adı sayacı. systemd'nin otomatik boot değerlendirmesi, boot girdisinin dosya adına gömülü kalan-deneme sayacını her açılışta yeniden adlandırarak azaltır;
systemd-bless-bootbaşarıyı işaretler.
Buradaki en yaygın tasarım hatası, geçişi iki ayrı yazmaya bölmektir: önce aktif yuvayı değiştirmek, sonra boot sayacını sıfırlamak. İkisinin arasında güç giderse cihaz yeni yuvadan açılmaya çalışır ama sayacı zaten sınırda olduğu için ya anında geri döner ya da bir boot döngüsüne girer. Doğru yol, geçişle ilgili bütün değişkenleri tek bir işlemde yazmaktır:
# Pasif yuvaya yaz — bu adım tekrarlanabilir, geri alınabilir, kritik değil
dd if=rootfs.ext4 of=/dev/mmcblk0p3 bs=1M conv=fsync status=none
# dd'nin dönmesi kalıcılık DEĞİLDİR: blok katmanını ve cihaz önbelleğini boşalt
blockdev --flushbufs /dev/mmcblk0p3
sync
# Yazdığını geri oku ve manifestteki özetle karşılaştır
IMG_BYTES=$(stat -c %s rootfs.ext4)
head -c "$IMG_BYTES" /dev/mmcblk0p3 | sha256sum -
# ATOMİK GEÇİŞ: bütün değişkenler tek bir ortam yazımında
cat > /run/ota-switch.env <<'EOF'
boot_slot b
bootcount 0
upgrade_available 1
EOF
fw_setenv -s /run/ota-switch.env
fw_setenv -s bir betik dosyasındaki bütün ad-değer çiftlerini okuyup ortamı bir kez yazar; yedekli ortam yapılandırmasıyla birlikte bu, akıştaki tek tanımsız-durum riskini kapatır. bootlimit gibi politika değerleri imaj üretiminde sabitlenmeli, güncelleme anında yazılmamalıdır — güncelleyicinin dokunduğu her ek değişken, atomik pencerenin genişlemesi demektir.
İmza doğrulama sırası: neyi, ne zaman, neye karşı
OTA'da tek bir imza kontrolü yoktur; birbirini tamamlayan üç kapı vardır ve sıraları anlamlarını belirler.
Birinci kapı — paket imzası, tek bir bayt yazılmadan önce. RAUC, squashfs paketin yanındaki ayrık CMS imzasını kurulum başlamadan denetler. SWUpdate'te sw-description cpio arşivinin ilk girdisi olmak zorundadır; CONFIG_SIGNED_IMAGES etkinken imza kontrolü, handler'lar çalışmadan önce yapılır. Bu sıralama tesadüf değil, akış tabanlı kurulumu güvenli kılan şeyin ta kendisidir.
İkinci kapı — yük özeti, veri akarken. sw-description içindeki sha256 = "@rootfs.ext4.gz" alanı, imzalı manifestin içinden gelen bir özettir. Yükü diske indirip sonra doğrulamak yerine akış sırasında özetlemek, ekstra bir indirme alanı gereksinimini ortadan kaldırır — ama yalnızca özet imzalı manifestten geliyorsa.
Üçüncü kapı — çalışma anında. İmzalı bir FIT imajı ve dm-verity ile korunan bir rootfs, kurulumdan sonraki sessiz bozulmalara ve çevrimdışı değiştirmeye karşı çalışır. veritysetup ile üretilen kök karma değeri, imzalı çekirdek komut satırında taşınır.
Sık görülen üç hata:
- Geçişten sonra doğrulamak. Yeni yuvaya geçtikten sonra yapılan kontrol, hatayı bulduğunda çoktan geri dönüş yoluna muhtaçsınızdır. Kontrol geçişten önce biter.
- Doğrulanan veriyi değil, diskten yeniden okunanı kurmak. İndirilen dosyanın özetini alıp sonra kurulumu aynı dosyayı yeniden okuyarak yapmak klasik bir TOCTOU açığıdır. Kurulum, özeti alınan akışın kendisinden beslenmelidir.
- Doğrulayanın kendisini doğrulamamak. İmza kontrolünü yapan bileşen değiştirilebiliyorsa zincir yoktur. Bu yüzden OTA imzası, i.MX HAB gibi bir donanım kökünden başlayan zincirin son halkasıdır; ilk halkası değil.
Bir de sessiz bir zaman bombası var: RTC'si olmayan ya da saati güvenilmez bir cihazda, imzalama sertifikasının geçerlilik bitişine bakan bir doğrulama mantığı bütün filoyu aynı gün güncellenemez hâle getirir. RFC 9124'ün manifest bilgi modelinde tazelik ve sürüm denetiminin ayrı ayrı ele alınmasının sebebi budur: eskimişliği takvimle değil, monoton sürüm numarasıyla ölçün.
Yarım indirme: kimlik bayt sayısı değildir
Kesintili bir hücresel bağlantıda indirmenin yarıda kalması normaldir; HTTP Range ile kaldığı yerden devam etmek de öyle. Tuzak, devam etme kararının neye göre verildiğidir. Yalnızca bayt uzunluğuna bakarak devam eden bir istemci, sunucudaki paket bu arada değiştiyse iki farklı yapıyı tek dosyada birleştirir. İyi senaryoda özet tutmaz ve paket reddedilir; kötü senaryoda manifest her baytı kapsamıyorsa fark edilmez.
Kural basittir: yarım indirmenin yanında paketin kimliği (paket özeti veya sunucunun verdiği ETag) saklanır, devam etmeden önce karşılaştırılır, uyuşmazsa baştan indirilir. İkinci kural: yer kontrolü pasif yuvaya dokunmadan önce yapılır. Diskin dolduğunu kurulumun ortasında öğrenen bir güncelleyici, yarım yazılmış bir yuva bırakır.
Güç kesintisi: yazma sırası ve depolama gerçekleri
Bir yazma çağrısının dönmesi, verinin kalıcı olduğu anlamına gelmez. Sıra şudur: yaz → blok katmanını boşalt → cihaz önbelleğini boşalt → geri okuyup karşılaştır → ancak sonra geçiş kaydını yaz. JESD84-B51, eMMC için bir önbellek boşaltma komutu tanımlar; sürücü bunu sync yolunda çağırır, ama O_DIRECT kullanan bir yazıcı bunu kendisi tetiklemek zorundadır.
NAND tabanlı ortamlarda ikinci bir gerçek daha var: programlama sırasında güç giderse, o anda yazılan blok bozulabilir. Bu, geçiş kaydının neden iki kopya + sağlama ile tutulması gerektiğini açıklar; ayrıca kaydın, sık yeniden yazılan verilerle aynı silme bloğunda durmaması gerektiğini de. Ham NAND kullanıyorsanız UBI'nin birim güncelleme işareti (UBI_IOCVOLUP) aynı işi yapar: yarıda kalan güncelleme, birimi kullanılamaz olarak işaretli bırakır, yarım veri sessizce mount edilmez.
Rollback ve anti-rollback aynı sistemde nasıl yaşar?
İki gereksinim doğrudan çelişir. Rollback, bozuk bir güncellemeden sizi kurtarır. Anti-rollback, düzgün imzalanmış ama açığı olan eski bir sürümü yeniden kuran saldırgandan sizi korur. İkisi de gereklidir ve ikisini aynı anda kurmanın yolu, ikisini farklı zaman ölçeklerine yerleştirmektir.
Rollback, yalnızca deneme penceresi içinde ve yalnızca bir önceki yuvaya izinlidir. Anti-rollback ise donanımdaki monoton bir sayaçla kurulur — i.MX ailesinde SRK iptali, ARM Trusted Firmware'de NV sayaç, UEFI tarafında ESRT üzerinden bildirilen en düşük desteklenen sürüm. Bu sayaçlar tek yönlü kapılardır: ilerlettikten sonra geri alamazsınız.
Buradan çıkan sert kural: monoton sayacı, geçiş yapılan boot'ta asla ilerletmeyin. Sayaç yandı, sağlık kontrolü kaldı senaryosunda cihaz ne ileri ne geri gidebilir; kurtarma için servis teknisyeni gerekir. Doğru sıra: geçiş → sağlık onayı → bir sonraki normal açılışta, yalnızca gerçekten bir açık kapatan sürümler için sayacı ilerlet.
Aynı çelişkinin veri tarafındaki karşılığı da unutulmamalı: yeni sürüm veri bölümündeki şemayı değiştirdiyse, geri dönülen eski sürüm o veriyi okuyamaz. Rollback yolunun gerçekten çalışması için disk üzerindeki şemanın en az bir sürüm geriye okunabilir kalması gerekir.
Watchdog ile sağlık onayı: "boot etti" ile "çalışıyor" aynı şey değil
A/B düzeninin rollback'i, ancak birisi "bu güncelleme iyi" demediği sürece geri döneceği için işe yarar. O "birisi"nin ne olduğunu tanımlamak, OTA tasarımının en çok atlanan parçasıdır. Üç katman gerekir ve üçü farklı arızaları yakalar.
Donanım watchdog'u (/dev/watchdog0, WDIOC_SETTIMEOUT, WDIOC_KEEPALIVE) tamamen kilitlenmiş bir çekirdekten kurtaran tek katmandır. systemd katmanı servis düzeyindeki takılmaları yakalar. Uygulama kapısı ise ürüne özgüdür: alan veri yolu ayakta mı, sensör akıyor mu, sunucuya bağlanılabiliyor mu?
# /etc/systemd/system/ota-health-gate.service
[Unit]
Description=OTA saglik kapisi ve commit
After=network-online.target spikedge-app.service
Requires=spikedge-app.service
[Service]
Type=oneshot
RemainAfterExit=yes
# Urune ozgu kontrol: veri yolu, sensor akisi, backend erisimi
ExecStart=/usr/bin/ota-health-check
# Kontrol gectiyse guncellemeyi kalici yap; gecmezse commit calismaz,
# bir sonraki acilista bootloader eski yuvaya doner
ExecStart=/usr/bin/rauc status mark-good booted
[Install]
WantedBy=multi-user.target
# /etc/systemd/system.conf.d/watchdog.conf
[Manager]
RuntimeWatchdogSec=default
RebootWatchdogSec=default
Servis tarafında Type=notify ve WatchdogSec= kullanan bir uygulamanın sd_notify(0, "WATCHDOG=1") çağrısını asıl iş döngüsünden göndermesi gerekir; ayrı bir zamanlayıcı iş parçacığından gönderilen kalp atışı, uygulama takılsa bile devam eder ve watchdog'u dekora çevirir.
Nerede yanlış gider
1. Bootloader A/B'nin dışında kalır. A/B, güncellenen şeyi korur; güncellenmeyeni korumaz. Bootloader, cihaz ağacı ve fuse durumu paylaşımlıdır. eMMC'de bootloader'ı boot0/boot1 arasında PART_CONFIG ile değiştirmek bu boşluğu kapatır; kapatmazsanız filonun tek kurtarılamaz bileşeni bootloader olur.
2. Rollback yolu hiç çalıştırılmamıştır. CI'da yalnızca başarılı güncelleme koşuluyorsa geri dönüş yolu test edilmemiş koddur. Her sürümde en az bir senaryo kasten sağlık kontrolünden düşürülmeli ve cihazın eski yuvaya döndüğü görülmelidir.
3. Sağlık kapısı yalnızca multi-user.target'a bakar. Ağ yapılandırması bozuk, uygulama çöküyor ama systemd hedefine ulaşmış bir cihaz bu kontrolü geçer ve bozuk güncelleme kalıcı olur.
4. Watchdog magic close ile sessizce kapanır. /dev/watchdog aygıtına kapatmadan önce V karakteri yazılırsa watchdog devre dışı kalır. Çöken bir güncelleyici dosya tanıtıcısını düzgün kapattığında koruma da gider. CONFIG_WATCHDOG_NOWAYOUT bu kapıyı kapatır — bedeli, watchdog'un bir daha hiç durdurulamamasıdır. Ayrıca sürücü tek açılışa izin verdiği için watchdog'un sahibinin systemd mi uygulama mı olduğuna bir kez karar verilmelidir.
5. OTA durumu uygulamayla aynı ortam alanını paylaşır. Boot sayacı, uygulamanın da fw_setenv ile yazdığı bir alanda duruyorsa alakasız bir yazma sayacı sıfırlar ve rollback tetiklenmez.
6. Yeniden başlatma anını cihaz seçer. Hareket hâlindeki bir makinede kendi kararıyla yeniden başlayan bir cihaz, güncelleme sorunu değil güvenlik sorunudur. Kurulum ile etkinleştirmeyi ayırın: yazma her an yapılabilir, geçiş operatör onayına veya tanımlı bir duruş penceresine bağlanır. Otomotivde yazılım güncelleme süreçlerini tarif eden düzenlemelerin (UNECE R156, ISO 24089) ısrarla üzerinde durduğu ayrım da budur.
7. Kampanya tek seferde bütün filoya gider. Kademeli dağıtım ve önceden tanımlanmış bir durdurma ölçütü yoksa, ilk dalgada ortaya çıkan bir hata bütün filoya yayılmış olur.
A/B mi, tek bölüm + kurtarma imajı mı?
| Kriter | A/B (iki rootfs) | Tek bölüm + kurtarma imajı |
|---|---|---|
| Flash maliyeti | İki kat rootfs alanı | Rootfs + küçük kurtarma imajı |
| Yazma sırasında cihaz | Normal çalışır | Kurtarma imajına düşer, hizmet durur |
| Kesinti toleransı | Doğal: aktif yuvaya dokunulmaz | Kurtarma imajının sağlamlığına bağlı |
| Kurtarma yolunun sağlığı | Her güncellemede kullanılır | Nadiren kullanılır, sessizce çürür |
| Kurtarma imajını güncellemek | Kavram yok | Kendisinin kurtarma yolu yoktur |
| Ağ bağımlılığı | Yok | Genellikle var |
Kurtarma imajı modelinin asıl zayıflığı depolama değil, kullanılmamaktan doğan çürümedir: yıllarca çalıştırılmayan bir imaj, ihtiyaç duyulan gün ilk kez denenir. Bu modeli seçiyorsanız kurtarma imajını düzenli olarak, tercihen her kampanya öncesinde bir kez gerçekten boot ettirin. Rootfs'i iki kopya sığdıramayacak kadar büyükse, ikinci kopyayı eklemeden önce rootfs'i küçültmek çoğu zaman daha ucuz bir mühendislik işidir; Yocto ile ihtiyaca göre BSP kurmak tam olarak bunu hedefler.
Karar kriteri
Tasarıma başlamadan önce şu altı soruyu sırayla cevaplayın; her biri bir sonrakini kilitler.
- Flash bütçesi iki rootfs kopyasını kaldırıyor mu? Kaldırıyorsa A/B. Kaldırmıyorsa önce rootfs'i küçültmeyi deneyin; kurtarma imajı ikinci tercihtir ve düzenli olarak boot ettirilmek zorundadır.
- Geçiş tek bir atomik yazma mı? Değilse tasarım bitmemiştir. Yedekli ortam,
PART_CONFIGya da öznitelik bitleri — hangisi olursa olsun, yazma sayısı bir olmalıdır. - Sağlık kapısını yazabiliyor musunuz? "Cihaz çalışıyor" ifadesini ürün diliyle tanımlayamıyorsanız A/B'nin rollback'i dekordur. Kapıyı tanımlayın, sonra bir watchdog katmanıyla destekleyin.
- Rollback yolu CI'da koşuyor mu? Koşmuyorsa o yol yoktur. Kasten başarısız bir güncelleme senaryosu, sürüm kabul ölçütünüzün parçası olmalıdır.
- Cihaza fiziksel erişim riski var mı? Varsa imza zinciri bootloader'dan başlamalıdır; OTA imzası tek başına yeterli değildir. Anahtar yenileme planı da bugünden yapılmalıdır.
- Geri dönüşü kalıcı olarak yasaklamanız gerekiyor mu? Yalnızca gerçekten açık kapatan sürümler için, yalnızca onay verildikten sonraki açılışta monoton sayacı ilerletin.
Bu altı sorunun tamamı, ilk kartın gelmesinden önce cevaplanabilir ve cevaplanmalıdır; sahaya çıktıktan sonra bunların her biri bir servis çağrısı fiyatına öğrenilir. Mevcut bir güncelleme tasarımını üretime çıkmadan bağımsız bir gözle görmek isterseniz, güvenli boot ve OTA yaşam döngüsü yetkinliğimiz ve gömülü sistem mimari denetimi tam olarak bu katmanları inceler.
Kaynaklar
- IETF — A Firmware Update Architecture for Internet of Things, RFC 9019 — https://www.rfc-editor.org/rfc/rfc9019
- IETF — A Manifest Information Model for Firmware Updates in IoT Devices, RFC 9124 — https://www.rfc-editor.org/rfc/rfc9124
- JEDEC — Embedded Multi-Media Card (eMMC) Electrical Standard 5.1, JESD84-B51 — https://www.jedec.org/standards-documents/docs/jesd84-b51
- Linux çekirdek dokümantasyonu — The Linux Watchdog driver API — https://docs.kernel.org/watchdog/watchdog-api.html
- systemd — Automatic Boot Assessment — https://systemd.io/AUTOMATIC_BOOT_ASSESSMENT/
- U-Boot dokümantasyonu — https://docs.u-boot.org/en/latest/
- RAUC — Update Framework documentation — https://rauc.readthedocs.io/
- SWUpdate — Software Update for Embedded Systems — https://sbabic.github.io/swupdate/
Kaynaklara erişim tarihi: 5 Eylül 2026. Bootloader, çekirdek ve güncelleme çatısı belgeleri sürüm alır; yapılandırma seçeneği adları ve aygıt yolları her zaman kullandığınız sürümün belgesinden doğrulanmalıdır.
