C Програмиране

Прочетете Syscall Linux

Прочетете Syscall Linux
Така че трябва да четете двоични данни? Може да искате да четете от FIFO или гнездо? Виждате ли, можете да използвате стандартната функция на библиотеката C, но като направите това, няма да се възползвате от специалните функции, предоставени от Linux Kernel и POSIX. Например, може да искате да използвате таймаути за четене в определен час, без да прибягвате до анкета. Освен това може да се наложи да прочетете нещо, без да се интересувате дали това е специален файл или сокет или нещо друго. Единствената ви задача е да прочетете някои двоични съдържания и да ги вземете във вашето приложение. Там блести прочетеният syscall.

Прочетете нормален файл с Linux syscall

Най-добрият начин да започнете да работите с тази функция е като прочетете нормален файл. Това е най-простият начин да използвате този syscall и по причина: той няма толкова ограничения, колкото другите видове поток или тръба. Ако мислите за това, това е логика, когато четете изхода на друго приложение, трябва да имате готовност за изход, преди да го прочетете и така ще трябва да изчакате това приложение да напише този изход.

Първо, ключова разлика със стандартната библиотека: изобщо няма буфериране. Всеки път, когато извикате функцията за четене, ще извикате ядрото на Linux и така това ще отнеме време - почти мигновено е, ако го извикате веднъж, но може да ви забави, ако го извикате хиляди пъти в секунда. За сравнение стандартната библиотека ще буферира въведеното за вас. Така че, когато се обадите за четене, трябва да прочетете повече от няколко байта, а по-скоро голям буфер като няколко килобайта - освен ако това, от което се нуждаете, е наистина малко байта, например ако проверите дали файлът съществува и не е празен.

Това обаче има предимство: всеки път, когато се обадите за четене, сте сигурни, че получавате актуализираните данни, ако някое друго приложение модифицира файла в момента. Това е особено полезно за специални файлове като тези в / proc или / sys.

Време е да ви покажа с реален пример. Тази програма C проверява дали файлът е PNG или не. За целта той чете файла, посочен в пътя, който предоставяте в аргумента на командния ред, и проверява дали първите 8 байта съответстват на PNG заглавката.

Ето кода:

#include
#include
#include
#include
#include
#include
#include
 
typedef enum
IS_PNG,
ТВЪРДЕ КРАТЪК,
INVALID_HEADER
pngStatus_t;
 
неподписан int isSyscallSuccessful (const ssize_t readStatus)
връщане readStatus> = 0;
 

 
/ *
* checkPngHeader проверява дали масивът pngFileHeader съответства на PNG
* заглавна част на файла.
*
* В момента той проверява само първите 8 байта от масива. Ако масивът е по-малък
* от 8 байта се връща TOO_SHORT.
*
* pngFileHeaderLength трябва да поддържа kength на tye масива. Всяка невалидна стойност
* може да доведе до недефинирано поведение, като например срив на приложението.
*
* Връща IS_PNG, ако отговаря на заглавка на PNG файл. Ако има поне
* 8 байта в масива, но това не е PNG заглавка, се връща INVALID_HEADER.
*
* /
pngStatus_t checkPngHeader (const неподписан знак * const pngFileHeader,
size_t pngFileHeaderLength) const неподписан знак, очакванPngHeader [8] =
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A;
int i = 0;
 
if (pngFileHeaderLength < sizeof(expectedPngHeader))
връщане TOO_SHORT;
 

 
за (i = 0; i < sizeof(expectedPngHeader); i++)
if (pngFileHeader [i] != очакванPngHeader [i])
връщане INVALID_HEADER;
 


 
/ * Ако достигне тук, всички първите 8 байта отговарят на PNG заглавие. * /
връщане IS_PNG;

 
int main (int argumentLength, char * argumentList [])
char * pngFileName = NULL;
неподписан знак pngFileHeader [8] = 0;
 
ssize_t readStatus = 0;
/ * Linux използва номер за идентифициране на отворен файл. * /
int pngFile = 0;
pngStatus_t pngCheckResult;
 
