Embedded Linux Boot Süreci (Beagle Bone Black)

Serbay Özkan
5 min readJan 24, 2021

--

Embedded Linux konusuna ilgisi bulunan her gömülü yazılımcının elinde genelde Raspberry Pi, Beagle Bone, Orange Pi gibi kolay ulaşılabilir geliştirme kitlerinden birisi bulunur. Bu kitleri SD Card içine yüklenen özelleştirilmiş bir linux dağıtımıyla çalışmaya hazır hale getirip üzerinde C, C++, Python, Java, JavaScript gibi bir çok programlama diliyle yazılmış programlarımızı kolay bir şekilde çalıştırabiliyoruz.

Bu yazıda Beagle Bone Black geliştirme kiti üzerinde uygulama yazmadan ziyade uygulama yazmak için gerekli olan işletim sisteminin nasıl bir süreç içerisinde yüklendiğini daha önceki kişisel çalışmalarımdan, okuduğum kitaplardan ve yazı sonunda da referans verdiğim oldukça başarılı bulduğum bir eğitimden aldığım notları derleyerek anlatmaya çalışacağım.

AM335X Fonksiyonel Blok Diyagram
Beagle Bone Black Linux Boot Süreci

ROM Bootloader (RBL)

BBB kartına ilk enerji verdiğimizde ROM içerisinde yer alan ROM Bootloader (RBL) denilen bir program çalışır. Bu program üretim sırasında üretici firma tarafından ROM’a gömülmektedir ve bunu değiştirme olanağımız bulunmamaktadır. Bu programın ana amacı secondary stage bootloader olarak da bilinen Secondary Program Loader (SPL)’i yüklemektir. ROM kodu sırasıyla aşağıdaki işlemleri gerçekleştirmektedir.

  • Sistem ilklendirme ve stack setup
  • Watchdog ilklendirmesi
  • PLL ve Clock Konfigürasyonları
  • ROM Code Booting

Buradaki watchdog timer’ın varsayılan değeri 3 dakika olarak ayarlanmaktadır. Eğer SPL yüklenemez veya ilklendirme sırasında bir hata alınırsa 3 dakika sonra watchdog devreye girerek reset işlemi gerçekleştirmektedir.

PLL ve Clock ayarlamaları ise varsayılan çarpanlar ile yapılmaktadır. ROM kodu kart üzerinde hangi frekansta crystal bağlandığını SYSBOOT[15:14] pinlerine bakarak anlamaktadır.

ROM Code Booting sürecinde ise RBL SYSBOOT[4:0] pinlerine bakarak SPL/MLO’yu hangi bellek arayüzünden yüklemesi gerektiğini anlamaktadır.

  • eMMC Boot
  • SD Boot
  • Serial Boot
  • USB Boot

Default olarak eMMC Boot seçeneği aktiftir. En hızlı boot süreci eMMC ile sağlanmaktadır. SYSBOOT pinlerin konfigürasyonuna göre RBL ilgili bellek arayüzünden SPL/MLO’yu alarak bunu internal SRAM (64 + 64 KB) alanına yazar. SPL/MLO ilk arayüzde bulunamaz ise konfigürasyona göre bir sonraki bellek arayüzüne bakılır. Bu şekilde 4 hafıza arayüzü de taranır. Hiçbirinde bulamaz ise belli bir süre sonra watchdog devreye girerek işlemciyi resetler.

SPL (Secondary Program Loader) / MLO (Memory Loader)

RBL, SPL/MLO’yu ilgili BOOT pinlerine bakarak eMMC/SD/Serial/USB arayüzlerinden birinden alarak internal SRAM’e yazıyor demiştik. SPL/MLO’nun görevleri ise aşağıdaki gibidir.

  • Debug mesajlarını dış dünyaya ulaştırabilmemiz için UART ilklendirmesi yapılır.
  • PLL istediğimiz değere tekrardan yapılandırılır.
  • DDR RAM’i kullanabilmek için DDR Memory Registerları ilklendirilir.
  • Pin Muxing işlemleri yapılır. Örneğin board üzerindeki default olarak GPIO olarak tanımlanmış pinleri ADC olarak kullanmak isteyebilirsiniz. Bu durumda kullanıcının pin mux ayarlamaları yapması gerekmektedir.
  • Son olarak da SPL/MLO U-Boot’u MMC0/1 arayüzünden alarak DDR3 RAM’e kopyalar ve süreci U-Boot’a devreder.

U-Boot image header’inda U-Boot image’in hangi adrese yüklenmesi gerektiği belirtilmektedir. SPL/MLO’da U-boot image header’ina bakarak bu bilgiyi parse eder ve image’i MMC arayüzünden alarak DDR3 RAM’in ih_load adresine kopyalanmasını sağlar.

Bu adımda farkettiysek U-boot image internal SRAM’e yüklenmek yerine DDR3 RAM’e yüklendi. Bunun nedeni ise internal SRAM’de U-boot image için yeterli bir alan bulunmamasıdır. Bu yüzden de U-boot image DDR3 RAM’e yüklenmektedir.

Bu noktada genelde söyle bir soru işareti de oluşabilmektedir. RBL neden direk U-Boot’u DDR RAM’e yüklemiyor da önce SPL/MLO yüklüyor ondan sonra SPL/MLO üzerinden bu işlem gerçekleşiyor?

