8

FreeRTOS Notları #5: Semaphore

    Semaphore konusuna başlamadan önce FreeRTOS‘da kesme(interrupt) yönetimine değinmek istiyorum.
    Gömülü gerçek zamanlı sistemler ortamdan kaynaklanan olaylara(events) göre belli aksiyonlar(actions) almak zorundadır. Önemsiz olmayan sistemlerin farklı işlem ve cevap süreleri gerektiren birden fazla kaynaktan gelen olaylara hizmet etmesi gerekecektir. Her durumda en iyi olay işleme(event processing) stratejisi hakkında karar verilmelidir.
  1.  Olay(event) nasıl algılanmalı? Kesmeler normal olarak kullanılır ancak input’lar polling ile algılanır.
  2. Kesmeler kullanıldığında ISR(Interrupt Service Routine) içerisinde ne kadar işlem yapılmalı? Genelde her ISR in olabildiğince kısa tutulması tavsiye edilir.
  3. Olaylar ana kod(ISR içinde olmayan) ile nasıl iletişime geçirilmeli?
    FreeRTOS , geliştiriciye olayların işlenme stratejisi ile ilgili herhangi bir yük yüklemez ancak seçilen stratejinin basit ve sürdürülebilir şekilde kullanılmasına izin veren özellikler sağlar.
    Bir taskın önceliği ile bir kesmenin önceliği arasında seçim yapmak önemlidir.
  • Task, FreeRTOS un üzerinde çalıştığı donanım ile ilgisi olmayan bir yazılım özelliğidir. Bir taskın önceliği yazılımcı tarafından atanır ve buna göre scheduler hangi taskın running state de bulunacağına karar verir.
  • ISR bir donanım özelliğidir çünkü hangi ISR’in çalışacağını ve ne zaman çalışacağını donanım kontrol eder. Tasklar sadece ISR’in yürütülmediği zamanlarda yürütülür. Bu nedenle en düşük öncelikli ISR en yüksek öncelikli taskı kesintiye uğratabilir.

FreeRTOS API Fonksiyonlarının Bir ISR İçerisinden Kullanılması

Interrupt Safe API

    Bazen FreeRTOS API fonksiyonlarını ISR içerisinde kullanmak gerekebilir fakat çoğu FreeRTOS API fonksiyonları ISR içerisinde geçerli olmayan işlemler gerçekleştirir. Örneğin; bir taskı bloklamak. FreeRTOS bu problemi aynı API fonksiyonunun farklı bir versiyonunu sağlayarak çözer. Aynı işlemi yapan bir versiyon task tarafından, diğer versiyonu ISR tarafından kullanılabilir. ISR içerisinden kullanılacak fonksiyonun sonunda “fromISR” bulunur. Bu duruma “interrupt-safe” adı verilir. Kesme içerisinde kullanılmak üzere bir API’ye sahip olmak , ISR fonksiyonunun daha verimli ve kesmeye girişin daha kolay olmasını sağlar.

Semaphore

    FreeRTOS’da semaphore‘lar karşılıklı dışlama(mutual exclusion) ve senkronizasyon amacı ile kullanılır. Semaphore bir taskı bloklayabilir. Bu bloğu sadece farklı bir task veya ISR kaldırabilir. FreeRTOS da iki tip semaphore vardır. Bunlar;
  • Binary Semaphore
  • Counting Semaphore

Binary Semaphore

    Binary semaphore ve mutex birbirine çok benzer ama bazı ince farklılıkları vardır. Mutex’ler bir öncelik kalıtım mekanizması içerirken, binary semaphore içermez.  Bu binary semaphore’u tasklar veya task-interrupt arasındaki senkronizasyon için iyi bir seçim yapar.
    Semaphore API fonksiyonları bir blok süresi belirtmeye izin verir. Blok süresi xSemaphoreTake() fonksiyonu ile semaphore alınamıyorsa maksimum tick cinsinden bloklanmış durumda ne kadar bekleneceğidir. Eğer birden fazla task aynı semaphore ile bloklanmış ise semaphore xSemaphoreGive() fonksiyonu ile bırakıldığında ilk çalışacak task önceliği en yüksek olandır.
    Binary semaphore’u ,tek eleman tutan bir kuyruk(queue) gibi düşünebiliriz. Bloklanmış durumdaki tasklar bu kuyrukta ne olduğuyla değil ,dolu mu yoksa boş mu olduğuyla ilgilenecektir. Böyle bir mekanizma ile task bir interrupt ile senkronize bir şekilde çalışabilir.
    Bir çevrebirimini kullanan bir task düşünün. Polling metodu ile çevrebiriminin durumunu kontrol etmek CPU nun boşa çalışması ve diğer taskların daha az çalışması anlamına gelir. Bu nedenle taskların zamanın çoğunu bloklanmış durumda geçirmesi ve sadece ihtiyaç duyulduğunda yürütülmesi tercih edilir. Bu durum semaphore’un task tarafından alınması(take) ile sağlanabilir ve istenilen bir ISR fonksiyonu veya başka bir task fonksiyonu içerisinden semaphore’un serbest bırakılarak(give) bloklanmış durumdaki taskın kaldığı yerden çalışması sağlanabilir.