if (argumentLength != 2)
fputs ("Трябва да извикате тази програма, използвайки isPng вашето име на файл.\ n ", stderr);
връщане EXIT_FAILURE;
 

 
pngFileName = argumentList [1];
pngFile = отворен (pngFileName, O_RDONLY);
 
ако (pngFile == -1)
perror („Отварянето на предоставения файл не бе успешно“);
връщане EXIT_FAILURE;
 

 
/ * Прочетете няколко байта, за да установите дали файлът е PNG. * /
readStatus = read (pngFile, pngFileHeader, sizeof (pngFileHeader));
 
if (isSyscallSuccessful (readStatus))
/ * Проверете дали файлът е PNG, тъй като е получил данните. * /
pngCheckResult = checkPngHeader (pngFileHeader, readStatus);
 
if (pngCheckResult == TOO_SHORT)
printf ("Файлът% s не е PNG файл: той е твърде кратък.\ n ", pngFileName);
 
иначе ако (pngCheckResult == IS_PNG)
printf ("Файлът% s е PNG файл!\ n ", pngFileName);
 
друго
printf ("Файлът% s не е във формат PNG.\ n ", pngFileName);
 

 
друго
perror („Четенето на файла не бе успешно“);
връщане EXIT_FAILURE;
 

 
/ * Затворете файла ... * /
if (close (pngFile) == -1)
perror („Неуспешно затваряне на предоставения файл“);
връщане EXIT_FAILURE;
 

 
pngFile = 0;
 
връщане EXIT_SUCCESS;
 

Вижте, това е пълен пример, работещ и компилируем пример. Не се колебайте да го компилирате сами и да го тествате, наистина работи. Трябва да извикате програмата от терминал по следния начин:

./ isPng вашето име на файл

Нека сега се съсредоточим върху самото повикване за четене:

pngFile = отворен (pngFileName, O_RDONLY);
ако (pngFile == -1)
perror („Отварянето на предоставения файл не бе успешно“);
връщане EXIT_FAILURE;

/ * Прочетете няколко байта, за да установите дали файлът е PNG. * /
readStatus = read (pngFile, pngFileHeader, sizeof (pngFileHeader));

Подписът за четене е следният (извлечен от man-страниците на Linux):

ssize_t четене (int fd, void * buf, size_t count);

Първо, аргументът fd представлява дескриптора на файла. Обясних малко тази концепция в статията си за вилицата.  Файловият дескриптор е int, представляващ отворен файл, сокет, тръба, FIFO, устройство, добре, това е много неща, при които данните могат да се четат или записват, обикновено по начин, подобен на поток. Ще разгледам по-задълбочено за това в следваща статия.

отворената функция е един от начините да кажа на Linux: Искам да направя нещата с файла по този път, моля, намерете го там, където е и ми дайте достъп до него. Ще ви върне този int, наречен дескриптор на файл и сега, ако искате да направите нещо с този файл, използвайте този номер. Не забравяйте да се обадите на close, когато приключите с файла, както в примера.

Затова трябва да предоставите този специален номер за четене. Тогава има аргумент buf. Тук трябва да предоставите указател към масива, където четенето ще съхранява вашите данни. И накрая, броят е колко байта ще прочете най-много.

Връщаната стойност е от тип ssize_t. Странен тип, нали? Това означава "подписан размер_т", всъщност това е дълъг int. Той връща броя на байтовете, които успешно чете, или -1, ако има проблем. Можете да намерите точната причина за проблема в грешната глобална променлива, създадена от Linux, дефинирана в . Но за да отпечатате съобщение за грешка, използването на perror е по-добре, тъй като отпечатва грешно от ваше име.

В нормални файлове - и само в този случай - четенето ще върне по-малко от броя, само ако сте стигнали до края на файла. Предоставеният от вас масив buf трябва да да бъде достатъчно голям, за да побере поне байтове, или вашата програма може да се срине или да създаде грешка в сигурността.

Сега четенето не е полезно само за нормални файлове и ако искате да усетите неговите супер-сили - Да, знам, че не е в нито един комикс на Marvel, но има истински сили - ще искате да го използвате с други потоци като тръби или контакти. Нека да разгледаме това:

