C ile Linux Sistem Çağrı Eğitimi

Linux System Call Tutorial With C



hakkındaki son makalemizde Linux Sistem Çağrıları , bir sistem çağrısı tanımladım, bunları bir programda kullanma nedenlerini tartıştım ve avantajlarını ve dezavantajlarını araştırdım. Montajda C içinde kısa bir örnek bile verdim. Noktayı gösterdi ve çağrının nasıl yapılacağını açıkladı, ancak üretken hiçbir şey yapmadı. Tam olarak heyecan verici bir geliştirme alıştırması değil, ama noktayı gösterdi.

Bu yazıda, C programımızda gerçek iş yapmak için gerçek sistem çağrılarını kullanacağız. İlk olarak, bir sistem çağrısı kullanmanız gerekip gerekmediğini inceleyeceğiz, ardından dosya kopyalama performansını önemli ölçüde artırabilecek sendfile() çağrısını kullanarak bir örnek sunacağız. Son olarak, Linux sistem çağrılarını kullanırken hatırlanması gereken bazı noktaların üzerinden geçeceğiz.







Yüksek performansı veya belirli bir tür işlevselliği hedeflemediğiniz sürece, C geliştirme kariyerinizin bir noktasında bir sistem çağrısı kullanmanız kaçınılmaz olsa da, büyük Linux dağıtımlarında yer alan glibc kitaplığı ve diğer temel kitaplıklar bunların çoğunu halledecektir. ihtiyaçlarınız.



glibc standart kitaplığı, aksi takdirde sisteme özel sistem çağrıları gerektirecek işlevleri yürütmek için platformlar arası, iyi test edilmiş bir çerçeve sağlar. Örneğin, fscanf(), fread(), getc(), vb. ile bir dosyayı okuyabilir veya read() Linux sistem çağrısını kullanabilirsiniz. glibc işlevleri daha fazla özellik sağlar (yani daha iyi hata işleme, biçimlendirilmiş IO, vb.) ve herhangi bir sistem glibc desteğinde çalışır.



Öte yandan, tavizsiz performansın ve kesin uygulamanın kritik olduğu zamanlar vardır. fread()'in sağladığı sarmalayıcı ek yük getirecek ve küçük olmasına rağmen tamamen şeffaf değil. Ek olarak, sarmalayıcının sağladığı ekstra özellikleri istemeyebilir veya bunlara ihtiyaç duymayabilirsiniz. Bu durumda, en iyi şekilde bir sistem çağrısı ile hizmet alırsınız.





Henüz glibc tarafından desteklenmeyen işlevleri gerçekleştirmek için sistem çağrılarını da kullanabilirsiniz. Glibc kopyanız güncelse, bu pek sorun olmaz, ancak daha yeni çekirdeklerle eski dağıtımlarda geliştirme yapmak bu tekniği gerektirebilir.

Sorumluluk reddi beyanlarını, uyarıları ve olası sapmaları okuduğunuza göre, şimdi bazı pratik örnekleri inceleyelim.



Hangi CPU'dayız?

Çoğu programın muhtemelen sormayı düşünmediği, ancak yine de geçerli bir soru. Bu, glibc ile kopyalanamayan ve bir glibc sarmalayıcısı ile kapsanmayan bir sistem çağrısı örneğidir. Bu kodda, getcpu() çağrısını doğrudan syscall() işlevi aracılığıyla çağıracağız. Sistem çağrısı işlevi aşağıdaki gibi çalışır:

sistem çağrısı(SYS_call,arg1,arg2,...);

İlk argüman, SYS_call, sistem çağrısının numarasını temsil eden bir tanımdır. sys/syscall.h dosyasını eklediğinizde, bunlar dahil edilir. İlk kısım SYS_ ve ikinci kısım sistem çağrısının adıdır.

Çağrı için argümanlar yukarıdaki arg1, arg2'ye gider. Bazı çağrılar daha fazla argüman gerektirir ve kılavuz sayfalarından sırayla devam ederler. Çoğu argümanın, özellikle dönüşler için, işaretçilerin dizileri veya malloc işlevi aracılığıyla tahsis edilen belleği gerektirdiğini unutmayın.

örnek1.c

#Dahil etmek
#Dahil etmek
#Dahil etmek
#Dahil etmek