xSemaphoreCreateBinary() Fonksiyonu

    FreeRTOS’da binary semaphore oluşturmak için xSemaphoreCreateBinary() API fonksiyonu kullanılır.

    Görüldüğü gibi bu fonksiyon bir parametre almaz. Bu fonksiyon eğer semaphore oluşturmada bir hata oluştu ise(bellekten kaynaklı) NULL değeri döndürür. Başarılı bir şekilde semaphore oluşturuldu ise SemaphoreHandle_t tipinde bir handle döndürür.

xSemaphoreTake() Fonksiyonu

    Semaphore’u almak için ise xSemaphoreTake() fonksiyonu kullanılır. Binary semaphore sadece bir kez alınabilir. Recursive mutex hariç bütün FreeRTOS semaphore tipleri bu fonksiyon ile semaphore’u alır. Bu fonksiyon interrupt fonksiyonu içerisinde kullanılmamalıdır(çünkü bir kesme bloklanamaz).

  • xSemaphore: Alınacak olan semaphore’un girildiği parametredir. Birden fazla semaphore olabilir. Bu parametre xSemaphoreCreateBinary() fonksiyonunun döndürdüğü değeri alır.
  • xTicksToWait:  Eğer semaphore verilmemiş ise ne kadar süre ile blocked state de kalacağının belirlendiği parametredir. portMAX_DELAY ile süresiz olarak bloklanabilir.( FreeRTOSConfig.h dosyasında INCLUDE_vTaskSuspended makrosu 1 olarak tanımlı ise)
  • Return Değeri: Semaphore alındı ise pdTRUE, semaphore elde edilemediyse ve blok süresi dolduysa pdFALSE değerini döndürür.

xSemaphoreGive() ve xSemaphoreGiveFromISR() Fonksiyonu

    Semaphore vermek veya bırakmak için xSemaphoreGive() fonksiyonu kullanılır. Bu fonksiyonun aldığı tek parametre verilecek olan semaphore’un handle’ıdır.

    Eğer interrupt fonksiyonu içerisinden bir semaphore bırakılacak ise xSemaphoreGiveFromISR() API fonksiyonu kullanılmalıdır. Bu fonksiyon xSemaphoreGive() fonksiyonunun interrupt-safe versiyonudur.

  • xSemaphore: Bırakılacak olan semaphore handle’ı
  • *pxHigherPriorityTaskWoken: Bu parametreyi anlamak biraz zor. Eğer xSemaphoreGiveFromISR() fonksiyonu bu parametreyi pdTRUE olarak ayarlarsa semaphore’u alan taskın önceliği, o anda çalışan taskın(kesme ile kesintiye uğrayan task) önceliğinden yüksek demektir. Eğer böyle bir durum var ise ISR fonksiyonundan çıkmadan FreeRTOS’u  “context switch” denen bir işleme zorlamak gerekir. Context switching scheduler’ın tasklar arasında yaptığı geçiştir. Bu geçişi ise ISR fonksiyonunun sonunda portYIELD_FROM_ISR( xHigherPriorityTaskWoken ) şeklinde bir kod kullanarak yapabiliriz.
  • Return Değeri: Eğer semaphore verildi(give) ise pdPASS, semaphore zaten mevcut ise pdFAIL değeri döndürür.

