Урок за системни обаждания на Linux с C.

Linux System Call Tutorial With C



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

В тази статия ще използваме реални системни обаждания, за да свършим реална работа в нашата програма C. Първо, ще прегледаме дали трябва да използвате системно обаждане, след което даваме пример с извикването sendfile (), което може драстично да подобри производителността на копиране на файлове. И накрая, ще преминем през някои точки, които трябва да запомним, докато използваме системни обаждания на Linux.







Въпреки че е неизбежно ще използвате системно обаждане в даден момент от кариерата си за разработка на C, освен ако не сте насочени към висока производителност или функционалност от определен тип, библиотеката glibc и други основни библиотеки, включени в големите дистрибуции на Linux, ще се погрижат за по -голямата част от вашите нужди.



Стандартната библиотека на glibc осигурява кросплатформена, добре тествана рамка за изпълнение на функции, които иначе биха изисквали специфични за системата системни повиквания. Например, можете да прочетете файл с fscanf (), fread (), getc () и т.н., или можете да използвате системното извикване read () Linux. Функциите glibc предоставят повече функции (т.е. по -добро управление на грешки, форматиран IO и т.н.) и ще работят на всякакви системни glibc поддръжки.



От друга страна, има моменти, в които безкомпромисната производителност и точното изпълнение са от решаващо значение. Обвивката, която fread () предоставя, ще добави режийни разходи и макар и незначителна, не е напълно прозрачна. Освен това може да не искате или да се нуждаете от допълнителни функции, които опаковката предоставя. В този случай най -добре ще получите системно обаждане.





Можете също да използвате системни повиквания за изпълнение на функции, които все още не се поддържат от glibc. Ако вашето копие на glibc е актуално, това едва ли ще бъде проблем, но разработването на по -стари дистрибуции с по -нови ядра може да изисква тази техника.

Сега, след като сте прочели отказ от отговорност, предупреждения и потенциални заобикаляния, нека сега да разгледаме някои практически примери.



На кой процесор сме?

Въпрос, който повечето програми вероятно не мислят да зададат, но все пак валиден. Това е пример за системно обаждане, което не може да се дублира с glibc и не е покрито с обвивка на glibc. В този код ще извикаме извикването getcpu () директно чрез функцията syscall (). Функцията syscall работи по следния начин:

syscall(SYS_call,arg1,arg2,...);

Първият аргумент, SYS_call, е определение, което представлява номера на системното обаждане. Когато включите sys/syscall.h, те са включени. Първата част е SYS_, а втората част е името на системния разговор.

Аргументите за повикването отиват в arg1, arg2 по -горе. Някои обаждания изискват повече аргументи и те ще продължат по ред от страницата си за управление. Не забравяйте, че повечето аргументи, особено за връщането, ще изискват указатели за оформяне на масиви или памет, разпределена чрез функцията malloc.

пример1.в

#включва
#включва
#включва
#включва