Bunun nedeni ise ROM kodunun board üzerinde hangi DDR RAM’in kullanıldığını bilmemesidir. Yani board üzerindeki RAM üreticisine göre DDR tuning parametreleri (speed, bandwidth, clock…) değişebileceği için ilgili parametreler SPL/MLO tarafından konfigüre edilerek ilklendirilir bu sayede de SPL/MLO DDR RAM’e ulaşabilir ve U-boot image’ini buraya kopyalayabilir. Örneğin BBB üzerinde Kingston DDR3 RAM’i bulunmaktadır. Bunun yerine başka bir firmanın RAM’i kullanılsaydı seçilen yeni RAM’e göre parametreler güncellenip SPL/MLO tekrardan derlenip yüklenmesi gerekirdi.

U-boot

SPL/MLO’nun U-boot image’ini DDR3 RAM’e yüklemesinin ardından artık boot görevini U-boot devralmaktadır. U-boot’un bu noktadaki görevlerini aşağıdaki gibi sıralayabiliriz.

  • I2C, NAND/NOR Flash, Ethernet, USB, UART, MMC arayüzlerinin ilklendirilmesi yapılmaktadır çünkü U-boot bu arayüzlerin hepsinden kernel’in yüklenmesine destek vermektedir.
  • Linux Kernel image’ini yukarıdaki belirtilen boot kaynaklarından birinden alıp DDR3 RAM’e yüklemektedir.
  • Boot argümanlarını kernel’e iletmektedir.

U-boot çevresel değişkenlerini uEnv.txt adındaki bir dosyadan almaktadır. Bu dosya içerisinde aşağıdaki değişkenlerin ayarlanmaları yapılabilmektedir.

uEnv.txt ile Kernel Image ve Device Tree Binary dosyasının nereden çekileceği ve hangi memory adresine yazılacağını belirtiyoruz. Ayrıca console ayarlamarını da bu dosya üzerinden yapabiliyoruz. Örnek dosyada olmayan ancak ek olarak yazılabilecek bir çok parametre olabilir. Örneğin bu dosya üzerinden scriptler çalıştırılabilir. Mesala SD karttan boot ettiğimiz Linux image’ini board üzerindeki eMMC Flash’a yazacak bir script’in buradan çağrılması mümkündür.

Linux Kernel

Bu aşamada U-boot kontrolü artık Linux Kernel içerisinde yer alan Bootstrap Loader’a bırakır. Bootstrap Loader, Kernel’i decompress eder. Kernel decompress işlemi bittikten sonra kontrol Bootstrap Loader’dan tamamen Linux Kernel’a bırakılır. Linux Kernel’in uncompress edilmesi ve yeniden yerleştirilmesi U-boot’un sorumluluğunda değildir. Bu yüzden Bootstrap Loader bu görevi üstlenir.

Bu aşamada gerçekleştirilen adımlar aşağıdaki gibidir.

  • CPU spesifik ilklendirmeler yapılır
  • İşlemci mimarisi kontrol edilir.
  • Page tablosu ilklendirilir.
  • İşlemci mimarisine göre MMU (Memory Management Unit) ilklendirilir ve hazır hale getirilir.
  • Virtual Memory desteği sağlanması için MMU çalışır hale getirilir.
  • start_kernel fonksiyonu çağrılır.

start_kernel fonksiyonuna kadar yapılan tüm işlemler mimariye bağımlıydı (Architecture Dependent). start_kernel ile birlikte mimariden bağımsız bir sürece girilmiştir.

Kernel kaynak kodlarından bakarsak start_kernel fonksiyonu içerisinde onlarca ilklendirme fonksiyonu bulunmaktadır. Bu ilklendirmeler zinciri sonrasında da arch_call_rest_init() fonksiyonu çağrılmaktadır.

Yukarıdaki kaynak kodunu incelersek kernel_init adında pid değeri 1 olan bir kernel thread yaratılır. Bu thread de ilk user application thread’i çalıştırmaktadır. Diğer kernel thread’ler için de kthreadd adında pid değeri 2 olan bir kernel thread yaratılır.

kernel_init fonksiyonunu incelersek eğer init aşamasında kullanılan bellek alanları burada free edilmektedir. En sonda da ilk user application programı çağıran bir if yapısı bulunmaktadır. Burada C’deki lojik operatörlerin kısa devre özelliği kullanılmıştır. Yani /sbin/init process edilebilirse diğerleri denenmeden fonksiyon return edecektir. Bu yüzden de sırasıyla hangisi başarılı olursa o process ile user application başlayacaktır. En sondaki /bin/sh shell uygulamasını çağırmaktadır. Eğer bunların hiçbiri başarılı olmaz ise de kernel panic durumu oluşturulacaktır.

BBB üzerinde dummy bir process oluşturup daha sonra bütün processleri aşağıdaki gibi listeyebiliriz. PID (Process Identifier), PPID (Parent Process Identifier) anlamına gelmektedir.

gcc dummy_process.c -o dummy_process.out./dummy_process.outpid of main process: 1321

Yukarıdaki process listesinden incelersek PID 1321 numarasına sahip process’in dummy_process.out olduğu gözükmektedir. Ve parent processleri sırasıyla bash, ssh processleri ile birlikte en son PID 1'e kadar gitmektedir. Yani oluşturduğumuz user app’in ata parent process’i 1 yani yine listeden bakarsak PID’si 1 olan /sbin/init gözükmektedir.

Aynı şekilde yine dikkat edersek önceden de bahsettiğim gibi kthreadd kernel thread’in PID’sinin 2 olduğunu ilk satırdan gözlemleyebilirsiniz. Sistemde oluşan diğer kernel thread’lerin hepsinin parent process’i PID 2 kthreadd’dir.

Bu yazı içeriği hakkında daha detaylı bilgiler edinmek için referanslarda gösterdiğim eğitimi ve dokümanları takip edebilirsiniz.

Diğer yazılarımda görüşmek üzere…

Referanslar ve Faydalı Kaynaklar

--

--

No responses yet