C Programmēšana

Izlasiet Syscall Linux

Izlasiet Syscall Linux
Tāpēc jums ir jālasa binārie dati? Varat vēlēties lasīt no FIFO vai kontaktligzdas? Redzat, ka jūs varat izmantot C standarta bibliotēkas funkciju, taču, to darot, jūs negūsit labumu no īpašajām funkcijām, ko nodrošina Linux kodols un POSIX. Piemēram, varat izmantot taimautus, lai lasītu noteiktā laikā, neizmantojot aptauju. Tāpat jums var būt nepieciešams kaut ko izlasīt, nerūpējoties, ja tas ir īpašs fails vai kontaktligzda vai kas cits. Jūsu vienīgais uzdevums ir izlasīt kādu bināro saturu un iegūt to savā lietojumprogrammā. Tur spīd lasītais syscall.

Lasiet parastu failu ar Linux syscall

Labākais veids, kā sākt strādāt ar šo funkciju, ir parastā faila lasīšana. Šis ir vienkāršākais veids, kā izmantot šo sistēmas zvanu, un kāda iemesla dēļ: tam nav tik daudz ierobežojumu kā cita veida straumēm vai caurulēm. Ja jūs domājat par to, kas ir loģika, lasot citas lietojumprogrammas izvadi, pirms lasīšanas jums ir jābūt gatavai izejai, tāpēc jums būs jāgaida, kamēr šī lietojumprogramma rakstīs šo izvadi.

Pirmkārt, galvenā atšķirība ar standarta bibliotēku: buferizēšana vispār nav. Katru reizi, kad izsaucat lasīšanas funkciju, jūs izsauksit Linux kodolu, un tāpēc tam būs vajadzīgs laiks - tas ir gandrīz tūlītējs, ja jūs to piezvanāt vienreiz, taču tas var palēnināt, ja jūs to saucat tūkstošiem reižu sekundē. Salīdzinājumam, standarta bibliotēka ievadīs jums buferi. Tāpēc ikreiz, kad piezvanāt lasīt, vajadzētu nolasīt vairāk nekā dažus baitus, bet drīzāk lielu buferi, piemēram, dažus kilobaitus - izņemot gadījumus, ja jums ir nepieciešams maz baitu, piemēram, ja pārbaudāt, vai fails pastāv un vai tas nav tukšs.

Tomēr tam ir priekšrocība: katru reizi, kad zvanāt lasīt, esat pārliecināts, ka saņemat atjauninātos datus, ja kāda cita lietojumprogramma pašlaik modificē failu. Tas ir īpaši noderīgi īpašiem failiem, piemēram, failiem / proc vai / sys.

Laiks parādīt jums ar reālu piemēru. Šī C programma pārbauda, ​​vai fails ir PNG vai nav. Lai to izdarītu, tas nolasa failu, kas norādīts ceļā, kuru norādījāt komandrindas argumentā, un pārbauda, ​​vai pirmie 8 baiti atbilst PNG galvenei.

Šeit ir kods:

# iekļaut
# iekļaut
# iekļaut
# iekļaut
# iekļaut
# iekļaut
# iekļaut
 
typedef enum
IS_PNG,
PĀRĀK ĪSS,
INVALID_HEADER
pngStatus_t;
 
neparakstīts int isSyscallSuccessful (const ssize_t readStatus)
atgriezties readStatus> = 0;
 

 
/ *
* checkPngHeader pārbauda, ​​vai masīvs pngFileHeader atbilst PNG
* faila galvene.
*
* Pašlaik tas pārbauda tikai pirmos 8 masīva baitus. Ja masīvs ir mazāks
* nekā 8 baiti, tiek atgriezta TOO_SHORT.
*
* pngFileHeaderLength ir jāparedz tye masīva garums. Jebkura nederīga vērtība
* var izraisīt nedefinētu rīcību, piemēram, lietojumprogrammas avāriju.
*
* Atgriež IS_PNG, ja tas atbilst PNG faila galvenei. Ja tur vismaz ir
* 8 baiti masīvā, bet tas nav PNG galvene, tiek atgriezta INVALID_HEADER.
*
* /
pngStatus_t checkPngHeader (const neparakstīts char * const pngFileHeader,
size_t pngFileHeaderLength) const neparakstīta rakstzīme gaidāmaPngHeader [8] =
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A;
int i = 0;
 
