Samouczek połączenia systemu Linux z C

Samouczek połączenia systemu Linux z C
W naszym ostatnim artykule na temat połączeń systemowych Linux zdefiniowałem wywołanie systemowe, omówiłem powody, dla których można ich użyć w programie i zagłębiać się w ich zalety i wady. Podałem nawet krótki przykład w montażu w C. Zilustrowało to punkt i opisał, jak wykonać połączenie, ale nie zrobił nic produktywnego. Nie do końca ekscytujące ćwiczenie rozwojowe, ale ilustruje to punkt.

W tym artykule użyjemy rzeczywistych połączeń systemowych, aby wykonywać prawdziwą pracę w naszym programie C. Najpierw przejrzymy, jeśli będziesz musiał użyć połączenia systemowego, podaj przykład za pomocą wywołania sendFile (), który może znacznie poprawić wydajność kopiowania plików. Na koniec omówimy kilka punktów do zapamiętania podczas korzystania z połączeń systemowych Linux.

Czy potrzebujesz połączenia systemowego?

Chociaż jest to nieuniknione, użyjesz połączenia systemowego w pewnym momencie swojej kariery rozwojowej C, chyba że kierujesz się wysoką wydajnością lub określoną funkcją, biblioteka GLIBC i inne podstawowe biblioteki zawarte w dużych rozkładach Linux zajmie się większością Twoje potrzeby.

Biblioteka standardowa GLIBC zapewnia wieloplatformową, dobrze przetestowaną framework do wykonywania funkcji, które w przeciwnym razie wymagałyby wywołania systemu specyficznego dla systemu. Na przykład możesz odczytać plik z fscanf (), fread (), getC () itp., lub możesz użyć wywołania systemu Linux Linux. Funkcje GLIBC zapewniają więcej funkcji (i.mi. Lepsze obsługi błędów, sformatowane IO itp.) i będzie działać na dowolnym systemie obsługi GLIBC.

Z drugiej strony są czasy, w których bezkompromisowa wydajność i dokładne wykonanie są krytyczne. Opakowanie, które zapewnia Fread (), doda koszty ogólne i choć niewielkie, nie jest całkowicie przejrzyste. Dodatkowo możesz nie chcieć lub potrzebujesz dodatkowych funkcji, które podaje opakowanie. W takim przypadku najlepiej służyć z rozmową systemową.

Możesz także używać wywołań systemowych do wykonywania funkcji, które nie są jeszcze obsługiwane przez Glibc. Jeśli twoja kopia Glibc jest aktualna, nie będzie to problemem, ale opracowanie starszych dystrybucji z nowszymi jądrami może wymagać tej techniki.

Teraz, gdy przeczytałeś zrzeczenia się, ostrzeżenia i potencjalne objazdy, teraz zagłębiajmy się w niektóre praktyczne przykłady.

Na jakim procesorze jesteśmy?

Pytanie, którego większość programów prawdopodobnie nie ma, ale mimo to ważne. To jest przykład wywołania systemowego, którego nie można powielić za pomocą glibc i nie jest pokryte opakowaniem glibc. W tym kodzie wywołamy wywołanie getCPu () bezpośrednio za pomocą funkcji SysScall (). Funkcja Syscall działa w następujący sposób:

SYSCALL (SYS_CALL, Arg1, Arg2,…);

Pierwszy argument, sys_call, to definicja reprezentująca liczbę wywołania systemu. Po uwzględnieniu SYS/SYSCALL.h, są one wliczone. Pierwsza część to Sys_, a druga część to nazwa wywołania systemu.

Argumenty za wezwaniem przejdź do arg1, arg2 powyżej. Niektóre połączenia wymagają większej liczby argumentów i będą kontynuować na stronie Man. Pamiętaj, że większość argumentów, szczególnie w przypadku zwrotów, będzie wymagać wskaźników do tablic szarań lub pamięci przydzielonej za pośrednictwem funkcji Malloc.

