C Programování

Přečtěte si Syscall Linux

Přečtěte si Syscall Linux
Musíte tedy číst binární data? Možná budete chtít číst z FIFO nebo zásuvky? Vidíte, že můžete použít standardní knihovní funkci C, ale tím nebudete těžit ze speciálních funkcí poskytovaných Linux Kernel a POSIX. Můžete například použít časové limity ke čtení v určitou dobu, aniž byste se uchýlili k dotazování. Možná budete muset něco přečíst, aniž byste se starali, jestli je to speciální soubor nebo zásuvka nebo cokoli jiného. Vaším jediným úkolem je přečíst nějaký binární obsah a dostat ho do své aplikace. To je místo, kde svítí přečtený syscall.

Přečtěte si normální soubor se systémem Linux Syscall

Nejlepší způsob, jak s touto funkcí začít pracovat, je čtení normálního souboru. Toto je nejjednodušší způsob, jak tento syscall použít, a to z nějakého důvodu: nemá tolik omezení jako jiné typy proudu nebo potrubí. Pokud o tom přemýšlíte, je to logické, když čtete výstup jiné aplikace, musíte mít připravený nějaký výstup, než si ji přečtete, takže budete muset počkat, až tato aplikace tento výstup zapíše.

Nejprve klíčový rozdíl oproti standardní knihovně: Neexistuje vůbec žádné ukládání do vyrovnávací paměti. Pokaždé, když zavoláte funkci čtení, zavoláte jádro Linuxu, takže to bude nějakou dobu trvat - je to téměř okamžité, pokud jej zavoláte jednou, ale může vás zpomalit, pokud zavoláte tisíckrát za sekundu. Ve srovnání bude standardní knihovna vyrovnávat vstup pro vás. Takže kdykoli zavoláte na čtení, měli byste přečíst více než několik bajtů, ale spíše velkou vyrovnávací paměť, jako je několik kilobajtů - kromě případů, kdy to, co potřebujete, je opravdu málo bytů, například pokud zkontrolujete, zda soubor existuje a není prázdný.

To však má výhodu: pokaždé, když zavoláte čtení, máte jistotu, že získáte aktualizovaná data, pokud některá aplikace aktuálně upravuje soubor. To je užitečné zejména pro speciální soubory, jako jsou například soubory v / proc nebo / sys.

Je čas ukázat vám skutečný příklad. Tento program C kontroluje, zda je soubor PNG nebo ne. K tomu načte soubor zadaný v cestě, kterou zadáte v argumentu příkazového řádku, a zkontroluje, zda prvních 8 bajtů odpovídá hlavičce PNG.

Tady je kód:

#zahrnout
#zahrnout
#zahrnout
#zahrnout
#zahrnout
#zahrnout
#zahrnout
 
typedef enum
IS_PNG,
MOC KRÁTKÝ,
INVALID_HEADER
pngStatus_t;
 
unsigned int isSyscallSuccessful (const ssize_t readStatus)
návrat readStatus> = 0;
 

 
/ *
* checkPngHeader kontroluje, zda pole pngFileHeader odpovídá PNG
* záhlaví souboru.
*
* Aktuálně kontroluje pouze prvních 8 bajtů pole. Pokud je pole menší
* než 8 bajtů, vrátí se TOO_SHORT.
*
* pngFileHeaderLength musí udržovat sílu tye pole. Jakákoli neplatná hodnota
* může vést k nedefinovanému chování, například k selhání aplikace.
*
* Vrací IS_PNG, pokud odpovídá hlavičce souboru PNG. Pokud existuje alespoň
* 8 bajtů v poli, ale nejde o záhlaví PNG, vrátí se INVALID_HEADER.
*
* /
pngStatus_t checkPngHeader (const nepodepsaný znak * const pngFileHeader,
size_t pngFileHeaderLength) const nepodepsaný znak očekávánPngHeader [8] =
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A;
int i = 0;
 
if (pngFileHeaderLength < sizeof(expectedPngHeader))
vrátit TOO_SHORT;
 

 
pro (i = 0; i < sizeof(expectedPngHeader); i++)
if (pngFileHeader [i] != expectPngHeader [i])
vrátit INVALID_HEADER;
 


 
/ * Pokud to dosáhne zde, všech prvních 8 bajtů odpovídá hlavičce PNG. * /
vrátit IS_PNG;

 
int main (int argumentLength, char * argumentList [])
char * pngFileName = NULL;
unsigned char pngFileHeader [8] = 0;
 
