Teknomerkez
TR EN ES
Konular
Proje Sayfaları
Görevci Tanıtım İsmail Sahillioğlu 24.11.2023

Görevciye Genel Bakış

Yazar: İsmail Sahillioğlu
Tarih: 25.11.2023
Türkçe


Görevci Tanıtım sayfasında Görevciyi kısaca tanıtmıştım. Bu yazıda, şöyle bir genel bakış yapalım istedim. Görevci nasıl edinilebilir, APIleri (Application Programming Interface, Uygulama Programlama Arayüzü) nelerdir, bir görevin yapısı kodda neye benzer gibi soruları kısaca yanıtlamaya çalışacağım. Hadi başlayalım.

Görevciyi Edinme

Görevci farklı yöntemlerle uygulamanıza tümleştirilebilir.

  • Kaynak kodunu Github reposundan clon edip uygulamanın proje ağacına eklemek.
  • Uygulamanızda git sürümleme kullanıyorsanız bir git alt modülü (submodule) olarak eklemek.

Yakında CMake desteği eklenmesi de planlanmaktadır.

Görevciyi API’leri

Bir görevin bir olay beklemesi ya da gecikmesi gerekiyorsa CPU’yu boşuna meşgul etmemek, gerektiğinde beklemesi gereken görevi bloklayıp çalışmaya hazır başka bir görevin çalışmasını sağlamak Görevcinin temel amaçlarındandır. Ancak bu mekanizmanın düzgün bir şekilde işlemesi için görev bloğunun ana döngüsü içerisinde return, continue ve break gibi döngü kontrol komutları kesinlikle kullanılmamalıdır. Böyle yapmak görevin düzgün ve beklendiği gibi çalışmamasına neden olur. Bunun yerine aşağıdaki örnek durumlara uygun düşen API’ler kullanılmalıdır. Bu durumu imgeleyen örnek bir kod vermek gerekirse:

char gorev(gorevTutucu_t tutucu) {
    
    static char karakter;
    
    grvBASLA(tutucu);

    /* Bu döngü bir görev bloğunun ana döngüsüdür. for(;;) biçiminde de
     * yazılabilir. Burada sonsuz döngüde kalmalı, kesinlikle break veya
     * return ile döngüden çıkılmamalıdır.
     */
    while(1) {
        // break komutu ana döngüyü kıracak ve; elle yeniden çalıştırılmadığı
        // sürece görevi sonlandıracaktır.
        break;
        
        // continue olası Görevci API'lerinin görmezden gelinerek döngünün başa 
        // dönmesine neden olacağından beklenmeyen davranışlara neden olacaktır.
        continue;
        
        // return, değer verilmeden yazılırsa derleme hatasına neden olur.
        // Bir değer ile kullanılırsa yine görevin çıkmasına neden olurken, 
        // göreve bir dahaki girişte görevin beklendiği gibi davranmamasına 
        // neden olabilir.
        return; // derleme hatası!
        return 1; // Görevden çıkar ve kestirilemez davranışlara yol açabilir.
    }

    grvBITIR(tutucu);
}

Nitekim ana döngü içinde kullanılan alt döngülerde continue ve break komutları dikkatlice kullanılabilir:

char gorev(gorevTutucu_t tutucu) {
    
    static char karakter;
    
    grvBASLA(tutucu);

    /* Ana döngü  */
    while(1) {
    
        // Bir alt döngü
        for(uint8_t i = 0; i < 5; i++) {
            // break, alt döngüler için kullanılabilir, bu, ana döngüyü etkilemez.
            if(a < i) break;
        }
        
        // Veya...
        while(a != b) {
            ...
            ...
            // Bu durum da ana döngüyü etkilemeyeceği için sorun yaratmaz.
            if(c == '0') continue;
            ...
        }
    }

    grvBITIR(tutucu);
}