ja (pngFileHeaderLength < sizeof(expectedPngHeader))
atgriezties TOO_SHORT;
 

 
par (i = 0; i < sizeof(expectedPngHeader); i++)
ja (pngFileHeader [i] != paredzamaisPngHeader [i])
atgriešanās INVALID_HEADER;
 


 
/ * Ja tas sasniedz šeit, visi pirmie 8 baiti atbilst PNG galvenei. * /
atgriešanās IS_PNG;

 
int main (int argumentsLength, char * argumentList [])
char * pngFileName = NULL;
neparakstīta rakstzīme pngFileHeader [8] = 0;
 
ssize_t readStatus = 0;
/ * Linux izmanto numuru, lai identificētu atvērtu failu. * /
int pngFails = 0;
pngStatus_t pngCheckResult;
 
ja (argumentsLength != 2)
fputs ("Jums jāzvana šai programmai, izmantojot isPng your filename.\ n ", stderr);
atgriešanās EXIT_FAILURE;
 

 
pngFileName = argumentu saraksts [1];
pngFile = atvērts (pngFileName, O_RDONLY);
 
ja (pngFile == -1)
perror ("Neizdevās atvērt norādīto failu");
atgriešanās EXIT_FAILURE;
 

 
/ * Lasiet dažus baitus, lai identificētu, vai fails ir PNG. * /
readStatus = lasīt (pngFile, pngFileHeader, sizeof (pngFileHeader));
 
if (isSyscallSuccessful (readStatus))
/ * Pārbaudiet, vai fails ir PNG, jo tas ieguva datus. * /
pngCheckResult = checkPngHeader (pngFileHeader, readStatus);
 
ja (pngCheckResult == TOO_SHORT)
printf ("Fails% s nav PNG fails: tas ir pārāk īss.\ n ", pngFileName);
 
else if (pngCheckResult == IS_PNG)
printf ("Fails% s ir PNG fails!\ n ", pngFileName);
 
cits
printf ("Fails% s nav PNG formātā.\ n ", pngFileName);
 

 
cits
perror ("Faila lasīšana neizdevās");
atgriešanās EXIT_FAILURE;
 

 
/ * Aizveriet failu ... * /
ja (aizvērt (pngFile) == -1)
perror ("Neizdevās aizvērt iesniegto failu");
atgriešanās EXIT_FAILURE;
 

 
pngFails = 0;
 
atgriešanās EXIT_SUCCESS;
 

Redziet, tas ir pilnīgs, darbīgs un apkopojams piemērs. Nevilcinieties pats to apkopot un pārbaudīt, tas patiešām darbojas. Jums vajadzētu izsaukt programmu no šāda termināļa:

./ isPng jūsu faila nosaukums

Tagad pievērsīsimies pašam lasītajam zvanam:

pngFile = atvērts (pngFileName, O_RDONLY);
ja (pngFile == -1)
perror ("Neizdevās atvērt norādīto failu");
atgriešanās EXIT_FAILURE;

/ * Lasiet dažus baitus, lai identificētu, vai fails ir PNG. * /
readStatus = lasīt (pngFile, pngFileHeader, sizeof (pngFileHeader));

Izlasītais paraksts ir šāds (iegūts no Linux rokasgrāmatām):

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

Pirmkārt, fd arguments attēlo faila deskriptoru. Es mazliet paskaidroju šo jēdzienu savā dakšu rakstā.  Faila deskriptors ir int, kas apzīmē atvērtu failu, ligzdu, cauruli, FIFO, ierīci, un tas ir daudz kas, kur datus var lasīt vai rakstīt, parasti plūsmā līdzīgā veidā. Es to sīkāk aplūkošu nākamajā rakstā.

atvērtā funkcija ir viens no veidiem, kā pateikt Linux: es gribu darīt lietas ar failu tajā ceļā, lūdzu, atrodiet to, kur tas atrodas, un dodiet man piekļuvi. Tas jums atgriezīs šo int saucamo faila deskriptoru, un tagad, ja vēlaties kaut ko darīt ar šo failu, izmantojiet šo numuru. Neaizmirstiet piezvanīt tuvu, kad esat pabeidzis failu, kā parādīts piemērā.

