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

Учебное пособие по системным вызовам Linux с C

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

В этой статье мы собираемся использовать реальные системные вызовы для реальной работы в нашей программе на C. Сначала мы рассмотрим, нужно ли вам использовать системный вызов, а затем предоставим пример с использованием вызова sendfile (), который может значительно повысить производительность копирования файлов. Наконец, мы рассмотрим некоторые моменты, которые следует помнить при использовании системных вызовов Linux.

Вам нужен системный вызов?

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

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

С другой стороны, бывают случаи, когда бескомпромиссная производительность и точное исполнение имеют решающее значение. Обертка, которую предоставляет fread (), добавит накладных расходов и, хотя и незначительна, не полностью прозрачна. Кроме того, вам могут не понадобиться или не потребуются дополнительные функции, которые предоставляет оболочка. В этом случае лучше всего использовать системный вызов.

Вы также можете использовать системные вызовы для выполнения функций, которые еще не поддерживаются glibc. Если ваша копия glibc обновлена, это вряд ли будет проблемой, но для разработки на старых дистрибутивах с новыми ядрами может потребоваться этот метод.

Теперь, когда вы прочитали заявления об отказе от ответственности, предупреждения и возможные обходные пути, давайте рассмотрим несколько практических примеров.

Какой у нас процессор?

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

системный вызов (SYS_call, arg1, arg2,…);

Первый аргумент SYS_call - это определение, представляющее номер системного вызова. Когда вы включаете sys / syscall.ч, они включены. Первая часть - это SYS_, а вторая - имя системного вызова.

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

example1.c

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

 
Для компиляции и запуска:
 
gcc example1.c -o example1
./ example1

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

Sendfile: превосходная производительность

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

В этом примере мы собираемся скопировать 64 МБ данных из одного файла в другой. В одном тесте мы собираемся использовать стандартные методы чтения / записи в стандартной библиотеке. С другой стороны, мы будем использовать системные вызовы и вызов sendfile () для переноса этих данных из одного места в другое.

test1.c (glibc)

#включать
#включать
#включать
#включать
 
#define BUFFER_SIZE 67108864
#define BUFFER_1 "buffer1"
#define BUFFER_2 "buffer2"
 
int main ()
 
FILE * fOut, * fIn;
 
printf ("\ nТест ввода-вывода с традиционными функциями glibc.\ n \ n ");
 
// Захватываем буфер BUFFER_SIZE.
// В буфере будут случайные данные, но нас это не волнует.
printf ("Выделение буфера 64 МБ:");
символ * буфер = (символ *) malloc (BUFFER_SIZE);
printf ("ВЫПОЛНЕНО \ n");
 
// Записываем буфер в fOut
printf ("Запись данных в первый буфер:");
fOut = fopen (БУФЕР_1, "wb");
fwrite (буфер, sizeof (char), BUFFER_SIZE, fOut);
fclose (fOut);
printf ("ВЫПОЛНЕНО \ n");
 
printf ("Копирование данных из первого файла во второй:");
fIn = fopen (БУФЕР_1, "rb");
fOut = fopen (БУФЕР_2, "wb");
fread (буфер, sizeof (char), BUFFER_SIZE, fIn);
fwrite (буфер, sizeof (char), BUFFER_SIZE, fOut);
fclose (fIn);
fclose (fOut);
printf ("ВЫПОЛНЕНО \ n");
 
printf ("Освобождение буфера:");
бесплатно (буфер);
printf ("ВЫПОЛНЕНО \ n");
 
printf ("Удаление файлов:");
удалить (БУФЕР_1);
удалить (БУФЕР_2);
printf ("ВЫПОЛНЕНО \ n");
 
возврат 0;
 

test2.c (системные вызовы)

#включать
#включать
#включать
#включать
#включать
#включать
#включать
#включать
#включать
 
#define BUFFER_SIZE 67108864
 
int main ()
 
int fOut, fIn;
 
printf ("\ nТест ввода-вывода с помощью sendfile () и связанных системных вызовов.\ n \ n ");
 
// Захватываем буфер BUFFER_SIZE.
// В буфере будут случайные данные, но нас это не волнует.
printf ("Выделение буфера 64 МБ:");
символ * буфер = (символ *) malloc (BUFFER_SIZE);
printf ("ВЫПОЛНЕНО \ n");
 
// Записываем буфер в fOut
printf ("Запись данных в первый буфер:");
fOut = open ("буфер1", O_RDONLY);
запись (fOut, & буфер, BUFFER_SIZE);
закрыть (fOut);
printf ("ВЫПОЛНЕНО \ n");
 
printf ("Копирование данных из первого файла во второй:");
fIn = open ("буфер1", O_RDONLY);
fOut = open ("буфер2", O_RDONLY);
sendfile (fOut, fIn, 0, BUFFER_SIZE);
закрыть (фин);
закрыть (fOut);
printf ("ВЫПОЛНЕНО \ n");
 
printf ("Освобождение буфера:");
бесплатно (буфер);
printf ("ВЫПОЛНЕНО \ n");
 
printf ("Удаление файлов:");
unlink ("buffer1");
unlink ("buffer2");
printf ("ВЫПОЛНЕНО \ n");
 
возврат 0;
 

Компиляция и запуск тестов 1 и 2

Для создания этих примеров вам потребуются инструменты разработки, установленные в вашем дистрибутиве. В Debian и Ubuntu это можно установить с помощью:

apt install build-essentials

Затем скомпилируйте с помощью:

gcc test1.c -o test1 && gcc test2.c -o test2

Чтобы запустить оба и проверить производительность, запустите:

время ./ test1 && время ./ test2

Вы должны получить такие результаты:

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

Выделение буфера 64 МБ: ГОТОВО
Запись данных в первый буфер: ГОТОВО
Копирование данных из первого файла во второй: ГОТОВО
Освобождение буфера: ВЫПОЛНЕНО
Удаление файлов: ГОТОВО
реальный 0m0.397с
пользователь 0m0.000 с
sys 0m0.203с
Тест ввода-вывода с помощью sendfile () и связанных системных вызовов.
Выделение буфера 64 МБ: ГОТОВО
Запись данных в первый буфер: ГОТОВО
Копирование данных из первого файла во второй: ГОТОВО
Освобождение буфера: ВЫПОЛНЕНО
Удаление файлов: ГОТОВО
реальный 0m0.019с
пользователь 0m0.000 с
sys 0m0.016s

Как видите, код, использующий системные вызовы, работает намного быстрее, чем его эквивалент в glibc.

То, что нужно запомнить

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

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

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

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

Надеюсь, вам понравилось это более глубокое погружение в страну системных вызовов Linux. Полный список системных вызовов Linux см. В нашем основном списке.

Экранный трекпад и указатель мыши AppyMouse для планшетов с Windows
Пользователи планшетов часто пропускают указатель мыши, особенно когда они привыкли пользоваться ноутбуками. Смартфоны и планшеты с сенсорным экраном ...
Средняя кнопка мыши не работает в Windows 10
В средняя кнопка мыши помогает пролистывать длинные веб-страницы и экраны с большим объемом данных. Если это прекратится, вы в конечном итоге будете и...
Как изменить левую и правую кнопки мыши на ПК с Windows 10
Совершенно нормально, что все устройства компьютерной мыши эргономичны для правшей. Но есть мышиные устройства, специально разработанные для левшей ил...