TEKNİK YAZI / Backend Sistemleri

Sistem Programlama Nedir?

Sistem programlamanın süreç, bellek, dosya tanıtıcısı ve işletim sistemi API'leriyle ilişkisini; güvenli bir C başlangıcı üzerinden öğrenin.

Sistem programlama, uygulama ile işletim sistemi veya donanım arasındaki sınırda çalışan yazılımları geliştirir. Süreçler, bellek, dosya sistemleri, ağ soketleri, aygıtlar ve eşzamanlılık bu alanın temel konularıdır. C sık kullanılan dillerden biridir; fakat sistem programlama yalnızca C sözdizimi öğrenmekten ibaret değildir.

Uygulama programlamadan farkı

Yüksek seviyeli bir uygulamada runtime birçok ayrıntıyı yönetebilir. Sistem katmanında ise şu sorular doğrudan görünür hâle gelir:

Bu nedenle doğruluk kadar hata kodu, kaynak yaşam döngüsü ve sınır kontrolü de tasarımın parçasıdır.

C geliştirme ortamı

Debian/Ubuntu tabanlı bir ortamda derleyici ve temel araçlar şu paketle kurulabilir:

sudo apt update
sudo apt install build-essential
gcc --version

İlk program:

#include <stdio.h>

int main(void) {
    puts("Merhaba, sistem programlama");
    return 0;
}

Uyarıları açarak derleyin:

gcc -std=c17 -Wall -Wextra -Wpedantic -O2 main.c -o main
./main

Uyarıları kapatmak yerine nedenini anlayın. Derleyici uyarısı her zaman hata değildir; ancak tür dönüşümü, başlatılmamış değer veya yanlış format belirteci gibi gerçek kusurları erken gösterebilir.

Sistem çağrısı ve kütüphane fonksiyonu

C standart kütüphanesi fopen ve printf gibi taşınabilir yüzeyler sunar. POSIX sistem çağrıları ve ilgili wrapper’lar ise open, read, write, fork ve exec gibi işletim sistemi kaynaklarına daha doğrudan erişir.

Basit bir dosya okuma örneği:

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main(void) {
    char buffer[4096];
    int fd = open("example.txt", O_RDONLY);

    if (fd == -1) {
        fprintf(stderr, "open: %s\n", strerror(errno));
        return 1;
    }

    ssize_t count = read(fd, buffer, sizeof buffer);
    if (count == -1) {
        fprintf(stderr, "read: %s\n", strerror(errno));
        close(fd);
        return 1;
    }

    if (write(STDOUT_FILENO, buffer, (size_t)count) == -1) {
        fprintf(stderr, "write: %s\n", strerror(errno));
        close(fd);
        return 1;
    }

    if (close(fd) == -1) {
        fprintf(stderr, "close: %s\n", strerror(errno));
        return 1;
    }

    return 0;
}

Bu kısa örnekte bile her çağrının başarısız olabileceği, dosya tanıtıcısının kapatılması ve byte sayısının doğru türle taşınması gerekir. Gerçek uygulamada read ve write kısmi sonuç döndürebileceğinden döngü ve EINTR davranışı ayrıca ele alınır.

Bellek ve sınırlar

Dinamik bellek kullanırken tahsis sonucu ve boyut hesabı kontrol edilmelidir:

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

int *allocate_values(size_t count) {
    if (count > SIZE_MAX / sizeof(int)) {
        return NULL;
    }

    return calloc(count, sizeof(int));
}

int main(void) {
    int *values = allocate_values(100);
    if (values == NULL) {
        fputs("allocation failed\n", stderr);
        return 1;
    }

    values[0] = 42;
    free(values);
    return 0;
}

Taşma kontrolü yapılmadan count * sizeof(int) hesaplamak, küçük bir tahsis ve ardından sınır dışı yazma oluşturabilir. AddressSanitizer ve UndefinedBehaviorSanitizer gibi araçlar geliştirme sırasında bu hata sınıflarını görünür kılmaya yardımcı olur:

gcc -std=c17 -Wall -Wextra -fsanitize=address,undefined -g main.c -o main

Öğrenme sırası

Sistem programlama için yararlı bir sıra:

  1. C türleri, pointer, dizi ve kaynak yaşam döngüsü
  2. dosya tanıtıcıları ve POSIX I/O
  3. süreçler, sinyaller, fork ve exec
  4. sanal bellek ve allocation
  5. thread, mutex ve yarış koşulları
  6. TCP/UDP soketleri
  7. hata ayıklama, profiler ve sanitizer araçları

Her adımda yalnızca başarılı yolu değil, sistem çağrısının kısmi veya hatalı sonucunu da test edin.

Sonuç

Sistem programlama, makineye yakın kod yazmaktan önce kaynakların ve hata durumlarının açık sahipliğini kurma disiplinidir. Küçük programlarla başlayın, tüm dönüş değerlerini kontrol edin, uyarıları açık tutun ve bellek hatalarını araçlarla doğrulayın. Bu temel, daemon, ağ servisi, veritabanı motoru veya gömülü yazılım gibi daha büyük sistemleri anlamayı kolaylaştırır.

Kaynaklar