Tāpēc lasīšanai jums jānorāda šis īpašais numurs. Tad ir buf arguments. Šeit jums jānorāda masīva rādītājs, kurā lasot tiks glabāti jūsu dati. Visbeidzot, saskaita, cik baitu tā nolasīs.

Atgriešanās vērtība ir ssize_t tipa. Dīvains tips, vai ne? Tas nozīmē "parakstīts lielums_t", būtībā tas ir garš vidējais. Tas atgriež veiksmīgi nolasīto baitu skaitu vai -1, ja rodas problēma. Precīzu problēmas cēloni varat atrast Linux izveidotajā globālajā mainīgajā errno, kas definēts . Bet, lai izdrukātu kļūdas ziņojumu, ir labāk izmantot kļūdu, jo tas izdrukā errno jūsu vārdā.

Normālos failos - un tikai šajā gadījumā - lasīšana atgriezīs mazāk nekā skaits tikai tad, ja esat sasniedzis faila beigas. Jūsu sniegtais buf masīvs jābūt jābūt pietiekami lielam, lai ietilptu vismaz baitu skaitīšana, vai arī jūsu programma var avarēt vai izveidot drošības kļūdu.

Tagad lasīšana ir noderīga ne tikai parastajiem failiem un, ja vēlaties sajust tās lielvaras - Jā, es zinu, ka tas nav neviena Marvel komiksā, bet tam ir patiesas pilnvaras - jūs vēlaties to izmantot kopā ar citām plūsmām, piemēram, caurulēm vai kontaktligzdām. Apskatīsim to:

Linux īpašie faili un lasiet sistēmas izsaukumu

Lasītais fakts darbojas ar dažādiem failiem, piemēram, caurulēm, kontaktligzdām, FIFOs vai īpašām ierīcēm, piemēram, disku vai seriālo portu, kas padara to patiešām jaudīgāku. Izmantojot dažus pielāgojumus, jūs varat izdarīt patiešām interesantas lietas. Pirmkārt, tas nozīmē, ka jūs varat burtiski rakstīt funkcijas, kas strādā ar failu, un tā vietā to izmantot ar cauruli. Tas ir interesanti, lai pārsūtītu datus, nekad nedarbojoties diskā, nodrošinot vislabāko veiktspēju.

Tomēr tas izraisa arī īpašus noteikumus. Ņemsim piemēru par līnijas nolasīšanu no termināļa, salīdzinot ar parasto failu. Kad piezvanāt lasīt parastajā failā, tas prasa tikai dažas milisekundes uz Linux, lai iegūtu pieprasīto datu apjomu.

Bet, kas attiecas uz termināli, tas ir cits stāsts: pieņemsim, ka jūs lūdzat lietotājvārdu. Lietotājs ieraksta savu lietotājvārdu un nospiež Enter. Tagad jūs sekojat manam iepriekš sniegtajam ieteikumam un zvanāt lasīt, izmantojot lielu buferi, piemēram, 256 baitus.

Ja lasīšana darbotos tāpat kā ar failiem, pirms atgriešanās tā gaidītu, kamēr lietotājs ierakstīs 256 rakstzīmes! Jūsu lietotājs gaidīs mūžīgi un pēc tam diemžēl nogalināja jūsu lietojumprogrammu. Tas noteikti nav tas, ko vēlaties, un jums būtu liela problēma.

Labi, jūs varētu izlasīt vienu baitu vienlaikus, bet šis risinājums ir šausmīgi neefektīvs, kā es jums teicu iepriekš. Tam jādarbojas labāk nekā tam.

