C Programmering

Läs Syscall Linux

Läs Syscall Linux
Så du måste läsa binär data? Du kanske vill läsa från en FIFO eller ett uttag? Du förstår, du kan använda C-standardbiblioteksfunktionen, men genom att göra det kommer du inte att dra nytta av specialfunktioner från Linux Kernel och POSIX. Du kanske till exempel vill använda timeouts för att läsa vid en viss tid utan att tillgripa polling. Du kan också behöva läsa något utan att bry dig om det är en speciell fil eller sockel eller något annat. Din enda uppgift är att läsa lite binärt innehåll och få det i din applikation. Det är där den lästa syscall lyser.

Läs en vanlig fil med en Linux-syscall

Det bästa sättet att börja arbeta med den här funktionen är att läsa en vanlig fil. Detta är det enklaste sättet att använda syscall och av en anledning: det har inte så många begränsningar som andra typer av ström eller rör. Om du tänker på det är logik, när du läser utdata från en annan applikation, måste du ha lite utdata redo innan du läser den, så du måste vänta tills den här applikationen skriver denna utdata.

Först en viktig skillnad med standardbiblioteket: Det finns ingen buffring alls. Varje gång du ringer till läsfunktionen kommer du att ringa Linux Kernel, så det här kommer att ta tid - det är nästan omedelbart om du ringer det en gång, men kan sakta ner dig om du ringer det tusentals gånger på en sekund. Som jämförelse kommer standardbiblioteket att buffra inmatningen åt dig. Så när du ringer ska du läsa mer än några byte, utan snarare en stor buffert som få kilobyte - förutom om du verkligen behöver få byte, till exempel om du kontrollerar om en fil finns och inte är tom.

Detta har dock en fördel: varje gång du ringer för att läsa är du säker på att du får de uppdaterade uppgifterna, om någon annan applikation för närvarande ändrar filen. Detta är särskilt användbart för speciella filer som de i / proc eller / sys.

Dags att visa dig ett riktigt exempel. Detta C-program kontrollerar om filen är PNG eller inte. För att göra det läser den filen som anges i sökvägen som du anger i kommandoradsargumentet, och den kontrollerar om de första 8 byten motsvarar en PNG-rubrik.

Här är koden:

#omfatta
#omfatta
#omfatta
#omfatta
#omfatta
#omfatta
#omfatta
 
typedef enum
IS_PNG,
FÖR KORT,
INVALID_HEADER
pngStatus_t;
 
osignerad int isSyscallSuccessful (const ssize_t readStatus)
return readStatus> = 0;
 

 
/ *
* checkPngHeader kontrollerar om pngFileHeader-matrisen motsvarar en PNG
* filrubrik.
*
* För närvarande kontrollerar den bara de första 8 bytes i matrisen. Om matrisen är mindre
* än 8 byte returneras TOO_SHORT.
*
* pngFileHeaderLength måste cintain kength tye array. Alla ogiltiga värden
* kan leda till odefinierat beteende, till exempel att program kraschar.
*
* Returnerar IS_PNG om det motsvarar en PNG-filrubrik. Om det finns åtminstone
* 8 byte i matrisen men det är inte ett PNG-rubrik, INVALID_HEADER returneras.
*
* /
pngStatus_t checkPngHeader (const osignerad char * const pngFileHeader,
size_t pngFileHeaderLength) const osignerad char förväntatPngHeader [8] =
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A;
int i = 0;
 
if (pngFileHeaderLength < sizeof(expectedPngHeader))
återvänd TOO_SHORT;
 

 
för (i = 0; i < sizeof(expectedPngHeader); i++)
if (pngFileHeader [i] != förväntadPngHeader [i])
returnera INVALID_HEADER;
 


 
/ * Om den når hit, överensstämmer alla de första 8 byten med en PNG-rubrik. * /
returnera IS_PNG;

 
int main (int argumentLength, char * argumentList [])
char * pngFileName = NULL;
osignerad char pngFileHeader [8] = 0;
 
ssize_t readStatus = 0;
/ * Linux använder ett nummer för att identifiera en öppen fil. * /
int pngFile = 0;
pngStatus_t pngCheckResult;
 