Przykład 1.C

#włączać
#włączać
#włączać
#włączać
int main ()
Niepodpisany procesor, węzeł;
// Pobierz bieżący węzeł CPU i węzeł NUMA za pomocą połączenia systemowego
// zauważ, że to nie ma opakowania glibc, więc musimy to nazwać bezpośrednio
SYSCALL (SYS_GETCPU, i CPU, & NODE, NULL);
// Wyświetl informacje
printf („Ten program działa na CPU Core %u i Node %u.\ n \ n ”, CPU, węzeł);
powrót 0;

Aby skompilować i uruchomić:
Przykład GCC1.C -O Przykład 1
./Przykład 1

Aby uzyskać więcej interesujących rezultat.

Sendfile: doskonała wydajność

SendFile stanowi doskonały przykład zwiększania wydajności za pomocą wywołań systemowych. Funkcja sendFile () kopiuje dane z jednego deskryptora pliku do drugiego. Zamiast używać wielu funkcji Fread () i Fwrite (), SendFile wykonuje transfer w przestrzeni jądra, zmniejszając ogólne narzut, a tym samym zwiększając wydajność.

W tym przykładzie skopiujemy 64 MB danych z jednego pliku do drugiego. W jednym teście użyjemy standardowych metod odczytu/zapisu w standardowej bibliotece. Z drugiej strony użyjemy połączeń systemowych i wywołania sendFile (), aby wysadzić te dane z jednej lokalizacji do drugiej.

test1.C (GLIBC)