intглавен() {

без подписпроцесор,възел;

// Вземете текущото ядро ​​на процесора и NUMA възела чрез системно обаждане
// Обърнете внимание, че това няма glibc обвивка, така че трябва да го извикаме директно
syscall(SYS_getcpu, &процесор, &възел,НУЛА);

// Показване на информация
printf („Тази програма работи на ядрото на процесора %u и възела NUMA %u.нн',процесор,възел);

връщане 0;

}

За да компилирате и стартирате:

Пример за gcc1.° С -o пример 1
./пример 1

За по -интересни резултати можете да завъртите нишки чрез библиотеката pthreads и след това да извикате тази функция, за да видите на кой процесор работи вашата нишка.

Sendfile: Превъзходно представяне

Sendfile предоставя отличен пример за подобряване на производителността чрез системни повиквания. Функцията sendfile () копира данни от един файлов дескриптор в друг. Вместо да използва множество функции fread () и fwrite (), sendfile извършва прехвърлянето в пространството на ядрото, като намалява режийните разходи и по този начин увеличава производителността.

В този пример ще копираме 64 MB данни от един файл в друг. В един тест ще използваме стандартните методи за четене/запис в стандартната библиотека. В другата ще използваме системни повиквания и извикването sendfile (), за да прехвърлим тези данни от едно място на друго.

test1.c (glibc)

#включва
#включва
#включва
#включва

#define BUFFER_SIZE 67108864
#define BUFFER_1 'buffer1'
#define BUFFER_2 'buffer2'

intглавен() {

ФАЙЛ*погрешно, *край;

printf ('нI/O тест с традиционни glibc функции.нн');

// Вземете буфер BUFFER_SIZE.
// Буферът ще съдържа случайни данни, но не ни интересува това.
printf ('Разпределяне на 64 MB буфер:');
char *буфер= (char *) malloc (РАЗМЕР НА БУФЕРА);
printf ('СВЪРШЕНн');

// Запишете буфера във fOut
printf ('Записване на данни в първия буфер:');
погрешно= fopen (BUFFER_1, 'wb');
писане (буфер, размер на(char),РАЗМЕР НА БУФЕРА,погрешно);
отблизо (погрешно);
printf ('СВЪРШЕНн');

printf ('Копиране на данни от първия файл във втория:');
край= fopen (BUFFER_1, 'rb');
погрешно= fopen (BUFFER_2, 'wb');
fread (буфер, размер на(char),РАЗМЕР НА БУФЕРА,край);
писане (буфер, размер на(char),РАЗМЕР НА БУФЕРА,погрешно);
отблизо (край);
отблизо (погрешно);
printf ('СВЪРШЕНн');

printf ('Освобождаване на буфер:');
Безплатно (буфер);
printf ('СВЪРШЕНн');

printf ('Изтриване на файлове:');
Премахване (BUFFER_1);
Премахване (BUFFER_2);
printf ('СВЪРШЕНн');

връщане 0;

}

test2.c (системни повиквания)

#включва
#включва
#включва
#включва
#включва
#включва
#включва
#включва
#включва

#define BUFFER_SIZE 67108864

intглавен() {

intпогрешно,край;

printf ('нI/O тест с sendfile () и свързани системни повиквания.нн');

// Вземете буфер BUFFER_SIZE.
// Буферът ще съдържа случайни данни, но не ни интересува това.
printf ('Разпределяне на 64 MB буфер:');
char *буфер= (char *) malloc (РАЗМЕР НА БУФЕРА);
printf ('СВЪРШЕНн');


// Запишете буфера във fOut
printf ('Записване на данни в първия буфер:');
погрешно=отворен('buffer1',O_RDONLY);
пиши(погрешно, &буфер,РАЗМЕР НА БУФЕРА);
близо(погрешно);
printf ('СВЪРШЕНн');

printf ('Копиране на данни от първия файл във втория:');
край=отворен('buffer1',O_RDONLY);
погрешно=отворен('buffer2',O_RDONLY);
sendfile(погрешно,край, 0,РАЗМЕР НА БУФЕРА);
близо(край);
близо(погрешно);
printf ('СВЪРШЕНн');

printf ('Освобождаване на буфер:');
Безплатно (буфер);
printf ('СВЪРШЕНн');

printf ('Изтриване на файлове:');
прекратяване на връзката('buffer1');
прекратяване на връзката('buffer2');
printf ('СВЪРШЕНн');

връщане 0;

}

Съставяне и изпълнение на тестове 1 и 2

За да изградите тези примери, ще ви трябват инструменти за разработка, инсталирани във вашата дистрибуция. На Debian и Ubuntu можете да инсталирате това с:

подходящИнсталирайосновни компоненти

След това компилирайте с:

gcctest1.c-илитест1&& gcctest2.c-илитест2

За да стартирате и двете и да тествате производителността, изпълнете:

време./тест1&& време./тест2

Трябва да получите такива резултати:

I/O тест с традиционни glibc функции.

Разпределяне на буфер от 64 MB: ГОТОВО
Записване на данни в първия буфер: ГОТОВО
Копиране на данни от първия файл във втори: ГОТОВО
Освобождаващ буфер: ГОТОВО
Изтриване на файлове: ГОТОВО
реални 0m0.397s
потребител 0m0.000s
sys 0m0.203s
I/O тест с sendfile () и свързани системни повиквания.
Разпределяне на буфер от 64 MB: ГОТОВО
Записване на данни в първия буфер: ГОТОВО
Копиране на данни от първия файл във втори: ГОТОВО
Освобождаващ буфер: ГОТОВО
Изтриване на файлове: ГОТОВО
реални 0m0.019s
потребител 0m0.000s
sys 0m0.016s

Както можете да видите, кодът, който използва системните повиквания, работи много по -бързо от еквивалента на glibc.

Неща, които трябва да запомните

Системните обаждания могат да увеличат производителността и да осигурят допълнителна функционалност, но те не са без недостатъците си. Ще трябва да прецените предимствата, които системните повиквания предоставят, срещу липсата на преносимост на платформата и понякога намалена функционалност в сравнение с библиотечните функции.

Когато използвате някои системни повиквания, трябва да се погрижите да използвате ресурсите, върнати от системните обаждания, а не библиотечните функции. Например структурата FILE, използвана за функциите glibc fopen (), fread (), fwrite () и fclose (), не е същата като номера на файловия дескриптор от системното извикване open () (върнато като цяло число). Смесването им може да доведе до проблеми.

Като цяло системните обаждания на Linux имат по -малко ленти за броня, отколкото функциите glibc. Макар да е вярно, че системните обаждания имат известна обработка на грешки и докладване, ще получите по -подробна функционалност от функция glibc.

И накрая, няколко думи за сигурността. Системните повиквания директно взаимодействат с ядрото. Ядрото на Linux има обширна защита срещу измамници от земята на потребителите, но съществуват неоткрити грешки. Не вярвайте, че системното обаждане ще потвърди вашите данни или ще ви изолира от проблеми със сигурността. Разумно е да се гарантира, че данните, които предавате на системно обаждане, са дезинфекцирани. Естествено, това е добър съвет за всяко извикване на API, но не можете да бъдете внимателни, когато работите с ядрото.

Надявам се да ви хареса това по -дълбоко гмуркане в страната на системните обаждания на Linux. За пълен списък на системните повиквания на Linux вижте нашия главен списък.