Programowanie C

Samouczek wywołania systemu Linux z C

Samouczek wywołania systemu Linux z C
W naszym ostatnim artykule na temat Linux System Calls zdefiniowałem wywołanie systemowe, omówiłem powody, dla których można ich używać w programie oraz zagłębiłem się w ich zalety i wady. Podałem nawet krótki przykład montażu w C. Ilustrował ten punkt i opisywał, jak nawiązać połączenie, ale nie dał nic produktywnego. Niezbyt ekscytujące ćwiczenie rozwojowe, ale ilustrowało to sedno.

W tym artykule użyjemy rzeczywistych wywołań systemowych do wykonania prawdziwej pracy w naszym programie C. Najpierw sprawdzimy, czy musisz użyć wywołania systemowego, a następnie przedstawimy przykład z wywołaniem sendfile(), które może znacznie poprawić wydajność kopiowania plików. Na koniec omówimy kilka punktów, o których należy pamiętać podczas korzystania z wywołań systemowych Linux.

Czy potrzebujesz połączenia systemowego??

Chociaż jest to nieuniknione, w którymś momencie swojej kariery programistycznej C użyjesz wywołania systemowego, chyba że dążysz do wysokiej wydajności lub funkcjonalności określonego typu, biblioteka glibc i inne podstawowe biblioteki zawarte w głównych dystrybucjach Linuksa zajmą się większością Twoje potrzeby.

Biblioteka standardowa glibc zapewnia wieloplatformowy, dobrze przetestowany framework do wykonywania funkcji, które w innym przypadku wymagałyby wywołań systemowych specyficznych dla systemu. Na przykład możesz odczytać plik za pomocą fscanf(), fread(), getc() itp., lub możesz użyć linuksowego wywołania systemowego read(). Funkcje glibc dostarczają więcej funkcji (i.mi. lepsza obsługa błędów, sformatowane IO itp.) i będzie działać na każdym systemie, który obsługuje glibc.

Z drugiej strony są chwile, w których bezkompromisowa wydajność i dokładne wykonanie są krytyczne. Opakowanie dostarczane przez fread() będzie zwiększać obciążenie i chociaż niewielkie, nie jest całkowicie przezroczyste. Ponadto możesz nie chcieć lub nie potrzebować dodatkowych funkcji zapewnianych przez opakowanie. W takim przypadku najlepiej służy Ci wywołanie systemowe.

Możesz także użyć 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 stanowiło problemu, ale programowanie na starszych dystrybucjach z nowszymi jądrami może wymagać tej techniki.

Teraz, gdy przeczytałeś zastrzeżenia, ostrzeżenia i potencjalne objazdy, teraz przyjrzyjmy się kilku praktycznym przykładom.

Na jakim procesorze pracujemy??

Pytanie, którego większość programów prawdopodobnie nie myśli zadać, ale mimo to jest ważne. To jest przykład wywołania systemowego, które nie może być zduplikowane w glibc i nie jest objęte opakowaniem glibc. W tym kodzie wywołamy wywołanie getcpu() bezpośrednio za pomocą funkcji syscall(). Funkcja syscall działa w następujący sposób:

syscall(SYS_call, arg1, arg2,… ​​);

Pierwszy argument, SYS_call, jest definicją reprezentującą numer wywołania systemowego. Po dodaniu sys/syscall.h, te są wliczone. Pierwsza część to SYS_, a druga to nazwa wywołania systemowego.

Argumenty dla wywołania idą w arg1, arg2 powyżej. Niektóre wywołania wymagają większej liczby argumentów i będą kontynuowane w kolejności ze swojej strony podręcznika. Pamiętaj, że większość argumentów, szczególnie dla zwrotów, będzie wymagać wskaźników do tablic znaków lub pamięci przydzielonej za pomocą funkcji mallocloc.

Przykład 1.do

#zawierać
#zawierać
#zawierać
#zawierać
 
int main()
 
procesor bez znaku, węzeł;
 
// Uzyskaj aktualny rdzeń procesora i węzeł NUMA za pomocą wywołania systemowego
// Zauważ, że nie ma otoki glibc, więc musimy wywołać ją bezpośrednio
syscall(SYS_getcpu, &cpu, &node, NULL);
 
