C Програмування

Підручник з системних викликів Linux із C

Підручник з системних викликів Linux із C
У нашій останній статті про системні дзвінки Linux я визначив системний виклик, обговорив причини, за якими їх можна використовувати в програмі, і заглибився в їх переваги та недоліки. Я навіть дав короткий приклад збірки в C. Він проілюстрував суть справи та описав, як здійснити дзвінок, але нічого продуктивного не зробив. Не зовсім захоплююча вправа для розвитку, але вона проілюструвала суть.

У цій статті ми будемо використовувати фактичні системні дзвінки для реальної роботи в нашій програмі C. Спочатку ми розглянемо, чи потрібно вам використовувати системний виклик, а потім надамо приклад із використанням виклику sendfile (), який може суттєво покращити продуктивність копіювання файлів. Нарешті, ми розглянемо деякі моменти, які слід пам’ятати під час використання системних дзвінків Linux.

Вам потрібен системний дзвінок?

Хоча це неминуче, ви будете використовувати системний дзвінок у певний момент вашої кар’єри розробника C, якщо ви не націлитесь на високу продуктивність або функціональність певного типу, бібліотека glibc та інші основні бібліотеки, включені до основних дистрибутивів Linux, подбають про більшість ваші потреби.

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

З іншого боку, бувають випадки, коли безкомпромісні показники та точне виконання є критичними. Обгортка, яку надає fread (), додасть накладні витрати, і хоча незначна, але не зовсім прозора. Крім того, ви можете не захотіти або не потребувати додаткових функцій, які надає обгортка. У такому випадку вам найкраще забезпечити системний дзвінок.

Ви також можете використовувати системні дзвінки для виконання функцій, які ще не підтримуються glibc. Якщо ваша копія glibc оновлена, це навряд чи буде проблемою, але розробка на старих дистрибутивах з новішими ядрами може зажадати цієї техніки.

Тепер, коли ви прочитали застереження, застереження та потенційні об’їзні шляхи, тепер давайте розглянемо кілька практичних прикладів.

На якому процесорі ми?

Питання, яке більшість програм, мабуть, і не думають задавати, але тим не менш слушне. Це приклад системного виклику, який не можна продублювати за допомогою glibc і не покритий обгорткою glibc. У цьому коді ми будемо викликати виклик getcpu () безпосередньо через функцію syscall (). Функція syscall працює наступним чином:

syscall (SYS_call, arg1, arg2,…);

Перший аргумент, SYS_call, є визначенням, яке представляє номер системного виклику. Коли ви включаєте sys / syscall.h, вони включені. Перша частина - SYS_, а друга частина - ім'я системного виклику.

Аргументи для дзвінка переходять до аргументів arg1, arg2 вище. Деякі дзвінки вимагають більшої кількості аргументів, і вони продовжуватимуть порядок зі своєї довідкової сторінки. Пам’ятайте, що для більшості аргументів, особливо для повернень, потрібні вказівники на позначення масивів або пам’яті, виділених за допомогою функції malloc.

приклад1.c

#включати
#включати
#включати
#включати
 
int main ()
 
беззнаковий процесор, вузол;
 
// Отримати поточне ядро ​​процесора та вузол NUMA за допомогою системного виклику
// Зверніть увагу, що у нас немає обгортки glibc, тому ми повинні викликати її безпосередньо
syscall (SYS_getcpu, & cpu, & node, NULL);
 
// Відображення інформації
printf ("Ця програма працює на ядрі процесора% u та на вузлі NUMA% u.\ n \ n ", процесор, вузол);
 
повернути 0;
 

 
Щоб скомпілювати та запустити:
 
gcc приклад1.c -o приклад1
./ приклад1

Для отримання більш цікавих результатів ви можете обертати потоки через бібліотеку pthreads, а потім викликати цю функцію, щоб побачити, на якому процесорі працює ваш потік.

Sendfile: Чудова продуктивність

Sendfile - чудовий приклад підвищення продуктивності за допомогою системних дзвінків. Функція sendfile () копіює дані з одного дескриптора файлу в інший. Замість використання декількох функцій fread () і fwrite (), sendfile виконує передачу в просторі ядра, зменшуючи накладні витрати і тим самим підвищуючи продуктивність.

У цьому прикладі ми збираємося скопіювати 64 МБ даних з одного файлу в інший. В одному тесті ми будемо використовувати стандартні методи читання / запису у стандартній бібліотеці. В іншому ми використовуватимемо системні дзвінки та виклик sendfile () для передачі цих даних з одного місця в інше.

тест1.c (glibc)

#включати
#включати
#включати
#включати
 
#define BUFFER_SIZE 67108864
#define BUFFER_1 "buffer1"
#define BUFFER_2 "buffer2"
 
int main ()
 
ФАЙЛ * fOut, * fIn;
 
printf ("\ n Тест вводу / виводу з традиційними функціями glibc.\ n \ n ");
 
// Візьміть буфер BUFFER_SIZE.
// Буфер матиме випадкові дані, але нас це не хвилює.
printf ("Виділення буфера 64 МБ:");
char * buffer = (char *) malloc (BUFFER_SIZE);
printf ("ВИКОНАНО \ n");
 
// Запишіть буфер у fOut
printf ("Запис даних у перший буфер:");
fOut = fopen (BUFFER_1, "wb");
fwrite (буфер, sizeof (char), BUFFER_SIZE, fOut);
fclose (fOut);
printf ("ВИКОНАНО \ n");
 
