ukasol

Blog Zephyr Serisi · 06/07

Kconfig ile proje yapılandırma nasıl çalışır

Kaynak koda dokunmadan proje yapılandırmak: Kconfig değerleri nereden gelir, hangi sırayla birleşir ve atadığınız bir değer neden tutmadı, nasıl bulunur.

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'
  1. Kartın _defconfig dosyası; sahibi kartın bakımını yapan kişi.
  2. prj.conf; uygulamanızın ana yapılandırması.
  3. Uygulamanızdaki boards/<board>.conf, varsa; yalnızca tek bir kart için geçerli değerler için.
  4. EXTRA_CONF_FILE parçaları; debug build’i gibi varyantlar için.
  5. Komut satırındaki -DCONFIG_...=; build bunu üretilen bir extra_kconfig_options.conf dosyası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 bir select: 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.

Sıradaki · Zephyr fundamentals native_sim ile donanım olmadan firmware geliştirme