// Wyświetl informacje
printf("Ten program działa na rdzeniu procesora %u i węźle NUMA %u.\n\n", procesor, węzeł);
 
zwróć 0;
 

 
Aby skompilować i uruchomić:
 
Przykład gcc1.c -o przykład1
./Przykład 1

Aby uzyskać bardziej interesujące wyniki, możesz obracać wątki za pomocą biblioteki pthreads, a następnie wywołać tę funkcję, aby zobaczyć, na którym procesorze działa twój wątek.

Wyślij plik: doskonała wydajność

Sendfile stanowi doskonały przykład zwiększenia wydajności poprzez wywołania systemowe. 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 obciążenie, 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. W drugim użyjemy wywołań systemowych i wywołania sendfile(), aby przesłać te dane z jednego miejsca do drugiego.

test1.c (glibc)

#zawierać
#zawierać
#zawierać
#zawierać
 
#define BUFFER_SIZE 67108864
#define BUFFER_1 „bufor1”
#define BUFFER_2 "bufor2"
 
int main()
 
PLIK *fOut, *fIn;
 
printf("\nTest I/O z tradycyjnymi funkcjami glibc.\n\n");
 
// Chwyć bufor BUFFER_SIZE.
// Bufor będzie zawierał losowe dane, ale nas to nie obchodzi.
printf("Przydzielam 64 MB bufora:                     ");
char *buffer = (znak *) malloc(BUFFER_SIZE);
printf("GOTOWE\n");
 
// Zapisz bufor do fOut
printf("Zapis danych do pierwszego bufora:               ");
fOut = fopen(BUFOR_1, "wb");
fwrite(bufor, sizeof(char), BUFFER_SIZE, fOut);
fclose(fOut);
printf("GOTOWE\n");
 
printf("Kopiowanie danych z pierwszego pliku do drugiego:     ");
fIn = fopen(BUFOR_1, "rb");
fOut = fopen(BUFOR_2, "wb");
fread(bufor, sizeof(char), BUFFER_SIZE, fIn);
fwrite(bufor, sizeof(char), BUFFER_SIZE, fOut);
fzamknij(fIn);
fclose(fOut);
printf("GOTOWE\n");
 
printf("Zwalnianie bufora:                                  ");
wolny(bufor);
printf("GOTOWE\n");
 
printf("Usuwanie plików:                                     ");
usuń(BUFOR_1);
usuń(BUFOR_2);
printf("GOTOWE\n");
 
zwróć 0;
 

test2.c (wywołania systemowe)

#zawierać
#zawierać
#zawierać
#zawierać
#zawierać
#zawierać
#zawierać
#zawierać
#zawierać
 
#define BUFFER_SIZE 67108864
 
int main()
 
int fOut, fIn;
 
printf("\nTest I/O z sendfile() i powiązanymi wywołaniami systemowymi.\n\n");
 
// Pobierz bufor BUFFER_SIZE.
// Bufor będzie zawierał losowe dane, ale nas to nie obchodzi.
printf("Przydzielam 64 MB bufora:                     ");
char *buffer = (znak *) malloc(BUFFER_SIZE);
printf("GOTOWE\n");
 
// Zapisz bufor do fOut
printf("Zapis danych do pierwszego bufora:               ");
fOut = open("bufor1", O_RDONLY);
write(fOut, &buffer, BUFFER_SIZE);
zamknij(fOut);
printf("GOTOWE\n");
 
printf("Kopiowanie danych z pierwszego pliku do drugiego:     ");
fIn = open("bufor1", O_RDONLY);
fOut = open("bufor2", O_RDONLY);
sendfile(fOut, fIn, 0, BUFFER_SIZE);
zamknij(fIn);
zamknij(fOut);
printf("GOTOWE\n");
 
printf("Zwalnianie bufora:                                  ");
wolny(bufor);
printf("GOTOWE\n");
 
printf("Usuwanie plików:                                     ");
odłącz("bufor1");
odłącz("bufor2");
printf("GOTOWE\n");
 