ssize_t readStatus = 0;
/ * Linux používá k identifikaci otevřeného souboru číslo. * /
int pngFile = 0;
pngStatus_t pngCheckResult;
 
if (argumentDélka != 2)
fputs ("Tento program musíte zavolat pomocí isPng váš název.\ n ", stderr);
návrat EXIT_FAILURE;
 

 
pngFileName = argumentList [1];
pngFile = open (pngFileName, O_RDONLY);
 
if (pngFile == -1)
perror ("Otevření poskytnutého souboru se nezdařilo");
návrat EXIT_FAILURE;
 

 
/ * Přečtěte si několik bajtů a zjistěte, zda je soubor PNG. * /
readStatus = read (pngFile, pngFileHeader, sizeof (pngFileHeader));
 
if (isSyscallSuccessful (readStatus))
/ * Zkontrolujte, zda je soubor PNG, protože obsahuje data. * /
pngCheckResult = checkPngHeader (pngFileHeader, readStatus);
 
if (pngCheckResult == TOO_SHORT)
printf ("Soubor% s není soubor PNG: je příliš krátký.\ n ", název souboru png);
 
else if (pngCheckResult == IS_PNG)
printf ("Soubor% s je soubor PNG!\ n ", název souboru png);
 
else
printf ("Soubor% s není ve formátu PNG.\ n ", název souboru png);
 

 
else
perror ("Načtení souboru se nezdařilo");
návrat EXIT_FAILURE;
 

 
/ * Zavřít soubor ... * /
if (close (pngFile) == -1)
perror ("Zavření zadaného souboru se nezdařilo");
návrat EXIT_FAILURE;
 

 
pngFile = 0;
 
vrátit EXIT_SUCCESS;
 

Podívejte se, je to plnohodnotný, funkční a kompilační příklad. Neváhejte si to sami sestavit a otestovat, opravdu to funguje. Program byste měli zavolat z terminálu, jako je tento:

./ isPng váš název

Nyní se zaměřme na samotné volání čtení:

pngFile = open (pngFileName, O_RDONLY);
if (pngFile == -1)
perror ("Otevření poskytnutého souboru se nezdařilo");
návrat EXIT_FAILURE;

/ * Přečtěte si několik bajtů, abyste zjistili, zda je soubor PNG. * /
readStatus = read (pngFile, pngFileHeader, sizeof (pngFileHeader));

Čtený podpis je následující (extrahovaný z manuálových stránek Linuxu):

ssize_t read (int fd, void * buf, size_t count);

Nejprve argument fd představuje deskriptor souboru. Trochu jsem tento koncept vysvětlil ve svém článku o vidličce.  Deskriptor souboru je int představující otevřený soubor, soket, potrubí, FIFO, zařízení, no je to spousta věcí, kde lze data číst nebo zapisovat, obecně proudovým způsobem. Podrobněji se tomu věnuji v budoucím článku.

otevřená funkce je jedním ze způsobů, jak říct Linuxu: Chci dělat věci se souborem na této cestě, prosím najděte jej tam, kde je, a dejte mi k němu přístup. Vrátí vám tento int nazývaný deskriptor souboru a nyní, pokud chcete s tímto souborem něco udělat, použijte toto číslo. Nezapomeňte zavolat zavřít, až budete hotovi se souborem, jako v příkladu.

Musíte tedy zadat toto speciální číslo ke čtení. Pak je tu argument buf. Zde byste měli poskytnout ukazatel na pole, kde read uloží vaše data. Nakonec se počítá, kolik bytů přečte nanejvýš.

Návratová hodnota je typu ssize_t. Divný typ, že?? Znamená to „signed size_t“, v podstatě je to dlouhý int. Vrátí počet bajtů, které úspěšně načte, nebo -1, pokud nastane problém. Přesnou příčinu problému najdete v globální proměnné errno vytvořené systémem Linux, definované v . Chcete-li však vytisknout chybovou zprávu, je lepší použít perror, protože tiskne chybně vaším jménem.

V normálních souborech - a pouze v tomto případě - čtení vrátí méně než počet, pouze pokud jste dosáhli konce souboru. Pole bufetu, které poskytnete musí být dostatečně velký, aby se vešel alespoň do počtu bajtů, nebo by váš program mohl selhat nebo vytvořit bezpečnostní chybu.

Čtení nyní není užitečné pouze pro normální soubory a pokud chcete pocítit jeho supervelmoc - Ano, vím, že to není v žádném komiksu Marvel, ale má skutečné schopnosti - budete jej chtít použít s jinými streamy, jako jsou potrubí nebo zásuvky. Pojďme se na to podívat:

Linux speciální soubory a číst systémové volání