Bet, lai izvairītos no šīs problēmas, Linux izstrādātāji domāja lasīt citādi:

  • Lasot parastos failus, tas mēģina pēc iespējas vairāk nolasīt baitus, un, ja tas ir nepieciešams, tas aktīvi iegūs baitus no diska.
  • Visiem citiem failu tipiem tas atgriezīsies tiklīdz ir pieejami daži dati un maksimāli saskaitīt baitus:
    1. Attiecībā uz termināļiem tas ir vispārīgi kad lietotājs nospiež taustiņu Enter.
    2. Attiecībā uz TCP ligzdām tas notiek tiklīdz jūsu dators kaut ko saņem, nav svarīgi, cik baitu tas saņem.
    3. Attiecībā uz FIFO vai caurulēm tā parasti ir tāda pati summa, ko rakstīja otra lietojumprogramma, taču Linux kodols var piegādāt mazāk vienlaikus, ja tas ir ērtāk.

Tātad jūs varat droši piezvanīt, izmantojot savu 2 KiB buferi, nepaliekot uz visiem laikiem bloķēts. Ņemiet vērā, ka tas var tikt pārtraukts arī tad, ja lietojumprogramma saņem signālu. Tā kā lasīšana no visiem šiem avotiem var aizņemt sekundes vai pat stundas - līdz galu galā otra puse nolemj rakstīt - signālu pārtraukšana ļauj pārtraukt pārāk ilgu laiku bloķēšanos.

Tomēr tam ir arī trūkums: kad vēlaties precīzi nolasīt 2 KiB ar šiem īpašajiem failiem, jums jāpārbauda read atgriešanās vērtība un vairākas reizes jāsazinās ar zvanu. lasījums reti aizpildīs visu jūsu buferi. Ja jūsu lietojumprogramma izmanto signālus, jums arī jāpārbauda, ​​vai lasīšana neizdevās ar -1, jo to pārtrauca signāls, izmantojot errno.

Ļaujiet man jums parādīt, kā var būt interesanti izmantot šo īpašo īpašību:

#define _POSIX_C_SOURCE 1 / * apzīmējums nav pieejams bez šī #define. * /
# iekļaut
# iekļaut
# iekļaut
# iekļaut
# iekļaut
# iekļaut
/ *
* isSignal norāda, vai lasītais sistēmas zvans ir pārtraukts ar signālu.
*
* Atgriež vērtību PATIESA, ja lasītais sistēmas zvans ir pārtraukts ar signālu.
*
* Globālie mainīgie: tas skan errno, kas definēts errno.h
* /
neparakstīts int isSignal (const ssize_t readStatus)
atgriešanās (readStatus == -1 && errno == EINTR);

neparakstīts int isSyscallSuccessful (const ssize_t readStatus)
atgriezties readStatus> = 0;

/ *
* shouldRestartRead stāsta, kad lasāmo sistēmas zvanu pārtrauca a
* signāla notikums vai nē, un, ņemot vērā, ka šī "kļūdas" iemesls ir pārejošs, mēs varam
* droši restartējiet lasīto zvanu.
*
* Pašlaik tas pārbauda tikai to, vai lasīšana ir pārtraukta ar signālu, bet tas notiek
* varētu uzlabot, lai pārbaudītu, vai mērķa baitu skaits ir nolasīts un vai tas ir
* nav gadījums, atgrieziet TRUE, lai lasītu vēlreiz.
*
* /
neparakstīts int shouldRestartRead (const ssize_t readStatus)
atgriešanās irSignal (readStatus);

/ *
* Mums ir nepieciešams tukšs apstrādātājs, jo lasītais sistēmas zvans tiks pārtraukts tikai tad, ja
* tiek apstrādāts signāls.
* /
void emptyHandler (int ignorēts)
atgriešanās;

int main ()
/ * Ir sekundēs. * /
const int alarmInterval = 5;
const struct sigaction emptySigaction = emptyHandler;
char lineBuf [256] = 0;
ssize_t readStatus = 0;
neparakstīts int waitTime = 0;
/ * Nemainiet sigaction, ja vien jūs precīzi zināt, ko darāt. * /
sigaction (SIGALRM, & emptySigaction, NULL);
trauksme (alarmInterval);
ievades ("Jūsu teksts: \ n", stderr);
darīt
/ * Neaizmirstiet “\ 0” * /
readStatus = lasīt (STDIN_FILENO, lineBuf, sizeof (lineBuf) - 1);
if (isSignal (readStatus))
waitTime + = alarmInterval;
trauksme (alarmInterval);
fprintf (stderr, "% u secs of neaktivitāte ... \ n", waitTime);