intana() {

imzasızİşlemci,düğüm;

// Sistem çağrısı yoluyla mevcut CPU çekirdeğini ve NUMA düğümünü alın
// Bunun glibc sarmalayıcısı olmadığına dikkat edin, bu yüzden onu doğrudan çağırmalıyız
sistem çağrısı(SYS_getcpu, &İşlemci, &düğüm,BOŞ);

// Bilgileri göster
baskı ('Bu program CPU çekirdeği %u ve NUMA düğümü %u üzerinde çalışıyor. ',İşlemci,düğüm);

dönüş 0;

}

Derlemek ve çalıştırmak için:

gcc örneği1.C -o örnek1
./örnek 1

Daha ilginç sonuçlar için, dizileri pthreads kitaplığı aracılığıyla döndürebilir ve ardından dizinizin hangi işlemcide çalıştığını görmek için bu işlevi çağırabilirsiniz.

Sendfile: Üstün Performans

Sendfile, sistem çağrıları yoluyla performansı artırmanın mükemmel bir örneğini sunar. sendfile() işlevi, verileri bir dosya tanıtıcısından diğerine kopyalar. Birden fazla fread() ve fwrite() işlevi kullanmak yerine sendfile, aktarımı çekirdek alanında gerçekleştirir, ek yükü azaltır ve böylece performansı artırır.

Bu örnekte, 64 MB veriyi bir dosyadan diğerine kopyalayacağız. Bir testte, standart kitaplıktaki standart okuma/yazma yöntemlerini kullanacağız. Diğerinde, bu verileri bir konumdan diğerine göndermek için sistem çağrılarını ve sendfile() çağrısını kullanacağız.

test1.c (glibc)

#Dahil etmek
#Dahil etmek
#Dahil etmek
#Dahil etmek

#define BUFFER_SIZE 67108864
#define BUFFER_1 'buffer1'
#define BUFFER_2 'buffer2'

intana() {

DOSYA*yanlış, *son;

baskı (' Geleneksel glibc işlevleriyle I/O testi. ');

// BUFFER_SIZE arabelleği alın.
// Tamponun içinde rastgele veriler olacak ama bu bizi ilgilendirmiyor.
baskı ('64 MB arabellek ayırma:');
karakter *tampon= (karakter *) malloc (BUFFER_SIZE);
baskı ('TAMAMLAMAK ');

// Tamponu fOut'a yaz
baskı ('İlk ara belleğe veri yazılıyor:');
yanlış= fopen (BUFFER_1, 'wb');
fwrite (tampon, boyutu(karakter),BUFFER_SIZE,yanlış);
fclose (yanlış);
baskı ('TAMAMLAMAK ');

baskı ('İlk dosyadan ikinciye veri kopyalanıyor:');
son= fopen (BUFFER_1, 'rb');
yanlış= fopen (BUFFER_2, 'wb');
korku (tampon, boyutu(karakter),BUFFER_SIZE,son);
fwrite (tampon, boyutu(karakter),BUFFER_SIZE,yanlış);
fclose (son);
fclose (yanlış);
baskı ('TAMAMLAMAK ');

baskı ('Arabelleği boşaltma:');
Bedava (tampon);
baskı ('TAMAMLAMAK ');

baskı ('Dosyaları silme:');
kaldırmak (BUFFER_1);
kaldırmak (BUFFER_2);
baskı ('TAMAMLAMAK ');

dönüş 0;

}

test2.c (sistem çağrıları)

#Dahil etmek
#Dahil etmek
#Dahil etmek
#Dahil etmek
#Dahil etmek
#Dahil etmek
#Dahil etmek
#Dahil etmek
#Dahil etmek

#define BUFFER_SIZE 67108864

intana() {

intyanlış,son;

baskı (' sendfile() ve ilgili sistem çağrıları ile I/O testi. ');

// BUFFER_SIZE arabelleği alın.
// Tamponun içinde rastgele veriler olacak ama bu bizi ilgilendirmiyor.
baskı ('64 MB arabellek ayırma:');
karakter *tampon= (karakter *) malloc (BUFFER_SIZE);
baskı ('TAMAMLAMAK ');


// Tamponu fOut'a yaz
baskı ('İlk ara belleğe veri yazılıyor:');
yanlış=açık('tampon1',O_RDONLY);
yazı yazmak(yanlış, &tampon,BUFFER_SIZE);
kapat(yanlış);
baskı ('TAMAMLAMAK ');

baskı ('İlk dosyadan ikinciye veri kopyalanıyor:');
son=açık('tampon1',O_RDONLY);
yanlış=açık('tampon2',O_RDONLY);
dosya Gönder(yanlış,son, 0,BUFFER_SIZE);
kapat(son);
kapat(yanlış);
baskı ('TAMAMLAMAK ');

baskı ('Arabelleği boşaltma:');
Bedava (tampon);
baskı ('TAMAMLAMAK ');

baskı ('Dosyaları silme:');
bağlantıyı kaldır('tampon1');
bağlantıyı kaldır('tampon2');
baskı ('TAMAMLAMAK ');

dönüş 0;

}