Bir diğer önemli konu, görev blokları içinde kullanılan değişkenlerdir. Görevci, yapısı gereği dinamik bellek yönetimi ve içerik değiştirme (context-switching) kullanmamaktadır. Görevcinin küçük ölçekli birçok aygıtta çalışabilmesini sağlayan en önemli özelliği az önce bahsedilen, bellek ve çalışma zamanı açısından küçük aygıtlar için çok maliyetli olan bu mekanizmaları kullanmamasıdır. Ancak görev blokları özünde birer işlev (function) olduğundan, görevler bloklandığında değişkenler normalde derleyici optimizasyonundan kaçamaz ve tuttukları verileri yitirirler. Bunu önlemek için değerini koruması gereken değişkenler tanımlanırken static niteleyicisi ile tanımlanmalıdır. Bu niteleyici derleyiciye ilgili değişkeni işlevden dönerken yok etmemesini, işlev bir dahaki sefer çağrıldığında bu değişkenin içeriğinin kullanılacağını bildirir.

Bir görevde gecikme yapılmak isteniyorsa

  • grvGECIK_MS() - işletim sistemlerinde sleep() işlevlerine benzer
  • grvKOSULLU_GECIK_MS() - işletim sistemlerinde sleep() işlevlerine benzer

Bir koşul veya olayın beklenmesi isteniyorsa

  • grvKOSUL_BEKLE()
  • grvBU_KOSULDA_BEKLE()

Verilere erişimde senkronizasyon gerekiyorsa

  • grvBAYRAK_BEKLE() - işletim sistemlerindeki wait() işlevlerine benzer
  • grvBAYRAK_IMLE() - işletim sistemlerindeki signal() işlevlerine benzer

CPU kontrolünden vazgeçmek gerekiyorsa

Bazen bir görev kendi isteğiyle kontrolü çalışmak için bekleyen başka bir göreve vermek isteyebilir. Böyle bir durumda:

  • grvVAZGEC() - işletim sistemlerindeki yield() işlevlerine benzer
  • grvKOSULA_DEK_VAZGEC()

Bu API’lerin herbirinin örnek kullanımları için birer yazı yazacağım, takipte kalın.

Görevlerin Temel Yapısı

Bir görevin normal bir C işlevinden pek bir farkı yoktur, yalnızca biraz daha yapılandırılmıştır ve sürekli çalışması gereken görevler içlerinde bir sonsuz döngü içerir. Bir görevin temel yapısı şu şekilde olmalıdır:

char gorev(gorevTutucu_t tutucu) {
    /* Görev kapsamında (scope) kullanılacak değişkenler burada
     * tanımlanabilir. Değerini koruması gereken değişkenler "static"
     * niteleyicisiyle tanımlanmalıdır.
     */
    static char karakter;

    /* Bir görev ana görev döngüsünden hemen önce her zaman görev yapısına
     * başvuru olarak parametre alan grvBASLA() ile başlamalıdır.
     */
    grvBASLA(tutucu);

    /* Buraya bir kereye mahsus çalışacak kodlar. Örneğin bir giriş - çıkış
     * portunu ilkleme veya bir analog ucunu ilkleme kodları gibi.
     * Buradaki kodlar görevin yaşam süresi boyunca yalnızca bir kez
     * çalışacağı için ilklendirme işlemlerini yapmak için idealdir.
     */

    /* Bu döngü bir görev bloğunun ana döngüsüdür. for(;;) biçiminde de
     * yazılabilir. Burada sonsuz döngüde kalmalı, kesinlikle break veya
     * return ile döngüden çıkılmamalıdır.
     */
    while(1) {
        // Buraya görev kodları ve bloklayıcı API çağrıları
    }

    /* Tüm görev işlevleri görev yapısına başvuru olarak parametre alan
     * grvBITIR() ile sonlanmalıdır. Akış sonsuz döngüden buraya buraya
     * gelmesi görevin bir daha çalışmamasına neden olabilir.
     */
    grvBITIR(tutucu);
}

Pekala, yazının girişindeki soruları kısaca yanıtlamaya çalıştım. Sonraki yazılarda Görevci API’lerinin kullanımlarını örneklendirerek, onların nasıl yerine uygun bir şekilde kullanılabileceğini betimleyeceğim. Sonrakinde görüşmek üzere, herkese mutlu kodlamalar.

İlgili yazılar
İsmail Sahillioğlu 24.11.2023