while (shouldRestartRead (readStatus));
if (isSyscallSuccessful (readStatus))
/ * Pārtrauciet virkni, lai izvairītos no kļūdas, nodrošinot to fprintf. * /
lineBuf [readStatus] = '\ 0';
fprintf (stderr, "Jūs ievadījāt% lu rakstzīmes. Lūk, jūsu virkne: \ n% s \ n ", strlen (lineBuf),
lineBuf);
cits
perror ("Lasīšana no stdin neizdevās");
atgriešanās EXIT_FAILURE;

atgriešanās EXIT_SUCCESS;

Atkal šī ir pilna C lietojumprogramma, kuru varat apkopot un faktiski palaist.

Tas rīkojas šādi: tas nolasa rindu no standarta ievades. Tomēr ik pēc 5 sekundēm tas izdrukā līniju, kas paziņo lietotājam, ka vēl nav ievadīts ievads.

Piemērs, ja es gaidu 23 sekundes, pirms ievadu “Penguin”:

$ alarm_read
Jūsu teksts:
5 sekundes neaktivitātes…
10 sekundes neaktivitātes…
15 sekundes neaktivitātes…
20 sekundes neaktivitātes…
Pingvīns
Jūs ierakstījāt 8 rakstzīmes. Lūk, jūsu virkne:
Pingvīns

Tas ir neticami noderīgi. To var izmantot, lai bieži atjauninātu lietotāja interfeisu, lai izdrukātu jūsu veiktās lietojumprogrammas lasīšanas vai apstrādes gaitu. To var izmantot arī kā noildzes mehānismu. Jūs varētu arī pārtraukt jebkurš cits signāls, kas varētu būt noderīgs jūsu lietojumprogrammai. Jebkurā gadījumā tas nozīmē, ka jūsu lietojumprogramma tagad var būt atsaucīga, nevis palikt iesprūdusi uz visiem laikiem.

Tātad ieguvumi atsver iepriekš aprakstīto trūkumu. Ja jūs domājat, vai jums vajadzētu atbalstīt īpašus failus lietojumprogrammā, kas parasti strādā ar parastajiem failiem - un tā sauc lasīt cilpā - Es teiktu, ka dariet to, izņemot gadījumus, kad jūs steidzaties, mana personīgā pieredze bieži pierādīja, ka faila aizstāšana ar pīpi vai FIFO var burtiski padarīt lietojumprogrammu daudz noderīgāku ar nelielām pūlēm. Internetā ir pat iepriekš izveidotas C funkcijas, kas īsteno šo cilpu jums: to sauc par izlasītām funkcijām.

Secinājums

Kā redzat, rieva un lasīšana var izskatīties līdzīgi, tā nav. Ar tikai dažām izmaiņām lasītāja darbībā C izstrādātājam, lasīšana ir daudz interesantāka, lai izstrādātu jaunus risinājumus problēmām, ar kurām jūs sastopaties lietojumprogrammas izstrādes laikā.

Nākamreiz es jums pastāstīšu, kā darbojas syscall rakstīšana, jo lasīšana ir forša, bet spēja izdarīt abus ir daudz labāk. Pa to laiku eksperimentējiet ar lasīto, iepazīstiet to un es novēlu jums laimīgu Jauno gadu!

Kontrolējiet un pārvaldiet peles kustību starp vairākiem monitoriem sistēmā Windows 10
Dual Display Mouse Manager ļauj kontrolēt un konfigurēt peles kustību starp vairākiem monitoriem, palēninot tās kustību robežas tuvumā. Windows 10/8 ļ...
WinMouse ļauj jums pielāgot un uzlabot peles rādītāja kustību Windows datorā
Ja vēlaties uzlabot peles rādītāja noklusējuma funkcijas, izmantojiet bezmaksas programmatūru WinMouse. Tas pievieno vairāk funkciju, kas palīdzēs jum...
Peles kreisā klikšķa poga nedarbojas operētājsistēmā Windows 10
Ja ar klēpjdatoru vai galddatoru izmantojat īpašu peli, bet nedarbojas peles kreisās klikšķa poga kādu iemeslu dēļ operētājsistēmā Windows 10/8/7 šeit...