#włączać
#włączać
#włączać
#włączać
#definicja bufora_Size 67108864
#definicja bufora_1 „Buffer1”
#zdefiniuj bufor_2 „Buffer2”
int main ()
Plik *fout, *fin;
printf ("\ ni/o test z tradycyjnymi funkcjami GLIBC.\ n \ n ”);
// Chwyć bufor Buffer_Size.
// Bufor będzie miał w sobie losowe dane, ale nie dbamy o to.
printf („alokacja buforu 64 MB:”);
char *bufor = (char *) malloc (buffer_size);
printf („Done \ n”);
// Napisz bufor do Fout
printf („Zapisywanie danych do pierwszego bufora:”);
fout = fopen (buffer_1, „wb”);
fwrite (bufor, sizeof (char), buffer_size, fout);
fclose (fout);
printf („Done \ n”);
printf („Kopiowanie danych z pierwszego pliku do drugiego:”);
fin = fopen (buffer_1, „rb”);
fout = fopen (buffer_2, „wb”);
Fread (bufor, sizeof (char), buffer_size, fin);
fwrite (bufor, sizeof (char), buffer_size, fout);
FCLOSE (FIN);
fclose (fout);
printf („Done \ n”);
printf („Uwolnienie bufora:”);
darmowy (bufor);
printf („Done \ n”);
printf („Usuwanie plików:”);
usuń (buffer_1);
usuń (buffer_2);
printf („Done \ n”);
powrót 0;

test2.C (wywołania systemowe)

#włączać
#włączać
#włączać
#włączać
#włączać
#włączać
#włączać
#włączać
#włączać
#definicja bufora_Size 67108864
int main ()
int Fout, Fin;
printf („\ ni/o test z sendFile () i powiązanymi wywołaniami systemowymi.\ n \ n ”);
// Chwyć bufor Buffer_Size.
// Bufor będzie miał w sobie losowe dane, ale nie dbamy o to.
printf („alokacja buforu 64 MB:”);
char *bufor = (char *) malloc (buffer_size);
printf („Done \ n”);
// Napisz bufor do Fout
printf („Zapisywanie danych do pierwszego bufora:”);
fout = Open („buffer1”, o_rdonly);
Write (Fout i Buffer, Buffer_Size);
Zamknij (fout);
printf („Done \ n”);
printf („Kopiowanie danych z pierwszego pliku do drugiego:”);
fin = Open („Buffer1”, o_rdonly);
fout = Open („Buffer2”, o_rdonly);
sendFile (Fout, Fin, 0, buffer_size);
Zamknij (płetwa);
Zamknij (fout);
printf („Done \ n”);
printf („Uwolnienie bufora:”);
darmowy (bufor);
printf („Done \ n”);
printf („Usuwanie plików:”);
unlink („buffer1”);
unlink („buffer2”);
printf („Done \ n”);
powrót 0;

Testy kompilacji i uruchamiania 1 i 2

Aby zbudować te przykłady, będziesz potrzebować narzędzi programistycznych zainstalowanych w dystrybucji. W Debian i Ubuntu możesz to zainstalować z:

apt instal instaluj liczbę kompilacji

Następnie skompiluj z:

test GCC1.C -O TEST1 && GCC TEST2.C -O TEST2

Aby uruchomić oba i przetestować wydajność, uruchom:

czas ./TEST1 && czas ./test2

Powinieneś uzyskać takie wyniki:

Test I/O z tradycyjnymi funkcjami GLIBC.

Przydzielanie bufora 64 MB: gotowe
Pisanie danych do pierwszego bufora: gotowe
Kopiowanie danych z pierwszego pliku do drugiego: gotowe
Uwolnienie bufora: gotowe
Usuwanie plików: gotowe
Real 0m0.397s
Użytkownik 0M0.000
Sys 0m0.203s
Test I/O z połączeniami SendFile () i powiązanymi systemami systemowymi.
Przydzielanie bufora 64 MB: gotowe
Pisanie danych do pierwszego bufora: gotowe
Kopiowanie danych z pierwszego pliku do drugiego: gotowe
Uwolnienie bufora: gotowe
Usuwanie plików: gotowe
Real 0m0.019s
Użytkownik 0M0.000
Sys 0m0.016s

Jak widać, kod, który korzysta z połączeń systemowych, działa znacznie szybciej niż równoważnik GLIBC.

Rzeczy do zapamiętania

Wywołania systemowe mogą zwiększyć wydajność i zapewnić dodatkową funkcjonalność, ale nie są pozbawione ich wad. Będziesz musiał zważyć korzyści, które wywoływają systemy, które zapewniają brak przenośności platformy, a czasem zmniejszona funkcjonalność w porównaniu z funkcjami biblioteki.

Korzystając z niektórych połączeń systemowych, musisz uważać na korzystanie z zasobów zwróconych z wywołań systemowych, a nie funkcji biblioteki. Na przykład struktura pliku używana do funkcji Fopen (), Fread (), fwrite () i fclose () nie są takie same jak numer deskryptora pliku z wywołania systemowego Open () (zwrócone jako liczba całkowita). Mieszanie ich może prowadzić do problemów.

Zasadniczo wywołania systemu Linux mają mniej pasów zderzaków niż funkcje GLIBC. Chociaż prawdą jest, że wywołania systemowe mają pewne obsługę błędów i raportowanie, otrzymasz bardziej szczegółową funkcjonalność z funkcji Glibc.

I wreszcie słowo o bezpieczeństwie. System wywołuje bezpośrednio interfejs z jądrem. Kernel Linux ma obszerną ochronę przed shenanigans przed ziemią użytkownika, ale istnieją nieodkryte błędy. Nie ufaj, że połączenie systemowe potwierdzi twoje dane wejściowe lub odizolują Cię od problemów związanych z bezpieczeństwem. Rozsądnie jest upewnienie się, że dane, które przekazujesz do połączenia systemowego, są zdezynfekowane. Oczywiście jest to dobra rada dla każdego wezwania API, ale nie możesz być ostrożny podczas pracy z jądrem.

Mam nadzieję, że podobało Ci się to głębsze zanurzenie się w krainie połączeń systemowych Linux. Aby uzyskać pełną listę połączeń systemowych Linux, zobacz naszą listę główną.