1. ve 2. Testleri Derleme ve Çalıştırma

Bu örnekleri oluşturmak için dağıtımınızda kurulu geliştirme araçlarına ihtiyacınız olacak. Debian ve Ubuntu'da bunu şu şekilde yükleyebilirsiniz:

uygunYüklemekyapı temelleri

Ardından şununla derleyin:

gcctest1.c-veyatest1&& gcctest2.c-veyatest2

Her ikisini de çalıştırmak ve performansı test etmek için şunu çalıştırın:

zaman./test1&& zaman./test2

Bunun gibi sonuçlar almalısınız:

Geleneksel glibc işlevleriyle I/O testi.

64 MB arabellek ayırma: TAMAMLANDI
İlk ara belleğe veri yazma: TAMAMLANDI
İlk dosyadan ikinciye veri kopyalama: TAMAMLANDI
Arabelleği boşaltma: TAMAMLANDI
Dosyaları silme: BİTTİ
gerçek 0m0.397s
kullanıcı 0m0.0000s
sistem 0m0.203s
sendfile() ve ilgili sistem çağrıları ile I/O testi.
64 MB arabellek ayırma: TAMAMLANDI
İlk ara belleğe veri yazma: TAMAMLANDI
İlk dosyadan ikinciye veri kopyalama: TAMAMLANDI
Arabelleği boşaltma: TAMAMLANDI
Dosyaları silme: BİTTİ
gerçek 0m0.019s
kullanıcı 0m0.0000s
sistem 0m0.016s

Gördüğünüz gibi, sistem çağrılarını kullanan kod, glibc eşdeğerinden çok daha hızlı çalışır.

Hatırlanacak şeyler

Sistem çağrıları performansı artırabilir ve ek işlevsellik sağlayabilir, ancak dezavantajları da vardır. Sistem çağrılarının sağladığı faydaları, platform taşınabilirliği eksikliğine ve bazen kütüphane işlevlerine kıyasla azaltılmış işlevselliğe karşı tartmanız gerekecek.

Bazı sistem çağrılarını kullanırken kütüphane fonksiyonlarından ziyade sistem çağrılarından dönen kaynakları kullanmaya özen göstermelisiniz. Örneğin, glibc'nin fopen(), fread(), fwrite() ve fclose() işlevleri için kullanılan FILE yapısı, open() sistem çağrısındaki (tamsayı olarak döndürülen) dosya tanımlayıcı numarasıyla aynı değildir. Bunları karıştırmak sorunlara yol açabilir.

Genel olarak, Linux sistem çağrıları, glibc işlevlerinden daha az tampon şeridine sahiptir. Sistem çağrılarının bazı hata işleme ve raporlamaya sahip olduğu doğru olsa da, bir glibc işlevinden daha ayrıntılı işlevsellik elde edeceksiniz.

Ve son olarak, güvenlik üzerine bir kelime. Sistem çağrıları doğrudan çekirdekle arayüz oluşturur. Linux çekirdeği, kullanıcı topraklarından gelen maskaralıklara karşı kapsamlı korumaya sahiptir, ancak keşfedilmemiş hatalar mevcuttur. Bir sistem çağrısının girişinizi doğrulayacağına veya sizi güvenlik sorunlarından yalıtacağına güvenmeyin. Bir sistem çağrısına verdiğiniz verilerin sterilize edilmesini sağlamak akıllıca olacaktır. Doğal olarak, bu herhangi bir API çağrısı için iyi bir tavsiyedir, ancak çekirdekle çalışırken dikkatli olamazsınız.

Umarım Linux sistem çağrıları diyarına bu derin dalıştan keyif almışsınızdır. Linux Sistem Çağrılarının tam listesi için ana listemize bakın.