Ускорение работы библиотек: отключение перехвата функций
В прошлый раз мы рассмотрели флаг компоновщика -Bsymbolic и узнали, как будет разрешена ссылка на глобальный символ, определенный сразу в нескольких местах: исполняемом файле и разделяемой библиотеке.
В этой публикации продолжим разбор данного флага и узнаем, как его использование в связке с параметром компилятора -fno-semantic-interposition может уменьшить размер исполняемого файла и ускорить процесс выполнения кода.
В обычной ситуации, если вы компилируете разделяемую библиотеку с помощью GCC, каждый вызов функции проходит через таблицу PLT для определения адреса и поддержки возможности, в случае чего, использовать стороннюю реализацию при перехвате (LD_PRELOAD). Это говорит о том, что если в библиотеке определены функции foo и bar и при этом foo вызывает bar, то вызов bar будет не прямым, а косвенным (через PLT заглушку):
Такая система вызовов:
1. Исключает возможные оптимизации на этапе генерации кода компилятором. Как пример, то же встраивание функций (inline) не будет отработано.
2. Приводит к большему размеру файла библиотеки за счет дополнительных машинных инструкций, необходимых для заполнения таблиц PLT и GOT.
3. Нагружает CPU instruction cache и приводит к более длительному исполнению кода.
4. Приводит к неоднозначности по отношению к используемой реализации: будет вызвана функция, определенная компоновщиком первой. Не факт, что это будет именно тот экземпляр, который реализован внутри библиотеки. Функции, которые определены в исполняемом файле, имеют больший приоритет.
Хммм, для чего тогда нужна такая система, раз уж столько в ней недостатков? По другому не получится, если нужен функционал перехвата функций через переменную LD_PRELOAD. В таком случае можно подменять реализации символов на те, которые указаны в передаваемой библиотеке:
Если мы соберем зависимость с флагом -Bsymbolic, все вызовы внутренних функций будут происходить напрямую, без PLT заглушек, что с одной стороны значительно уменьшит программный overhead, с другой исключит возможность перехвата символов в рантайме:
Таким образом, данный флаг не только приводит к однозначному использованию библиотечных реализаций вложенных функций, но и способствует лучшей производительности программ на этапе выполнения.
Как добиться лучшей производительности?
Все бы хорошо, но хочется полностью развязать компилятору руки и позволить эффективно оптимизировать код. Для данной цели предусмотрен флаг -fno-semantic-interposition, который исключает возможность перехвата символов на этапе компиляции и позволяет применять к функциям ряд различных оптимизаций. В GCC этот флаг по дефолту отключен и активируется под параметром -Ofast.
Для лучшей производительности, рассмотренные флаги лучше использовать вместе, тогда получится не только избавиться от PLT вызовов, но и сгенерировать более оптимальный код:
В прошлый раз мы рассмотрели флаг компоновщика -Bsymbolic и узнали, как будет разрешена ссылка на глобальный символ, определенный сразу в нескольких местах: исполняемом файле и разделяемой библиотеке.
В этой публикации продолжим разбор данного флага и узнаем, как его использование в связке с параметром компилятора -fno-semantic-interposition может уменьшить размер исполняемого файла и ускорить процесс выполнения кода.
В обычной ситуации, если вы компилируете разделяемую библиотеку с помощью GCC, каждый вызов функции проходит через таблицу PLT для определения адреса и поддержки возможности, в случае чего, использовать стороннюю реализацию при перехвате (LD_PRELOAD). Это говорит о том, что если в библиотеке определены функции foo и bar и при этом foo вызывает bar, то вызов bar будет не прямым, а косвенным (через PLT заглушку):
$ gdb ./prog
Dump of assembler code for function foo:
=> 0x0000fffff7fa059c <+8>: bl 0xfffff7fa0480 <bar@plt>
0x0000fffff7fa05a8 <+20>: ret
Такая система вызовов:
1. Исключает возможные оптимизации на этапе генерации кода компилятором. Как пример, то же встраивание функций (inline) не будет отработано.
2. Приводит к большему размеру файла библиотеки за счет дополнительных машинных инструкций, необходимых для заполнения таблиц PLT и GOT.
3. Нагружает CPU instruction cache и приводит к более длительному исполнению кода.
4. Приводит к неоднозначности по отношению к используемой реализации: будет вызвана функция, определенная компоновщиком первой. Не факт, что это будет именно тот экземпляр, который реализован внутри библиотеки. Функции, которые определены в исполняемом файле, имеют больший приоритет.
Хммм, для чего тогда нужна такая система, раз уж столько в ней недостатков? По другому не получится, если нужен функционал перехвата функций через переменную LD_PRELOAD. В таком случае можно подменять реализации символов на те, которые указаны в передаваемой библиотеке:
$ gcc -Wall -o prog main.c ./libdemo.so
$ LD_PRELOAD=./libdemo2.so ./prog
Function "bar" is called from libdemo2.so
Если мы соберем зависимость с флагом -Bsymbolic, все вызовы внутренних функций будут происходить напрямую, без PLT заглушек, что с одной стороны значительно уменьшит программный overhead, с другой исключит возможность перехвата символов в рантайме:
$ gdb ./prog
Dump of assembler code for function foo:
=> 0x0000fffff7fa0628 <+8>: bl 0xfffff7fa05f4 <bar>
0x0000fffff7fa0634 <+20>: ret
Таким образом, данный флаг не только приводит к однозначному использованию библиотечных реализаций вложенных функций, но и способствует лучшей производительности программ на этапе выполнения.
Как добиться лучшей производительности?
Все бы хорошо, но хочется полностью развязать компилятору руки и позволить эффективно оптимизировать код. Для данной цели предусмотрен флаг -fno-semantic-interposition, который исключает возможность перехвата символов на этапе компиляции и позволяет применять к функциям ряд различных оптимизаций. В GCC этот флаг по дефолту отключен и активируется под параметром -Ofast.
Для лучшей производительности, рассмотренные флаги лучше использовать вместе, тогда получится не только избавиться от PLT вызовов, но и сгенерировать более оптимальный код:
gcc -Wl,-Bsymbolic -Ofast -shared -o libdemo.so mod1.c
👍8❤🔥4🔥4👾1
Друзья, далеко мы уже зашли с библиотеками, как самочувствие?)
Хотите разбавить материал? Предлагаю на выбор следующие варианты🥸
Хотите разбавить материал? Предлагаю на выбор следующие варианты
Anonymous Poll
13%
Продолжаем бомбить либы и собираем статью на Хабр
23%
Делаем немного постов по C и C++ (структурное выравнивание, extern C и т.д)
60%
Рассматриваем базис Linux (работа с процессами, команды оболочки и т.д)
3%
Есть другие идеи, напишу в комменты 💬
Процессы и программы
Процессом является экземпляр выполняемой программы. В данном посте мы подробно разберем это определение и вы узнаете разницу между программой и процессом.
Программа представляет собой файл, содержащий различную информацию о том, как сконструировать процесс в ходе выполнения. В эту информацию входит:
1. Идентификационный признак двоичного формата.
Каждый программный файл включает в себя метаинформацию с описанием формата исполняемого файла. Это позволяет ядру корректно интерпретировать все остальные данные внутри файла.
Изначально для исполняемых файлов UNIX было предусмотрено два широко используемых формата: исходный формат a.out (assembler output — вывод на языке ассемблера) и появившийся позже более сложный общий формат объектных файлов COFF (Common Object File Format).
В настоящее время в большинстве реализаций UNIX (включая Linux) применяется формат исполняемых и компонуемых файлов ELF (Executable and Linking Format), предоставляющий множество преимуществ по сравнению с предшественниками.
2. Машинный код.
В нем закодирован алгоритм программы.
3. Адрес входа в программу.
В нем указывается место той инструкции, с которой должно начаться выполнение программы.
4. Данные.
В программном файле содержатся значения, используемые для инициализации переменных, а также применяемые программой символьные константы (например, строки).
5. Таблицы имен и переадресации.
В них дается описание расположений имен функций и переменных внутри программы. Эти таблицы предназначены для различных целей, включая отладку и разрешение имен в ходе выполнения программы (динамическое связывание).
6. Информация о совместно используемых библиотеках и динамической компоновке.
В программный файл включаются поля, где перечисляются совместно используемые библиотеки, которые программе потребуются в ходе выполнения, а также путевое имя динамического компоновщика, который должен применяться для загрузки этих библиотек.
Одна программа может использоваться для построения множества процессов, или же, если наоборот, во множестве процессов может быть запущена одна и та же программа.
Определение процесса, которое было дано в начале, можно переформулировать следующим образом: процесс является абстрактной сущностью, которая установлена ядром и которой, для выполнения программы, выделяются системные ресурсы.
С позиции ядра процесс состоит из памяти пользовательского пространства, внутри которой содержится код программы, а также из ряда структур данных, хранящих информацию о состоянии процесса.
Некоторая информация, записанная в структурах данных ядра, включает в себя: различные идентификаторы, связанные с процессом (PID, PPID ...), таблицы виртуальной памяти и дескрипторов открытых файлов, сведения, относящиеся к текущему рабочему каталогу, доставке и обработке сигналов, использованию и ограничениям ресурсов процесса.
#linuxcore #linux
Процессом является экземпляр выполняемой программы. В данном посте мы подробно разберем это определение и вы узнаете разницу между программой и процессом.
Программа представляет собой файл, содержащий различную информацию о том, как сконструировать процесс в ходе выполнения. В эту информацию входит:
1. Идентификационный признак двоичного формата.
Каждый программный файл включает в себя метаинформацию с описанием формата исполняемого файла. Это позволяет ядру корректно интерпретировать все остальные данные внутри файла.
Изначально для исполняемых файлов UNIX было предусмотрено два широко используемых формата: исходный формат a.out (assembler output — вывод на языке ассемблера) и появившийся позже более сложный общий формат объектных файлов COFF (Common Object File Format).
В настоящее время в большинстве реализаций UNIX (включая Linux) применяется формат исполняемых и компонуемых файлов ELF (Executable and Linking Format), предоставляющий множество преимуществ по сравнению с предшественниками.
2. Машинный код.
В нем закодирован алгоритм программы.
3. Адрес входа в программу.
В нем указывается место той инструкции, с которой должно начаться выполнение программы.
4. Данные.
В программном файле содержатся значения, используемые для инициализации переменных, а также применяемые программой символьные константы (например, строки).
5. Таблицы имен и переадресации.
В них дается описание расположений имен функций и переменных внутри программы. Эти таблицы предназначены для различных целей, включая отладку и разрешение имен в ходе выполнения программы (динамическое связывание).
6. Информация о совместно используемых библиотеках и динамической компоновке.
В программный файл включаются поля, где перечисляются совместно используемые библиотеки, которые программе потребуются в ходе выполнения, а также путевое имя динамического компоновщика, который должен применяться для загрузки этих библиотек.
Одна программа может использоваться для построения множества процессов, или же, если наоборот, во множестве процессов может быть запущена одна и та же программа.
Определение процесса, которое было дано в начале, можно переформулировать следующим образом: процесс является абстрактной сущностью, которая установлена ядром и которой, для выполнения программы, выделяются системные ресурсы.
С позиции ядра процесс состоит из памяти пользовательского пространства, внутри которой содержится код программы, а также из ряда структур данных, хранящих информацию о состоянии процесса.
Некоторая информация, записанная в структурах данных ядра, включает в себя: различные идентификаторы, связанные с процессом (PID, PPID ...), таблицы виртуальной памяти и дескрипторов открытых файлов, сведения, относящиеся к текущему рабочему каталогу, доставке и обработке сигналов, использованию и ограничениям ресурсов процесса.
#linuxcore #linux
👍12🔥6⚡3❤2🥰1👾1
Структура памяти процесса
Память, выделяемая каждому процессу, состоит из нескольких частей, которые обычно называют сегментами. К числу таких сегментов относятся следующие:
1. Текстовый сегмент (.text)
Cодержит машинный код, который принадлежит программе, запущенной процессом. Текстовый сегмент создается только для чтения, чтобы процесс не мог случайно изменить свои собственные инструкции (из-за read-only статуса, некоторые компиляторы могут записывать туда константы).
Поскольку многие процессы могут выполнять одну и ту же программу, текстовый сегмент создается с возможностью совместного использования - единственная копия кода программы может быть отображена на виртуальное адресное пространство всех процессов.
2. Сегмент инициализированных данных (.data)
Хранит глобальные и статические переменные, инициализированные явным образом. Значения этих переменных считываются из исполняемого файла при загрузке программы в память.
3. Сегмент неинициализированных данных (.bss - block started by symbol)
Содержит глобальные и статические переменные, не инициализированные явным образом. Перед запуском программы система определяет всю память в этом сегменте значением 0.
Основная причина помещения прошедших инициализацию переменных в отдельный от неинициализированных переменных сегмент заключается в том, что, когда программа сохраняется на диске, нет никакого смысла выделять пространство под неинициализированные данные.
Вместо этого исполняемой программе просто нужно записать местоположение и размер, требуемый для сегмента неинициализированных данных, и это пространство выделяется загрузчиком программы в ходе ее выполнения.
4. Динамически увеличивающийся и уменьшающийся сегмент стека (stack)
Содержит стековые фреймы: для каждой отдельной функции выделяется один стековый фрейм, в котором хранятся ее локальные переменные, аргументы и возвращаемое значение. По мере вызова функций и возврата из них стек расширяется и сжимается.
Текущая вершина стека отслеживается в специально предназначенном для этого регистре - указателе стека. Процесс сжатия стека называется расруткой.
5. Динамическая память (heap)
Область, которая предназначена для динамического выделения памяти в ходе выполнения программы (malloc, calloc, free, realloc - под капотом используют системные вызовы brk и sbrk).
6. Сегмент аргументов командной строки
Сегмент для хранения переменных, которые переданы программе в качестве аргументов (argc и argv[]), где argc хранит количество переданных аргументов, а argv хранит значение фактических значений вместе с именем файла:
Далее продемонстрированы различные типы переменных в коде и комментарии, указывающие на сегменты их размещения (в случае оптимизаций, часто используемые переменные могут быть помещены в регистры или вообще исключены):
В комменты я отправил несколько изображений для визуализации материала.
#linuxcore #linux #cpp #cppcore
Память, выделяемая каждому процессу, состоит из нескольких частей, которые обычно называют сегментами. К числу таких сегментов относятся следующие:
1. Текстовый сегмент (.text)
Cодержит машинный код, который принадлежит программе, запущенной процессом. Текстовый сегмент создается только для чтения, чтобы процесс не мог случайно изменить свои собственные инструкции (из-за read-only статуса, некоторые компиляторы могут записывать туда константы).
Поскольку многие процессы могут выполнять одну и ту же программу, текстовый сегмент создается с возможностью совместного использования - единственная копия кода программы может быть отображена на виртуальное адресное пространство всех процессов.
2. Сегмент инициализированных данных (.data)
Хранит глобальные и статические переменные, инициализированные явным образом. Значения этих переменных считываются из исполняемого файла при загрузке программы в память.
3. Сегмент неинициализированных данных (.bss - block started by symbol)
Содержит глобальные и статические переменные, не инициализированные явным образом. Перед запуском программы система определяет всю память в этом сегменте значением 0.
Основная причина помещения прошедших инициализацию переменных в отдельный от неинициализированных переменных сегмент заключается в том, что, когда программа сохраняется на диске, нет никакого смысла выделять пространство под неинициализированные данные.
Вместо этого исполняемой программе просто нужно записать местоположение и размер, требуемый для сегмента неинициализированных данных, и это пространство выделяется загрузчиком программы в ходе ее выполнения.
4. Динамически увеличивающийся и уменьшающийся сегмент стека (stack)
Содержит стековые фреймы: для каждой отдельной функции выделяется один стековый фрейм, в котором хранятся ее локальные переменные, аргументы и возвращаемое значение. По мере вызова функций и возврата из них стек расширяется и сжимается.
Текущая вершина стека отслеживается в специально предназначенном для этого регистре - указателе стека. Процесс сжатия стека называется расруткой.
5. Динамическая память (heap)
Область, которая предназначена для динамического выделения памяти в ходе выполнения программы (malloc, calloc, free, realloc - под капотом используют системные вызовы brk и sbrk).
6. Сегмент аргументов командной строки
Сегмент для хранения переменных, которые переданы программе в качестве аргументов (argc и argv[]), где argc хранит количество переданных аргументов, а argv хранит значение фактических значений вместе с именем файла:
~$ ./prog 100 23 43 69
Далее продемонстрированы различные типы переменных в коде и комментарии, указывающие на сегменты их размещения (в случае оптимизаций, часто используемые переменные могут быть помещены в регистры или вообще исключены):
/* Сегмент неинициализированных данных */
char globBuf[65536];
/* Сегмент инициализированных данных */
int primes[] = { 2, 3, 5, 7 };
/* Размещается в фрейме для square() */
static int square(int x)
{
/* Размещается в фрейме для square() */
int result;
result = x * x;
/* Возвращаемое значение передается через регистр */
return result;
}
/* Размещается в фрейме для doCalc() */
static void doCalc(int val)
{
square(val)
if (val < 1000) {
/* Размещается в фрейме для doCalc() */
int t;
t = val * val * val;
}
}
/* Размещается в фрейме для main() */
int main(int argc, char *argv[])
{
printf("File name = %s\n", argv[0]);
printf("Number of arguments = %d\n", argc - 1);
/* Сегмент инициализированных данных */
static int key = 9973;
/* Сегмент неинициализированных данных */
static char mbuf[10240000];
/* Размещается в фрейме для main() */
char *p;
/* Указывает на память в сегменте кучи */
p = malloc(1024);
doCalc(key);
}
В комменты я отправил несколько изображений для визуализации материала.
#linuxcore #linux #cpp #cppcore
👍19🔥7❤3✍2🥰1👾1
Системные и библиотечные вызовы [1]
Ну чтож, начинаем погружение в интересную тему - рассмотрим то, без чего не обходится ни одно системное приложение - системные и библиотечные вызовы.
Тут, на самом деле, можно довольно глубоко копнуть. Если проявится ваш интерес, сделаем ряд постов, где вдоль и поперек разберем принцип работы вызовов, практики их использования и т.д.
Что такое системный вызов?
Можно считать, что все пользовательские программы работают внутри некоторой песочницы, из которой нельзя так просто выполнять ряд действий: получать доступ к файлам, передавать данные по сети, выводить текст на экран, создавать процессы и т.д. Для этого нужно обратиться к внутренним сервисам ядра и попросить его сделать что требуется через системные вызовы.
Системный вызов изменяет состояние процессора, переводя его из пользовательского режима в режим ядра, позволяя ему таким образом получить доступ к защищенной памяти ядра.
(Системные вызовы Linux перечисляются на странице руководства syscalls(2))
Это может вас немного напугать, но, обычно, системные вызовы недоступны напрямую. Все, с чем вы работаете - это тонкие функции-обертки библиотки glibc. Да-да, не удивляйтесь: read, write, fork являются всего лишь понятным пользователю интерфейсом, через который отрабатывают системные вызовы.
И да, важно помнить то, что системные вызовы - это платформозависимая история. Чтение файлов, взаимодействие с процессами, работа с сокетами, управление памятью и прочие системные операции реализованы по разному на каждой системе и могут отличаться друг от друга по API.
Как сделать прямой системный вызов?
Иногда бывает полезно явно обраться к системному вызову в случае, если для него не реализована сишная функция-обертка. Сделать это можно через библиотечный вызов syscall() из unistd.h, который первым параметром принимает номер системного вызова из "sys/syscall.h", а далее - аргументы самого вызова:
Таким образом, системный вызов write() можно инициировать по разному:
Что такое библиотечный вызов?
Библиотечным вызовом можно считать вызов функции стандартной библиотеки C, в которой не содержится прямой реализации системного вызова: системного прерывания, переноса аргументов в определенные, необходимые ядру, регистры, копирования номера системного вызова в регистр (%eax) и т.д.
Эти функции предназначены для решения широкого круга разнообразных задач: открытия файлов, преобразования времени в формат, понятный человеку, сравнения двух символьных строк и т.д.
Многие библиотечные функции вообще не используют системные вызовы (например, функции для работы со сроками). С другой стороны, некоторые библиотечные функции являются надстройками над системными вызовами с платформозависимой реализацией.
Например, библиотечная функция fopen() использует для открытия файла системный вызов open().
Зачастую библиотечные функции разработаны для предоставления более удобного интерфейса вызова по сравнению с тем, что имеется у исходного системного вызова.
Например, функция printf() предоставляет форматирование вывода и буферизацию данных, а внутренний системный вызов write() просто выводит блок байтов.
Аналогично этому функции malloc() и free() выполняют различные вспомогательные задачи, существенно облегчающие выделение и высвобождение оперативной памяти по сравнению с использованием исходного системного вызова brk().
#linuxcore #linux #cpp #cppcore
Ну чтож, начинаем погружение в интересную тему - рассмотрим то, без чего не обходится ни одно системное приложение - системные и библиотечные вызовы.
Тут, на самом деле, можно довольно глубоко копнуть. Если проявится ваш интерес, сделаем ряд постов, где вдоль и поперек разберем принцип работы вызовов, практики их использования и т.д.
Что такое системный вызов?
Можно считать, что все пользовательские программы работают внутри некоторой песочницы, из которой нельзя так просто выполнять ряд действий: получать доступ к файлам, передавать данные по сети, выводить текст на экран, создавать процессы и т.д. Для этого нужно обратиться к внутренним сервисам ядра и попросить его сделать что требуется через системные вызовы.
Системный вызов изменяет состояние процессора, переводя его из пользовательского режима в режим ядра, позволяя ему таким образом получить доступ к защищенной памяти ядра.
(Системные вызовы Linux перечисляются на странице руководства syscalls(2))
Это может вас немного напугать, но, обычно, системные вызовы недоступны напрямую. Все, с чем вы работаете - это тонкие функции-обертки библиотки glibc. Да-да, не удивляйтесь: read, write, fork являются всего лишь понятным пользователю интерфейсом, через который отрабатывают системные вызовы.
И да, важно помнить то, что системные вызовы - это платформозависимая история. Чтение файлов, взаимодействие с процессами, работа с сокетами, управление памятью и прочие системные операции реализованы по разному на каждой системе и могут отличаться друг от друга по API.
Как сделать прямой системный вызов?
Иногда бывает полезно явно обраться к системному вызову в случае, если для него не реализована сишная функция-обертка. Сделать это можно через библиотечный вызов syscall() из unistd.h, который первым параметром принимает номер системного вызова из "sys/syscall.h", а далее - аргументы самого вызова:
extern long int syscall (long int __sysno, ...) __THROW;
Таким образом, системный вызов write() можно инициировать по разному:
#include <unistd.h>
#include <sys/syscall.h>
int main(void) {
write(1, "hello, world!\n", 14)
syscall(SYS_write, 1, "hello, world!\n", 14);
return 0;
}
Что такое библиотечный вызов?
Библиотечным вызовом можно считать вызов функции стандартной библиотеки C, в которой не содержится прямой реализации системного вызова: системного прерывания, переноса аргументов в определенные, необходимые ядру, регистры, копирования номера системного вызова в регистр (%eax) и т.д.
Эти функции предназначены для решения широкого круга разнообразных задач: открытия файлов, преобразования времени в формат, понятный человеку, сравнения двух символьных строк и т.д.
Многие библиотечные функции вообще не используют системные вызовы (например, функции для работы со сроками). С другой стороны, некоторые библиотечные функции являются надстройками над системными вызовами с платформозависимой реализацией.
Например, библиотечная функция fopen() использует для открытия файла системный вызов open().
Зачастую библиотечные функции разработаны для предоставления более удобного интерфейса вызова по сравнению с тем, что имеется у исходного системного вызова.
Например, функция printf() предоставляет форматирование вывода и буферизацию данных, а внутренний системный вызов write() просто выводит блок байтов.
Аналогично этому функции malloc() и free() выполняют различные вспомогательные задачи, существенно облегчающие выделение и высвобождение оперативной памяти по сравнению с использованием исходного системного вызова brk().
#linuxcore #linux #cpp #cppcore
👍32🔥6❤🔥5❤2🖕1👾1
Разбор команд: strace
Продолжаем погружаться во вселенную системных вызовов - сегодня поговорим про команду strace и получим знания о том, как залезть в недра процесса и посмотреть на его общение с ядром через набор системных вызовов.
Команда strace преимущественно используется для того, чтобы отследить системные вызовы со стороны процесса. По сравнению с gdb, это довольно легкая в использовании утилита, которая позволяет вам приоткрыть ширму высокоуровневого API и посмотреть на то, что происходит "under the hood".
Давайте разберем принцип ее работы на простом примере:
Теперь давайте прогоним это через strace и посмотрим на результат:
Воу, это лишь малая часть того, что вывелось в консоль... Как говорилось ранее, strace дает нам список всех системных вызовов, сделанных нашей программой.
Сейчас давайте обратим внимание на один конкретный вызов, который произошел входе выполнения и подробно разберем структуру, по которой strace формирует вывод информации:
Во-первых, можно увидеть то, что наша программа где-то, вероятно, через библиотечную функцию printf(), делает обращение к вызову write() для того, чтобы записать строку "Hello, world" в файл по дескриптору 1, также известному, как "standard output" (13 - это размер буфера на вывод):
Во-вторых, так как вывод команды strace и вывод нашей программы произошел в один и тот же терминал, строка "Hello, world" прилипла к информации про сам вызов:
В-третьих, было напечатано возвращаемое значение 13, которое передается нашей программе и говорит о том, что вызов успешно записал 13 символов по указанному дескриптору:
Несколько сценариев использования:
1. Выяснить, какие конфиги читает программа на старте. Бывало же у вас такое: запускаете софт, ожидаете получить один результат, а тут Оппа, либо ничего не работает либо происходит то, чего вы вообще не ожидали...
strace может показать вам перечень интересующих вызовов, по которым вы сможете отследить код возврата и понять, в чем дело. В данном случае, нас интересует вызов open(), на который мы указываем через флаг '-e':
В результате видно, что процессу не получилось открыть "/usr/local/bin/php.ini" и он пошел к "/usr/local/lib/php.ini", с которым проблем не возникло. Вызов успешно отработал и вернул файловый дескриптор 4.
2. Узнать вызов, на котором процесс завис. Если программа по какой-то причине перестала отвечать на запросы, возможно, она в блокирующем режиме ожидает получения данных или что-то в этом духе. Найти причину можно попробовать через запуск с флагом "-p <pid>":
Да, мало информации, известно только то, что завис на вызове futex(). Если вы не обладаете дополнительной информацией, то, скорее всего, придется лезть в исходники. Однако, базовое представление о проблеме получить можно.
3. Понять, на что уходит процессорное время. Иногда бывает полезно быстро пристроить strace к программе и посмотреть, на полезную ли работу тратится наше CPU или мы просто чего-то ждем... Запуститься в режиме профилирования можно через флаг '-c':
Продолжаем погружаться во вселенную системных вызовов - сегодня поговорим про команду strace и получим знания о том, как залезть в недра процесса и посмотреть на его общение с ядром через набор системных вызовов.
Команда strace преимущественно используется для того, чтобы отследить системные вызовы со стороны процесса. По сравнению с gdb, это довольно легкая в использовании утилита, которая позволяет вам приоткрыть ширму высокоуровневого API и посмотреть на то, что происходит "under the hood".
Давайте разберем принцип ее работы на простом примере:
#include <stdio.h>
int main(int argc, char **argv)
{
printf("Hello, world\n");
return 0;
}
Теперь давайте прогоним это через strace и посмотрим на результат:
$ strace ./hello_world
execve("./hello_world", ["./hello_world"], [/* 50 vars */]) = 0
brk(0) = 0xa7e000 access("/etc/ld.so.nohwcap", F_OK) = -1
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY) = 3
Воу, это лишь малая часть того, что вывелось в консоль... Как говорилось ранее, strace дает нам список всех системных вызовов, сделанных нашей программой.
Сейчас давайте обратим внимание на один конкретный вызов, который произошел входе выполнения и подробно разберем структуру, по которой strace формирует вывод информации:
write(1, "Hello, world\n", 13Hello, world ) = 13
Во-первых, можно увидеть то, что наша программа где-то, вероятно, через библиотечную функцию printf(), делает обращение к вызову write() для того, чтобы записать строку "Hello, world" в файл по дескриптору 1, также известному, как "standard output" (13 - это размер буфера на вывод):
write(1, "Hello, world\n", 13
Во-вторых, так как вывод команды strace и вывод нашей программы произошел в один и тот же терминал, строка "Hello, world" прилипла к информации про сам вызов:
write(1, "Hello, world\n", 13Hello, world
В-третьих, было напечатано возвращаемое значение 13, которое передается нашей программе и говорит о том, что вызов успешно записал 13 символов по указанному дескриптору:
write(1, "Hello, world\n", 13Hello, world ) = 13
Несколько сценариев использования:
1. Выяснить, какие конфиги читает программа на старте. Бывало же у вас такое: запускаете софт, ожидаете получить один результат, а тут Оппа, либо ничего не работает либо происходит то, чего вы вообще не ожидали...
strace может показать вам перечень интересующих вызовов, по которым вы сможете отследить код возврата и понять, в чем дело. В данном случае, нас интересует вызов open(), на который мы указываем через флаг '-e':
$ strace -e open php 2>&1 | grep php.ini
open("/usr/local/bin/php.ini", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/local/lib/php.ini", O_RDONLY) = 4
В результате видно, что процессу не получилось открыть "/usr/local/bin/php.ini" и он пошел к "/usr/local/lib/php.ini", с которым проблем не возникло. Вызов успешно отработал и вернул файловый дескриптор 4.
2. Узнать вызов, на котором процесс завис. Если программа по какой-то причине перестала отвечать на запросы, возможно, она в блокирующем режиме ожидает получения данных или что-то в этом духе. Найти причину можно попробовать через запуск с флагом "-p <pid>":
$ strace -p 15427
Process 15427 attached - interrupt to quit
futex(0x402f4900, FUTEX_WAIT, 2, NULL
Process 15427 detached
Да, мало информации, известно только то, что завис на вызове futex(). Если вы не обладаете дополнительной информацией, то, скорее всего, придется лезть в исходники. Однако, базовое представление о проблеме получить можно.
3. Понять, на что уходит процессорное время. Иногда бывает полезно быстро пристроить strace к программе и посмотреть, на полезную ли работу тратится наше CPU или мы просто чего-то ждем... Запуститься в режиме профилирования можно через флаг '-c':
$ strace -c -p 11084
% time seconds usecs/call calls errors syscall
94.59 0.001014 48 21 select
....
👍25🔥17❤🔥3👾2❤1
Разбор команд: strace -c
В прошлый раз мы рассмотрели базовый функционал и принцип работы утилиты strace. Сегодня уделим внимание конкретному ее флагу '-c', который позволяет поверхностно оценить производительность через пулл полезной информации про каждый системный вызов, выполненный для целевого процесса.
Давайте на простом примере посмотрим, что же можно выцепить из приложения, если прогнать его через "strace -c":
Этот код просто занимается тем, что постоянно пишет в файл один и тот же буфер с данными.... Да, супер полезная работа, но нам для примера подойдет. Как думаете, к чему это приведет? Правильно, к потреблению CPU в 100%!
Не всегда у пользователя есть возможность и желание копаться в исходниках программы. Для начала хочется базово продебажить софт и проверить, лежит ли проблема на поверхности: в этом нам поможет команда "strace -c", через которую мы поймем, на что тратятся ресурсы системы.
Для того, чтобы получить статистику, нужно просто запустить утилиту с указанными параметрами, выждать необходимое количество времени и прервать выполнение через "ctrl-c":
Вывод дает нам следующую информацию про каждый системный вызов:
1) time - процент процессорного времени, затраченного на выполнение вызовов (от общего времени);
2) seconds - фактическое процессорное время, затраченное на выполнение вызовов;
3) usecs/call - усредненное количество микросекунд, затраченных на выполнение 1 вызова;
4) calls - количество обращений к вызову;
5) errors - количество вызовов, вернувших ошибку;
6) syscall - название системного вызова;
В результате мы пониманием, что что-то не так: почти все время уходит на выполнение одной и той же операции, причем количество этих операций аномальное...
Дополнительно хочется акцентировать внимание на том, что не все системное процессорное время уходит только на выполнение вызовов. По этой причине временные показатели команды time могут отличаться от того, что покажет strace, так как последний учитывает исключительно то время, которое затрачено ядром на обработку вызовов для целевого процесса:
В прошлый раз мы рассмотрели базовый функционал и принцип работы утилиты strace. Сегодня уделим внимание конкретному ее флагу '-c', который позволяет поверхностно оценить производительность через пулл полезной информации про каждый системный вызов, выполненный для целевого процесса.
Давайте на простом примере посмотрим, что же можно выцепить из приложения, если прогнать его через "strace -c":
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main()
{
int fd = open("input.txt", O_WRONLY);
char* buffer = "Why am I doing this?";
while(1) {
write(fd, buffer, strlen(buffer));
}
close(fd);
return 0;
}
Этот код просто занимается тем, что постоянно пишет в файл один и тот же буфер с данными.... Да, супер полезная работа, но нам для примера подойдет. Как думаете, к чему это приведет? Правильно, к потреблению CPU в 100%!
Не всегда у пользователя есть возможность и желание копаться в исходниках программы. Для начала хочется базово продебажить софт и проверить, лежит ли проблема на поверхности: в этом нам поможет команда "strace -c", через которую мы поймем, на что тратятся ресурсы системы.
Для того, чтобы получить статистику, нужно просто запустить утилиту с указанными параметрами, выждать необходимое количество времени и прервать выполнение через "ctrl-c":
$ strace -c ./prog
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ------
99.74 0.245607 8 28230 write
0.06 0.000145 24 6 mmap
0.05 0.000111 27 4 mprotect
0.03 0.000073 24 3 openat
0.01 0.000033 16 2 close
.....
Вывод дает нам следующую информацию про каждый системный вызов:
1) time - процент процессорного времени, затраченного на выполнение вызовов (от общего времени);
2) seconds - фактическое процессорное время, затраченное на выполнение вызовов;
3) usecs/call - усредненное количество микросекунд, затраченных на выполнение 1 вызова;
4) calls - количество обращений к вызову;
5) errors - количество вызовов, вернувших ошибку;
6) syscall - название системного вызова;
В результате мы пониманием, что что-то не так: почти все время уходит на выполнение одной и той же операции, причем количество этих операций аномальное...
Дополнительно хочется акцентировать внимание на том, что не все системное процессорное время уходит только на выполнение вызовов. По этой причине временные показатели команды time могут отличаться от того, что покажет strace, так как последний учитывает исключительно то время, которое затрачено ядром на обработку вызовов для целевого процесса:
$ strace -c ./prog
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----
99.95 1.188828 8 140343 write
0.01 0.000157 26 6 mmap
....
------ ----------- ----------- --------- ---------
100.00 1.189413 8 140375 1 total
$ time ./prog
real 0m7.520s
user 0m1.048s
sys 0m6.274s
🔥15👍11❤🔥6🥰1👀1
Что такое бинарный пакет (deb, rpm)?
На сегодняшний день поверх линукса разворачивается огромное количество дистрибутивов, каждый из которых предлагает пользователю определенную пакетную базу, каждый элемент которой необходим для развертывания софта на целевой системе.
Репозитории для серверных изданий содержат одни пакеты, хомяки (Home Edition), в свою очередь, могут поставляться с другими: окружения рабочего стола, графические утилиты и т.д. Самыми распространенными расширениями пакетов в Linux являются .deb (Debian) и .rpm (Red Hat Package Manager).
Что такое бинарный пакет?
Бинарный пакет - архив, содержащий скомпилированные (готовые к запуску) приложения и набор вспомогательных файлов, необходимых для максимально гибкого и качественного распространения.
Вспомогательные файлы могут включать в себя библиотеки, от которых зависит приложение, конфиги, мануалы с подробным описанием функционала, инструкций по развертыванию, список необходимых зависимостей и т.д.
Примером бинарного пакета может быть обычный apk файл, с которым каждый владелец андройда когда-либо взаимодействовал. Прелесть его в том, что разработчику один раз нужно скомпилировать и собрать софт под целевую систему, прописать правила инсталляции и вуаля - все окружение для бесперебойной работы настраивается на этапе установки.
Пользователю не нужно подтягивать исходный код приложения и решать головоломку с его компиляцией - на его стороне все решается одним кликом.
Чем отличается .deb от .rpm?
Пакеты с расширением .deb используются в "Debian based" дистрибутивах: Ubuntu, Mint, Deepin, AntiX, Kali и т.д. Установка пакетов данного типа выполняется через утилиту dpkg (Debian Package):
Также существует ряд утилит семейства dpkg, которые входят в пакет dpkg-dev и необходимых для создания и администрирования архивов формата .deb:
А что же такое apt (Advanced Package Tool)? Apt - это набор утилит высокого уровня, необходимых для администрирования .deb пакетов и работы с внешними репозиториями.
Apt, в отличии от dpkg, позволяет подтягивать из локальных либо удаленных репозиториев необходимые зависимости, мониторить список доступных пакетов, следить за обновлениями и много-много чего еще:
Прописать список источников, с которыми менеджер apt будет работать, можно в файл "/etc/apt/sources.list":
RPM - это расширение для пакетов, используемых в операционных системах, основанных на Red Hat, это вся ветка дистрибутивов: Fedora, OpenSUSE, Red Hat, CentOS и т д.
Изначально это пакетное расширение и одноименный менеджер были разработаны в компании Red Hat еще в 1997 году и только для их дистрибутива, но затем все это пошло дальше и распространилось в другие операционные системы.
По аналогии с dpkg, утилита rpm используется для того, чтобы локально обслуживать .rpm пакеты. Для более высокоуровневого взаимодействия и возможности работать с репозиториями, необходимо использовать ряд пакетных менеджеров: zypper (OpenSUSE), dnf (Fedora), urpmi (Mageia), yum - для многих дистрибутивов, основанных на Fedora:
Тема пакетирования и распространения софта очень обширная: существует множество техник и практик, которым нужно следовать для того, чтобы качественно разворачивать программы на системах. Пишите, что бы вы хотели узнать в рамках топика, разгоним всю эту историю до серии постов.
На сегодняшний день поверх линукса разворачивается огромное количество дистрибутивов, каждый из которых предлагает пользователю определенную пакетную базу, каждый элемент которой необходим для развертывания софта на целевой системе.
Репозитории для серверных изданий содержат одни пакеты, хомяки (Home Edition), в свою очередь, могут поставляться с другими: окружения рабочего стола, графические утилиты и т.д. Самыми распространенными расширениями пакетов в Linux являются .deb (Debian) и .rpm (Red Hat Package Manager).
Что такое бинарный пакет?
Бинарный пакет - архив, содержащий скомпилированные (готовые к запуску) приложения и набор вспомогательных файлов, необходимых для максимально гибкого и качественного распространения.
Вспомогательные файлы могут включать в себя библиотеки, от которых зависит приложение, конфиги, мануалы с подробным описанием функционала, инструкций по развертыванию, список необходимых зависимостей и т.д.
Примером бинарного пакета может быть обычный apk файл, с которым каждый владелец андройда когда-либо взаимодействовал. Прелесть его в том, что разработчику один раз нужно скомпилировать и собрать софт под целевую систему, прописать правила инсталляции и вуаля - все окружение для бесперебойной работы настраивается на этапе установки.
Пользователю не нужно подтягивать исходный код приложения и решать головоломку с его компиляцией - на его стороне все решается одним кликом.
Чем отличается .deb от .rpm?
Пакеты с расширением .deb используются в "Debian based" дистрибутивах: Ubuntu, Mint, Deepin, AntiX, Kali и т.д. Установка пакетов данного типа выполняется через утилиту dpkg (Debian Package):
dpkg -i packageName.deb
Также существует ряд утилит семейства dpkg, которые входят в пакет dpkg-dev и необходимых для создания и администрирования архивов формата .deb:
$ dpkg -L dpkg-dev | grep -i bin
/usr/bin/dpkg-architecture
/usr/bin/dpkg-buildapi
/usr/bin/dpkg-buildflags
...
А что же такое apt (Advanced Package Tool)? Apt - это набор утилит высокого уровня, необходимых для администрирования .deb пакетов и работы с внешними репозиториями.
Apt, в отличии от dpkg, позволяет подтягивать из локальных либо удаленных репозиториев необходимые зависимости, мониторить список доступных пакетов, следить за обновлениями и много-много чего еще:
$ apt-cache search mesa
libd3dadapter9-mesa - state-tracker for Direct3D9
mesa-vdpau-drivers - Mesa VDPAU video acceleration drivers
mesa-vulkan-drivers - Mesa Vulkan graphics drivers
...
Прописать список источников, с которыми менеджер apt будет работать, можно в файл "/etc/apt/sources.list":
$ cat /etc/apt/sources.list
Types: deb
URIs: http://ports.ubuntu.com/ubuntu-ports/
Suites: noble noble-updates noble-backports
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
RPM - это расширение для пакетов, используемых в операционных системах, основанных на Red Hat, это вся ветка дистрибутивов: Fedora, OpenSUSE, Red Hat, CentOS и т д.
Изначально это пакетное расширение и одноименный менеджер были разработаны в компании Red Hat еще в 1997 году и только для их дистрибутива, но затем все это пошло дальше и распространилось в другие операционные системы.
По аналогии с dpkg, утилита rpm используется для того, чтобы локально обслуживать .rpm пакеты. Для более высокоуровневого взаимодействия и возможности работать с репозиториями, необходимо использовать ряд пакетных менеджеров: zypper (OpenSUSE), dnf (Fedora), urpmi (Mageia), yum - для многих дистрибутивов, основанных на Fedora:
rpm -i package.rpm
yum install package.rpm
dnf install package.rpm
Тема пакетирования и распространения софта очень обширная: существует множество техник и практик, которым нужно следовать для того, чтобы качественно разворачивать программы на системах. Пишите, что бы вы хотели узнать в рамках топика, разгоним всю эту историю до серии постов.
👍29🔥8❤🔥5🥰1
Разбор команд: lsof
Ну что, братья-айтишники, сегодня говорим про команду lsof. Решил ее рассмотреть довольно спонтанно, т.к. также спонтанно она мне пригодилась)
Как мы знаем, в linux немалую роль играют файлы. Многие говорят, что все в linux - файл. В своей основе, утилита lsof (list open files) позволяет просматривать список открытых файлов и выступает в роли интерфейса для мониторинга виртуальной файловой системы proc.
Подробнее про файловую систему proc:
Данная файловая система состоит из набора каталогов и файлов, смонтированных в одноименной директории. Называется она виртуальной потому, что фактически не находятся на диске и заполняется ядром на лету. Нужна она, в основном, для того, чтобы на стороне пользователя (через файлы) просматривать информацию о ядре и его структурах.
Говоря простым языком, анализ VFS (virtual file system) proc позволяет ответить на следующие вопросы:
1. Сколько процессов запущено в системе и кто ими владеет?
2. Какие файлы открыты процессом?
3. Какие файлы в данный момент заблокированы и какие процессы удерживают эти блокировки?
4. Какие сокеты используются в системе?
Разбор вывода команды lsof:
Давайте начнем с простого - без дополнительных флагов запустим утилиту и проанализируем ее вывод:
В результате мы получаем пулл информации про активные утилиты и используемые ими ресурсы (файлы, каталоги, сокеты и т.д.).
Спектр возможных значений для каждого поля тут довольно широк, поэтому за детальной информацией предлагаю обратиться к мануалу.
Давайте на конкретном примере посмотрим, о чем нас проинформирует lsof:
1. COMMAND - название утилиты, которая использует ресурс .
2. PID - идентификатор процесса для программы prog_wr.
3. USER - имя пользователя, которому принадлежит процесс.
4. FD - поле, предоставляющее информацию о ресурсе, который использует процесс. Значение поля может состоять из нескольких частей. В случае принадлежности к зарезервированному типу, значениями могут быть: cwd, rtd, txt, mem и т.д.
Если же, как в данном случае, речь идет о ресурсе, к которому можно обратиться по локальному дескриптору, структура может быть следующей: порядковый номер дескриптора (3) + права доступа (w - открыт на запись).
5. TYPE - тип ресурса. В данном случае используется обычный файл (REG). Также, ресурсами могут быть: директории (DIR), каналы передачи данных (FIFO), сетевые сокеты (INET) и т.д.
6. DEVICE - номер устройства, к которому относится ресурс (в формате major:minor). Обычно, информация берется об устройствах из каталога dev.
7. SIZE/OFF - размер файла input.txt в байтах. Если размер ресурса получить не удается, значением поля будет его отступ в памяти.
8. NODE - номер ресурса в таблице индексного дескриптора "inode". В нашем случае, файл input.txt числится под номером 1445679 в общесистемной таблице.
9. NAME - имя ресурса, представленное абсолютным путем.
Примеры использования команды lsof:
Вот и мне эта программка пригодилась для того, чтобы понять, какие процессы используют интересующую меня динамическую библиотеку.
Если у вас уже есть подозрения и вы знаете PID процесса, можно воспользоваться флагом '-p', который отобразит информацию только для указанного процесса:
Если же у вас 0 идей, можете прямо прописать путь к целевому ресурсу и утилита вам покажет все процессы, которые его используют:
Ну что, братья-айтишники, сегодня говорим про команду lsof. Решил ее рассмотреть довольно спонтанно, т.к. также спонтанно она мне пригодилась)
Как мы знаем, в linux немалую роль играют файлы. Многие говорят, что все в linux - файл. В своей основе, утилита lsof (list open files) позволяет просматривать список открытых файлов и выступает в роли интерфейса для мониторинга виртуальной файловой системы proc.
Подробнее про файловую систему proc:
Данная файловая система состоит из набора каталогов и файлов, смонтированных в одноименной директории. Называется она виртуальной потому, что фактически не находятся на диске и заполняется ядром на лету. Нужна она, в основном, для того, чтобы на стороне пользователя (через файлы) просматривать информацию о ядре и его структурах.
Говоря простым языком, анализ VFS (virtual file system) proc позволяет ответить на следующие вопросы:
1. Сколько процессов запущено в системе и кто ими владеет?
2. Какие файлы открыты процессом?
3. Какие файлы в данный момент заблокированы и какие процессы удерживают эти блокировки?
4. Какие сокеты используются в системе?
Разбор вывода команды lsof:
Давайте начнем с простого - без дополнительных флагов запустим утилиту и проанализируем ее вывод:
$ lsof
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
init 1 root cwd DIR 253,0 4096 2 /
init 1 root txt REG 253,0 145180 147164 /sbin/init
init 1 root 4w FIFO 0,8 0t0 8449 pipe
...
В результате мы получаем пулл информации про активные утилиты и используемые ими ресурсы (файлы, каталоги, сокеты и т.д.).
Спектр возможных значений для каждого поля тут довольно широк, поэтому за детальной информацией предлагаю обратиться к мануалу.
Давайте на конкретном примере посмотрим, о чем нас проинформирует lsof:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
prog_wr 9957 xodefender 3w REG 253,0 46442 1445679 /home/xodefender/input.txt
1. COMMAND - название утилиты, которая использует ресурс .
2. PID - идентификатор процесса для программы prog_wr.
3. USER - имя пользователя, которому принадлежит процесс.
4. FD - поле, предоставляющее информацию о ресурсе, который использует процесс. Значение поля может состоять из нескольких частей. В случае принадлежности к зарезервированному типу, значениями могут быть: cwd, rtd, txt, mem и т.д.
Если же, как в данном случае, речь идет о ресурсе, к которому можно обратиться по локальному дескриптору, структура может быть следующей: порядковый номер дескриптора (3) + права доступа (w - открыт на запись).
5. TYPE - тип ресурса. В данном случае используется обычный файл (REG). Также, ресурсами могут быть: директории (DIR), каналы передачи данных (FIFO), сетевые сокеты (INET) и т.д.
6. DEVICE - номер устройства, к которому относится ресурс (в формате major:minor). Обычно, информация берется об устройствах из каталога dev.
7. SIZE/OFF - размер файла input.txt в байтах. Если размер ресурса получить не удается, значением поля будет его отступ в памяти.
8. NODE - номер ресурса в таблице индексного дескриптора "inode". В нашем случае, файл input.txt числится под номером 1445679 в общесистемной таблице.
9. NAME - имя ресурса, представленное абсолютным путем.
Примеры использования команды lsof:
Вот и мне эта программка пригодилась для того, чтобы понять, какие процессы используют интересующую меня динамическую библиотеку.
Если у вас уже есть подозрения и вы знаете PID процесса, можно воспользоваться флагом '-p', который отобразит информацию только для указанного процесса:
$ lsof -p 23708
vlc 23708 root mem REG 8,2 264888 2365462 /usr/lib64/vlc/plugins/codec/libomxil_plugin.so
Если же у вас 0 идей, можете прямо прописать путь к целевому ресурсу и утилита вам покажет все процессы, которые его используют:
$ lsof ./libomxil_plugin.so
vlc 23708 root mem REG 8,2 264888 2365462 ./libomxil_plugin.so
👍25❤🔥9🔥8❤1🥰1👾1
Наконец-то руки дорвались)
На днях купил себе вот такую штуку, поставил стартер кит (fallout, forza, witcher, cup head ...) и радуюсь безумно.
Пока полет нормальный, cup head работает бесперебойно (за него больше всего переживал😂 ).
Короткое ревью следующее: в большинство платформеров и игр времен ps4 играется отлично, даже при запуске тяжелых проектов система сильно не шумит и нагрев в области контроллеров (по бокам) не ощутим. Вот что ощутимо - быстрый разряд батареи...
Качество сборки тоже хорошее. Всегда любил формат геймпадов и ощущение монолитности, тут 0 вопросов к качеству и удобству.
Если у кого есть такой же аппарат, какие впечатления?)
На днях купил себе вот такую штуку, поставил стартер кит (fallout, forza, witcher, cup head ...) и радуюсь безумно.
Пока полет нормальный, cup head работает бесперебойно (за него больше всего переживал
Короткое ревью следующее: в большинство платформеров и игр времен ps4 играется отлично, даже при запуске тяжелых проектов система сильно не шумит и нагрев в области контроллеров (по бокам) не ощутим. Вот что ощутимо - быстрый разряд батареи...
Качество сборки тоже хорошее. Всегда любил формат геймпадов и ощущение монолитности, тут 0 вопросов к качеству и удобству.
Если у кого есть такой же аппарат, какие впечатления?)
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥19👍7🤯4😐1
разбор команд: source
Разбирал я значит баш скрипты на сборочных серверах и наткнулся на интересную команду source. Вызов выглядел следующим образом:
Мне сразу стало интересно, что лежит в .build файле... Там был прописан обычный bash скрипт, который определял ряд переменных и функций для компиляции и установки приложения:
Следующий вопрос - почему именно source? Можно же выполнить скрипт внутри командной оболочки через обращение к файлу через путь:
Как оказалось, есть несколько причин, по которым данная команда существует и используется, давайте разбираться.
Принцип работы команды source:
Данная команда является полным аналогом оператора . - встроенным в оболочку инструментом, который позволяет выполнять ряд инструкций целевого файла в рамках текущего "shell environment", что, как минимум, сохраняет доступ ко всем, локально определенным, переменным оболочки:
Когда вы вызываете source и передаете ему файл на чтение, все команды внутри будут последовательно выполнены так, будто вы их вручную прописали в терминале. Файл тут можно передавать несколькими способами:
1) Через абсолютный/относительный путь:
2) По имени. В данном случае утилита попытается найти файл в одном из каталогов, указанных для переменной $PATH. Если попытка окончилась провалом, программа попытается взять его из текущей активной директории:
Отличия в запуске скрипта по пути "./" и через команду source:
Как говорилось ранее, команда source выполняет инструкции в рамках текущей оболочки, не отпочковываясь и не создавая новый процесс, что позволяет использовать локальные переменные.
Запуск скрипта по пути, в свою очередь, создает дочерний процесс и запускает дополнительную оболочку со своим пуллом переменных:
Если переменная оболочки не была переведена в переменную окружения через ключевое слово "export", дочерний процесс не сможет к ней обратиться. В таком случае, необходимо явно передать и определить ее при запуске:
Также, нужно помнить, что, когда вы вызываете скрипт по имени, для корректной работы следует прописывать shebang "#!<path to app>" в начале файла. Таким образом, системе будет понятно то, каким приложением необходимо обработать указанные инструкции:
В случае с source это не имеет смысла - тут мы работаем на уровне текущей оболочки. Операционной системой не будет запущен очередной интерпретатор, как следствие, данная строка будет проигнорирована.
Пример использования команды source:
Как говорилось в начале поста, я наткнулся на source, пока шарился на сборочном сервере. Стояла задача дебианизировать пакет, который должен был при инсталляции корректно встроить в систему ряд медиа библиотек и конфигов для декодирования видео на GPU.
Как правило, проекты такой сложности требуют выполнения немалого количества действий на разных этапах: при конфигурации нужно передать "миллион" флагов в cmake, при установке построить глубокую иерархию директорий, перенести файлы по путям и т.д.
Все эти процессы могли бы быть отображены и проработаны в файле rules, но тогда в нем было бы безумно тяжело ориентироваться. Было принято решение разделить логику на группу функций: src_install(), src_compile(), src_config(). После чего, локально, не в рамках новой оболочки, их определять и вызывать.
Определения были скомпонованы в отдельном файле, что позволило красиво и локанично оформить rules:
Разбирал я значит баш скрипты на сборочных серверах и наткнулся на интересную команду source. Вызов выглядел следующим образом:
source <filename>.build
Мне сразу стало интересно, что лежит в .build файле... Там был прописан обычный bash скрипт, который определял ряд переменных и функций для компиляции и установки приложения:
#!/bin/bash
BDIR="Release"
src_config() {
cmake -DCMAKE_BUILD_TYPE=${BDIR}
}
src_compile() {
make
}
Следующий вопрос - почему именно source? Можно же выполнить скрипт внутри командной оболочки через обращение к файлу через путь:
./<filename>.build
Как оказалось, есть несколько причин, по которым данная команда существует и используется, давайте разбираться.
Принцип работы команды source:
Данная команда является полным аналогом оператора . - встроенным в оболочку инструментом, который позволяет выполнять ряд инструкций целевого файла в рамках текущего "shell environment", что, как минимум, сохраняет доступ ко всем, локально определенным, переменным оболочки:
$ MY_VAR=123
$ cat noscript.sh
echo $MY_VAR
$ source noscript.sh
123
$ . noscript.sh
123
Когда вы вызываете source и передаете ему файл на чтение, все команды внутри будут последовательно выполнены так, будто вы их вручную прописали в терминале. Файл тут можно передавать несколькими способами:
1) Через абсолютный/относительный путь:
source ./XTouch/xtouch.sq
2) По имени. В данном случае утилита попытается найти файл в одном из каталогов, указанных для переменной $PATH. Если попытка окончилась провалом, программа попытается взять его из текущей активной директории:
source xtouch.sq
Отличия в запуске скрипта по пути "./" и через команду source:
Как говорилось ранее, команда source выполняет инструкции в рамках текущей оболочки, не отпочковываясь и не создавая новый процесс, что позволяет использовать локальные переменные.
Запуск скрипта по пути, в свою очередь, создает дочерний процесс и запускает дополнительную оболочку со своим пуллом переменных:
$ ps -auxf
\_ /bin/bash
\_ /bin/bash ./test.sh
Если переменная оболочки не была переведена в переменную окружения через ключевое слово "export", дочерний процесс не сможет к ней обратиться. В таком случае, необходимо явно передать и определить ее при запуске:
$ MY_VAR=123
$ source noscript.sh
123
$ MY_VAR="$MY_VAR" noscript.sh
123
Также, нужно помнить, что, когда вы вызываете скрипт по имени, для корректной работы следует прописывать shebang "#!<path to app>" в начале файла. Таким образом, системе будет понятно то, каким приложением необходимо обработать указанные инструкции:
#!/usr/bin/make -f
install:
dh_installdirs
dh_install
В случае с source это не имеет смысла - тут мы работаем на уровне текущей оболочки. Операционной системой не будет запущен очередной интерпретатор, как следствие, данная строка будет проигнорирована.
Пример использования команды source:
Как говорилось в начале поста, я наткнулся на source, пока шарился на сборочном сервере. Стояла задача дебианизировать пакет, который должен был при инсталляции корректно встроить в систему ряд медиа библиотек и конфигов для декодирования видео на GPU.
Как правило, проекты такой сложности требуют выполнения немалого количества действий на разных этапах: при конфигурации нужно передать "миллион" флагов в cmake, при установке построить глубокую иерархию директорий, перенести файлы по путям и т.д.
Все эти процессы могли бы быть отображены и проработаны в файле rules, но тогда в нем было бы безумно тяжело ориентироваться. Было принято решение разделить логику на группу функций: src_install(), src_compile(), src_config(). После чего, локально, не в рамках новой оболочки, их определять и вызывать.
Определения были скомпонованы в отдельном файле, что позволило красиво и локанично оформить rules:
install:
source debian/pipeline.build && src_config && src_install
build:
source debian/pipeline.build && src_config && src_compile
👍31🔥6❤🔥3🍓1👾1
Cборка бинарного (.deb) пакета:
Ранее мы уже говорили о том, что такое бинарный пакет. Сегодня разберемся с тем, как его создать, рассмотрим архитектурные правила сборки и узнаем про практическую ценность некоторых файлов, которые используются при дебианизации.
Сразу хочу указать на то, что данный пост не является абсолютным HOWTO по созданию пакетов. Тут мы разберем основы и потом, в следующий публикациях будем уходить вглубь по отдельным аспектам, тем самым, шаг за шагом, формируя целостную картину.
Что такое дебианизация?
Дебианизация - это процесс упаковки ПО в .deb бинарный пакет для распространения внутри "Debian based" операционных системах, таких как Ubuntu, Mint, сам Debian и т.д. Хорошо проработанный пакет позволит без особых проблем развернуть необходимое ПО на целевой системе, без необходимости в ручной компиляции и сборке.
Что нужно для создания .deb пакета?
На примере моего любимого композитора picom разберем основные правила дебианизации, посмотрим на уже рабочую структуру и поймем, что к чему.
Сборка любого .deb пакета начинается с создания debian директории в корне проекта. Данный каталог будет содержать в себе все необходимые для дебианизации файлы, в которых будет прописан ряд правил по упаковке софта:
Для конфигурации пакета может использоваться множество различных файлов, основными необходимыми являются control, rules и changelog:
1. control - содержит различного рода информацию, необходимую инструментам пакетирования: dpkg, apt-get, apt-cache и т.д. В данный файл принято прописывать список зависимостей, которые пакетный менеджер будет проверять и, по возможности, подтягивать из репозиториев.
Control может включать в себя уйму полей. Как пример, Package: <name>, необходим для определения имени пакета и создания, на этапе сборки, каталога установки:
При инсталляции, файлы, которые лежать внутри picom, по идентичной иерархии перенесутся в системные директории.
2. rules - исполняемый Makefile, который в полной мере управляет процессом сборки и отрабатывает через утилиту make. Тут можно указать набор правил и инструкций, которые будут выполняться на разных этапах:
1) на этапе конфигурации создаем директорию build, в которю помещяем результат работы команды meson:
2) на этапе сборки вызываем, сгенерированные утилитой meson, ninja файлы и выполняем компиляцию проекта. Результат компиляции будет лежать в рамках каталога build:
3) на этапе установки заходим в директорию build, записываем в переменную DESTDIR путь, по которому ninja install выполнит перенос готовых бинарников и конфигов:
3. changelog - файл версионирования, который содержит историю изменений, внесенных в ПО. Его роль не ограничивается только отслеживанием правок. По указанной в нем информации, которая находится после названия приложения (<версия>-<ревизия>), формируется итоговое имя пакета:
В результате сборки мы получим пакет с именем picom_9.1-1_amd64.deb, где picom сформировался по значению поля Package из файла control, версия 9.1-1 подставилась из changelog и архитектура amd64 определилась автоматически.
И вот уже с этим минимальным набором файлов можно создать бинарный пакет. После того, как конфигурация готова, выходим из директории debian на уровень выше и запускаем сборочный процесс:
Ранее мы уже говорили о том, что такое бинарный пакет. Сегодня разберемся с тем, как его создать, рассмотрим архитектурные правила сборки и узнаем про практическую ценность некоторых файлов, которые используются при дебианизации.
Сразу хочу указать на то, что данный пост не является абсолютным HOWTO по созданию пакетов. Тут мы разберем основы и потом, в следующий публикациях будем уходить вглубь по отдельным аспектам, тем самым, шаг за шагом, формируя целостную картину.
Что такое дебианизация?
Дебианизация - это процесс упаковки ПО в .deb бинарный пакет для распространения внутри "Debian based" операционных системах, таких как Ubuntu, Mint, сам Debian и т.д. Хорошо проработанный пакет позволит без особых проблем развернуть необходимое ПО на целевой системе, без необходимости в ручной компиляции и сборке.
Что нужно для создания .deb пакета?
На примере моего любимого композитора picom разберем основные правила дебианизации, посмотрим на уже рабочую структуру и поймем, что к чему.
Сборка любого .deb пакета начинается с создания debian директории в корне проекта. Данный каталог будет содержать в себе все необходимые для дебианизации файлы, в которых будет прописан ряд правил по упаковке софта:
$ cd picom-10.2/debian
$ ls -l
-rw-rw-r-- changelog
-rw-rw-r-- control
drwxrwxr-x patches
-rwxrwxr-x rules
...
Для конфигурации пакета может использоваться множество различных файлов, основными необходимыми являются control, rules и changelog:
1. control - содержит различного рода информацию, необходимую инструментам пакетирования: dpkg, apt-get, apt-cache и т.д. В данный файл принято прописывать список зависимостей, которые пакетный менеджер будет проверять и, по возможности, подтягивать из репозиториев.
Control может включать в себя уйму полей. Как пример, Package: <name>, необходим для определения имени пакета и создания, на этапе сборки, каталога установки:
$ pwd
picom-10.2/debian/picom/
$ ls
etc usr
$ cd usr/bin && ls
compton picom
При инсталляции, файлы, которые лежать внутри picom, по идентичной иерархии перенесутся в системные директории.
2. rules - исполняемый Makefile, который в полной мере управляет процессом сборки и отрабатывает через утилиту make. Тут можно указать набор правил и инструкций, которые будут выполняться на разных этапах:
1) на этапе конфигурации создаем директорию build, в которю помещяем результат работы команды meson:
override_dh_auto_configure:
mkdir -p debian/build
cd debian/build
meson --prefix=/usr ../..
2) на этапе сборки вызываем, сгенерированные утилитой meson, ninja файлы и выполняем компиляцию проекта. Результат компиляции будет лежать в рамках каталога build:
override_dh_auto_build:
cd debian/build && ninja -v
3) на этапе установки заходим в директорию build, записываем в переменную DESTDIR путь, по которому ninja install выполнит перенос готовых бинарников и конфигов:
override_dh_auto_install:
cd debian/build
DESTDIR=${CURDIR}/debian/picom ninja install
3. changelog - файл версионирования, который содержит историю изменений, внесенных в ПО. Его роль не ограничивается только отслеживанием правок. По указанной в нем информации, которая находится после названия приложения (<версия>-<ревизия>), формируется итоговое имя пакета:
picom (9.1-1) unstable; urgency=medium
* New upstream version 9.1
-- Nikos Tsipinakis <nikos@tsipinakis.com>
В результате сборки мы получим пакет с именем picom_9.1-1_amd64.deb, где picom сформировался по значению поля Package из файла control, версия 9.1-1 подставилась из changelog и архитектура amd64 определилась автоматически.
И вот уже с этим минимальным набором файлов можно создать бинарный пакет. После того, как конфигурация готова, выходим из директории debian на уровень выше и запускаем сборочный процесс:
$ cd ..
$ sudo dpkg-buildpackage -b -us -uc
❤🔥10❤6🔥6👍3👾1
Файловая система proc
Ранее мы уже затрагивали виртуальную файловую систему proc, когда говорили про команду lsof. Сегодня копнем глубже и научимся получать из нее различную информацию о системе и процессах.
В старых реализациях UNIX не было простого способа выполнить интроспективный анализ атрибутов ядра для получения ответов на следующие вопросы:
1. Сколько процессов запущено в системе и кто их владельцы?
2. Какие файлы открыты процессом?
3. Какие файлы в данный момент заблокированы и какие процессы удерживают эти блокировки?
4. Какие сокеты используются в системе?
Чтобы предоставить более легкий доступ к информации ядра, во многих современных реализациях UNIX предусмотрена VFS (Virtual File System) proc. Она находится в одноименном каталоге и содержит различные файлы, предоставляющие информацию о ядре. Называется она виртуальной потому, что фактически не располагается на диске и заполняется ядром на лету.
Процессам можно беспрепятственно считывать эту информацию и, в некоторых случаях, вносить в нее изменения, используя обычные системные вызовы файлового ввода-вывода: read, write, open, close и т.д.
Получение информации о процессе (/proc/PID):
Для каждого процесса в системе ядро создает соответствующий каталог по имени /proc/PID (process id). Внутри этого каталога находятся различные файлы и подкаталоги, содержащие информацию о процессе. Важно помнить, что данные директории не существуют постоянно и затираются, как только процесс прекратил свое существование.
Например, просмотрев файлы в каталоге /proc/1, можно получить информацию о процессе init, PID которого всегда имеет значение 1. Давайте посмотрим на базовую информацию процесса init через файл status, который существует в каждом каталоге /proc/PID и предоставляет множество данных о целевом процессе:
Описание файлов и директорий для каждого каталога (/proc/PID):
1) cmdline - аргументы командной строки;
2) cwd - символьная ссылка на текущий рабочий каталог;
3) environ - пары вида ИМЯ=значение списка переменных среды;
4) exe - символьная ссылка на исполняемый файл;
5) fd - каталог, содержащий символьные ссылки на файлы, открытые процессом;
6) maps - отображения памяти;
7) mem - виртуальная память процесса;
8) mounts - точки монтирования для рассматриваемого процесса;
9) root - символьная ссылка на корневой каталог;
10) status - различная информация (идентификаторы процесса, права, использование памяти, сигналы);
11) task - содержит по одному подкаталогу для каждого потока в процессе;
Системная информация, находящаяся в proc:
Доступ к информации, распространяющейся на всю систему также предоставляется через различные файлы и директории внутри VFS proc. Давайте посмотрим на назначения отдельных из них:
1) /proc/net - информация состояния сети и сокетов;
2) /proc/sys/fs - настройки, относящиеся к файловым системам;
3) /proc/sys/kernel - различные общие настройки ядра;
4) /proc/sys/net - настройки сети и сокетов;
5) /proc/sys/vm - настройки, касающиеся управления памятью;
6) /proc/sysvipc - информация об IPC-объектах System V;
7) /proc/cpuinfo - информация о центральном процессоре;
Обычно, команд cat, more и less должно хватать для того, чтобы разобрать различную информацию внутри proc. В качестве примера, давайте выхватим немного данных про наш CPU:
Однако из некоторых файлов бывает тяжело выцепить что-то полезное. Для таких случаев существуют утилиты lsof, lspci, apm и top, которые являются интерфейсом для proc и предоставляют легко читаемую для пользователя информацию.
Ранее мы уже затрагивали виртуальную файловую систему proc, когда говорили про команду lsof. Сегодня копнем глубже и научимся получать из нее различную информацию о системе и процессах.
В старых реализациях UNIX не было простого способа выполнить интроспективный анализ атрибутов ядра для получения ответов на следующие вопросы:
1. Сколько процессов запущено в системе и кто их владельцы?
2. Какие файлы открыты процессом?
3. Какие файлы в данный момент заблокированы и какие процессы удерживают эти блокировки?
4. Какие сокеты используются в системе?
Чтобы предоставить более легкий доступ к информации ядра, во многих современных реализациях UNIX предусмотрена VFS (Virtual File System) proc. Она находится в одноименном каталоге и содержит различные файлы, предоставляющие информацию о ядре. Называется она виртуальной потому, что фактически не располагается на диске и заполняется ядром на лету.
Процессам можно беспрепятственно считывать эту информацию и, в некоторых случаях, вносить в нее изменения, используя обычные системные вызовы файлового ввода-вывода: read, write, open, close и т.д.
Получение информации о процессе (/proc/PID):
Для каждого процесса в системе ядро создает соответствующий каталог по имени /proc/PID (process id). Внутри этого каталога находятся различные файлы и подкаталоги, содержащие информацию о процессе. Важно помнить, что данные директории не существуют постоянно и затираются, как только процесс прекратил свое существование.
Например, просмотрев файлы в каталоге /proc/1, можно получить информацию о процессе init, PID которого всегда имеет значение 1. Давайте посмотрим на базовую информацию процесса init через файл status, который существует в каждом каталоге /proc/PID и предоставляет множество данных о целевом процессе:
$ cat /proc/1/status
Name: init - Имя исполняемого файла
State: S (sleeping) - Состояние процесса
Tgid: 1 - ID группы потоков (обычный PID, getpid())
Pid: 1 - Фактически ID потока (gettid())
PPid: 0 - ID родительского процесса
TracerPid: 0 - PID отслеживающего процесса
...
Описание файлов и директорий для каждого каталога (/proc/PID):
1) cmdline - аргументы командной строки;
2) cwd - символьная ссылка на текущий рабочий каталог;
3) environ - пары вида ИМЯ=значение списка переменных среды;
4) exe - символьная ссылка на исполняемый файл;
5) fd - каталог, содержащий символьные ссылки на файлы, открытые процессом;
6) maps - отображения памяти;
7) mem - виртуальная память процесса;
8) mounts - точки монтирования для рассматриваемого процесса;
9) root - символьная ссылка на корневой каталог;
10) status - различная информация (идентификаторы процесса, права, использование памяти, сигналы);
11) task - содержит по одному подкаталогу для каждого потока в процессе;
Системная информация, находящаяся в proc:
Доступ к информации, распространяющейся на всю систему также предоставляется через различные файлы и директории внутри VFS proc. Давайте посмотрим на назначения отдельных из них:
1) /proc/net - информация состояния сети и сокетов;
2) /proc/sys/fs - настройки, относящиеся к файловым системам;
3) /proc/sys/kernel - различные общие настройки ядра;
4) /proc/sys/net - настройки сети и сокетов;
5) /proc/sys/vm - настройки, касающиеся управления памятью;
6) /proc/sysvipc - информация об IPC-объектах System V;
7) /proc/cpuinfo - информация о центральном процессоре;
Обычно, команд cat, more и less должно хватать для того, чтобы разобрать различную информацию внутри proc. В качестве примера, давайте выхватим немного данных про наш CPU:
$ cat /proc/cpuinfo
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 154
model name : 12th Gen Intel(R) Core(TM) i7-1260P
stepping : 3
microcode : 0x423
cpu MHz : 710.883
cache size : 18432 KB
Однако из некоторых файлов бывает тяжело выцепить что-то полезное. Для таких случаев существуют утилиты lsof, lspci, apm и top, которые являются интерфейсом для proc и предоставляют легко читаемую для пользователя информацию.
👍21❤🔥6🔥6❤2👾1
Итоги квартала
Для простоты навигации по контенту попробуем внедрить практику подведения квартальных результатов:
Разделяемые библиотеки:
1. Общая информация про разделяемые библиотеки
2. Компоновка с динамическими библиотеками [1]
3. Компоновка с динамическими библиотеками [2]
4. Компоновка с динамическими библиотеками [3]
5. Версионирование разделяемых библиотек
6. Отложенное связывание символов (Lazy Binding)
7. Ускорение работы библиотек: отключение ленивого связывания
8. Использование флага компоновщика -Bsymbolic
9. Ускорение работы библиотек: отключение перехвата функций
Разбор команд:
1. Разбор команд: strace
2. Разбор команд: strace -c
3. Разбор команд: lsof
4. Разбор команд: source
Базовый linux:
1. Процессы и программы
2. Структура памяти процесса
3. Системные и библиотечные вызовы [1]
4. Файловая система proc
Создание бинарных пакетов:
1. Разбор пакетов: build-essential
2. Что такое бинарный пакет (deb, rpm)?
3. Cборка бинарного (.deb) пакета
Для простоты навигации по контенту попробуем внедрить практику подведения квартальных результатов:
Разделяемые библиотеки:
1. Общая информация про разделяемые библиотеки
2. Компоновка с динамическими библиотеками [1]
3. Компоновка с динамическими библиотеками [2]
4. Компоновка с динамическими библиотеками [3]
5. Версионирование разделяемых библиотек
6. Отложенное связывание символов (Lazy Binding)
7. Ускорение работы библиотек: отключение ленивого связывания
8. Использование флага компоновщика -Bsymbolic
9. Ускорение работы библиотек: отключение перехвата функций
Разбор команд:
1. Разбор команд: strace
2. Разбор команд: strace -c
3. Разбор команд: lsof
4. Разбор команд: source
Базовый linux:
1. Процессы и программы
2. Структура памяти процесса
3. Системные и библиотечные вызовы [1]
4. Файловая система proc
Создание бинарных пакетов:
1. Разбор пакетов: build-essential
2. Что такое бинарный пакет (deb, rpm)?
3. Cборка бинарного (.deb) пакета
15👍35❤🔥16🔥11🫡1
LinuxCamp | DevOps pinned «Итоги квартала Для простоты навигации по контенту попробуем внедрить практику подведения квартальных результатов: Разделяемые библиотеки: 1. Общая информация про разделяемые библиотеки 2. Компоновка с динамическими библиотеками [1] 3. Компоновка с динамическими…»
Установка Decky Loader через терминал
Decky Loader - это самодельный лаунчер плагинов для Steam Deck, который позволяет вам, через подкачку различных дополнений, кастомизировать ваше устройство по всем фронтам: стилизовать меню, изменять системные звуки, модифицировать экран загрузки и т.д.
Сам поставил себе это расширение, т.к не осталось баллов для анимаций, да и ассортимент в магазине стима, будем честны, требует расширения)
Для установки требуется:
1. Создать пароль для пользователя:
2. Подгрузить установочный .sh скрипт и передать его интерпретатору sh на выполнение:
Тут вам и команда curl и перенаправление вывода через '|' pipe. Аххх, ладно, все это мы еще разберем в отдельных постах, сегодня чилл🍿
Decky Loader - это самодельный лаунчер плагинов для Steam Deck, который позволяет вам, через подкачку различных дополнений, кастомизировать ваше устройство по всем фронтам: стилизовать меню, изменять системные звуки, модифицировать экран загрузки и т.д.
Сам поставил себе это расширение, т.к не осталось баллов для анимаций, да и ассортимент в магазине стима, будем честны, требует расширения)
Для установки требуется:
1. Создать пароль для пользователя:
$ passwd deck
2. Подгрузить установочный .sh скрипт и передать его интерпретатору sh на выполнение:
curl -L https://github.com/SteamDeckHomebrew/decky-installer/releases/latest/download/install_release.sh | sh
Тут вам и команда curl и перенаправление вывода через '|' pipe. Аххх, ладно, все это мы еще разберем в отдельных постах, сегодня чилл
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13⚡4❤🔥3❤1
Говорим про либы: GTK и QT [1]
Уж очень часто в современной разработке софта под Linux, программистам приходится взаимодействовать с библиотеками GTK и QT. Будь-то приложение по управлению системами IOT (Internet of things) либо сетевой менеджер, не всех может устроить взаимодействие с утилитой через CLI (command line interface), поэтому для повышения комфорта в использовании, приходится прибегать к графическому туллкиту.
В этой серии постов я, через призму личного опыта, постараюсь объяснить вам то, что из себя представляют библиотеки GTK и QT, для чего они необходимы, c какими ЯП используются и, на сколько востребованы при трудоустройстве.
Что вообще такое это ваше GTK и QT?
Для разработки графических приложений нам нужен какой-то, желательно, простой в использовании, инструментарий - API, через которое можно попросить ряд подсистем нашей операционки создать окно, отрисовать кнопку, повесить на нее ивент и выполнить определенный набор инструкций по требованию.
Парочка GTK/QT, как раз и является тем самым высокоуровневым API над такими платформозависимыми технологиями, как X11, WinAPI, WaylandAPI и т.д.
Целые окружения рабочего стола, со всеми их графическими утилитами, написаны на этих библиотеках - так GNOME использует GTK, KDE, в свою очередь, построен на QT.
Вид некоторых виджетов GTK приложений сильно отличается от тех, которые предлагает QT, из-за чего желательно, чтобы системные приложения внутри определенного DE (Desktop Environment) были написаны на том же графическом туллките, что и сами компоненты окружения.
GTK - это многомодульный GUI туллкит, который разработан на языке программирования C в рамках проекта GNOME и необходим для создания графических интерфейсов под разные системные платформы: Windows, Linux, MacOS. Преимущественно, конечно же, именно под Linux.
Когда-то, еще в бородатые годы, пробовал запуститься на Windows, но, ребята, оно того не стоит. Для MacOS и Windows существуют гораздо более предпочтительные альтернативы.
Для GTK, дабы облегчить процесс создания интерфейсов и избавить вас от необходимости кодом прописывать свойства для виджетов, придумали ряд полезных инструментов, которые позволяют визуально прорабатывать дизайн, накидывать виджеты на макет и выдавать готовый XML.
Самыми удобными и проработанными утилитами, из тех, что мне приходилось использовать, могу назвать GLADE и Cambalache.
Qt уже не отнесешь к группе "просто" GUI туллкитов. В отличии от GTK, Qt является полноценным фреймворком для разработки приложений: гораздо более функциональным, громоздким и, написанным на C++.
Тут вам и API по построению клиент-серверной архитектуры и кастомные типы данных и различные медиа классы и готовые сущности для работы с базами данных и SQL.
Возможностей фреймворка может сполна хватать для разработки разного рода приложений, благодаря чему, программисту не приходится подключать ряд сторонних зависимостей, разбираться в их структуре и проверять работоспособность на разных платформах.
На Qt, без всяких там boost, ASIO и, уж тем более, платформозависимых системных обращений, можно построить многопоточный сетевой аппликейшн.
Приложение, написанное на том, что Qt предоставляет из коробки будет структурно выглядеть приемлемо - иметь единый стиль и, с высокой вероятностью, работать корректно на разных поддерживаемых платформах, среди которых: Windows, Linux, MacOS, Android, IOS.
При работе с GTK тоже можно использовать реализации различных структур данных, API под разные задачи - все это присутствует в сторонних библиотеках, разработанных проектом GNOME (GLib, GIO и т.д.), но по удобству использования и функциональности это, конечно, не дотягивает до Qt.
Qt также может похвастаться, как мне кажется, хорошим IDE (Qt Creator), который позволяет вам удобно вести полный цикл разработки приложения - переключаться между версткой дизайна и кодом, заниматься отладкой и профилированием, взаимодействовать с системой контроля версий и т.д.
Linux++ | IT-Образование
Уж очень часто в современной разработке софта под Linux, программистам приходится взаимодействовать с библиотеками GTK и QT. Будь-то приложение по управлению системами IOT (Internet of things) либо сетевой менеджер, не всех может устроить взаимодействие с утилитой через CLI (command line interface), поэтому для повышения комфорта в использовании, приходится прибегать к графическому туллкиту.
В этой серии постов я, через призму личного опыта, постараюсь объяснить вам то, что из себя представляют библиотеки GTK и QT, для чего они необходимы, c какими ЯП используются и, на сколько востребованы при трудоустройстве.
Что вообще такое это ваше GTK и QT?
Для разработки графических приложений нам нужен какой-то, желательно, простой в использовании, инструментарий - API, через которое можно попросить ряд подсистем нашей операционки создать окно, отрисовать кнопку, повесить на нее ивент и выполнить определенный набор инструкций по требованию.
Парочка GTK/QT, как раз и является тем самым высокоуровневым API над такими платформозависимыми технологиями, как X11, WinAPI, WaylandAPI и т.д.
Целые окружения рабочего стола, со всеми их графическими утилитами, написаны на этих библиотеках - так GNOME использует GTK, KDE, в свою очередь, построен на QT.
Вид некоторых виджетов GTK приложений сильно отличается от тех, которые предлагает QT, из-за чего желательно, чтобы системные приложения внутри определенного DE (Desktop Environment) были написаны на том же графическом туллките, что и сами компоненты окружения.
GTK - это многомодульный GUI туллкит, который разработан на языке программирования C в рамках проекта GNOME и необходим для создания графических интерфейсов под разные системные платформы: Windows, Linux, MacOS. Преимущественно, конечно же, именно под Linux.
Когда-то, еще в бородатые годы, пробовал запуститься на Windows, но, ребята, оно того не стоит. Для MacOS и Windows существуют гораздо более предпочтительные альтернативы.
Для GTK, дабы облегчить процесс создания интерфейсов и избавить вас от необходимости кодом прописывать свойства для виджетов, придумали ряд полезных инструментов, которые позволяют визуально прорабатывать дизайн, накидывать виджеты на макет и выдавать готовый XML.
Самыми удобными и проработанными утилитами, из тех, что мне приходилось использовать, могу назвать GLADE и Cambalache.
Qt уже не отнесешь к группе "просто" GUI туллкитов. В отличии от GTK, Qt является полноценным фреймворком для разработки приложений: гораздо более функциональным, громоздким и, написанным на C++.
Тут вам и API по построению клиент-серверной архитектуры и кастомные типы данных и различные медиа классы и готовые сущности для работы с базами данных и SQL.
Возможностей фреймворка может сполна хватать для разработки разного рода приложений, благодаря чему, программисту не приходится подключать ряд сторонних зависимостей, разбираться в их структуре и проверять работоспособность на разных платформах.
На Qt, без всяких там boost, ASIO и, уж тем более, платформозависимых системных обращений, можно построить многопоточный сетевой аппликейшн.
Приложение, написанное на том, что Qt предоставляет из коробки будет структурно выглядеть приемлемо - иметь единый стиль и, с высокой вероятностью, работать корректно на разных поддерживаемых платформах, среди которых: Windows, Linux, MacOS, Android, IOS.
При работе с GTK тоже можно использовать реализации различных структур данных, API под разные задачи - все это присутствует в сторонних библиотеках, разработанных проектом GNOME (GLib, GIO и т.д.), но по удобству использования и функциональности это, конечно, не дотягивает до Qt.
Qt также может похвастаться, как мне кажется, хорошим IDE (Qt Creator), который позволяет вам удобно вести полный цикл разработки приложения - переключаться между версткой дизайна и кодом, заниматься отладкой и профилированием, взаимодействовать с системой контроля версий и т.д.
Linux++ | IT-Образование
1👍28🔥14❤5🥰2🎃1
Ресурсы процессов
Каждый процесс потребляет системные ресурсы, такие как память и процессорное время. Этот пост расскажет вам о системных вызовах, которые необходимы для получения подобной информацией о потребляемых ресурсах и контроля ряда ограничений.
Мы начнем с вызова getrusage(), который позволяет процессу следить за ресурсами, потребленными им или его потомками. Затем будут рассмотрены вызовы setrlimit() и getrlimit(), которые позволяют изменять и получать данные об установленных для вызывающего процесса ограничениях на различные ресурсы.
Ресурсы, использующиеся процессом:
Системный вызов getrusage() возвращает статистику, которая касается различных ресурсов системы, потребленных самим вызывающим процессом или всеми его потомками:
Данный вызов возвращает 0 при успешном завершении или –1, если произошла ошибка. Аргумент who обозначает процесс, для которого будет извлекаться информация о потреблении ресурсов. Он может принимать одно из следующих значений:
1. RUSAGE_SELF — возвращает сведения о вызывающем процессе.
2. RUSAGE_CHILDREN — возвращает сведения обо всех потомках вызывающего процесса.
3. RUSAGE_THREAD — возвращает сведения о вызывающем потоке (поддерживается только в Linux).
Аргумент res_usage представляет собой указатель на структуру типа rusage, поля которой заполняются в момент вызова:
Нужно помнить, что далеко не все поля структуры rusage заполняются вызовом getrusage(). Некоторые поля могут игнорироваться в Linux, но учитываться в других реализациях UNIX. В Linux они присутствуют на случай, если их реализуют в будущем - это позволит поддерживать бинарную совместимость с уже существующими приложениями.
Давайте на примере посмотрим, как можно получить "user/system" процессорное время, затраченное процессом на выполнение кода:
Ограничения на ресурсы для отдельных процессов:
Каждый процесс обладает набором параметров, с помощью которых можно ограничить объем тех или иных системных ресурсов, которые он может потребить. Сведения об ограничениях на ресурсы процесса можно получать и изменять с помощью системных вызовов getrlimit() и setrlimit():
Аргумент resource обозначает ограничение на ресурсы, которое нужно получить либо изменить. Рассмотрим несколько примеров возможных значений:
Аргумент rlim используется либо для возвращения существующих значений ограничения, либо для задания новых. Он представляет собой структуру, состоящую из двух полей:
Эти поля имеют целочисленный тип rlim_t и соответствуют двум ограничениям на один ресурс: мягкому (rlim_cur) и жесткому (rlim_max). Эти ограничения управляют тем, какой объем целевого ресурса может быть использован.
Мягкое ограничение регулирует количество ресурсов, которые могут быть использованы процессом. Процесс может использовать ресурсы в пределах этого значения. Жесткое ограничение является абсолютным максимумом, который нельзя превысить, даже при попытке изменить мягкое ограничение.
Linux++ | IT-Образование
Каждый процесс потребляет системные ресурсы, такие как память и процессорное время. Этот пост расскажет вам о системных вызовах, которые необходимы для получения подобной информацией о потребляемых ресурсах и контроля ряда ограничений.
Мы начнем с вызова getrusage(), который позволяет процессу следить за ресурсами, потребленными им или его потомками. Затем будут рассмотрены вызовы setrlimit() и getrlimit(), которые позволяют изменять и получать данные об установленных для вызывающего процесса ограничениях на различные ресурсы.
Ресурсы, использующиеся процессом:
Системный вызов getrusage() возвращает статистику, которая касается различных ресурсов системы, потребленных самим вызывающим процессом или всеми его потомками:
#include <sys/resource.h>
int getrusage(int who, struct rusage *res_usage);
Данный вызов возвращает 0 при успешном завершении или –1, если произошла ошибка. Аргумент who обозначает процесс, для которого будет извлекаться информация о потреблении ресурсов. Он может принимать одно из следующих значений:
1. RUSAGE_SELF — возвращает сведения о вызывающем процессе.
2. RUSAGE_CHILDREN — возвращает сведения обо всех потомках вызывающего процесса.
3. RUSAGE_THREAD — возвращает сведения о вызывающем потоке (поддерживается только в Linux).
Аргумент res_usage представляет собой указатель на структуру типа rusage, поля которой заполняются в момент вызова:
struct rusage {
/* Процессорное время, потребленное пользователем */
struct timeval ru_utime;
/* Процессорное время, потребленное системой */
struct timeval ru_stime;
/* Размер страницы памяти, выделенной процессу */
long ru_maxrss;
...
}
Нужно помнить, что далеко не все поля структуры rusage заполняются вызовом getrusage(). Некоторые поля могут игнорироваться в Linux, но учитываться в других реализациях UNIX. В Linux они присутствуют на случай, если их реализуют в будущем - это позволит поддерживать бинарную совместимость с уже существующими приложениями.
Давайте на примере посмотрим, как можно получить "user/system" процессорное время, затраченное процессом на выполнение кода:
#include <stdio.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <unistd.h>
void main() {
/* Some Work... */
struct rusage usage;
if (getrusage(RUSAGE_SELF, &usage) == 0) {
printf("User CPU time: %ld.%06ld seconds\n",
usage.ru_utime.tv_sec, usage.ru_utime.tv_usec);
printf("System CPU time: %ld.%06ld seconds\n",
usage.ru_stime.tv_sec, usage.ru_stime.tv_usec);
}
}
Ограничения на ресурсы для отдельных процессов:
Каждый процесс обладает набором параметров, с помощью которых можно ограничить объем тех или иных системных ресурсов, которые он может потребить. Сведения об ограничениях на ресурсы процесса можно получать и изменять с помощью системных вызовов getrlimit() и setrlimit():
int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);
Аргумент resource обозначает ограничение на ресурсы, которое нужно получить либо изменить. Рассмотрим несколько примеров возможных значений:
RLIMIT_AS Размер виртуальной памяти процесса (байты)
RLIMIT_CORE Размер дампа памяти (байты)
RLIMIT_CPU Процессорное время (секунды)
...
Аргумент rlim используется либо для возвращения существующих значений ограничения, либо для задания новых. Он представляет собой структуру, состоящую из двух полей:
struct rlimit {
rlim_t rlim_cur;
rlim_t rlim_max;
};
Эти поля имеют целочисленный тип rlim_t и соответствуют двум ограничениям на один ресурс: мягкому (rlim_cur) и жесткому (rlim_max). Эти ограничения управляют тем, какой объем целевого ресурса может быть использован.
Мягкое ограничение регулирует количество ресурсов, которые могут быть использованы процессом. Процесс может использовать ресурсы в пределах этого значения. Жесткое ограничение является абсолютным максимумом, который нельзя превысить, даже при попытке изменить мягкое ограничение.
Linux++ | IT-Образование
1👍21❤6❤🔥5🔥2
Демоны в Linux
Демон - это процесс, обладающий длинным жизненным циклом. Часто демоны создаются на этапе загрузки системы и работают до момента ее выключения. Выполняется они в фоновом режиме, не имеют контролирующего терминала и обычно не привязаны к конкретной пользовательской сессии.
Примеры системных демонов
У каждого системного демона есть своя микроцель существования. Одни контролируют сетевые соединения, другие отвечают за взаимодействие между приложениями по системе dbus. Примерами популярных демонов являются:
1) cron — демон, который выполняет команды в запланированное время;
2) sshd — демон, который отвечает за обработку SSH-подключений;
3) httpd — демон HTTP-сервера (Apache), который обслуживает веб-страницы;
Вы можете и сами "поймать" демонов через различные утилиты: ps, top, pstree. Базовыми отличительными параметрами таких процессов являются имя, которое заканчивается на 'd', родительский процесс init и отсутствующий терминал:
Дополнительно хочется отметить, что процессам-демонам присуща особенность, которая гарантирует, что ядро не сможет генерировать для них никаких сигналов, связанных с терминалом (SIGINT, SIGTSTP и SIGHUP).
Создание демона
Глобально существует 2 типа демонов: "SysV Daemons" и "New-Style Daemons". Первый тип является традиционным и преимущественно использовался до появления systemd. Второй, в свою очередь, опирается на инфраструктуру systemd и является сервисом.
Сейчас не будем вдаваться в принцип работы сервисов и рассмотрим инициализацию каноничного SysV демона. Для того, чтобы стать демоном, программа должна выполнить следующие шаги:
1. Сделать вызов fork(), после которого родитель завершается, а потомок продолжает работать. Это нужно для отделения демона от терминала, из которого он был запущен. В результате, процесс становится потомком для init:
2. Дочерний процесс вызывает setsid(), чтобы начать новую сессию, стать ее лидером и разорвать любые связи с контролирующим терминалом:
3. Проигнорировать сигнал SIGHUP для того, чтобы не завершиться при закрытии терминала, внутри которого был воспроизведен запуск:
4. Повторно выполнить fork(). Этот шаг иногда выполняется для предотвращения возможности захвата вновь созданного демона новым управляющим терминалом.
5. Очистить атрибут umask, чтобы файлы и каталоги, созданные демоном, имели запрашиваемые права доступа, указанные в вызовах open() и mkdir():
6. Поменять текущий рабочий каталог процесса (обычно на корневой). Это необходимо для исключения блокировки файловой системы и возможности, в случае необходимости, сделать для нее unmount:
7. Закрыть все открытые файловые дескрипторы, которые демон унаследовал от своего родителя. Поскольку демон потерял свой контролирующий терминал и работает в фоновом режиме, ему больше не нужно хранить дескрипторы с номерами 0, 1 и 2, их тоже закрываем:
8. Переоткрыть дескрипторы STDIN, STDOUT, STDERR и перенаправить стандартные потоки в виртуальное устройство "/dev/null". Данный шаг необходим по нескольким причинам.
Во-первых, вновь открытые файлы неизбежно возьмут себе минимально доступный порядковый номер (0, 1, 2 ...), что может привести к нежелательным записям со стороны функций, которые работают с этими дескрипторами.
Во-вторых, данное действие позволяет избежать ошибок при вызове библиотечных функций, которые выполняют операции ввода/вывода с этими дескрипторами:
9. Запустить основной цикл, в котором демон будет выполнять свою работу:
Linux++ | IT-Образование
Демон - это процесс, обладающий длинным жизненным циклом. Часто демоны создаются на этапе загрузки системы и работают до момента ее выключения. Выполняется они в фоновом режиме, не имеют контролирующего терминала и обычно не привязаны к конкретной пользовательской сессии.
Примеры системных демонов
У каждого системного демона есть своя микроцель существования. Одни контролируют сетевые соединения, другие отвечают за взаимодействие между приложениями по системе dbus. Примерами популярных демонов являются:
1) cron — демон, который выполняет команды в запланированное время;
2) sshd — демон, который отвечает за обработку SSH-подключений;
3) httpd — демон HTTP-сервера (Apache), который обслуживает веб-страницы;
Вы можете и сами "поймать" демонов через различные утилиты: ps, top, pstree. Базовыми отличительными параметрами таких процессов являются имя, которое заканчивается на 'd', родительский процесс init и отсутствующий терминал:
$ ps -o pid,ppid,cmd
PID PPID CMD
1239 1 /usr/lib/snapd/snapd
1250 1 /usr/libexec/udisks2/udisksd
Дополнительно хочется отметить, что процессам-демонам присуща особенность, которая гарантирует, что ядро не сможет генерировать для них никаких сигналов, связанных с терминалом (SIGINT, SIGTSTP и SIGHUP).
Создание демона
Глобально существует 2 типа демонов: "SysV Daemons" и "New-Style Daemons". Первый тип является традиционным и преимущественно использовался до появления systemd. Второй, в свою очередь, опирается на инфраструктуру systemd и является сервисом.
Сейчас не будем вдаваться в принцип работы сервисов и рассмотрим инициализацию каноничного SysV демона. Для того, чтобы стать демоном, программа должна выполнить следующие шаги:
1. Сделать вызов fork(), после которого родитель завершается, а потомок продолжает работать. Это нужно для отделения демона от терминала, из которого он был запущен. В результате, процесс становится потомком для init:
pid_t pid = fork();
if (pid < 0)
exit(EXIT_FAILURE);
if (pid > 0)
exit(EXIT_SUCCESS);
2. Дочерний процесс вызывает setsid(), чтобы начать новую сессию, стать ее лидером и разорвать любые связи с контролирующим терминалом:
if (setsid() < 0)
exit(EXIT_FAILURE);
3. Проигнорировать сигнал SIGHUP для того, чтобы не завершиться при закрытии терминала, внутри которого был воспроизведен запуск:
signal(SIGHUP, SIG_IGN);
4. Повторно выполнить fork(). Этот шаг иногда выполняется для предотвращения возможности захвата вновь созданного демона новым управляющим терминалом.
5. Очистить атрибут umask, чтобы файлы и каталоги, созданные демоном, имели запрашиваемые права доступа, указанные в вызовах open() и mkdir():
umask(0);
6. Поменять текущий рабочий каталог процесса (обычно на корневой). Это необходимо для исключения блокировки файловой системы и возможности, в случае необходимости, сделать для нее unmount:
chdir("/");
7. Закрыть все открытые файловые дескрипторы, которые демон унаследовал от своего родителя. Поскольку демон потерял свой контролирующий терминал и работает в фоновом режиме, ему больше не нужно хранить дескрипторы с номерами 0, 1 и 2, их тоже закрываем:
for (int x = sysconf(_SC_OPEN_MAX); x>=0; x--)
close (x);
8. Переоткрыть дескрипторы STDIN, STDOUT, STDERR и перенаправить стандартные потоки в виртуальное устройство "/dev/null". Данный шаг необходим по нескольким причинам.
Во-первых, вновь открытые файлы неизбежно возьмут себе минимально доступный порядковый номер (0, 1, 2 ...), что может привести к нежелательным записям со стороны функций, которые работают с этими дескрипторами.
Во-вторых, данное действие позволяет избежать ошибок при вызове библиотечных функций, которые выполняют операции ввода/вывода с этими дескрипторами:
int fd0 = open("/dev/null", O_RDWR);
int fd1 = dup(0);
int fd2 = dup(0);
9. Запустить основной цикл, в котором демон будет выполнять свою работу:
while (1) {
}
Linux++ | IT-Образование
1👍34❤🔥12❤7🔥7👾2😈1
Идентификаторы процессов: пользователи и группы [1]
У каждого процесса есть набор, связанных с ним числовых идентификаторов пользователей (UID) и групп (GID). В число этих идентификаторов входят:
1) реальный (real) ID пользователя и группы;
2) действующий (effective) ID пользователя и группы;
3) сохранённый ID пользователя (set-user-ID) и группы (set-group-ID);
4) характерный для Linux пользовательский и групповой ID файловой системы;
5) дополнительные идентификаторы групп (supplementary group);
Реальный идентификатор пользователя и группы (RUID)
Реальные идентификаторы определяют пользователя и группу, которым принадлежит процесс. При входе в систему, оболочка получает свои ID пользователя и группы из полей файла "/etc/passwd". Каждый новый процесс наследует данные идентификаторы у своего родительского процесса:
Действующий идентификатор пользователя и группы (EUID)
В большинстве реализаций UNIX действующие UID и GID используются для определения полномочий, которыми наделен процесс, при его попытке выполнения различных операций (в том числе, системных вызовов).
Эти ID используются ядром для определения прав, которые будет иметь процесс при доступе к общим ресурсам, таким как: очереди сообщений, общая память, объекты межпроцессного взаимодействия (IPC) и семафоры.
В большинстве систем UNIX эти ID также определяют права доступа к файлам (в Linux для этой задачи используются ID файловой системы).
Процесс, чей действующий идентификатор пользователя имеет значение 0, называется привилегированным и принадлежит пользователю с именем root (имеет все полномочия суперпользователя). Некоторые системные вызовы могут быть выполнены только привилегированными процессами.
ID действующего пользователя и группы процесса можно получить с помощью вызовов geteuid и getegid.
Проводя сравнение между EUID и RUID можно сказать, что RUID обозначает пользователя, который владеет процессом, EUID является параметром, на который смотрит ядро ОС для того, чтобы разрешить вам выполнить действие либо отклонить запрос:
Также, важно помнить, что значение EUID может быть изменено и некоторые программы, такие как sudo, используют это для того, чтобы выполнять ряд привилегированных операций.
Сохранённые set-user-ID и set-group-ID (SUID)
Эти ID используются в программах с set-user-ID и set-group-ID битами для сохранения копии соответствующих EUID. Бит SUID можно опознать по букве 's', заменяющей обычный бит выполнения 'x'. Это говорит нам о том, что файл будет выполняться с правами владельца, а не пользователя, который его запускает:
Когда set-user-ID-программа запускается (загружается в память процесса с помощью команды exec()), ядро устанавливает для действующего EUID точно такое же значение, что и у пользовательского UID исполняемого файла:
После запуска, EUID пользователя будет скопирован в SUID. Идентификаторы процесса будут проставлены следующим образом: RUID=1000, ЕUID=0, SUID=0.
Программа с set-user-ID может повышать и понижать права, переключая свой ID действующего пользователя (EUID) между значениями RUID и SUID. Это полезно в случаях, когда, например, привилегированному процессу нужно выполнить непривилегированную задачу.
Такое переключение производится с помощью вызовов seteuid, setreuid или setresuid. Программа с set-group-ID выполняет аналогичные задачи с помощью setegid, setregid или setresgid.
Сохранённые set-user-ID и set-group-ID процесса можно получить с помощью getresuid и getresgid соответственно.
Linux++ | IT-Образование
У каждого процесса есть набор, связанных с ним числовых идентификаторов пользователей (UID) и групп (GID). В число этих идентификаторов входят:
1) реальный (real) ID пользователя и группы;
2) действующий (effective) ID пользователя и группы;
3) сохранённый ID пользователя (set-user-ID) и группы (set-group-ID);
4) характерный для Linux пользовательский и групповой ID файловой системы;
5) дополнительные идентификаторы групп (supplementary group);
Реальный идентификатор пользователя и группы (RUID)
Реальные идентификаторы определяют пользователя и группу, которым принадлежит процесс. При входе в систему, оболочка получает свои ID пользователя и группы из полей файла "/etc/passwd". Каждый новый процесс наследует данные идентификаторы у своего родительского процесса:
$ echo $UID
1000
$ grep $LOGNAME /etc/passwd
hiko:x:1000:1000:hiko:/home/hiko:/bin/bash
$ stat -c "%u %g" /proc/$pid/
1000 1000
Действующий идентификатор пользователя и группы (EUID)
В большинстве реализаций UNIX действующие UID и GID используются для определения полномочий, которыми наделен процесс, при его попытке выполнения различных операций (в том числе, системных вызовов).
Эти ID используются ядром для определения прав, которые будет иметь процесс при доступе к общим ресурсам, таким как: очереди сообщений, общая память, объекты межпроцессного взаимодействия (IPC) и семафоры.
В большинстве систем UNIX эти ID также определяют права доступа к файлам (в Linux для этой задачи используются ID файловой системы).
Процесс, чей действующий идентификатор пользователя имеет значение 0, называется привилегированным и принадлежит пользователю с именем root (имеет все полномочия суперпользователя). Некоторые системные вызовы могут быть выполнены только привилегированными процессами.
ID действующего пользователя и группы процесса можно получить с помощью вызовов geteuid и getegid.
Проводя сравнение между EUID и RUID можно сказать, что RUID обозначает пользователя, который владеет процессом, EUID является параметром, на который смотрит ядро ОС для того, чтобы разрешить вам выполнить действие либо отклонить запрос:
$ ps -eo pid,euid,ruid | grep 2254
2254 0 0
Также, важно помнить, что значение EUID может быть изменено и некоторые программы, такие как sudo, используют это для того, чтобы выполнять ряд привилегированных операций.
Сохранённые set-user-ID и set-group-ID (SUID)
Эти ID используются в программах с set-user-ID и set-group-ID битами для сохранения копии соответствующих EUID. Бит SUID можно опознать по букве 's', заменяющей обычный бит выполнения 'x'. Это говорит нам о том, что файл будет выполняться с правами владельца, а не пользователя, который его запускает:
$ stat -c "%A %a %n" /usr/bin/passwd
-rwsr-xr-x 4755 /usr/bin/passwd
Когда set-user-ID-программа запускается (загружается в память процесса с помощью команды exec()), ядро устанавливает для действующего EUID точно такое же значение, что и у пользовательского UID исполняемого файла:
$ su
# chown root app
# chmod u+s app
# ls -l app
-rwsr-xr-x 1 root users
После запуска, EUID пользователя будет скопирован в SUID. Идентификаторы процесса будут проставлены следующим образом: RUID=1000, ЕUID=0, SUID=0.
Программа с set-user-ID может повышать и понижать права, переключая свой ID действующего пользователя (EUID) между значениями RUID и SUID. Это полезно в случаях, когда, например, привилегированному процессу нужно выполнить непривилегированную задачу.
Такое переключение производится с помощью вызовов seteuid, setreuid или setresuid. Программа с set-group-ID выполняет аналогичные задачи с помощью setegid, setregid или setresgid.
Сохранённые set-user-ID и set-group-ID процесса можно получить с помощью getresuid и getresgid соответственно.
Linux++ | IT-Образование
1👍24🔥5❤2❤🔥2🥰1
Идентификаторы процессов: пользователи и группы [2]
И так, в прошлый раз мы посмотрели на первые 3 базовых идентификатора: RUID, EUID и SUID. Сегодня пойдем дальше и добьем последние 2:
1) ID пользователя и группы файловой системы (FSUID);
2) ID дополнительных групп (supplementary group);
ID пользователя и группы файловой системы (FSUID)
В Linux для определения прав доступа к файлам, применяются не пользовательские и групповые EUID, а соответствующие ID файловой системы (FSUID). Они используются в этом качестве наряду с идентификаторами дополнительных групп, описанных ниже.
Обычно идентификаторы файловой системы имеют те же значения, что и соответствующие действующие собраты. Более того, каждый раз при изменении EUID ядро также автоматически изменяет и FSUID, присваивая ему аналогичное значение:
Однако, никто не запрещает вам изменить ID файловой системы и сделать его отличным от действующих ID. Реализовать это можно через вызовы setfsuid и setfsgid:
А зачем в Linux вообще предоставляются идентификаторы файловой системы и при каких обстоятельствах нам могут понадобиться разные значения для EUID и FSUID?
Причины главным образом имеют исторические корни. Идентификаторы файловой системы впервые появились в Linux 1.2. В этой версии ядра один процесс мог отправлять сигнал другому, лишь если EUID отправителя совпадал с RUID или EUID целевого процесса.
Это повлияло на некоторые программы, например на программу сервера Linux NFS (Network File System), которой нужна была возможность доступа к файлам, как будто у нее есть действующие идентификаторы соответствующих клиентских процессов.
Но, если бы NFS-сервер изменял свой действующий идентификатор пользователя, он стал бы уязвим от сигналов непривилегированных пользовательских процессов.
Для предотвращения этой возможности были придуманы отдельные FSUID. Оставляя неизмененными свои действующие идентификаторы, но изменяя идентификаторы файловой системы, NFS-сервер может выдавать себя за другого пользователя с целью обращения к файлам без уязвимости от сигналов пользовательских процессов.
Начиная с версии ядра 2.0, приняты правила относительно разрешений на отправку сигналов, в результате чего, наличие FSUID утратило свою актуальность. В наше время ID файловой системы считаются некой экзотикой и, как правило, совпадают по значениям с EUID.
ID дополнительных групп (supplementary group)
В ранних реализациях unix, процесс мог принадлежать только к одной группе, прописанной в "/etc/passwd". Сейчас же можно принадлежать сразу к нескольким.
Новый процесс наследует эти идентификаторы от своего родительского процесса. Оболочка входа в систему получает свои дополнительные идентификаторы групп из файла групп системы "/etc/group":
Как уже ранее отмечалось, эти идентификаторы используются в совокупности с EUID и FSUID для определения полномочий по доступу к файлам, IPC-объектам и другим системным ресурсам.
Процесс может получить список ID дополнительных групп с помощь getgroups, и изменить этот список с помощью setgroups:
Linux++ | IT-Образование
И так, в прошлый раз мы посмотрели на первые 3 базовых идентификатора: RUID, EUID и SUID. Сегодня пойдем дальше и добьем последние 2:
1) ID пользователя и группы файловой системы (FSUID);
2) ID дополнительных групп (supplementary group);
ID пользователя и группы файловой системы (FSUID)
В Linux для определения прав доступа к файлам, применяются не пользовательские и групповые EUID, а соответствующие ID файловой системы (FSUID). Они используются в этом качестве наряду с идентификаторами дополнительных групп, описанных ниже.
Обычно идентификаторы файловой системы имеют те же значения, что и соответствующие действующие собраты. Более того, каждый раз при изменении EUID ядро также автоматически изменяет и FSUID, присваивая ему аналогичное значение:
$ ps -eo pid,uid,euid,suid,fsuid,comm
PID UID EUID SUID FSUID COMMAND
1 0 0 0 0 systemd
Однако, никто не запрещает вам изменить ID файловой системы и сделать его отличным от действующих ID. Реализовать это можно через вызовы setfsuid и setfsgid:
#include <sys/fsuid.h>
int setfsuid(uid_t fsuid);
int setfsgid(gid_t fsgid);
А зачем в Linux вообще предоставляются идентификаторы файловой системы и при каких обстоятельствах нам могут понадобиться разные значения для EUID и FSUID?
Причины главным образом имеют исторические корни. Идентификаторы файловой системы впервые появились в Linux 1.2. В этой версии ядра один процесс мог отправлять сигнал другому, лишь если EUID отправителя совпадал с RUID или EUID целевого процесса.
Это повлияло на некоторые программы, например на программу сервера Linux NFS (Network File System), которой нужна была возможность доступа к файлам, как будто у нее есть действующие идентификаторы соответствующих клиентских процессов.
Но, если бы NFS-сервер изменял свой действующий идентификатор пользователя, он стал бы уязвим от сигналов непривилегированных пользовательских процессов.
Для предотвращения этой возможности были придуманы отдельные FSUID. Оставляя неизмененными свои действующие идентификаторы, но изменяя идентификаторы файловой системы, NFS-сервер может выдавать себя за другого пользователя с целью обращения к файлам без уязвимости от сигналов пользовательских процессов.
Начиная с версии ядра 2.0, приняты правила относительно разрешений на отправку сигналов, в результате чего, наличие FSUID утратило свою актуальность. В наше время ID файловой системы считаются некой экзотикой и, как правило, совпадают по значениям с EUID.
ID дополнительных групп (supplementary group)
В ранних реализациях unix, процесс мог принадлежать только к одной группе, прописанной в "/etc/passwd". Сейчас же можно принадлежать сразу к нескольким.
Новый процесс наследует эти идентификаторы от своего родительского процесса. Оболочка входа в систему получает свои дополнительные идентификаторы групп из файла групп системы "/etc/group":
$ cat /etc/group | grep xodefender
adm:x:4:syslog,xodefender
cdrom:x:24:xodefender
sudo:x:27:xodefender
dip:x:30:xodefender
video:x:44:xodefender
Как уже ранее отмечалось, эти идентификаторы используются в совокупности с EUID и FSUID для определения полномочий по доступу к файлам, IPC-объектам и другим системным ресурсам.
Процесс может получить список ID дополнительных групп с помощь getgroups, и изменить этот список с помощью setgroups:
#include <unistd.h>
int getgroups(int size, gid_t list[]);
#include <grp.h>
int setgroups(size_t size, const gid_t *_Nullable list);
Linux++ | IT-Образование
1👍17❤3🔥3👾2