Counting Semaphore

    Counting Semaphore’da bir semaphore her bırakıldığında yani xSemaphoreGive() fonksiyonu her çağrıldığında semaphore’un sayaç değeri artar. Herhangi bir task semaphore’u xSemaphoreTake() fonksiyonu ile almak istediğinde sayaç değeri azalır. Eğer sayaç değeri sıfıra ulaşmış ise task bloklanabilir. Counting semaphore’u kullanabilmek için FreeRTOSConfig.h dosyasındaki configUSE_COUNTING_SEMAPHORES  makrosu 1 olarak ayarlanmalıdır.
    Binary semaphore’u tek elamanlı bir kuyruk gibi düşünmüştük. Counting semaphore ise birden fazla eleman tutan bir kuyruk olarak düşünülebilir. Bu durumda task’lar kuyruğun içindeki veriler ile değil kuyruğun boş olup olmaması ile ilgilenecektir. Her xSemaphoreGive() fonksiyonu çağrıldığında kuyruğa bir değer eklenmiş gibi düşünebiliriz. xSemaphoreTake() fonksiyonu ise bu kuyruktaki verileri azaltacak ve en sonunda kuyrukta eleman kalmayınca taskı bloklayacakmış gibi düşünebiliriz.

xSemaphoreCreateCounting() Fonksiyonu

    Counting semaphore oluşturmak için xSemaphoreCreateCounting fonksiyonu kullanılır.

  • uxMaxCount: Maksimum count değerini ifade eder. Semaphore bu değere ulaştığında xSemaphoreGive() fonksiyonu ile daha fazla semafor verilemez.
  • uxInitialCount: Başlangıçtaki count değerinin giridiği parametredir.
  • Return Değeri: Semaphore oluşturulamadı ise NULL,oluşturuldu ise bir handle döndürür.

FreeRTOS’daki semaphore ile ilgili diğer fonksiyonlar hakkında daha fazla bilgi için buraya tıklayabilirsiniz.

FreeRTOS Semaphore Kullanımına Ait Örnek

    Bu örnekte bir butona bastığımızda bir taskın çalışmasını sağlayacak bir uygulama yapalım. Burada butonu dış kesme(external interrupt) olarak kullanalım ve semaphore’un ISR içerisinden kullanımını görelim.
    Semaphore kullanabilmek için “semaphr.h” isimli kütüphaneyi dahil ediyoruz.

    Bu örnekte sadece binary semahore kullanılmıştır. Binary semaphore için bir handle aşağıdaki gibi oluşturulmuştur.

    xSemaphoreCreateBinary() fonksiyonu ile binary semaphore oluşturulur ve fonksiyonun döndürdüğü değer tanımlanan semaphore handle’na atanır.

    İki adet task aşağıdaki gibi oluşturulmuştur. Burada kesme tarafından tetiklenecek taskın(HandlerTask) önceliği diğerine göre yüksek seçilmiştir.

    Task ve ISR fonksiyonları aşağıdaki gibidir. Burada Task1 periyodik olarak her saniyede bir UART üzerinden string göndermektedir. Handler task ise başlangıçta çalışacak xSemaphoreTake() fonksiyonunun kullanıldığı satıra geldiğinde task süresiz olarak bloklanacaktır.

Önemli Düzeltme: Eğer bir kesme içerisinden interrupt-safe API fonksiyonlarını çağıracak isek o kesmenin önceliğine dikkat etmemiz gerekir. FreeRTOSConfig.h dosyası içerisinde aşağıdaki gibi tanımlanmış bir makro vardır.

    Kesme önceliğini ayarlar iken configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY  makrosuna göre ayarlamamız gerekir. Kesme önceliği atanırken 5 değerinden büyük eşit olmak zorundadır. ARM işlemcilerde öncelik numaraları ile önceliğin ters orantılı olduğunu unutmayalım. Yani öncelik numarası 4 olan  kesme öncelik numarası 5 olan kesmeden daha önceliklidir. FreeRTOS çekirdeği de arkaplanda kesmeler kullandığı için ve bu kesmeleri kesintiye uğratmamak için kendi kullanacağımız kesmelerin önceliğini beş veya beşten büyük tanımlamamız gerekir. Bu yüzden dış kesmenin önceliği 5 olarak aşağıdaki gibi tanımlanmıştır.

    FreeRTOS içeren bir projede eğer kesme içerisinde herhangi bir “fromISR” ile biten fonksiyon çağırmayacak iseniz bu makroya dikkat etmenize gerek yoktur. İstediğiniz önceliği verebilirsiniz.
    Sonuç olarak bu örnekte STM32F103 ün A0 pinine bir buton bağlanmıştır. Bu butona basıldığında bir kesme oluşacak ve program EXTI0_IRQHandler() fonksiyonuna dallanacaktır. Programın Bu fonksiyona girdiğini görmek için UART üzerinden başka bir string daha gönderilmiştir.
    Burada portYIELD_FROM_ISR() fonksiyonu xHigherPriorityTaskWoken değişkeni eğer pdTRUE ise kernel’ı context switching’e zorlayacaktır. Burası biraz karmaşık gelebilir. Kesme oluşmadan hemen önce Task1 çalışıyordu ve kesme oluştuktan sonra ISR fonksiyonu içerisinde context switching yani kernel’ın sıradaki taskı çalıştırmasını istemeseydik program ISR fonksiyonundan çıkıp Task1 içerisinde nerede kaldıysa yürütmeye devam edecekti. Eğer xHigherPriorityTaskWoken değeri xSemaphoreGiveFromISR() fonksiyonu tarafından pdTRUE olarak ayarlanmış ise sırada bekleyen daha yüksek öncelikli bir task var demektir. Bu yüzden portYIELD_FROM_ISR(xHigherPriorityTaskWoken); satırı programın Task1 içerisine değil sırada bekleyen daha yüksek öncelikli olan task(HandlerTask)  içerisine gitmesini sağlayacaktır.