Специални файлове на Linux и системно обаждане за четене

Фактът четене работи с различни файлове като тръби, контакти, FIFO или специални устройства като диск или сериен порт е това, което го прави наистина по-мощен. С някои адаптации можете да правите наистина интересни неща. Първо, това означава, че можете буквално да пишете функции, работещи върху файл, и вместо това да ги използвате с тръба. Интересно е да предавате данни, без никога да удряте диска, осигурявайки най-добра производителност.

Това обаче задейства и специални правила. Да вземем примера за четене на ред от терминал в сравнение с нормален файл. Когато извикате четене на нормален файл, на Linux му трябват само няколко милисекунди, за да получите количеството данни, които искате.

Но що се отнася до терминала, това е друга история: да речем, че поискате потребителско име. Потребителят пише в терминала своето потребителско име и натиска Enter. Сега следвате съвета ми по-горе и се обаждате на read с голям буфер като 256 байта.

Ако четенето работи както при файловете, ще изчака потребителят да напише 256 знака, преди да се върне! Потребителят ви ще чака завинаги и след това за съжаление ще убие вашето приложение. Със сигурност не е това, което искате, и бихте имали голям проблем.

Добре, можете да четете по един байт, но това решение е ужасно неефективно, както ви казах по-горе. Трябва да работи по-добре от това.

Но разработчиците на Linux смятат, че четат по различен начин, за да избегнат този проблем:

  • Когато четете нормални файлове, той се опитва колкото е възможно повече да прочете броя на байтовете и ще получи активно байтове от диска, ако това е необходимо.
  • За всички останали типове файлове той ще се върне възможно най-скоро има налични данни и най-много броя байтове:
    1. За терминалите е така в общи линии когато потребителят натисне клавиша Enter.
    2. За TCP сокетите, веднага щом компютърът получи нещо, няма значение количеството байтове, които получава.
    3. За FIFO или тръби обикновено е същата сума като тази, която пише другото приложение, но ядрото на Linux може да доставя по-малко наведнъж, ако това е по-удобно.

Така че можете безопасно да се обаждате с вашия буфер от 2 KiB, без да оставате заключени завинаги. Имайте предвид, че може също да се прекъсне, ако приложението получи сигнал. Тъй като четенето от всички тези източници може да отнеме секунди или дори часове - докато другата страна не реши да пише, в края на краищата - прекъсването от сигнали позволява да спрете да оставате блокирани твърде дълго.

Това обаче има и недостатък: когато искате да прочетете точно 2 KiB с тези специални файлове, ще трябва да проверите връщаната стойност на четенето и да извикате четене няколко пъти. read рядко ще запълни целия ви буфер. Ако вашето приложение използва сигнали, ще трябва също да проверите дали четенето е неуспешно с -1, защото е било прекъснато от сигнал, като използвате errno.

Позволете ми да ви покажа как може да бъде интересно да използвате това специално свойство на read:

#define _POSIX_C_SOURCE 1 / * sigaction не се предлага без това #define. * /
#include
#include
#include
#include
#include
#include
/ *
* isSignal казва дали прочетеният syscall е бил прекъснат от сигнал.
*
* Връща TRUE, ако прочетеният syscall е бил прекъснат от сигнал.
*
* Глобални променливи: чете errno, дефинирано в errno.з
* /
неподписан int isSignal (const ssize_t readStatus)
връщане (readStatus == -1 && errno == EINTR);

неподписан int isSyscallSuccessful (const ssize_t readStatus)
връщане readStatus> = 0;

/ *
* shouldRestartRead казва кога четеният syscall е прекъснат от
* сигнализира събитие или не и като се има предвид, че тази причина за "грешка" е преходна, можем
* рестартирайте безопасно прочетеното повикване.
*
* В момента той проверява само дали четенето е било прекъснато от сигнал, но той
* може да се подобри, за да се провери дали целевият брой байтове е прочетен и дали е
* не е случаят, върнете TRUE, за да прочетете отново.
*
* /
неподписан int shouldRestartRead (const ssize_t readStatus)
return isSignal (readStatus);