if (argumentLängd != 2)
fputs ("Du måste ringa det här programmet med isPng ditt filnamn.\ n ", stderr);
returnera EXIT_FAILURE;
 

 
pngFileName = argumentList [1];
pngFile = öppen (pngFileName, O_RDONLY);
 
om (pngFile == -1)
perror ("Öppna den angivna filen misslyckades");
returnera EXIT_FAILURE;
 

 
/ * Läs några byte för att identifiera om filen är PNG. * /
readStatus = read (pngFile, pngFileHeader, sizeof (pngFileHeader));
 
if (isSyscallSuccessful (readStatus))
/ * Kontrollera om filen är en PNG eftersom den fick data. * /
pngCheckResult = checkPngHeader (pngFileHeader, readStatus);
 
om (pngCheckResult == TOO_SHORT)
printf ("Filen% s är inte en PNG-fil: den är för kort.\ n ", pngFileName);
 
annat om (pngCheckResult == IS_PNG)
printf ("Filen% s är en PNG-fil!\ n ", pngFileName);
 
annat
printf ("Filen% s är inte i PNG-format.\ n ", pngFileName);
 

 
annat
perror ("Läsning av filen misslyckades");
returnera EXIT_FAILURE;
 

 
/ * Stäng filen ... * /
om (stäng (pngFile) == -1)
perror ("Att stänga den angivna filen misslyckades");
returnera EXIT_FAILURE;
 

 
pngFile = 0;
 
returnera EXIT_SUCCESS;
 

Se, det är ett fullständigt, fungerande och sammanställbart exempel. Tveka inte att kompilera det själv och testa det, det fungerar verkligen. Du bör ringa programmet från en sådan terminal:

./ isPng ditt filnamn

Låt oss nu fokusera på själva läsanropet:

pngFile = öppen (pngFileName, O_RDONLY);
om (pngFile == -1)
perror ("Öppna den angivna filen misslyckades");
returnera EXIT_FAILURE;

/ * Läs några byte för att identifiera om filen är PNG. * /
readStatus = read (pngFile, pngFileHeader, sizeof (pngFileHeader));

Den lästa signaturen är följande (extraherad från Linux-man-sidor):

ssize_t läs (int fd, void * buf, size_t count);

Först representerar fd-argumentet filbeskrivaren. Jag har förklarat lite detta koncept i min gaffelartikel.  En filbeskrivare är ett int som representerar en öppen fil, sockel, rör, FIFO, enhet, ja det är många saker där data kan läsas eller skrivas, vanligtvis på ett strömliknande sätt. Jag kommer att gå mer i detalj om det i en framtida artikel.

öppen funktion är ett sätt att berätta för Linux: Jag vill göra saker med filen på den vägen, vänligen hitta den där den är och ge mig tillgång till den. Det kommer att ge dig tillbaka den så kallade filbeskrivaren och nu, om du vill göra något med den här filen, använd det numret. Glöm inte att ringa nära när du är klar med filen, som i exemplet.

Så du måste ange detta speciella nummer att läsa. Sedan finns det buf-argumentet. Du bör här ange en pekare till matrisen där läst lagrar dina data. Slutligen räknar du hur många byte det läser högst.

Returvärdet är av typen ssize_t. Konstig typ, eller hur?? Det betyder "signerad storlek_t", i grund och botten är det en lång int. Det returnerar antalet byte som den läser framgångsrikt, eller -1 om det finns ett problem. Du kan hitta den exakta orsaken till problemet i errno global variabel skapad av Linux, definierad i . Men för att skriva ut ett felmeddelande är det bättre att använda perror eftersom det skriver fel på dina vägnar.

I normala filer - och endast i det här fallet - läs kommer endast att returnera mindre än att räkna om du har nått filens slut. Buf-arrayen du tillhandahåller måste vara tillräckligt stor för att åtminstone kunna räkna byte, annars kan ditt program krascha eller skapa ett säkerhetsfel.

Nu är läsning inte bara användbar för vanliga filer och om du vill känna dess superkrafter - Ja jag vet att det inte finns i någon Marvels serier men det har sanna krafter - du vill använda den med andra strömmar som rör eller uttag. Låt oss ta en titt på det:

Linux-specialfiler och läst systemanrop

Faktaläsning fungerar med en mängd olika filer som rör, uttag, FIFO eller specialenheter som en disk eller seriell port är det som gör den riktigt kraftfullare. Med vissa anpassningar kan du göra riktigt intressanta saker. För det första betyder det att du bokstavligen kan skriva funktioner som arbetar på en fil och istället använda den med ett rör. Det är intressant att skicka data utan att någonsin slå på disk, vilket garanterar bästa prestanda.

Men detta utlöser också speciella regler. Låt oss ta exemplet med att läsa en rad från terminalen jämfört med en vanlig fil. När du ringer för att läsa på en vanlig fil behöver det bara några millisekunder till Linux för att få den mängd data du begär.

Men när det gäller terminal är det en annan historia: låt oss säga att du ber om ett användarnamn. Användaren skriver in terminalens användarnamn och trycker på Enter. Nu följer du mitt råd ovan och du ringer läsa med en stor buffert som 256 byte.

Om läsningen fungerade som med filer, skulle den vänta på att användaren skulle skriva 256 tecken innan han återvände! Din användare skulle vänta för evigt och sedan tyvärr döda din ansökan. Det är verkligen inte vad du vill, och du skulle ha ett stort problem.

Okej, du kan läsa en bit i taget men den här lösningen är väldigt ineffektiv, som jag sa till dig ovan. Det måste fungera bättre än så.

Men Linux-utvecklare tänkte läsa annorlunda för att undvika detta problem:

  • När du läser vanliga filer försöker det så mycket som möjligt att läsa antal byte och det kommer aktivt att få byte från disk om det behövs.
  • För alla andra filtyper kommer den att returneras så snart som det finns en del data tillgängliga och som mest räkna byte:
    1. För terminaler är det allmänt när användaren trycker på Enter-tangenten.
    2. För TCP-uttag är det så snart din dator får något, det spelar ingen roll hur mycket byte det får.
    3. För FIFO eller rör är det i allmänhet samma mängd som det andra programmet skrev, men Linux-kärnan kan leverera mindre åt gången om det är mer bekvämt.

Så du kan säkert ringa med din 2 KiB-buffert utan att hålla dig låst för alltid. Observera att det också kan avbrytas om applikationen får en signal. Eftersom läsning från alla dessa källor kan ta sekunder eller till och med timmar - tills den andra sidan trots allt bestämmer sig för att skriva - avbryts av signaler gör det möjligt att sluta stanna kvar för länge.

Detta har dock också en nackdel: när du vill läsa exakt 2 KiB med dessa specialfiler måste du kontrollera läsets returvärde och samtalsläsa flera gånger. läs fyller sällan hela din buffert. Om din applikation använder signaler måste du också kontrollera om läsningen misslyckades med -1 eftersom den avbröts av en signal med hjälp av errno.

Låt mig visa dig hur det kan vara intressant att använda denna speciella egenskap att läsa:

#define _POSIX_C_SOURCE 1 / * sigaction är inte tillgängligt utan detta #define. * /
#omfatta
#omfatta
#omfatta
#omfatta
#omfatta
#omfatta
/ *
* isSignal berättar om läst syscall har avbrutits av en signal.
*
* Returnerar SANT om den lästa syscall har avbrutits av en signal.
*
* Globala variabler: den läser errno definierad i errno.h
* /
osignerad int isSignal (const ssize_t readStatus)
returnera (readStatus == -1 && errno == EINTR);

osignerad int isSyscallSuccessful (const ssize_t readStatus)
returnera readStatus> = 0;

/ *
* shouldRestartRead berättar när läst syscall har avbrutits av a
* signalhändelse eller inte, och med tanke på att detta "fel" anledning är övergående kan vi
* starta om läst samtalet säkert.
*
* För närvarande kontrollerar det bara om läsningen har avbrutits av en signal, men det
* kan förbättras för att kontrollera om målantalet byte lästes och om det är
* inte fallet, returnera SANT för att läsa igen.
*
* /
unsigned int shouldRestartRead (const ssize_t readStatus)
return isSignal (readStatus);