Skutečnost, že čtení funguje, pracuje s různými soubory, jako jsou kanály, zásuvky, FIFO nebo speciální zařízení, jako je disk nebo sériový port, díky nim je opravdu výkonnější. S některými úpravami můžete dělat opravdu zajímavé věci. Nejprve to znamená, že můžete doslova psát funkce pracující na souboru a místo toho je používat s rourou. Je zajímavé předávat data, aniž byste museli zasáhnout disk, a zajistit tak nejlepší výkon.

To však také spouští speciální pravidla. Vezměme si příklad čtení řádku z terminálu ve srovnání s normálním souborem. Když zavoláte na čtení v normálním souboru, potřebuje Linux jen několik milisekund, aby získal požadované množství dat.

Ale pokud jde o terminál, je to jiný příběh: řekněme, že požádáte o uživatelské jméno. Uživatel zadává do terminálu své uživatelské jméno a stiskněte klávesu Enter. Nyní se budete řídit mou radou výše a zavoláte čtení s velkou vyrovnávací pamětí, například 256 bajtů.

Pokud by čtení fungovalo jako u souborů, počkalo by, než uživatel vrátí 256 znaků, než se vrátí! Váš uživatel bude čekat navždy a poté vaši aplikaci smutně zabije. Určitě to není to, co chcete, a měli byste velký problém.

Dobře, můžete číst jeden bajt najednou, ale toto řešení je strašně neefektivní, jak jsem vám řekl výše. Musí to fungovat lépe.

Aby se však tomuto problému vyhnuli, si vývojáři Linuxu mysleli, že je třeba číst jinak:

  • Když čtete normální soubory, pokusí se co nejvíce přečíst počet bajtů a v případě potřeby aktivně získá bajty z disku.
  • U všech ostatních typů souborů se vrátí Jakmile jsou k dispozici nějaká data a nejvíce počet bytů:
    1. U terminálů to je obvykle když uživatel stiskne klávesu Enter.
    2. U soketů TCP je to, jakmile váš počítač něco přijme, nezáleží na množství bajtů, které dostane.
    3. Pro FIFO nebo roury je to obecně stejné množství, jaké napsala jiná aplikace, ale linuxové jádro může dodávat méně najednou, pokud je to pohodlnější.

Můžete tedy bezpečně dorovnat pomocí vyrovnávací paměti 2 KiB, aniž byste zůstali věčně zavřeni. Všimněte si, že se také může přerušit, pokud aplikace přijme signál. Protože čtení ze všech těchto zdrojů může trvat několik sekund nebo dokonce hodin - dokud se druhá strana nakonec nerozhodne psát - přerušení signály umožňuje přestat zůstat blokován příliš dlouho.

To má ale také nevýhodu: pokud chcete s těmito speciálními soubory přesně načíst 2 KiB, budete muset zkontrolovat návratovou hodnotu pro čtení a zavolat několikrát. čtení zřídka vyplní celý váš buffer. Pokud vaše aplikace používá signály, budete také muset pomocí errno zkontrolovat, zda se čtení nezdařilo s -1, protože byla přerušena signálem.

Ukážu vám, jak může být zajímavé použít tuto speciální vlastnost read:

#define _POSIX_C_SOURCE 1 / * sigaction není k dispozici bez tohoto #define. * /
#zahrnout
#zahrnout
#zahrnout
#zahrnout
#zahrnout
#zahrnout
/ *
* isSignal říká, zda byl přečtený syscall přerušen signálem.
*
* Vrací TRUE, pokud byl načtený systémový hovor přerušen signálem.
*
* Globální proměnné: čte errno definované v errno.h
* /
unsigned int isSignal (const ssize_t readStatus)
návrat (readStatus == -1 && errno == EINTR);

unsigned int isSyscallSuccessful (const ssize_t readStatus)
návrat readStatus> = 0;

/ *
* shouldRestartRead řekne, kdy byl přečtený syscall přerušen a
* signalizovat událost nebo ne a vzhledem k této „chybě“ je důvod přechodný, můžeme
* bezpečně restartujte přečtený hovor.
*
* Aktuálně kontroluje pouze to, zda bylo čtení přerušeno signálem, ale ano
* lze vylepšit, aby se zkontrolovalo, zda byl přečten cílový počet bajtů a zda je
* není tomu tak, vraťte TRUE a přečtěte si ji znovu.
*
* /
unsigned int shouldRestartRead (const ssize_t readStatus)
návrat isSignal (readStatus);