zwróć 0;
 

Kompilowanie i uruchamianie testów 1 i 2

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

apt install build-essentials

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ś otrzymać takie wyniki:

Test I/O z tradycyjnymi funkcjami glibc.

Przydzielanie 64 MB bufora:                      GOTOWE
Zapisywanie danych w pierwszym buforze:               GOTOWE
Kopiowanie danych z pierwszego pliku do drugiego:      GOTOWE
Zwalnianie bufora:                                 GOTOWE
Usuwanie plików:                               GOTOWE
realne    0m0.397s
użytkownik    0m0.000s
sys     0m0.203s
Test I/O z sendfile() i powiązanymi wywołaniami systemowymi.
Przydzielanie 64 MB bufora:                     ZROBIONE
Zapisywanie danych w pierwszym buforze:               GOTOWE
Kopiowanie danych z pierwszego pliku do drugiego:     GOTOWE
Zwalnianie bufora:                                 GOTOWE
Usuwanie plików:                                 GOTOWE
realne    0m0.019s
użytkownik    0m0.000s
sys     0m0.016s

Jak widać, kod korzystający z wywołań systemowych działa znacznie szybciej niż odpowiednik glibc.

Rzeczy do zapamiętania

Wywołania systemowe mogą zwiększyć wydajność i zapewnić dodatkową funkcjonalność, ale nie są pozbawione wad. Będziesz musiał rozważyć korzyści, jakie zapewniają wywołania systemowe z brakiem przenośności platformy i czasami ograniczoną funkcjonalnością w porównaniu z funkcjami bibliotecznymi.

Używając niektórych wywołań systemowych, musisz uważać, aby używać zasobów zwracanych z wywołań systemowych, a nie funkcji bibliotecznych. Na przykład struktura FILE używana w funkcjach fopen(), fread(), fwrite() i fclose() biblioteki glibc nie jest taka sama jak numer deskryptora pliku z wywołania systemowego open() (zwracany jako liczba całkowita). Mieszanie ich może prowadzić do problemów.

Ogólnie rzecz biorąc, wywołania systemowe w Linuksie mają mniej pasów zderzaka niż funkcje glibc. Chociaż prawdą jest, że wywołania systemowe mają pewną obsługę błędów i raportowanie, uzyskasz bardziej szczegółową funkcjonalność z funkcji glibc.

I na koniec słowo o bezpieczeństwie. Wywołania systemowe bezpośrednio łączą się z jądrem. Jądro Linuksa ma rozległe zabezpieczenia przed oszustwami z obszaru użytkownika, ale istnieją nieodkryte błędy. Nie ufaj, że wywołanie systemowe zweryfikuje Twoje dane wejściowe lub odizoluje Cię od problemów z bezpieczeństwem. Dobrze jest upewnić się, że dane, które przekazujesz w wywołaniu systemowym, są oczyszczone is. Oczywiście jest to dobra rada dla każdego wywołania 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 wywołań systemu Linux Linux. Pełną listę wywołań systemu Linux znajdziesz na naszej głównej liście.

Gry 5 najlepszych gier zręcznościowych dla systemu Linux
5 najlepszych gier zręcznościowych dla systemu Linux
W dzisiejszych czasach komputery to poważne maszyny używane do gier. Jeśli nie możesz uzyskać nowego wysokiego wyniku, będziesz wiedział, o co mi chod...
Gry Bitwa o Wesnoth 1.13.6 Wydanie rozwojowe
Bitwa o Wesnoth 1.13.6 Wydanie rozwojowe
Bitwa o Wesnoth 1.13.6 wydana w zeszłym miesiącu jest szóstą wersją rozwojową w 1.13.Seria x i zapewnia szereg ulepszeń, w szczególności w interfejsie...
Gry Jak zainstalować League Of Legends na Ubuntu 14.04
Jak zainstalować League Of Legends na Ubuntu 14.04
Jeśli jesteś fanem League of Legends, to jest okazja do przetestowania League of Legends. Pamiętaj, że LOL jest obsługiwany w PlayOnLinux, jeśli jeste...