/ *
* Vi behöver en tom hanterare eftersom den lästa syscall avbryts endast om
* signal hanteras.
* /
ogiltig tomHandler (int ignoreras)
lämna tillbaka;

int main ()
/ * Är på några sekunder. * /
const int alarmInterval = 5;
const struct sigaction emptySigaction = emptyHandler;
char lineBuf [256] = 0;
ssize_t readStatus = 0;
osignerad int väntetid = 0;
/ * Ändra inte sigaction förutom om du exakt vet vad du gör. * /
sigaction (SIGALRM, & emptySigaction, NULL);
alarm (alarmInterval);
fputs ("Din text: \ n", stderr);
gör
/ * Glöm inte '\ 0' * /
readStatus = läs (STDIN_FILENO, lineBuf, sizeof (lineBuf) - 1);
om (isSignal (readStatus))
waitTime + = alarmInterval;
alarm (alarmInterval);
fprintf (stderr, "% u sekunder av inaktivitet ... \ n", väntetid);

while (shouldRestartRead (readStatus));
if (isSyscallSuccessful (readStatus))
/ * Avsluta strängen för att undvika ett fel när du tillhandahåller den till fprintf. * /
lineBuf [readStatus] = '\ 0';
fprintf (stderr, "Du skrev% lu tecken. Här är din sträng: \ n% s \ n ", strlen (lineBuf),
lineBuf);
annat
perror ("Läsning från stdin misslyckades");
returnera EXIT_FAILURE;

returnera EXIT_SUCCESS;

Återigen är detta en fullständig C-applikation som du kan kompilera och faktiskt köra.

Den gör följande: den läser en rad från standardinmatningen. Men var 5: e sekund skriver den ut en rad som berättar för användaren att ingen inmatning gavs ännu.

Exempel om jag väntar 23 sekunder innan jag skriver “Penguin”:

$ alarm_read
Din text:
5 sekunder av inaktivitet ..
10 sekunder av inaktivitet ..
15 sekunder av inaktivitet ..
20 sekunder av inaktivitet ..
Pingvin
Du skrev 8 tecken. Här är din sträng:
Pingvin

Det är otroligt användbart. Det kan användas för att ofta uppdatera användargränssnittet för att skriva ut läsförloppet eller behandlingen av din applikation du gör. Det kan också användas som en timeout-mekanism. Du kan också bli avbruten av någon annan signal som kan vara användbar för din applikation. Hur som helst betyder det att din ansökan nu kan vara lyhörd istället för att hålla sig fast för alltid.

Så fördelarna uppväger nackdelen som beskrivs ovan. Om du undrar om du ska stödja speciella filer i ett program som normalt fungerar med vanliga filer - och så ringer läsa i en slinga - Jag skulle säga gör det förutom om du har bråttom, min personliga erfarenhet visade ofta att att ersätta en fil med ett rör eller FIFO bokstavligen kan göra en applikation mycket mer användbar med små ansträngningar. Det finns till och med färdiga C-funktioner på Internet som implementerar slingan åt dig: det kallas readn-funktioner.

Slutsats

Som du kan se kan fread och läsa se ut, de är inte. Och med bara få förändringar av hur läsning fungerar för C-utvecklaren är läsning mycket mer intressant för att utforma nya lösningar på problemen du möter under applikationsutveckling.

Nästa gång kommer jag att berätta hur skriva syscall fungerar, eftersom läsning är cool, men att kunna göra båda är mycket bättre. Under tiden, experimentera med läsning, lära känna den och jag önskar dig ett gott nytt år!

Gratis och öppen källkodsmotorer för utveckling av Linux-spel
Den här artikeln kommer att täcka en lista över gratis motorer med öppen källkod som kan användas för att utveckla 2D- och 3D-spel på Linux. Det finns...
Shadow of the Tomb Raider for Linux Tutorial
Shadow of the Tomb Raider är det tolfte tillskottet till Tomb Raider-serien - en action-äventyrsspelfranchise skapad av Eidos Montreal. Spelet mottogs...
Hur man förbättrar FPS i Linux?
FPS står för Bildrutor per sekund. FPS: s uppgift är att mäta bildfrekvensen i videouppspelningar eller spelprestanda. Med enkla ord betecknas antalet...