/ *
* Potřebujeme prázdnou obslužnou rutinu, protože čtení volání bude přerušeno, pouze pokud
* signál je zpracován.
* /
void emptyHandler (int ignorováno)
vrátit se;

int main ()
/ * Je v sekundách. * /
const int alarmInterval = 5;
const struct sigaction emptySigaction = emptyHandler;
char lineBuf [256] = 0;
ssize_t readStatus = 0;
unsigned int waitTime = 0;
/ * Neměňte sigaction kromě případů, kdy přesně víte, co děláte. * /
sigaction (SIGALRM, & emptySigaction, NULL);
alarm (alarmInterval);
fputs ("Váš text: \ n", stderr);
dělat
/ * Nezapomeňte na '\ 0' * /
readStatus = read (STDIN_FILENO, lineBuf, sizeof (lineBuf) - 1);
if (isSignal (readStatus))
waitTime + = alarmInterval;
alarm (alarmInterval);
fprintf (stderr, "% u sekund nečinnosti ... \ n", waitTime);

while (shouldRestartRead (readStatus));
if (isSyscallSuccessful (readStatus))
/ * Ukončete řetězec, abyste se vyhnuli chybě při jeho poskytování fprintf. * /
lineBuf [readStatus] = '\ 0';
fprintf (stderr, "Zadali jste% lu znaků. Tady je váš řetězec: \ n% s \ n ", strlen (lineBuf),
lineBuf);
else
perror ("Čtení ze standardního připojení selhalo");
návrat EXIT_FAILURE;

vrátit EXIT_SUCCESS;

Opět se jedná o úplnou aplikaci C, kterou můžete zkompilovat a skutečně spustit.

Provádí následující: čte řádek ze standardního vstupu. Každých 5 sekund však vytiskne řádek, který uživateli sdělí, že dosud nebyl zadán žádný vstup.

Příklad, když počkám 23 sekund před zadáním „Penguin“:

$ alarm_read
Tvůj text:
5 sekund nečinnosti…
10 sekund nečinnosti…
15 sekund nečinnosti…
20 sekund nečinnosti…
Tučňák
Zadali jste 8 znaků. Tady je váš řetězec:
Tučňák

To je neuvěřitelně užitečné. Lze jej použít k časté aktualizaci uživatelského rozhraní, aby se vytiskl postup čtení nebo zpracování vaší aplikace, kterou děláte. Může být také použit jako mechanismus časového limitu. Mohl by vás také přerušit jakýkoli jiný signál, který by mohl být pro vaši aplikaci užitečný. To každopádně znamená, že vaše aplikace nyní může reagovat, místo aby zůstala navždy zaseknutá.

Výhody tedy převažují nad výše popsanou nevýhodou. Pokud vás zajímá, zda byste měli podporovat speciální soubory v aplikaci, která normálně pracuje s normálními soubory - a tak volá číst ve smyčce - Řekl bych, že to udělejte, pokud nespěcháte, moje osobní zkušenost často dokázala, že nahrazení souboru pomocí kanálu nebo FIFO může doslova udělat aplikaci mnohem užitečnější s malým úsilím. Na internetu jsou dokonce předem připravené funkce C, které tuto smyčku implementují za vás: říká se jim funkce readn.

Závěr

Jak vidíte, fread a read mohou vypadat podobně, nejsou. A jen s několika změnami v tom, jak čtení funguje pro vývojáře jazyka C, je čtení mnohem zajímavější pro navrhování nových řešení problémů, se kterými se setkáte během vývoje aplikace.

Příště vám řeknu, jak psaní syscall funguje, protože čtení je v pohodě, ale schopnost obojího je mnohem lepší. Mezitím experimentujte s čtením, seznamte se s ním a přeji vám šťastný nový rok!

Hry Jak zobrazit překrytí OSD v linuxových aplikacích a hrách na celou obrazovku
Jak zobrazit překrytí OSD v linuxových aplikacích a hrách na celou obrazovku
Hraní her na celou obrazovku nebo používání aplikací v režimu celé obrazovky bez rozptýlení vás mohou odříznout od příslušných systémových informací v...
Hry Top 5 karet pro zachycení hry
Top 5 karet pro zachycení hry
Všichni jsme viděli a milovali streamování her na YouTube. PewDiePie, Jakesepticye a Markiplier jsou jen někteří z nejlepších hráčů, kteří vydělali mi...
Hry Jak vyvíjet hru na Linuxu
Jak vyvíjet hru na Linuxu
Před deseti lety by jen málo uživatelů Linuxu předpovídalo, že jejich oblíbený operační systém bude jednoho dne populární herní platformou pro komerčn...