printf ("Копіювання даних з першого файлу у другий:");
fIn = fopen (BUFFER_1, "rb");
fOut = fopen (BUFFER_2, "wb");
fread (буфер, sizeof (char), BUFFER_SIZE, fIn);
fwrite (буфер, sizeof (char), BUFFER_SIZE, fOut);
fclose (fIn);
fclose (fOut);
printf ("ВИКОНАНО \ n");
 
printf ("Звільнення буфера:");
безкоштовно (буфер);
printf ("ВИКОНАНО \ n");
 
printf ("Видалення файлів:");
видалити (BUFFER_1);
видалити (BUFFER_2);
printf ("ВИКОНАНО \ n");
 
повернути 0;
 

тест2.c (системні дзвінки)

#включати
#включати
#включати
#включати
#включати
#включати
#включати
#включати
#включати
 
#define BUFFER_SIZE 67108864
 
int main ()
 
int fOut, fIn;
 
printf ("\ n Тест вводу / виводу за допомогою sendfile () та відповідних системних викликів.\ n \ n ");
 
// Візьміть буфер BUFFER_SIZE.
// Буфер матиме випадкові дані, але нас це не хвилює.
printf ("Виділення буфера 64 МБ:");
char * buffer = (char *) malloc (BUFFER_SIZE);
printf ("ВИКОНАНО \ n");
 
// Запишіть буфер у fOut
printf ("Запис даних у перший буфер:");
fOut = відкритий ("buffer1", O_RDONLY);
запис (fOut, & buffer, BUFFER_SIZE);
закрити (fOut);
printf ("ВИКОНАНО \ n");
 
printf ("Копіювання даних з першого файлу у другий:");
fIn = відкритий ("buffer1", O_RDONLY);
fOut = відкритий ("buffer2", O_RDONLY);
файл посилання (fOut, fIn, 0, BUFFER_SIZE);
закрити (fIn);
закрити (fOut);
printf ("ВИКОНАНО \ n");
 
printf ("Звільнення буфера:");
безкоштовно (буфер);
printf ("ВИКОНАНО \ n");
 
printf ("Видалення файлів:");
від'єднати ("buffer1");
від'єднати ("buffer2");
printf ("ВИКОНАНО \ n");
 
повернути 0;
 

Складання та виконання тестів 1 і 2

Для побудови цих прикладів вам знадобляться інструменти розробки, встановлені у вашому дистрибутиві. На Debian та Ubuntu ви можете встановити це за допомогою:

apt встановити основи побудови

Потім скомпілюйте за допомогою:

тест gcc1.c -o test1 && gcc test2.c -o тест2

Щоб запустити обидва і перевірити продуктивність, запустіть:

час ./ test1 && час ./ тест2

Ви повинні отримати такі результати:

Тест вводу-виводу з традиційними функціями glibc.

Виділення буфера обсягом 64 МБ: ГОТОВО
Запис даних у перший буфер: ГОТОВО
Копіювання даних з першого файлу у другий: ГОТОВО
Буфер звільнення: ГОТОВО
Видалення файлів: ГОТОВО
реальна 0м0.397-ті
користувач 0m0.000
sys 0m0.203-ті
Тест вводу / виводу за допомогою sendfile () та відповідних системних викликів.
Виділення буфера обсягом 64 МБ: ГОТОВО
Запис даних у перший буфер: ГОТОВО
Копіювання даних з першого файлу у другий: ГОТОВО
Буфер звільнення: ГОТОВО
Видалення файлів: ГОТОВО
реальна 0м0.019-ті
користувач 0m0.000
sys 0m0.016с

Як бачите, код, який використовує системні виклики, працює набагато швидше, ніж еквівалент glibc.

Що слід пам’ятати

Системні дзвінки можуть підвищити продуктивність і надати додаткові функціональні можливості, але вони не позбавлені недоліків. Вам доведеться зважити переваги системних дзвінків із відсутністю переносимості платформи та інколи зниженою функціональністю порівняно з функціями бібліотеки.

Використовуючи деякі системні виклики, ви повинні подбати про те, щоб використовувати ресурси, що повертаються із системних викликів, а не функції бібліотеки. Наприклад, структура FILE, яка використовується для функцій glibc fopen (), fread (), fwrite () та fclose (), не збігається з номером дескриптора файлу із системного виклику open () (повертається як ціле число). Поєднання їх може призвести до проблем.

Загалом, системні дзвінки Linux мають менше смуг захисту, ніж функції glibc. Хоча це правда, що системні дзвінки мають деяку обробку помилок та звітування, ви отримаєте більш детальну функціональність за допомогою функції glibc.

І нарешті, слово про безпеку. Системні виклики безпосередньо взаємодіють з ядром. Ядро Linux дійсно має широкий захист від зловживань з боку користувачів, але невиявлені помилки існують. Не вірте, що системний дзвінок перевірить ваші дані або ізолює вас від проблем із безпекою. Доцільно забезпечити дезінфекцію даних, які ви передаєте для системного дзвінка. Звичайно, це хороша порада для будь-якого виклику API, але ви не можете бути обережними при роботі з ядром.

Сподіваюся, вам сподобалось це глибше занурення у країну системних дзвінків Linux. Повний список системних дзвінків Linux див. У нашому головному списку.

Best Linux Distros for Gaming in 2021
The Linux operating system has come a long way from its original, simple, server-based look. This OS has immensely improved in recent years and has no...
Як зафіксувати та транслювати ігровий сеанс на Linux
Раніше грати в ігри вважалося лише хобі, але з часом ігрова індустрія спостерігала величезний ріст з точки зору технологій та кількості гравців. Аудит...
Найкращі ігри для гри з відстеженням рук
Нещодавно Oculus Quest представив чудову ідею відстеження рук без контролерів. Завдяки постійно зростаючій кількості ігор та заходів, які виконують пі...