Device Tree donanımın ne olduğunu anlatıyor; Kconfig hangi kodun derleneceğini. Bir Zephyr projesini yapılandırmak ikisini birden bilmeyi gerektiriyor.
Kconfig de Linux’tan gelen bir konsept. Basitçe anlatmak gerekirse: kaynak kodu
değiştirmeden projenizi yapılandırmanızı sağlayan bir sistem. BLE stack’ini
açmak mı istiyorsunuz? Ağ katmanına ihtiyacınız var mı? Logging seviyesini
ayarlamak mı istiyorsunuz? Bunların hepsini prj.conf dosyasına birkaç satır
yazarak hallediyorsunuz.
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_LOG=y
Bu yazı bir seviye daha derine iniyor: değerler nereden geliyor, hangi sırayla
birleşiyor, arkalarındaki dil neye benziyor ve, en faydalısı, atadığınız bir
değerin neden tutmadığını nasıl bulursunuz. Aşağıdaki her şey Zephyr 4.4.2 ile
çalıştırıldı. Örnek proje
firmware-notes içindeki kconfig klasöründe;
CI onu her push’ta derleyip çalıştırıyor.
Peki arka planda ne oluyor?#
West build çalıştırdığınızda Kconfig sistemi, board’un varsayılan ayarları, SoC
konfigürasyonu ve sizin prj.conf dosyanızı birleştiriyor. Sonuçta
build/zephyr/.config adında bir dosya üretiyor. Bu dosyadan C kodu için
autoconf.h üretiliyor, CMake de aynı CONFIG_* değerlerini okuyup hangi
kaynakların derleneceğine karar veriyor.
İki yarıyı da görmek kolay. Örnek projede autoconf.h sade define’larla
doluyor:
#define CONFIG_APP_GREETING "hello from prj.conf"
#define CONFIG_APP_RETRIES 5
#define CONFIG_APP_VERBOSE 1
#define CONFIG_LOG 1
CMake tarafında ise Zephyr’in GPIO driver’ları böyle seçiliyor;
drivers/gpio/CMakeLists.txt içinde her driver için bir satır:
zephyr_library_sources_ifdef(CONFIG_GPIO_EMUL gpio_emul.c)
zephyr_library_sources_ifdef(CONFIG_GPIO_NRFX gpio_nrfx.c)
zephyr_library_sources_ifdef(CONFIG_GPIO_STM32 gpio_stm32.c)
Yani etkinleştirmediğiniz özellikler binary’ye girmiyor, gereksiz yere flash ve RAM harcamıyorsunuz. Kapalı bir driver yalnızca kullanılmamakla kalmıyor, hiç derlenmiyor.
Değerler nereden geliyor#
Bir değer birkaç yerde atanabiliyor ve sonra gelen kazanıyor. Build bu sırayı
her seferinde, .config dosyasını yazmadan hemen önce basıyor. Örnek projede
dört dosya ve bir komut satırı seçeneği var:
$ west build -b native_sim kconfig -- -DEXTRA_CONF_FILE=verbose.conf
Loaded configuration 'zephyr/boards/native/native_sim/native_sim_defconfig'
Merged configuration 'kconfig/prj.conf'
Merged configuration 'kconfig/boards/native_sim.conf'
Merged configuration 'kconfig/verbose.conf'
Configuration saved to 'build/zephyr/.config'
- Kartın
_defconfigdosyası; sahibi kartın bakımını yapan kişi. prj.conf; uygulamanızın ana yapılandırması.- Uygulamanızdaki
boards/<board>.conf, varsa; yalnızca tek bir kart için geçerli değerler için. EXTRA_CONF_FILEparçaları; debug build’i gibi varyantlar için.- Komut satırındaki
-DCONFIG_...=; build bunu üretilen birextra_kconfig_options.confdosyasına yazıp en son birleştiriyor.
Bu katmanlı yapı, her bare-metal takımının eninde sonunda sorduğu bir sorunun
cevabı: karta özel değerler nereye gidiyor? Bir #ifdef‘e değil. Kartın
overlay’inin yanına, boards/<board>.conf dosyasına.
Kendi seçeneklerinizi tanımlamak#
Kconfig yalnızca Zephyr’in seçenekleri için değil. Bir uygulama kendi
seçeneklerini de tanımlayabiliyor; kökündeki Kconfig adlı bir dosyada,
sonunda Zephyr’in ağacını dahil ederek:
mainmenu "firmware-notes Kconfig example"
menu "Application"
config APP_GREETING
string "Greeting printed at boot"
default "hello from the Kconfig default"
config APP_RETRIES
int "Attempts before giving up"
range 1 10
default 3
config APP_VERBOSE
bool "Log every attempt"
depends on LOG
help
Uses the logging subsystem, so it is only offered when logging is
enabled and cannot be switched on in a build without it.
endmenu
source "Kconfig.zephyr"
Her seçeneğin bir tipi, bir prompt’u, bir varsayılanı ve isteğe bağlı
kısıtları var: sayılar için range, koşullar için depends on. Kod onları
sıradan makrolar olarak kullanıyor:
printk("greeting: %s\n", CONFIG_APP_GREETING);
printk("retries: %d\n", CONFIG_APP_RETRIES);
for (int i = 1; i <= CONFIG_APP_RETRIES; i++) {
if (IS_ENABLED(CONFIG_APP_VERBOSE)) {
LOG_INF("attempt %d", i);
}
}
Boolean’lar için #ifdef yerine IS_ENABLED() tercih edin. İçerideki kod,
seçenek kapalıyken de derleniyor ve tip kontrolünden geçiyor, sonra optimizer
tarafından atılıyor; böylece kapalı bir dal sessizce çürüyemiyor.
Yukarıdaki dört dosyayla her katman çıktıda görünüyor: selamlama
prj.conf‘tan, beş deneme boards/native_sim.conf‘tan, logging de
verbose.conf ile açılmış:
greeting: hello from prj.conf
retries: 5
[00:00:00.000,000] <inf> app: attempt 1
...
[00:00:00.000,000] <inf> app: attempt 5
verbose: on
depends on, select ve build’i durdurmayan uyarı#
Seçeneklerin birbiriyle ilişkisini üç anahtar kelime belirliyor:
depends on, koşulları sağlanana kadar bir seçeneği gizliyor. Bir söz bu: bu seçenek, o seçenek olmadan asla açık olamaz.select, kullanıcının ne atadığına ve o sembolün kendi bağımlılıklarının sağlanıp sağlanmadığına bakmadan başka bir sembolü açmaya zorluyor. Güçlü ve atadığınız bir değerin tutmamasının olağan sebebi.imply, zayıf birselect: diğer sembolü varsayılan olarak açıyor ama kullanıcı yine de kapatabiliyor.
Şimdi hatalar ve gerçekte ürettikleri mesajlar. Yanlış yazılmış bir sembol build’i durduruyor:
typo.conf:1: warning: attempt to assign the value '5' to the undefined symbol APP_RETRYS
error: Aborting due to Kconfig warnings
Aralığın dışındaki bir değer de öyle:
warning: user value 20 on the int symbol APP_RETRIES (defined at kconfig/Kconfig:9) ignored due to being outside the active range ([1, 10]) -- falling back on defaults
error: Aborting due to Kconfig warnings
Ama CONFIG_LOG=y olmadan CONFIG_APP_VERBOSE=y atayın, şunu alırsınız:
warning: APP_VERBOSE (defined at kconfig/Kconfig:14) was assigned the value 'y' but got the value 'n'. Check these unsatisfied dependencies: LOG (=n).
Sonra build devam ediyor ve başarıyla bitiyor; özellik sessizce kapalı. Bu
bilinçli. Zephyr Kconfig uyarılarını hataya çeviriyor, tek bir istisnayla ve
kendi kaynak kodu da bunu söylüyor: atandığından farklı bir değer alan bir
sembol “şimdilik yalnızca uyarı” olarak bırakılıyor. Sembol .config içinde
görünmüyor bile, çünkü bağımlılıkları sağlanmayan bir seçenek hiç yazılmıyor.
İzlenmesi gereken uyarı bu, çünkü build yeşil kalıyor. CI’da build log’u
was assigned the value içerdiğinde job’u başarısız saymaya değer.
select aynı uyarıyı ters yönden üretiyor. native_sim’de printk’yı
CONFIG_PRINTK=n ile kapatmayı deneyin:
warning: PRINTK (defined at subsys/debug/Kconfig:145) was assigned the value 'n' but got the value 'y'.
Bu sefer listelenecek sağlanmamış bir bağımlılık yok; bu yüzden uyarı size nedenini söyleyemiyor. Bir şey PRINTK’yı select etti; neyin ettiğini bir sonraki bölüm nasıl bulacağınızı gösteriyor.
Nedenini bulmak: menuconfig, guiconfig ve traceconfig#
Hangi Kconfig seçeneğini nasıl ayarlayacağınızı bilmiyorsanız şunu çalıştırın:
$ west build -t menuconfig
Terminal tabanlı bir arayüz açılır, / tuşuyla istediğiniz sembolü arayabilir,
bağımlılıklarını görebilirsiniz. Zephyr ile çalışırken bu araç çok işe yarıyor.
west build -t guiconfig aynı şeyin pencereli hâli.
Bir tuzak: menuconfig yalnızca build/zephyr/.config dosyasını değiştiriyor.
Bir sonraki temiz build bu değişiklikleri atıyor. Doğru değerleri bulduktan
sonra onları prj.conf‘a kopyalayın ya da minimal bir yapılandırma kaydetmek
için D‘ye basıp satırları oradan alın.
“Neden” sorusunu cevaplayan araç traceconfig:
$ west build -t traceconfig
build/zephyr/kconfig-trace.md dosyasını yazıyor: her sembolün değerini, o
değeri neden aldığını ve nereden geldiğini gösteren bir tablo. Örnek proje
için, kısaltılmış hâliyle:
| Ad | Değer | Kaynak | Konum |
|---|---|---|---|
CONFIG_APP_GREETING |
“hello from prj.conf” | assigned | prj.conf:1 |
CONFIG_APP_RETRIES |
5 | assigned | boards/native_sim.conf:1 |
CONFIG_APP_VERBOSE |
y | assigned | verbose.conf:2 |
CONFIG_LOG |
y | assigned | verbose.conf:1 |
CONFIG_PRINTK |
y | selected | BOOT_BANNER |
Son satır, yukarıdaki printk bilmecesinin cevabı: boot banner’ı printk’yı
select ediyor, yani banner açıkken printk kapatılamaz. Bu, zephyr.dts içindeki
dosya ve satır yorumlarının Kconfig’teki karşılığı ve bir öğleden sonra
boyunca Kconfig dosyalarında arama yapmayı tek bir komuta indiriyor.
Hazır oradayken, west build -t hardenconfig yapılandırmanızı Zephyr’in
güvenlik sıkılaştırma önerileriyle karşılaştırıyor ve her seçenek için bir
satır basıyor:
| Name | Current | Recommended | Check result |
| CONFIG_BOOT_BANNER | y | n | FAIL |
| CONFIG_BUILD_OUTPUT_STRIPPED | n | y | FAIL |
Bir geliştirme build’inde satırların çoğu başarısız oluyor ve bu normal. Bir release’ten önce ise faydalı bir kontrol listesi.
FreeRTOSConfig.h ile karşılaştırma#
FreeRTOS’ta bunu tek bir FreeRTOSConfig.h dosyasında #define yazarak
yapıyordunuz. Kconfig ise bunu katmanlı ve bağımlılıkları yöneterek yapıyor. Bir
özelliği açtığınızda select ile bağlı semboller onunla birlikte açılıyor;
depends on koşulu sağlanmayan bir özellik ise hiç açılmıyor ve build bunu
bildiriyor.
Yan yana:
FreeRTOSConfig.h |
Kconfig | |
|---|---|---|
| Geçersiz kombinasyonlar | Derlemede, çalışırken ya da hiç fark edilmiyor | Hiçbir şey derlenmeden kontrol ediliyor |
| Bir seçeneği bulmak | Dokümantasyonu okumak | menuconfig’te yardım metniyle arama |
| Tek bir karta özel değerler | Header içinde #ifdef |
boards/<board>.conf |
| Neyin derleneceği | Build dosyalarında elle | CMake aynı sembolleri okuyor |
| Bir değerin neden öyle olduğu | grep | traceconfig dosyayı ve satırı söylüyor |
Bedeli öğrenilecek bir dil ve birkaç dosya daha. Karşılığı ise build’in kontrol edebildiği, açıklayabildiği ve üzerine işlem yapabildiği bir yapılandırma.
Detaylar için Kconfig dokümantasyonuna bakabilirsiniz. Serinin bir sonraki yazısı bunların hepsini hiç donanım olmadan işe koşuyor: native_sim.