/ *
* Имаме нужда от празен манипулатор, тъй като прочетеният syscall ще бъде прекъснат само ако
* сигнал се обработва.
* /
void emptyHandler (int се игнорира)
връщане;

int main ()
/ * Това е за секунди. * /
const int alarmInterval = 5;
const struct sigaction emptySigaction = emptyHandler;
char lineBuf [256] = 0;
ssize_t readStatus = 0;
неподписано int waitTime = 0;
/ * Не променяйте sigaction, освен ако точно знаете какво правите. * /
sigaction (SIGALRM, & emptySigaction, NULL);
аларма (alarmInterval);
fputs ("Вашият текст: \ n", stderr);
правя
/ * Не забравяйте '\ 0' * /
readStatus = четене (STDIN_FILENO, lineBuf, sizeof (lineBuf) - 1);
if (isSignal (readStatus))
waitTime + = alarmInterval;
аларма (alarmInterval);
fprintf (stderr, "% u секунди бездействие ... \ n", чакане);

while (shouldRestartRead (readStatus));
if (isSyscallSuccessful (readStatus))
/ * Прекратете низа, за да избегнете грешка, когато го предоставяте на fprintf. * /
lineBuf [readStatus] = '\ 0';
fprintf (stderr, "Написахте символи% lu. Ето вашия низ: \ n% s \ n ", strlen (lineBuf),
lineBuf);
друго
perror ("Четенето от stdin не бе успешно");
връщане EXIT_FAILURE;

връщане EXIT_SUCCESS;

Още веднъж, това е пълно приложение на C, което можете да компилирате и действително да стартирате.

Прави следното: чете ред от стандартен вход. На всеки 5 секунди обаче той отпечатва ред, който казва на потребителя, че все още не е даден вход.

Пример, ако изчакам 23 секунди, преди да напиша „Пингвин“:

$ alarm_read
Твоят текст:
5 секунди бездействие ..
10 секунди бездействие ..
15 секунди бездействие ..
20 секунди бездействие ..
Пингвин
Набрахте 8 символа. Ето вашия низ:
Пингвин

Това е невероятно полезно. Той може да се използва за често актуализиране на потребителския интерфейс, за да отпечата хода на четенето или на обработката на приложението, което правите. Може да се използва и като механизъм за изчакване. Можете също така да бъдете прекъснати от всеки друг сигнал, който може да бъде полезен за вашето приложение. Както и да е, това означава, че приложението ви вече може да реагира, вместо да остане заседнало завинаги.

Така че ползите надвишават гореописания недостатък. Ако се чудите дали да не поддържате специални файлове в приложение, което обикновено работи с нормални файлове - и така призовава Прочети в цикъл - Бих казал, че го направете, освен ако бързате, личният ми опит често доказва, че замяната на файл с тръба или FIFO може буквално да направи приложението много по-полезно с малки усилия. Има дори готови функции C в Интернет, които реализират този цикъл вместо вас: той се нарича readn функции.

Заключение

Както можете да видите, fread и read може да изглеждат сходно, те не са. И само с няколко промени в това как четенето работи за разработчика C, четенето е много по-интересно за проектиране на нови решения на проблемите, които срещате по време на разработването на приложения.

Следващия път ще ви разкажа как работи syscall за писане, тъй като четенето е страхотно, но да можете да правите и двете е много по-добре. Междувременно експериментирайте с четене, опознайте го и ви пожелавам честита Нова година!

Как да променяте настройките на мишката и тъчпада с помощта на Xinput в Linux
Повечето дистрибуции на Linux се доставят с библиотека “libinput” по подразбиране за обработка на входни събития в системата. Той може да обработва вх...
Пренастройте бутоните на мишката си по различен начин за различен софтуер с X-Mouse Button Control
Може би се нуждаете от инструмент, който може да промени контрола на мишката с всяко приложение, което използвате. Ако случаят е такъв, можете да изпр...
Преглед на безжична мишка на Microsoft Sculpt Touch
Наскоро прочетох за Microsoft Sculpt Touch безжична мишка и реших да я купя. След като го използвах известно време, реших да споделя опита си с него. ...