FreeRTOS ile Semaphore Kullanımı Tüm Kodlar

CMSIS-RTOS ile Semaphore Örneği

osSemaphoreDef() Makrosu

    osSemaphoreDef() “cmsis_os.h” dosyası içerisine tanımlanmış aşağıdaki gibi bir makrodur. Parametre olarak sadece semaphore’a verilecek olan ismi alır.

osSemaphoreCreate() Fonskiyonu

    CMSIS-RTOS ile semaphore oluşturmak için osSemaphoreCreate() fonksiyonu kullanılır.

  • *semaphore_def:  osSemaphoreDef() makrosu ile tanımlanmış olan semaphore isminin girildiği parametredir. Bu parametre osSemaphore() referansı ile girilmelidir.
  • count: Buradaki count parametresi semaphore’un binary mi yoksa counting mi olacağını belirler. Eğer bu parametre 1 girilirse binary semaphore, 1 den fazla bir değer girilirse counting semaphore olarak tanımlanır.
  • Return Değeri: Hata oluştu ise NULL, oluşmadı ise semaphore handle’ı döndürür.

    osSemaphoreCreate() fonksiyonu yukarıdaki gibi kullanılabilir. Bu fonksiyonu kullanmadan önce osSemaphoreId tipinde bir handle tanımlanmalıdır.

osSemaphoreWait() Fonskiyonu

    Semaphore’u almak için bu fonksiyon kullanılır. Yani FreeRTOS daki xSemaphoreTake() fonksiyonunun CMSIS-RTOS daki karşılığıdır.

  • semaphore_id: Alınacak olan semaphore handle’ı.
  • millisec: Semaphore yoksa ne kadar süre bekleneceği bu parametre ile belirlenir. Bu parametre yerine osWaitForever yazılırsa süresiz olarak beklenir.
  • Return Değeri : RTOS’da token( jeton) diye bir kavram vardır. Bu token sayısı kuyruktaki eleman sayısı gibi düşünülebilir. Bu fonksiyon kuyrukta bekleyen token yani jetonların sayısını döndürür. Counting semaphore kullanırken bu parametre kullanışlı olabilir. Eğer kuyrukta token kalmamışsa ve “millisec” parametresi osWaitForever dan farklı bir şey girilmiş ise bu süre sonunda -1 değeri döndürür. Bu demektir ki blok süresi dolmuş ve token yok.

osSemaphoreRelease() Fonskiyonu

    Bu fonksiyon semaphore’u bırakmak için kullanılır. CMSIS-RTOS’da bu fonksiyonun interrupt içinde de kullanılabilir. CMSIS-RTOS’da iki farklı versiyon fonksiyon oluşturmak yerine bu işlem tek fonskiyon ile yapılmış. Bu fonksiyon nereden çağrıldığını(interrupt veya task) biliyor. İlginç değil mi? Hatta eğer interrupt içinden çağrılmış ise context switching’i otomatik olarak yapıyor.

  • semaphore_id: Semaphore handle
  • Return Değeri: Eğer fonksiyon başarılı bir şekilde çalışmış ise osOK, hata oluşmuş ise osErrorOS değerini döndürür.
    Task ve ISR fonksiyonları aşağıdaki gibidir. FreeRTOS örneğindeki aynı işlemleri yapmaktadırlar.

    CMSIS-RTOS’da osSemaphoreWait() fonksiyonu ilk seferde semaphore’u almadı. Aşağıda da görüldüğü gibi program çalışır çalışmaz ilk olarak HandlerTask çalıştı. Bu bir sorun mu yoksa CMSIS-RTOS’da böyle mi çalışıyor bilmiyorum. Bunun önüne geçmek için osSemaphoreWait() fonksiyonu task içerisinde for döngüsünden önce de bir kere çağrılabilir. Daha sonra normal olarak taskı bloklayacaktır.

CubeMX Ayarları

 
    Yukarıda görüldüğü gibi HandlerTask ‘ın önceliği Task1’in önceliğinden daha yüksektir.
    CubeMX de her ne kadar binary ve counting semaphore için ayrı ayrı kutucuklar olsa da. CMSIS-RTOS’un her iki semaphore’u oluşturmak için aynı fonksiyonu kullandığını gördük.

CMSIS-RTOS ile Semaphore Tüm Kodlar

Kaynaklar

Mehmet Topuz

8 Comments

  1. Selamlar,
    STm32 cube ıde de freertos cimsis V2 kullanarak bir uygulama gerçeklemek istiyorum.
    Uygulamada aynı priority e sahip tasklar var. yeni oluşturacağım task da aynı önceliğe sahip olacak. bu task içerisinde bir sensörden kesme alıp bu kesmeye göre task ın bazı işlemleri gerçeklemesini istiyorum. semaphore kullanmayı denedim fakat başarılı olamadım. diğer task işlemlerine sürekli devam ederken bu yeni oluşturduğum task a dallanma yapmıyor. (preemptive scheduling.) bu kesme geldiğinde diğer task çalışmasını durdurması gerek. o yüzden hepsi aynı priority olarak ayarlandı. fakat kesme task ına geçiş yapamıyor. semaphore işe yaramadı. Nedeni ne olabilir? Fikir verebilir misiniz?

    • Merhaba Furkan
      Tam olarak sebebini bilmiyorum ama birkaç fikrim var. Kesmede kullandığın semaphore fonksiyonu interrupt safe versiyon olan olmayabilir. Ayarladığın kesme donanımsal bir kesme ise önceliği freertos’un kullandığı bir kesmeden düşük olabilir. Task’ın stack’i yetmiyor olabilir. Eğer task semaphore take fonksiyonunu çağırmadan kesme içinde semaphore release yapıyor olabilirsin. Şimdilik aklıma gelenler bunlar Furkan. Umarım yardımcı olabilmişimdir.

  2. Hocam merhaba, “fromISR” fonksiyonunu ben timer içinde kullanıyorum, bir veriyi sürekli kontrol ediyorum ve eğer gerekiyorsa task’a dallanıyor. Bazen sistemin çalışması duruyor heap size değerleri de yeterli seviyede. Yazınızda kesme önceliğinden bahsetmişsiniz bende değerler şu şekilde;
    /*Timer kesme önceliği*/
    NVIC_InitStructure.NVIC_IRQChannelPriority = 0;
    (Değer aralığı 0 ile 3 arasında)

    /* Freertos config */
    #define configPRIO_BITS 5
    #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
    #define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 – configPRIO_BITS) )

    Kullandığım işlemci Stm32f0 serisinden sizde configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 iken bende daha yüksek bundan kaynaklı donmalar oluyor olabilir mi?

    • Merhaba, yorumunu yeni gördüm.
      Bu donmalar bazen oluyorsa sorunun öncelikten kaynaklandığını düşünmüyorum. Heap size yeterli olup olmadığını neye göre karar verdin. Taskın içinde yaptığın fonksiyon çağrısına göre bile ayırdığın heap taşabilir. Tavisyem vApplicationStackOverflowHook fonksiyonu ile bir taşma olup olmadığını kontrol et. Bu fonksiyon içinde eğer taşma oluyorsa hangi tasktan kaynaklandığını bulabilirsin. Bu fonksiyonu kullanabilmek için FreeRTOS config dosyasında bir makro vardı galiba. O makroyu aktif etmek gerekecektir. Eğer sorun bundan kaynaklanmıyorsa tekrar yorum yaz tartışalım.

      • Merhaba, evet dediğiniz gibi öncelikten kaynaklı değilmiş. Problem tasklar başlamadan önce Timer kesmesi xSemaphoreGiveFromISR komutunu çağırıyor ve ondan sonra sistem donuyormuş:)

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.