Во, отличная шпаргалка, которая будет полезным дополнением к 2 предыдущим постам:
1) разница между chmod и chown;
2) файл групп /etc/group;
Много раз слышал от знакомых "Ой, вот помню, что 777 дает всем права на все, дальше хз...".
Та я и сам, когда лень вспоминать степени 2 и что-то там считать, обычно проставляю права через "буковки". Теперь, может, и другие комбинации в голове отложатся)
LinuxCamp
1) разница между chmod и chown;
2) файл групп /etc/group;
Много раз слышал от знакомых "Ой, вот помню, что 777 дает всем права на все, дальше хз...".
Та я и сам, когда лень вспоминать степени 2 и что-то там считать, обычно проставляю права через "буковки". Теперь, может, и другие комбинации в голове отложатся)
LinuxCamp
👍43🔥17🤯2
Старость - не радость!
Сейчас с дистрибутивами такая история, что несколько версий ядра может быть доступно пользователю на этапе установки системы.
Причем, рекомендованым к установке - тем, что средней опытности юзер, вероятнее всего, поставит, необязательно будет самое передовое.
Тут, конечно, на помощь приходят ядерщики, которые патчат ядра и активно следят за тем, чтобы все они работали корректно на разных системах в разных условиях.
Но за всем не уследишь. На днях, что вы думаете, нашел очередную проблему в модуле ядра - драйвере i915... По итогу, уже 2 очень неприятных графических бага решаются просто поднятием версии до плюс-минус актуальной.
Больше шансов столкнуться с проблемами у тех пользователей, которые сидят на старых ядрах и новом железе. Какие-то шестеренки linux kernel могут очень тяжело прокручиваться на вашем 144Гц мониторе или, к примеру, intel I9 последнего поколения.
В результате хочется отметить, что иногда простое обновление ядра до актуальной версии может быть быстрым и эффективным решением многих багов.
Linux++ | IT-Образование
Сейчас с дистрибутивами такая история, что несколько версий ядра может быть доступно пользователю на этапе установки системы.
Причем, рекомендованым к установке - тем, что средней опытности юзер, вероятнее всего, поставит, необязательно будет самое передовое.
Тут, конечно, на помощь приходят ядерщики, которые патчат ядра и активно следят за тем, чтобы все они работали корректно на разных системах в разных условиях.
Но за всем не уследишь. На днях, что вы думаете, нашел очередную проблему в модуле ядра - драйвере i915... По итогу, уже 2 очень неприятных графических бага решаются просто поднятием версии до плюс-минус актуальной.
Больше шансов столкнуться с проблемами у тех пользователей, которые сидят на старых ядрах и новом железе. Какие-то шестеренки linux kernel могут очень тяжело прокручиваться на вашем 144Гц мониторе или, к примеру, intel I9 последнего поколения.
В результате хочется отметить, что иногда простое обновление ядра до актуальной версии может быть быстрым и эффективным решением многих багов.
Linux++ | IT-Образование
🔥27👍9❤🔥5👎1
Модуль ядра — ключ к расширению возможностей Linux
Как вы знаете, или, может быть, слышали, ядро Linux изначально задумывалось монолитным - весь функционал отрабатывает в рамках одной программы:
Такая архитектура имеет ряд недостатков, например, невозможность установки новых драйверов без полной пересборки. Разработчики думали, думали и нашли решение этой проблеме, проработав систему модулей.
Сегодня ядро позволяет драйверам оборудования, файловых систем, и некоторым другим компонентам быть скомпилированными отдельно - в качестве модулей.
Модуль ядра - это программа, которая может отсоединяться от ядра и присоединяться к нему по необходимости, без повторной его компиляции и перезагрузки системы.
В общих терминах, модуль можно описать, как плагин, который расширяет функциональность ядра.
Такой подход не свел монолитность на нет - ядро таковым и осталось, за счет того, что работает вместе с модулями в одном адресном пространстве.
Находятся все модули в директории "/lib/modules/". Учитывая то, что они собираются под каждую отдельную версию ядра, в этом каталоге выстраивается структура папок - по штуке на установленную версию:
В директории целевого ядра находятся, как сами модули, так и дополнительные конфиги:
В ОС Linux все модули имеют расширение .ko (kernel object) или .ko.zst (модуль, сжатый с помощью алгоритма Zstandard). Подгружаются они, как правило, на этапе бута системы.
Помните, я тут недавно жаловался на драйвер i915, который в старых ядрах очень несовместим с некоторыми intel процессорами. Давайте посмотрим, где он засел:
Сегодня мы узнали, что такое модуль ядра. В следующей публикации дополнительно расширим спектр скиллов и рассмотрим основные команды администрирования модулей.
Linux++ | IT-Образование
Как вы знаете, или, может быть, слышали, ядро Linux изначально задумывалось монолитным - весь функционал отрабатывает в рамках одной программы:
/boot/vmlinuz-6.8.0-47-generic
Такая архитектура имеет ряд недостатков, например, невозможность установки новых драйверов без полной пересборки. Разработчики думали, думали и нашли решение этой проблеме, проработав систему модулей.
Сегодня ядро позволяет драйверам оборудования, файловых систем, и некоторым другим компонентам быть скомпилированными отдельно - в качестве модулей.
Модуль ядра - это программа, которая может отсоединяться от ядра и присоединяться к нему по необходимости, без повторной его компиляции и перезагрузки системы.
В общих терминах, модуль можно описать, как плагин, который расширяет функциональность ядра.
Такой подход не свел монолитность на нет - ядро таковым и осталось, за счет того, что работает вместе с модулями в одном адресном пространстве.
Находятся все модули в директории "/lib/modules/". Учитывая то, что они собираются под каждую отдельную версию ядра, в этом каталоге выстраивается структура папок - по штуке на установленную версию:
$ cd /lib/modules
$ ls
6.10.1-061001-generic 6.11.0-9-generic 6.8.0-060800-generic 6.9.0-060900-generic
В директории целевого ядра находятся, как сами модули, так и дополнительные конфиги:
$ cd 6.8.12-060812-generic
$ ls
build
modules.builtin
modules.dep.bin
kernel
...
В ОС Linux все модули имеют расширение .ko (kernel object) или .ko.zst (модуль, сжатый с помощью алгоритма Zstandard). Подгружаются они, как правило, на этапе бута системы.
Помните, я тут недавно жаловался на драйвер i915, который в старых ядрах очень несовместим с некоторыми intel процессорами. Давайте посмотрим, где он засел:
$ find /lib/modules/$(uname -r) -name *.ko* | grep i915
/lib/modules/6.8.12-060812-generic/kernel/drivers/gpu/drm/i915/i915.ko.zst
Сегодня мы узнали, что такое модуль ядра. В следующей публикации дополнительно расширим спектр скиллов и рассмотрим основные команды администрирования модулей.
Linux++ | IT-Образование
👍60🔥21❤🔥4
Подгружаем, отгружаем, управляем – просто и эффективно
Прошлый пост нам рассказал о том, что из себя представляет модуль ядра. Понимание этого - уже супер, но хочется больше!
Сегодня посмотрим на парочку команд и приемов по администрированию модулей.
Перечисление загруженных модулей
У вас есть возможность вывести список, подгруженных на данный момент, модулей. Выполнить это нам поможет lsmod - простая утилита, которая не принимает никаких опций или аргументов.
Все, что делает команда - читает файл "/proc/modules" и отображает его содержимое в хорошо отформатированном списке:
Результат дает нам следующую информацию:
1) Module - название модуля, выгруженного в память;
2) Size - количество памяти (в килобайтах), которое занимает модуль;
3) Used by - количество экземпляров модуля, используемое в настоящее время. Нулевое значение означает, что модуль не используется и его можно безопасно выгрузить.
Разделенный запятыми список после числа показывает, какие модули использует экземпляр;
Вывод информации о модуле
Команда modinfo отображает дополнительную информацию о модуле:
Загрузка модулей в рантайме
Как уже говорилось, подгрузка модулей - эффективный способ расширить функционал ядра. Как загрузить модуль? Использовать команды modprobe и insmod:
1) modprobe - умная команда, которая анализирует файл modules.dep, чтобы сначала загрузить зависимости, потом уже сам модуль:
2) insmod - более простая и менее гибкая команда, которая загружает модуль без проверки зависимостей:
Выгрузка модулей в рантайме
При необходимости можно удалить модули из работающей системы. Это также можно выполнить 2 способами: использовать rmmod или modprobe -r.
Последняя используется для более безопасного удаления, учитывая зависимости.
При выгрузке модуля через rmmod, остальные, которые от него зависят, будут пытаться функционировать.
Также команда поддерживает опцию "--force", которая ингода может быть полезна для агрессивной выгрузки:
Исключение модуля из автоматической загрузки
Для того, чтобы не загружать модуль на этапе бута системы, его требуется добавить в ЧС - файл blacklist.conf:
Такой прием бывает полезен, если требуется отключить проблемное оборудование или драйверы.
Автозагрузка модулей
Для того, чтобы каждый раз не производить ручную загрузку модулей, работать с которыми требуется регулярно, существует отдельный каталог, в котором можно настроить автоматическую загрузку модулей на старте системы "/etc/modules-load.d/".
В каталоге хранятся конфиги, содержащие наименования необходимых модулей:
При следующем запуске системы, указанные в файле модули, будут автоматически загружены.
Linux++ | IT-Образование
Прошлый пост нам рассказал о том, что из себя представляет модуль ядра. Понимание этого - уже супер, но хочется больше!
Сегодня посмотрим на парочку команд и приемов по администрированию модулей.
Перечисление загруженных модулей
У вас есть возможность вывести список, подгруженных на данный момент, модулей. Выполнить это нам поможет lsmod - простая утилита, которая не принимает никаких опций или аргументов.
Все, что делает команда - читает файл "/proc/modules" и отображает его содержимое в хорошо отформатированном списке:
$ lsmod
Module Size Used by
tcp_lp 12663 0
bluetooth 372662 7 bnep
rfkill 26536 3 bluetooth
Результат дает нам следующую информацию:
1) Module - название модуля, выгруженного в память;
2) Size - количество памяти (в килобайтах), которое занимает модуль;
3) Used by - количество экземпляров модуля, используемое в настоящее время. Нулевое значение означает, что модуль не используется и его можно безопасно выгрузить.
Разделенный запятыми список после числа показывает, какие модули использует экземпляр;
Вывод информации о модуле
Команда modinfo отображает дополнительную информацию о модуле:
$ modinfo i915
filename: /lib/modules/6.8.12-060812-generic/kernel/drivers/gpu/drm/i915/i915.ko.zst
license: GPL and additional rights
denoscription: Intel Graphics
author: Intel Corporation
...
Загрузка модулей в рантайме
Как уже говорилось, подгрузка модулей - эффективный способ расширить функционал ядра. Как загрузить модуль? Использовать команды modprobe и insmod:
1) modprobe - умная команда, которая анализирует файл modules.dep, чтобы сначала загрузить зависимости, потом уже сам модуль:
$ modprobe i915
$ lsmod | grep i915
i915 4268032 53
2) insmod - более простая и менее гибкая команда, которая загружает модуль без проверки зависимостей:
$ insmod helloWorld.ko
Welcome to Hello world Module.
Выгрузка модулей в рантайме
При необходимости можно удалить модули из работающей системы. Это также можно выполнить 2 способами: использовать rmmod или modprobe -r.
Последняя используется для более безопасного удаления, учитывая зависимости.
При выгрузке модуля через rmmod, остальные, которые от него зависят, будут пытаться функционировать.
Также команда поддерживает опцию "--force", которая ингода может быть полезна для агрессивной выгрузки:
$ rmmod module.ko
Goodbye, from module
$ modprobe -r module.ko
Goodbye, from module
Исключение модуля из автоматической загрузки
Для того, чтобы не загружать модуль на этапе бута системы, его требуется добавить в ЧС - файл blacklist.conf:
$ cat /etc/modprobe.d/blacklist.conf
blacklist eepro100
blacklist evbug
Такой прием бывает полезен, если требуется отключить проблемное оборудование или драйверы.
Автозагрузка модулей
Для того, чтобы каждый раз не производить ручную загрузку модулей, работать с которыми требуется регулярно, существует отдельный каталог, в котором можно настроить автоматическую загрузку модулей на старте системы "/etc/modules-load.d/".
В каталоге хранятся конфиги, содержащие наименования необходимых модулей:
$ cat modules.conf
video
e1000
serio_raw
При следующем запуске системы, указанные в файле модули, будут автоматически загружены.
Linux++ | IT-Образование
👍37🔥10❤🔥4❤1
Сокращаем команды: Мощь псевдонимов в Linux
Команда alias — это удобный инструмент для тех, кто постоянно работает в командной строке.
Пользователям часто приходится использовать одну и ту же команду. Нередко — с большим количеством опций или с одними и теми же аргументами.
Alias является, встроенной в оболочку командой, которая позволяет оптимизировать рутину и скрывать длинные вызовы под лаконичными псевдонимами.
К примеру, нам нужно понять, какие файлы занимают в целевом каталоге слишком много места и не надо ли их удалить...
Для реализации этой задачи нам потребуется команда ls и много-много аргументов:
Каждый раз набирать команду с таким количеством параметров не слишком удобно и хорошо бы это дело как-то сократить. Можно воспользоваться alias и определить ярлык для данного вызова:
Теперь запуск lsrt приведет к тому же результату, что и использование ls с параметрами.
Если нам больше не нужен ярлык, мы можем воспользоваться командой "unalias" и удалить его:
Если требуется вывести значение конкретного псевдонима, запустите alias и передайте его имя в качестве аргумента:
Важно: после начала нового сеанса оболочки псевдоним пропадет, а при попытке его использовать мы получим ошибку следующего вида:
Создание постоянных псевдонимов
Давайте, для начала, посмотрим, какие псевдонимы уже заданы в системе и доступны для текущей сессии:
Хммм, интересно, почему я ничего еще не делал, а уже что-то определено...
Да, в зависимости от дистрибутива, определенный набор псевдонимов уже будет заранее задан.
Как правило, найти и определить глобальные псевдонимы можно в скрипте "~/.bashrc", который выполняется каждый раз при инициализации оболочки:
Вот и они - те самые псевдонимы. Таким образом, для того, чтобы наш ярлык был доступен в разных терминалах целевого пользователя, нам требуется прописать его в локальном файле "~/.bashrc".
Если вы хотите, чтобы ваши алиасы были доступны для всех юзеров системы, необходимо использовать файл "/etc/bash.bashrc".
Linux++ | IT-Образование
Команда alias — это удобный инструмент для тех, кто постоянно работает в командной строке.
Пользователям часто приходится использовать одну и ту же команду. Нередко — с большим количеством опций или с одними и теми же аргументами.
Alias является, встроенной в оболочку командой, которая позволяет оптимизировать рутину и скрывать длинные вызовы под лаконичными псевдонимами.
К примеру, нам нужно понять, какие файлы занимают в целевом каталоге слишком много места и не надо ли их удалить...
Для реализации этой задачи нам потребуется команда ls и много-много аргументов:
$ ls --human-readable --size -1 -S --classify
Каждый раз набирать команду с таким количеством параметров не слишком удобно и хорошо бы это дело как-то сократить. Можно воспользоваться alias и определить ярлык для данного вызова:
$ alias lsrt='ls --human-readable --size -1 -S --classify'
Теперь запуск lsrt приведет к тому же результату, что и использование ls с параметрами.
Если нам больше не нужен ярлык, мы можем воспользоваться командой "unalias" и удалить его:
$ unalias lsrt
$ lsrt
Command 'lsrt' not found
Если требуется вывести значение конкретного псевдонима, запустите alias и передайте его имя в качестве аргумента:
$ alias g
alias g='grep'
Важно: после начала нового сеанса оболочки псевдоним пропадет, а при попытке его использовать мы получим ошибку следующего вида:
<your-alias-name> : command not found.
Создание постоянных псевдонимов
Давайте, для начала, посмотрим, какие псевдонимы уже заданы в системе и доступны для текущей сессии:
$ alias
alias l='ls -CF'
alias la='ls -A'
...
Хммм, интересно, почему я ничего еще не делал, а уже что-то определено...
Да, в зависимости от дистрибутива, определенный набор псевдонимов уже будет заранее задан.
Как правило, найти и определить глобальные псевдонимы можно в скрипте "~/.bashrc", который выполняется каждый раз при инициализации оболочки:
$ cat ~/.bashrc | grep alias
alias la='ls -A'
alias l='ls -CF'
Вот и они - те самые псевдонимы. Таким образом, для того, чтобы наш ярлык был доступен в разных терминалах целевого пользователя, нам требуется прописать его в локальном файле "~/.bashrc".
Если вы хотите, чтобы ваши алиасы были доступны для всех юзеров системы, необходимо использовать файл "/etc/bash.bashrc".
Linux++ | IT-Образование
👍43🔥17✍4❤3
Valve ускорила драйверы для AMD на 228%
Перед началом чуть-чуть разъясню: в Linux существует 3 драйвера для видеокарт AMD и Vulkan API:
1) AMDVLK (открытый и официальный);
2) RADV (открытый и неофициальный, из Mesa);
3) AMDGPU-PRO (закрытый и официальный);
В Mesa 24.3 наконец-то устранена основная проблема с драйвером RADV (Radeon Vulkan), которая приводила к снижению производительности по сравнению с проприетарными драйверами AMD: AMDVLK и AMDGPU-PRO.
Этот разрыв в производительности существовал почти 2 года в рамках "AMD FSR2 sample app".
Он был успешно устранён командой разработчиков Linux драйверов от Valve путём изменения нескольких строк кода.
Спасибо инженеру Сэмюэлю Питойсету, который, как сообщает Phoronix, выявил проблему и устранил её, изменив менее десятка строк кода.
Есть нюанс: эти самые 228% прироста производительности относятся только к демо-приложению для FSR2, а не к самому алгоритму, поэтому ликовать и думать, что сейчас на SteamDeck последние тайтлы будут идти на Ultra в 8K Super Resolution, не стоит...
С другой стороны, так как изменения вносились в сам драйвер, а не в "sample app", вероятно, определенный прирост производительности в каких-то кейсах мы все-таки получим.
Linux++ | IT-Образование
Перед началом чуть-чуть разъясню: в Linux существует 3 драйвера для видеокарт AMD и Vulkan API:
1) AMDVLK (открытый и официальный);
2) RADV (открытый и неофициальный, из Mesa);
3) AMDGPU-PRO (закрытый и официальный);
В Mesa 24.3 наконец-то устранена основная проблема с драйвером RADV (Radeon Vulkan), которая приводила к снижению производительности по сравнению с проприетарными драйверами AMD: AMDVLK и AMDGPU-PRO.
Этот разрыв в производительности существовал почти 2 года в рамках "AMD FSR2 sample app".
Он был успешно устранён командой разработчиков Linux драйверов от Valve путём изменения нескольких строк кода.
Спасибо инженеру Сэмюэлю Питойсету, который, как сообщает Phoronix, выявил проблему и устранил её, изменив менее десятка строк кода.
It looks like the fixed-func hardware is very slow to cull primitives with zero pos.w but shader based culling helps a lot.
This fixes a massive performance gap with the FSR2 demo compared to AMDGPU-PRO, +228% on RDNA2.
-Samuel Pitoiset (Credit: Phoronix)
Есть нюанс: эти самые 228% прироста производительности относятся только к демо-приложению для FSR2, а не к самому алгоритму, поэтому ликовать и думать, что сейчас на SteamDeck последние тайтлы будут идти на Ultra в 8K Super Resolution, не стоит...
It's since been clarified that this performance improvement is around the FSR2 sample application and not the FSR2 algorithm/implementation itself.
С другой стороны, так как изменения вносились в сам драйвер, а не в "sample app", вероятно, определенный прирост производительности в каких-то кейсах мы все-таки получим.
Linux++ | IT-Образование
🔥25👍13❤🔥4
Ускорь свою работу в командной строке - используй alias!
Приветствую! В прошлой публикации о псевдонимах мы познакомились с потрясающей командой оболочки alias и научились сокращать вызовы команд с длинным перечнем аргументов до пары символов.
Сегодня мы обратимся к моему опыту и рассмотрим ТОП команд, которые удобно использовать под псевдонимами.
Быстрый поиск команды по истории
Создание родительских директорий
Удаление директории
Перезагрузка и отключение системы
Переход назад по каталогам
Установка пакетов
Обновление пакетов
Топ 5 потребление памяти
Топ 5 потребление CPU
Сравнение файлов и директорий
Упаковка и распаковка .tar архива
Отображение скрытых файлов и каталогов
Linux++ | IT-Образование
Приветствую! В прошлой публикации о псевдонимах мы познакомились с потрясающей командой оболочки alias и научились сокращать вызовы команд с длинным перечнем аргументов до пары символов.
Сегодня мы обратимся к моему опыту и рассмотрим ТОП команд, которые удобно использовать под псевдонимами.
Быстрый поиск команды по истории
$ alias hgrep='history | grep'
$ hgrep cat
712 cat gtk-dark.css
Создание родительских директорий
$ alias mkdirs='mkdir -pv'
$ mkdirs ./dir1/dir2/dir3
Удаление директории
$ alias rmd='rm -rf'
$ rmd ./dir1
Перезагрузка и отключение системы
$ alias reboot='sudo /sbin/reboot'
$ alias poweroff='sudo /sbin/poweroff'
Переход назад по каталогам
$ alias ..="cd .."
$ alias ..2="cd ../.."
$ alias ..3="cd ../../.."
Установка пакетов
$ alias install='sudo apt install'
Обновление пакетов
$ alias upgrade='sudo apt update && sudo apt dist-upgrade'
Топ 5 потребление памяти
$ alias mem5='ps auxf | sort -nr -k 4 | head -5'
Топ 5 потребление CPU
$ alias cpu5='ps auxf | sort -nr -k 3 | head -5'
Сравнение файлов и директорий
$ alias diff='diff -Naur'
Упаковка и распаковка .tar архива
$ alias tar='tar -cvf'
$ alias untar='tar -xvf'
Отображение скрытых файлов и каталогов
$ alias ls.='ls -d .* --color=auto'
Linux++ | IT-Образование
1👍79❤🔥14👌7❤1
Оболочка - не терминал: что это и зачем нужно?
До того как значки и окна заполонили экраны наших мониторов, для взаимодействия с системой использовался командный интерпретатор (оболочка).
Оболочка — это специальная программа, которая предоставляет пользователю интерфейс для взаимодействия с ядром ОС.
Она принимает понятные человеку команды и выполняет их через обращения к ядру - на его языке - через набор системных вызовов: fork(), execve() и т.д.
Вот мы вводим команду "ls -l". Грубо говоря, как оболочка ее выполняет: создает дочерний процесс через вызов fork(), выполняет программу с заданными аргументами через execve() и дожидается ее завершения через wait().
Терминалом (эмулятором терминала) можно назвать более высокоуровневую программу, которая запускает оболочку и позволяет нам видеть ввод и вывод информации. Он, по сути, является оберткой для оболочки.
Никто же нам не мешает просто удалить исполняемые файлы оболочек (bash, sh, zsh) и запустить терминал... Так, конечно же, делать НЕ СТОИТ, но приложение, вероятно, запустится и выведет следующее сообщение:
При этом мы все еще сможем вводить текст и видеть его в окошке. Без оболочки терминал уже не так полезен - он понятия не имеет, как выполнить ту же команду "ls -l".
Также у оболочек может быть набор встроенных команд, которые могут отличаться от типа к типу. Помните, мы тут alias рассматривали? Так вот, это и есть та самая, встроенная в оболочку команда:
Команды оболочки - не отдельные программы. За их выполнением оболочка не пойдет по каталогам, прописанным в переменной $PATH: /usr/bin, /usr/sbin... К встроенным командам еще можно отнести: cd, case, export, pwd и т.д.
Linux++ | IT-Образование
До того как значки и окна заполонили экраны наших мониторов, для взаимодействия с системой использовался командный интерпретатор (оболочка).
Оболочка — это специальная программа, которая предоставляет пользователю интерфейс для взаимодействия с ядром ОС.
$ whereis bash
bash: /usr/bin/bash
Она принимает понятные человеку команды и выполняет их через обращения к ядру - на его языке - через набор системных вызовов: fork(), execve() и т.д.
Вот мы вводим команду "ls -l". Грубо говоря, как оболочка ее выполняет: создает дочерний процесс через вызов fork(), выполняет программу с заданными аргументами через execve() и дожидается ее завершения через wait().
Терминалом (эмулятором терминала) можно назвать более высокоуровневую программу, которая запускает оболочку и позволяет нам видеть ввод и вывод информации. Он, по сути, является оберткой для оболочки.
Никто же нам не мешает просто удалить исполняемые файлы оболочек (bash, sh, zsh) и запустить терминал... Так, конечно же, делать НЕ СТОИТ, но приложение, вероятно, запустится и выведет следующее сообщение:
Warning: Could not find an interactive shell to start
При этом мы все еще сможем вводить текст и видеть его в окошке. Без оболочки терминал уже не так полезен - он понятия не имеет, как выполнить ту же команду "ls -l".
Также у оболочек может быть набор встроенных команд, которые могут отличаться от типа к типу. Помните, мы тут alias рассматривали? Так вот, это и есть та самая, встроенная в оболочку команда:
$ type alias
alias is a shell builtin
Команды оболочки - не отдельные программы. За их выполнением оболочка не пойдет по каталогам, прописанным в переменной $PATH: /usr/bin, /usr/sbin... К встроенным командам еще можно отнести: cd, case, export, pwd и т.д.
Linux++ | IT-Образование
👍53🔥11❤6❤🔥2
Командные оболочки: от классики до инноваций
Наверняка многие знают оболочки sh и bash. Так же большинство из нас что-то слышали про zsh и fish. Однако на этом список не заканчивается.
В наши дни оболочек развелось немало, но далеко не все они используются. Сегодня мы рассмотрим самые основные экземпляры и посмотрим на их ключевые особенности.
Оболочка sh (Bourne shell)
Эта оболочка была написана Стивом Борном в 1977 году и является старейшей из известных публике.
Bourne shell была первой полноценной оболочкой и содержала функционал, который сейчас реализован всеми актуальными последователями: использование переменных, выполнение команд и функций, перенаправление ввода-вывода.
Сейчас sh по ряду причин является ссылкой на sh-совместимую оболочку:
В современных системах Bourne shell уже не используется в качестве пользовательской оболочки, однако полезен в роли командного интерпретатора.
Именно поэтому он и существует в качестве ссылки, чтобы не ломать совместимость для выполнения скриптов. Все же помнят про shebang?)
Оболочка bash (Bourne again shell)
Была разработана в рамках проекта GNU в качестве улучшенной реализации Bourne shell в 1989 году.
Основными создателями bash являются Брайан Фокс и Чет Рэми. Название можно перевести, как «Возрождённый шелл Борна». Скорее всего, самая популярная оболочка на сегодняшний день.
Данная оболочка является наследником sh и значительно расширяет его функционал. Однако все еще является древней и не такой красивой и конфигурируемой, как более новые zsh и fish.
Оболочка zsh (Z shell)
Свободная современная sh-совместимая оболочка, созданная в 1990 году. Имеет ряд преимуществ перед bash, касающихся в основном работы в интерактивном режиме.
Zsh поддерживает автодополнение, коррекцию опечаток, подсветку синтаксиса и довольно мощную конфигурацию внешнего вида и функционала через темы и плагины.
Однако zsh в полной мере раскрывается только через настройку конфигов. При первом запуске вы, вероятно, зададитесь вопросом: Зачем оно вообще надо - тот же самый bash... Да, его нужно вручную настраивать.
Очень рекомендованным дополнением к оболочке zsh является фреймворк "OH MY ZSH", который предназначен для управления настройками zsh и расширения его функционала за счет плагинов и тем.
Оболочка fish (friendly interactive shell)
Fish уже не такая "бородатая" оболочка. Первая версия датируется 2005 годом. На фоне основных коллег по цеху, которые были выпущены еще в прошлом веке fish — свежий огурчик.
Если вам нужен функционал больше, чем у bash, но вам не хочется зарываться в конфиги, как с zsh, можно рассмотреть данную оболочку.
Все бы хорошо, но есть нюанс: fish - POSIX-несовместимая оболочка. Это значит, что правила, продиктованные стандартом POSIX для ряда оболочек (bash, zsh и т.д.), не имеют никакого влияния на fish.
Пример. Вот так мы определяем локальные переменные в bash и zsh:
Давайте попробуем повторить то же самое в fish:
Тут так не получится. В fish переменные определяются следующим образом:
Уже поняли в чем суть? Если вы напишите скрипт на специфичном для fish синтаксисе и попытаетесь запустить его через интерпретатор bash, sh или zsh, вероятно, он упадет с ошибкой.
Linux++ | IT-Образование
Наверняка многие знают оболочки sh и bash. Так же большинство из нас что-то слышали про zsh и fish. Однако на этом список не заканчивается.
В наши дни оболочек развелось немало, но далеко не все они используются. Сегодня мы рассмотрим самые основные экземпляры и посмотрим на их ключевые особенности.
Оболочка sh (Bourne shell)
Эта оболочка была написана Стивом Борном в 1977 году и является старейшей из известных публике.
Bourne shell была первой полноценной оболочкой и содержала функционал, который сейчас реализован всеми актуальными последователями: использование переменных, выполнение команд и функций, перенаправление ввода-вывода.
Сейчас sh по ряду причин является ссылкой на sh-совместимую оболочку:
$ ls -l | grep sh
sh -> dash
В современных системах Bourne shell уже не используется в качестве пользовательской оболочки, однако полезен в роли командного интерпретатора.
Именно поэтому он и существует в качестве ссылки, чтобы не ломать совместимость для выполнения скриптов. Все же помнят про shebang?)
#!/bin/sh
Оболочка bash (Bourne again shell)
Была разработана в рамках проекта GNU в качестве улучшенной реализации Bourne shell в 1989 году.
Основными создателями bash являются Брайан Фокс и Чет Рэми. Название можно перевести, как «Возрождённый шелл Борна». Скорее всего, самая популярная оболочка на сегодняшний день.
Данная оболочка является наследником sh и значительно расширяет его функционал. Однако все еще является древней и не такой красивой и конфигурируемой, как более новые zsh и fish.
Оболочка zsh (Z shell)
Свободная современная sh-совместимая оболочка, созданная в 1990 году. Имеет ряд преимуществ перед bash, касающихся в основном работы в интерактивном режиме.
$ sudo apt install zsh
Zsh поддерживает автодополнение, коррекцию опечаток, подсветку синтаксиса и довольно мощную конфигурацию внешнего вида и функционала через темы и плагины.
Однако zsh в полной мере раскрывается только через настройку конфигов. При первом запуске вы, вероятно, зададитесь вопросом: Зачем оно вообще надо - тот же самый bash... Да, его нужно вручную настраивать.
Очень рекомендованным дополнением к оболочке zsh является фреймворк "OH MY ZSH", который предназначен для управления настройками zsh и расширения его функционала за счет плагинов и тем.
Оболочка fish (friendly interactive shell)
Fish уже не такая "бородатая" оболочка. Первая версия датируется 2005 годом. На фоне основных коллег по цеху, которые были выпущены еще в прошлом веке fish — свежий огурчик.
$ sudo apt-add-repository ppa:fish-shell/release-3
$ sudo apt update
$ sudo apt install fish
Если вам нужен функционал больше, чем у bash, но вам не хочется зарываться в конфиги, как с zsh, можно рассмотреть данную оболочку.
Все бы хорошо, но есть нюанс: fish - POSIX-несовместимая оболочка. Это значит, что правила, продиктованные стандартом POSIX для ряда оболочек (bash, zsh и т.д.), не имеют никакого влияния на fish.
Пример. Вот так мы определяем локальные переменные в bash и zsh:
$ MY_VAR="Hello"
Давайте попробуем повторить то же самое в fish:
$ MY_VAR="Hello"
fish: Unsupported use of '='. In fish, please use 'set MY_VAR "Hello"'
Тут так не получится. В fish переменные определяются следующим образом:
$ set MY_VAR "Hello"
Уже поняли в чем суть? Если вы напишите скрипт на специфичном для fish синтаксисе и попытаетесь запустить его через интерпретатор bash, sh или zsh, вероятно, он упадет с ошибкой.
Linux++ | IT-Образование
👍48❤9❤🔥7🔥2
Понимание типов оболочек: interactive, non-interactive, login, non-login
Да-да, с оболочками есть еще и такой прикол. Многих людей сбивает с толку процесс настройки оболочек именно по тому, что они не в курсе о типовых различиях. Сегодня я постараюсь дать вам базовое понимание каждого из типов, чтобы потом легче было разобраться с конфигами.
Оболочки "interactive"
Максимально упрощая, интерактивная оболочка - та, с которой мы работаем через ручной ввод - мы печатаем команду, передаем ее оболочке на вход и видим результат по завершению. Когда вы запускаете терминал, оболочка стартует в интерактивном режиме.
Оболочки "non-interactive"
Это тот режим запуска оболочки, когда она не взаимодействуют напрямую с пользователем. Обычно они запускаются для выполнения команд или скриптов и завершаются по мере окончания задачи. Все же помнят про тот самый shebang:
Так вот, для запуска скрипта мы изначально открываем bash (интерактивная оболочка) и указываем путь:
Далее bash запустит интерпретатор sh (неинтерактивная оболочка), после чего тот "втихую" выполнит скрипт.
Это ВАЖНО помнить! Скрипты отрабатывают в неинтерактивных оболочках и ваши настройки в файле "~/.bashrc" окажутся бесполезными. Почему такие оболочки не подгружают конфиги? Хороший вопрос. Есть несколько причин:
1) скрипту не следует полагаться на пользовательские настройки - быть "пользователезависимым". Если юзер "настругал" скрипт, опираясь, допустим, на свои алиасы, будет нарушена портируемость и у другого человека он, вероятно, не стартанет.
2) чтение и отработка конфигов может занимать время. Без дополнительных подготовок скрипт банально быстрее отработает.
Оболочки "login"
Такая оболочка создается первым пользовательским процессом, когда тот успешно логинится в системе и открывает сессию через tty или ssh. Если вы авторизовались через GUI - дисплей менеджер, "login shell" обычно заменяется оконным менеджером либо менеджером сессии.
Данный тип оболочки не создается все время, как мы открываем терминал и отличается тем, что читает дополнительные конфиги. Явно его можно идентифицировать по префиксу '-' для исполняемого файла. Давайте зайдем пользователем через tty и проверим:
А теперь сами запустим еще одну оболочку внутри родительской и убедимся, что статус "login shell" выдается невсегда:
Также узнать тип оболочки можно через переменную '0':
Оболочки "non-login"
Создаются при обычном старте - без авторизации пользователя. Все, что нужно для инициализации такой оболочки - просто открыть терминал либо самому запустить исполняемый файл без дополнительных флагов, которые переводят оболочку в режим "login": "-l" и "--login".
Linux++ | IT-Образование
Да-да, с оболочками есть еще и такой прикол. Многих людей сбивает с толку процесс настройки оболочек именно по тому, что они не в курсе о типовых различиях. Сегодня я постараюсь дать вам базовое понимание каждого из типов, чтобы потом легче было разобраться с конфигами.
Оболочки "interactive"
Максимально упрощая, интерактивная оболочка - та, с которой мы работаем через ручной ввод - мы печатаем команду, передаем ее оболочке на вход и видим результат по завершению. Когда вы запускаете терминал, оболочка стартует в интерактивном режиме.
Оболочки "non-interactive"
Это тот режим запуска оболочки, когда она не взаимодействуют напрямую с пользователем. Обычно они запускаются для выполнения команд или скриптов и завершаются по мере окончания задачи. Все же помнят про тот самый shebang:
#!/bin/sh
Так вот, для запуска скрипта мы изначально открываем bash (интерактивная оболочка) и указываем путь:
$ ./noscript.sh
Далее bash запустит интерпретатор sh (неинтерактивная оболочка), после чего тот "втихую" выполнит скрипт.
Это ВАЖНО помнить! Скрипты отрабатывают в неинтерактивных оболочках и ваши настройки в файле "~/.bashrc" окажутся бесполезными. Почему такие оболочки не подгружают конфиги? Хороший вопрос. Есть несколько причин:
1) скрипту не следует полагаться на пользовательские настройки - быть "пользователезависимым". Если юзер "настругал" скрипт, опираясь, допустим, на свои алиасы, будет нарушена портируемость и у другого человека он, вероятно, не стартанет.
2) чтение и отработка конфигов может занимать время. Без дополнительных подготовок скрипт банально быстрее отработает.
Оболочки "login"
Такая оболочка создается первым пользовательским процессом, когда тот успешно логинится в системе и открывает сессию через tty или ssh. Если вы авторизовались через GUI - дисплей менеджер, "login shell" обычно заменяется оконным менеджером либо менеджером сессии.
Данный тип оболочки не создается все время, как мы открываем терминал и отличается тем, что читает дополнительные конфиги. Явно его можно идентифицировать по префиксу '-' для исполняемого файла. Давайте зайдем пользователем через tty и проверим:
$ ps -auxf
(root) /bin/login -p --
(user) \_ -bash
\_ ps -auxf
А теперь сами запустим еще одну оболочку внутри родительской и убедимся, что статус "login shell" выдается невсегда:
$ bash
$ ps -auxf
/bin/login -p --
\_ -bash
\_ bash
\_ ps -auxf
Также узнать тип оболочки можно через переменную '0':
$ echo $0
-bash
Оболочки "non-login"
Создаются при обычном старте - без авторизации пользователя. Все, что нужно для инициализации такой оболочки - просто открыть терминал либо самому запустить исполняемый файл без дополнительных флагов, которые переводят оболочку в режим "login": "-l" и "--login".
Linux++ | IT-Образование
👍19❤🔥16🔥10
Конфигурация оболочки
И так, продолжаем топать дальше и вникать в устройство оболочек. Сегодня рассмотрим то, какие файлы конфигурации и в какой последовательности оболочка исполняет для того, чтобы настроить свое окружение.
Мы с вами будем говорить о конфигах в контексте оболочки bash, но между их вариациями различий не так много.
Этапы конфигурации "login shell"
И так, мы с вами успешно авторизовались и открыли сессию через tty либо ssh... Какие файлы были использованы нашей "прародительской ☝️" оболочкой "-bash" и в какой последовательности?
1) оболочка выполняет системный скрипт "/etc/profile"
2) скрипт "/etc/profile" выполняет каждый файл "*.sh" (доступный для чтения), что лежит в каталоге "/etc/profile.d/":
3) далее выполняется ТОЛЬКО один из следующих пользовательских файлов (прям в такой последовательности): ~/.bash_profile, ~/.bash_login, ~/.profile. Как правило, каждый из этих скриптов в результате должен позвать ~/.bashrc:
4) когда все, оболочка завершается, по возможности отрабатывает скрипт "~/.bash_logout".
Этапы конфигурации "non-login shell"
Тут обычно все проще: когда интерактивная оболочка запущена в обычном режиме (non-login), отрабатывает только пользовательский скрипт ~/.bashrc. Выполняется он каждый раз, как стартует оболочка, что подразумевает наличие в нем "многоразовых" команд.
Также, в некоторых дистрах, бинарник оболочки bash может быть собран с флагом "-DSYS_BASHRC", что приводит к исполнению системного скрипта "/etc/bash.bashrc" перед пользовательским "~/.bashrc".
Так, стоп, а "/etc/profile" не вызывается? Неа, задача файлов "profile" - выполнять команды только для оболочек формата "login". Их выполнение, как правило, требуется один раз за всю сессию.
Linux++ | IT-Образование
И так, продолжаем топать дальше и вникать в устройство оболочек. Сегодня рассмотрим то, какие файлы конфигурации и в какой последовательности оболочка исполняет для того, чтобы настроить свое окружение.
Мы с вами будем говорить о конфигах в контексте оболочки bash, но между их вариациями различий не так много.
Этапы конфигурации "login shell"
И так, мы с вами успешно авторизовались и открыли сессию через tty либо ssh... Какие файлы были использованы нашей "прародительской ☝️" оболочкой "-bash" и в какой последовательности?
1) оболочка выполняет системный скрипт "/etc/profile"
2) скрипт "/etc/profile" выполняет каждый файл "*.sh" (доступный для чтения), что лежит в каталоге "/etc/profile.d/":
if [ -d /etc/profile.d ]; then
for i in /etc/profile.d/*.sh; do
if [ -r $i ]; then
. $i
fi
done
unset i
fi
3) далее выполняется ТОЛЬКО один из следующих пользовательских файлов (прям в такой последовательности): ~/.bash_profile, ~/.bash_login, ~/.profile. Как правило, каждый из этих скриптов в результате должен позвать ~/.bashrc:
# include .bashrc if it exists
if [ -f "$HOME/.bashrc" ]; then
. "$HOME/.bashrc"
fi
4) когда все, оболочка завершается, по возможности отрабатывает скрипт "~/.bash_logout".
Этапы конфигурации "non-login shell"
Тут обычно все проще: когда интерактивная оболочка запущена в обычном режиме (non-login), отрабатывает только пользовательский скрипт ~/.bashrc. Выполняется он каждый раз, как стартует оболочка, что подразумевает наличие в нем "многоразовых" команд.
Также, в некоторых дистрах, бинарник оболочки bash может быть собран с флагом "-DSYS_BASHRC", что приводит к исполнению системного скрипта "/etc/bash.bashrc" перед пользовательским "~/.bashrc".
Так, стоп, а "/etc/profile" не вызывается? Неа, задача файлов "profile" - выполнять команды только для оболочек формата "login". Их выполнение, как правило, требуется один раз за всю сессию.
Linux++ | IT-Образование
👍21❤🔥9🔥7✍2❤1
Сигналы в Linux: что это и зачем нужно?
Предположим, что есть две программы. Одна программа должна передать другой большое количество данных - через файл.
Работает эта история таким образом, что одна программа открывает файл (на запись) и передает данные, другая открывает его же (на чтение) и получает инфу.
Проблема заключается в том, что вторая программа должна как-то узнать о том, что пришло время считывать данные.
Как реализовать процесс уведомления? Ну, можно через сигналы)
Сигнал — это механизм оповещения процесса о том, что произошло некое событие.
Иногда они описываются, как программные прерывания, т.к. приостанавливают нормальное выполнение программы.
Один процесс может отправить сигнал другому с помощью системного вызова kill(), который является аналогом команды оболочки kill:
Важно понимать, что сигналы доставляются процессам через ядро операционки:
Каждому сигналу присваивается уникальный идентификатор — целое число, начиная с 1. Эти числа определены в файле <signal.h>. Каждому номеру соответствует символьное обозначение.
В удобном формате посмотреть список сигналов можно с помощью команды "kill -l":
Сигналы можно разделить на две большие категории:
1) стандартные - используются ядром для оповещения процессов о свершении событий.
В Linux стандартные сигналы пронумерованы от 1 до 31;
2) сигналы реального времени - обычно используются для коммуникации между процессами или потоками.
В отличие от стандартных, у сигналов реального времени нет заранее определённых имён.
Они идентифицируются выражением вида (SIGRTMIN + n), где n — это целое число от 0 до (SIGRTMAX – SIGRTMIN):
Сигналы реального времени пронумерованы от 34 до 64: SIGRTMIN -> SIGRTMAX:
В ответ на сигнал, процесс либо выполняет заранее определенное действие (приостановка, завершение, возобновление работы), либо происходит перехват и отработка кастомного вызова, либо 0 реакции - игнор.
Игнорировать процесс может и сигналы, к которым подвязано какое-то действие. Реализуется это через определение сигнальной маски.
Таким образом мы гарантируем то, что выполнение фрагмента кода не будет прервано доставкой сигнала.
Linux++ | IT-Образование
Предположим, что есть две программы. Одна программа должна передать другой большое количество данных - через файл.
Работает эта история таким образом, что одна программа открывает файл (на запись) и передает данные, другая открывает его же (на чтение) и получает инфу.
Проблема заключается в том, что вторая программа должна как-то узнать о том, что пришло время считывать данные.
Как реализовать процесс уведомления? Ну, можно через сигналы)
Сигнал — это механизм оповещения процесса о том, что произошло некое событие.
Иногда они описываются, как программные прерывания, т.к. приостанавливают нормальное выполнение программы.
Один процесс может отправить сигнал другому с помощью системного вызова kill(), который является аналогом команды оболочки kill:
#include <signal.h>
int kill(pid_t pid, int sig);
Важно понимать, что сигналы доставляются процессам через ядро операционки:
процесс_1 -> ядро -> процесс_2
Каждому сигналу присваивается уникальный идентификатор — целое число, начиная с 1. Эти числа определены в файле <signal.h>. Каждому номеру соответствует символьное обозначение.
В удобном формате посмотреть список сигналов можно с помощью команды "kill -l":
$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
Сигналы можно разделить на две большие категории:
1) стандартные - используются ядром для оповещения процессов о свершении событий.
В Linux стандартные сигналы пронумерованы от 1 до 31;
2) сигналы реального времени - обычно используются для коммуникации между процессами или потоками.
В отличие от стандартных, у сигналов реального времени нет заранее определённых имён.
Они идентифицируются выражением вида (SIGRTMIN + n), где n — это целое число от 0 до (SIGRTMAX – SIGRTMIN):
$ kill -l
34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2
...
Сигналы реального времени пронумерованы от 34 до 64: SIGRTMIN -> SIGRTMAX:
#include <stdio.h>
#include <signal.h>
int main() {
printf("SIGRTMIN: %d\n", SIGRTMIN);
printf("SIGRTMAX: %d\n", SIGRTMAX);
return 0;
}
SIGRTMIN: 34
SIGRTMAX: 64
В ответ на сигнал, процесс либо выполняет заранее определенное действие (приостановка, завершение, возобновление работы), либо происходит перехват и отработка кастомного вызова, либо 0 реакции - игнор.
Игнорировать процесс может и сигналы, к которым подвязано какое-то действие. Реализуется это через определение сигнальной маски.
Таким образом мы гарантируем то, что выполнение фрагмента кода не будет прервано доставкой сигнала.
Linux++ | IT-Образование
👍48🔥18❤🔥6❤1🍌1
Перехват и обработка сигналов
Обработчик сигнала — функция, написанная программистом и выполняющая нужные действия при получении сигнала.
Например, для оболочки может быть определен обработчик сигнала SIGINT, который генерируется комбинацией Ctrl+C. Он заставляет оболочку прекратить выполнение текущей задачи и вернуть управление в основной цикл так, что пользователь опять видит приглашение на ввод команды:
И так, существует два способа перехвата сигнала: signal() и sigaction(). Функция signal() реализована в glibc как библиотечная, поверх системного вызова sigaction().
Давайте напишем мини-программу, которая никогда не завершается и все время что-то там выполняет:
Прям никогда не завершается?)) Еще как, нам всего-то стоит нажать Ctrl+C:
Что произошло? Оболочка и ее дочерний процесс (675989) получили сигнал SIGINT. Bash, т.к. отработал перехват, не завершил выполнение, а вот наша программа потухла, т.к. ничего в ней не переопределяет дефолтного поведения. Давайте внесем некоторые правки и кое-что добавим:
Если какой-то хитрый разраб такое провернет, знайте, что есть еще сигналы, на которые прога завершается. Отправим SIGTERM и, если на него не стоит обработчиков, продолжим спокойно работать:
Есть, кста, 1 сигнал, на который невозможно повесить обработчик - SIGKILL. Так устроено, что этот сигнал является для процесса ПРИКАЗОМ, нежели просьбой:
Обратите внимание, что невозможно установить перехват сигнала таким образом, чтобы, без явного указания, он завершал процесс. Максимум, как мы можем приблизиться к этому, — прописать в обработчике одну из двух функций: exit() или abort().
Функция abort() генерирует для процесса сигнал SIGABRT, который приводит к сбросу дампа и завершению. Даже если на SIGABRT повесить обработчик, программа все равно завершится - особенность реализации.
Linux++ | IT-Образование
Обработчик сигнала — функция, написанная программистом и выполняющая нужные действия при получении сигнала.
Например, для оболочки может быть определен обработчик сигнала SIGINT, который генерируется комбинацией Ctrl+C. Он заставляет оболочку прекратить выполнение текущей задачи и вернуть управление в основной цикл так, что пользователь опять видит приглашение на ввод команды:
parallels@ubuntu-linux-22-04-02:~$ ./app
App executes...
parallels@ubuntu-linux-22-04-02:~$
И так, существует два способа перехвата сигнала: signal() и sigaction(). Функция signal() реализована в glibc как библиотечная, поверх системного вызова sigaction().
Давайте напишем мини-программу, которая никогда не завершается и все время что-то там выполняет:
#include "stdio.h"
int main()
{
while (1) {
printf("Do some work, pid = %d\n", getpid());
sleep(1);
}
return 0;
}
Прям никогда не завершается?)) Еще как, нам всего-то стоит нажать Ctrl+C:
$ ./prog
Do some work, pid = 675989
Do some work, pid = 675989
^C
Что произошло? Оболочка и ее дочерний процесс (675989) получили сигнал SIGINT. Bash, т.к. отработал перехват, не завершил выполнение, а вот наша программа потухла, т.к. ничего в ней не переопределяет дефолтного поведения. Давайте внесем некоторые правки и кое-что добавим:
#include "stdio.h"
#include "unistd.h"
#include "signal.h"
void handler (int num) {
write(STDOUT_FILENO, "I won't die!\n", 13);
}
int main()
{
signal(SIGINT, handler);
while (1) {
printf("Do some work, pid = %d\n", getpid());
sleep(1);
}
return 0;
}
$ ./prog
Do some work, pid = 681049
Do some work, pid = 681049
Do some work, pid = 681049
^CI won't die!
Do some work, pid = 681049
^CI won't die!
Если какой-то хитрый разраб такое провернет, знайте, что есть еще сигналы, на которые прога завершается. Отправим SIGTERM и, если на него не стоит обработчиков, продолжим спокойно работать:
$ kill -TERM 681049
Do some work, pid = 681049
Terminated
Есть, кста, 1 сигнал, на который невозможно повесить обработчик - SIGKILL. Так устроено, что этот сигнал является для процесса ПРИКАЗОМ, нежели просьбой:
$ kill -KILL 683152
Обратите внимание, что невозможно установить перехват сигнала таким образом, чтобы, без явного указания, он завершал процесс. Максимум, как мы можем приблизиться к этому, — прописать в обработчике одну из двух функций: exit() или abort().
Функция abort() генерирует для процесса сигнал SIGABRT, который приводит к сбросу дампа и завершению. Даже если на SIGABRT повесить обработчик, программа все равно завершится - особенность реализации.
Linux++ | IT-Образование
👍34🔥10❤2❤🔥2
Рад всех видеть на моем канале! Го знакомиться, меня зовут Кирилл. Я - действующий программист, занимаюсь прикладной разработкой системного софта под Линукс.
Моя задача - в увлекательном формате делиться опытом и погружать вас в мир системной разработки и администрирования!
Разнообразие материала позволит найти тут свое место как абсолютным новичкам, так и до дыр обученным профессионалам. Будет интересно, полезно и, главное, по делу. Добро пожаловать в наше сообщество!
Навигация по контенту
План по разделяемым библиотекам
Итоги квартала (август 2024)
Итоги квартала (ноябрь 2024)
Итоги квартала (февраль 2025)
Итоги квартала (август 2025)
LinuxCamp | #info
Please open Telegram to view this post
VIEW IN TELEGRAM
👍51❤42🔥14❤🔥4😍1
LinuxCamp | DevOps pinned «👋 Приветственный пост Рад всех видеть на моем канале! Го знакомиться, меня зовут Кирилл. Я - действующий программист, занимаюсь прикладной разработкой системного софта под Линукс. Моя задача - в увлекательном формате делиться опытом и погружать вас в мир…»
Итоги квартала
Делаю сводку ключевых постов (сентябрь - ноябрь 2024).
Общая разработка:
1. Говорим про либы: GTK и QT [1]
Процессы и программы
1. Ресурсы процессов
2. Демоны в Linux
3. Идентификаторы процессов: пользователи и группы [1]
4. Идентификаторы процессов: пользователи и группы [2]
5. Процессы и программы: переменные окружения
6. Как демоны выполняют логгирование?
7. Базовые принципы коммуникации "пользователь -> приложение"
8. Топ команд по управлению процессами
Пользователи и группы
1. Файл групп: /etc/group
2. Файлы паролей: passwd и shadow
Разбор команд
1. stat
2. chmod и chown
3. visudo
4. chroot
5. history
6. alias
Командные оболочки
1. Что такое командная оболочка?
2. Особенности оболочек: bash, sh, zsh, fish
3. Понимание типов оболочек: interactive, non-interactive, login, non-login
4. Конфигурация оболочки
Модули ядра
1. Что такое модуль ядра?
2. Администрирование модулей ядра
Сигналы
1. Концепция сигналов в Linux
2. Перехват и обработка сигналов
LinuxCamp
Делаю сводку ключевых постов (сентябрь - ноябрь 2024).
Общая разработка:
1. Говорим про либы: GTK и QT [1]
Процессы и программы
1. Ресурсы процессов
2. Демоны в Linux
3. Идентификаторы процессов: пользователи и группы [1]
4. Идентификаторы процессов: пользователи и группы [2]
5. Процессы и программы: переменные окружения
6. Как демоны выполняют логгирование?
7. Базовые принципы коммуникации "пользователь -> приложение"
8. Топ команд по управлению процессами
Пользователи и группы
1. Файл групп: /etc/group
2. Файлы паролей: passwd и shadow
Разбор команд
1. stat
2. chmod и chown
3. visudo
4. chroot
5. history
6. alias
Командные оболочки
1. Что такое командная оболочка?
2. Особенности оболочек: bash, sh, zsh, fish
3. Понимание типов оболочек: interactive, non-interactive, login, non-login
4. Конфигурация оболочки
Модули ядра
1. Что такое модуль ядра?
2. Администрирование модулей ядра
Сигналы
1. Концепция сигналов в Linux
2. Перехват и обработка сигналов
LinuxCamp
👍50🔥16❤13❤🔥6
Отправка сигналов: kill()
Один процесс может отправить сигнал другому с помощью системного вызова kill(), который является базой для команды оболочки kill:
Термин kill был выбран потому, что в "древности" (в ранних версиях UNIX) для большинства сигналов действием по умолчанию было завершение процесса.
Для вызова kill, аргумент pid идентифицирует один или несколько процессов, в которые будет направлен сигнал, заданный параметром sig. Четыре различных случая определяют, каким образом интерпретируется значение аргумента pid:
1) pid > 0, сигнал отправляется в процесс, идентификатор которого указан в аргументе pid;
2) pid == 0, сигнал отправляется во все процессы той же группы, что и вызывающий процесс, в том числе и в сам вызывающий процесс;
3) pid < –1, сигнал отправляется во все процессы группы, идентификатор которой == абсолютному значению аргумента pid;
4) pid == –1, сигнал отправляется во все процессы, для которых у отправителя есть разрешение, кроме init (PID == 1) и самого себя;
Если сигнал отправляется привилегированным процессом, он будет доставлен во все процессы в системе, кроме двух выше обозначенных.
Если ни один из процессов не подходит под pid, функция kill() завершается с ошибкой и устанавливает для переменной errno значение ESRCH («Нет такого процесса»).
Важно помнить, что процессу для отправки сигнала другому требуются соответствующие разрешения:
1) процесс с привилегией CAP_KILL может игнорировать ограничения и посылать сигналы куда угодно. Выдать исполняемому файлу права можно через команду setcap:
2) процесс init, запущенный пользователем и группой root, — особый случай. В него можно посылать только те сигналы, для которых у него установлены обработчики.
Это предотвращает возникновение ситуаций, когда системный администратор случайно аварийно завершает процесс init, фундаментальный для работы системы;
3) непривилегированный процесс может отправлять сигнал другому, если его реальный (RUID) или эффективный (EUID), совпадает с теми же параметрами получателя. Про различные типы ID процессов мы ранее говорили вот тут;
4) сигнал SIGCONT обрабатывается по особым правилам. Непривилегированный процесс может послать этот сигнал в любой процесс, запущенный в той же сессии, минуя проверку ID пользователей;
Если у отправителя нет разрешения на отправку сигнала в процесс pid, вызов kill() завершится неудачно с установкой значения EPERM в errno.
Пример программы, отправляющей сигнал:
LinuxCamp
Один процесс может отправить сигнал другому с помощью системного вызова kill(), который является базой для команды оболочки kill:
#include <signal.h>
int kill(pid_t pid, int sig);
Термин kill был выбран потому, что в "древности" (в ранних версиях UNIX) для большинства сигналов действием по умолчанию было завершение процесса.
Для вызова kill, аргумент pid идентифицирует один или несколько процессов, в которые будет направлен сигнал, заданный параметром sig. Четыре различных случая определяют, каким образом интерпретируется значение аргумента pid:
1) pid > 0, сигнал отправляется в процесс, идентификатор которого указан в аргументе pid;
2) pid == 0, сигнал отправляется во все процессы той же группы, что и вызывающий процесс, в том числе и в сам вызывающий процесс;
3) pid < –1, сигнал отправляется во все процессы группы, идентификатор которой == абсолютному значению аргумента pid;
4) pid == –1, сигнал отправляется во все процессы, для которых у отправителя есть разрешение, кроме init (PID == 1) и самого себя;
Если сигнал отправляется привилегированным процессом, он будет доставлен во все процессы в системе, кроме двух выше обозначенных.
Если ни один из процессов не подходит под pid, функция kill() завершается с ошибкой и устанавливает для переменной errno значение ESRCH («Нет такого процесса»).
Важно помнить, что процессу для отправки сигнала другому требуются соответствующие разрешения:
1) процесс с привилегией CAP_KILL может игнорировать ограничения и посылать сигналы куда угодно. Выдать исполняемому файлу права можно через команду setcap:
setcap cap_kill+ep /path/to/bin
2) процесс init, запущенный пользователем и группой root, — особый случай. В него можно посылать только те сигналы, для которых у него установлены обработчики.
Это предотвращает возникновение ситуаций, когда системный администратор случайно аварийно завершает процесс init, фундаментальный для работы системы;
3) непривилегированный процесс может отправлять сигнал другому, если его реальный (RUID) или эффективный (EUID), совпадает с теми же параметрами получателя. Про различные типы ID процессов мы ранее говорили вот тут;
4) сигнал SIGCONT обрабатывается по особым правилам. Непривилегированный процесс может послать этот сигнал в любой процесс, запущенный в той же сессии, минуя проверку ID пользователей;
Если у отправителя нет разрешения на отправку сигнала в процесс pid, вызов kill() завершится неудачно с установкой значения EPERM в errno.
Пример программы, отправляющей сигнал:
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main(int argc, char *argv[])
{
long pid = strtol(argv[1], NULL, 10);
int signal = (int) strtol(argv[2], NULL, 10);
int rcode = kill(pid, signal);
printf("PID = %ld\n", pid);
printf("SIGID = %d\n", signal);
if (rcode == 0) {
printf("Process exists, the signal is sent\n");
}
else if (errno == EPERM) {
printf("We don't have rights\n");
}
else if (errno == ESRCH) {
printf("Process does not exist\n");
}
else {
exit(EXIT_FAILURE);
}
}
$ ./prog 20597 9
PID = 20597
SIGID = 9
Process exists, the signal is sent
LinuxCamp
🔥19👍11❤🔥4❤3
Отправка сигнала самому себе: raise()
Иногда полезной практикой является отправка процессом сигнала самому себе. Эту задачу выполняет функция стандартной библиотеки (libc) raise():
В программе с одним потоком, вызов raise() аналогичен:
В приложениях с несколькими потоками, вызов raise() аналогичен:
Такая реализация означает, что сигнал будет доставлен ТОЛЬКО в тот поток, из которого был выполнен raise(). Вызов kill(getpid(), sig) посылает сигнал в вызывающий процесс => сигнал может быть доставлен в любой его поток.
Приведу небольшой пример для демонстрации работы функции (вызов signal() и механизм перехвата объясняются вот тут):
Выполним программу и получим:
Возвращаемое значение
Обратите внимание, что функция raise() возвращает ненулевой результат (не обязательно –1) при возникновении ошибки. Единственная проблема, которая может тут возникнуть — EINVAL (неверное значение sig).
LinuxCamp
Иногда полезной практикой является отправка процессом сигнала самому себе. Эту задачу выполняет функция стандартной библиотеки (libc) raise():
#include <signal.h>
int raise(int sig);
В программе с одним потоком, вызов raise() аналогичен:
kill(getpid(), sig);
В приложениях с несколькими потоками, вызов raise() аналогичен:
pthread_kill(pthread_self(), sig);
Такая реализация означает, что сигнал будет доставлен ТОЛЬКО в тот поток, из которого был выполнен raise(). Вызов kill(getpid(), sig) посылает сигнал в вызывающий процесс => сигнал может быть доставлен в любой его поток.
Приведу небольшой пример для демонстрации работы функции (вызов signal() и механизм перехвата объясняются вот тут):
#include <signal.h>
#include <stdio.h>
void handler(int sig) {
printf("Signal received : %d\n", sig);
}
int main() {
signal(SIGILL, handler);
printf("Signal sent : %d\n", SIGILL);
raise(SIGILL);
return 0;
}
Выполним программу и получим:
$ ./prog
Signal sent : 4
Signal received : 4
Возвращаемое значение
Обратите внимание, что функция raise() возвращает ненулевой результат (не обязательно –1) при возникновении ошибки. Единственная проблема, которая может тут возникнуть — EINVAL (неверное значение sig).
LinuxCamp
👍22❤🔥6⚡4🔥2🤣2❤1
Защита от незваных гостей: сигнальная маска
Для каждого процесса ядро хранит специальную маску, которая определяет те сигналы, которые будут временно заблокированы и необработаны.
Если в процесс отправляется заблокированный сигнал, то доставка этого сигнала откладывается до тех пор, пока он не будет разблокирован путем удаления из сигнальной маски.
Как же нам определить эту чудо-маску и замутить пару сигналов?) Использовать системный вызов sigprocmask():
Аргумент how определяет то, каким образом набор сигналов, передаваемый в set, изменит текущую маску процесса:
1. SIG_BLOCK — сигналы, включенные set, добавляются в сигнальную маску. Другими словами, блокируемые сигналы остаются таковыми, а новые добавляются к ним;
2. SIG_UNBLOCK — сигналы, включенные set, исключаются из сигнальной маски;
3. SIG_SETMASK — сигналы, включенные set, переопределяют текущую маску. Сигналы, не включённые в новое множество, больше не блокируются;
Если аргумент oldset не равен NULL, то он указывает на переменную типа sigset_t, в которую записывается текущее значение сигнальной маски (до изменения).
Если мы хотим через oldset получить сигнальную маску без каких-то изменений, можно установить значение NULL для аргумента set, в этом случае аргумент how будет проигнорирован.
Ну все, давайте, ближе к делу, ближе к коду. Сейчас используем вообще все наши знания по сигналам: напишем перехватчик, отправим сигнал самому себе, определим маску и посмотрим на результат:
Теперь скомпилим и запустим прогу:
И что... Бам, никакого вывода мы не увидим - маска успешно отработала.
А теперь го еще прикол. Помните, ранее речь заходила о том, что отправленные сигналы не отменяются, а откладываются - как только мы уберем его из маски, процесс его примет и обработает.
Проверить мы это сможем, если добавим следующий код после вызова "raise(SIGINT);":
Ждем 5 сек и наблюдаем лог:
Теперь пару слов про сигналы: SIGKILL и SIGSTOP. Их мы игнорировать не можем, УВЫ и АХ. Если мы попытаемся заблокировать эти сигналы, функция sigprocmask() ошибку не выдаст, но и желаемого результата мы также не получим.
Для того чтобы добавить все сигналы в маску, кроме SIGKILL и SIGSTOP, мы можем использовать следующий вызов:
LinuxCamp
Для каждого процесса ядро хранит специальную маску, которая определяет те сигналы, которые будут временно заблокированы и необработаны.
Если в процесс отправляется заблокированный сигнал, то доставка этого сигнала откладывается до тех пор, пока он не будет разблокирован путем удаления из сигнальной маски.
Как же нам определить эту чудо-маску и замутить пару сигналов?) Использовать системный вызов sigprocmask():
#include <signal.h>
int sigprogmask(int how, const sigset_t *set, sigset_t *oldset);
Аргумент how определяет то, каким образом набор сигналов, передаваемый в set, изменит текущую маску процесса:
1. SIG_BLOCK — сигналы, включенные set, добавляются в сигнальную маску. Другими словами, блокируемые сигналы остаются таковыми, а новые добавляются к ним;
2. SIG_UNBLOCK — сигналы, включенные set, исключаются из сигнальной маски;
3. SIG_SETMASK — сигналы, включенные set, переопределяют текущую маску. Сигналы, не включённые в новое множество, больше не блокируются;
Если аргумент oldset не равен NULL, то он указывает на переменную типа sigset_t, в которую записывается текущее значение сигнальной маски (до изменения).
Если мы хотим через oldset получить сигнальную маску без каких-то изменений, можно установить значение NULL для аргумента set, в этом случае аргумент how будет проигнорирован.
Ну все, давайте, ближе к делу, ближе к коду. Сейчас используем вообще все наши знания по сигналам: напишем перехватчик, отправим сигнал самому себе, определим маску и посмотрим на результат:
#include <signal.h>
#include <unistd.h>
void handler (int num) {
write(STDOUT_FILENO, "I won't die!\n", 13);
}
int main() {
signal(SIGINT, handler);
sigset_t blockSet, prevMask;
sigemptyset(&blockSet);
sigaddset(&blockSet, SIGINT);
sigprocmask(SIG_BLOCK, &blockSet, &prevMask);
raise(SIGINT);
return 0;
}
Теперь скомпилим и запустим прогу:
$ gcc -o prog main.c
$ ./prog
И что... Бам, никакого вывода мы не увидим - маска успешно отработала.
А теперь го еще прикол. Помните, ранее речь заходила о том, что отправленные сигналы не отменяются, а откладываются - как только мы уберем его из маски, процесс его примет и обработает.
Проверить мы это сможем, если добавим следующий код после вызова "raise(SIGINT);":
sleep(5);
sigprocmask(SIG_SETMASK, &prevMask, NULL);
Ждем 5 сек и наблюдаем лог:
$ ./prog
I won't die!
Теперь пару слов про сигналы: SIGKILL и SIGSTOP. Их мы игнорировать не можем, УВЫ и АХ. Если мы попытаемся заблокировать эти сигналы, функция sigprocmask() ошибку не выдаст, но и желаемого результата мы также не получим.
Для того чтобы добавить все сигналы в маску, кроме SIGKILL и SIGSTOP, мы можем использовать следующий вызов:
sigfillset(&blockSet);
LinuxCamp
👍25🔥12❤🔥5✍2❤1
Работа с ожидающими сигналами
Друзья, еще недолго осталось ворочать сигналы, но нужно дожать) Сегодня обсудим то, как можно проработать сигналы, которые заблокированы маской, но направлены процессу.
Если процесс получает сигнал, который в данный момент подлежит блокированию - входит в сигнальную маску, то он добавляется в набор ожидающих сигналов. Когда сигнал будет разблокирован, он 100% будет доставлен в процесс.
Для определения того, какие сигналы процесса находятся в режиме ожидания и пытаются пробиться, мы можем использовать sigpending():
Функция возвращает в set набор сигналов, которые находятся в режиме ожидания процесса.
После этого мы сможем проверить содержимое set с помощью функции sigismember(), которая даст нам понимание того, входит ли указанный сигнал в структурку:
Функция возвращает 1, если sig является членом set, 0, если не является, и –1 при ошибке (например, sig не является допустимым номером сигнала):
Сигналы не ставятся в очередь
Набор ожидающих сигналов — это лишь маска, она показывает факт возникновения того или иного сигнала, но не их количество.
Иначе говоря, если один и тот же сигнал был сгенерирован несколько раз (будучи заблокированным), то он записывается в набор, а затем доставляется, но лишь ОДНАЖДЫ.
LinuxCamp
Друзья, еще недолго осталось ворочать сигналы, но нужно дожать) Сегодня обсудим то, как можно проработать сигналы, которые заблокированы маской, но направлены процессу.
Если процесс получает сигнал, который в данный момент подлежит блокированию - входит в сигнальную маску, то он добавляется в набор ожидающих сигналов. Когда сигнал будет разблокирован, он 100% будет доставлен в процесс.
Для определения того, какие сигналы процесса находятся в режиме ожидания и пытаются пробиться, мы можем использовать sigpending():
#include <signal.h>
int sigpending(sigset_t *set);
Функция возвращает в set набор сигналов, которые находятся в режиме ожидания процесса.
После этого мы сможем проверить содержимое set с помощью функции sigismember(), которая даст нам понимание того, входит ли указанный сигнал в структурку:
#include <signal.h>
int sigismember(sigset_t *set, int sig);
Функция возвращает 1, если sig является членом set, 0, если не является, и –1 при ошибке (например, sig не является допустимым номером сигнала):
sigset_t pending;
sigpending(&pending);
if (sigismember(&pending, SIGUSR1)) {
printf("SIGUSR1 заблокирован\n");
}
Сигналы не ставятся в очередь
Набор ожидающих сигналов — это лишь маска, она показывает факт возникновения того или иного сигнала, но не их количество.
Иначе говоря, если один и тот же сигнал был сгенерирован несколько раз (будучи заблокированным), то он записывается в набор, а затем доставляется, но лишь ОДНАЖДЫ.
LinuxCamp
👍23🔥8❤🔥4
Перехватывай сигналы, как профи: sigaction()
Сегодня поговорим про вызов, который активно рекомендуется к использованию в качестве альтернативы примитивному signal(). Несмотря на то, что sigaction() сложнее в использовании, взамен он предоставляет большую гибкость:
Аргумент sig означает сигнал, который мы хотим перехватить. Значением может быть любой экземпляр, кроме SIGKILL или SIGSTOP.
Аргумент act — указатель на структуру, определяющую условия перехвата. Если нам необходимо получить актуальное значения, без изменений, можно указать NULL.
Аргумент oldact — это указатель на структуру такого же типа. Используется для возврата информации о предыдущих правилах перехвата. Если нам это неинтересно, можно также задать значение NULL.
Структура, на которую указывают аргументы act и oldact, выглядит следующим образом:
Поле sa_handler соотносится с аргументом handler, передаваемым функции signal(). В данном поле указывается либо адрес обработчика либо одна из констант — SIG_IGN (сигнал будет проигнорирован) или SIG_DFL (процесс ответит дефолтным поведением на сигнал).
Поле sa_mask определяет список сигналов, блокируемых во время активации обработчика. После старта его выполнения, все сигналы из набора автоматически добавляются в сигнальную маску. После того, как произойдет возврат из обработчика, они автоматически удалятся.
Сделано это для того, чтобы никакой из сигналов не прервал выполнение обработчика.
Поле sa_flags — битовая маска, устанавливающая разные параметры, контролирующие обработку сигнала. Биты могут быть объединены в этом поле битовой операцией ИЛИ (|). Более детально ознакомиться с флагами можете в мануале.
Простой пример использования вызова sigaction():
И да, как видите, я проинициализировал почти все поля структуры. Если вы работаете на C, не забывайте, что это не C++ и тут нет конструкторов, деструкторов, дефолтной инициализации. Если явно не указать значения переменным, они могут спокойно заполниться мусором, что приведет к UB (undefined behavior).
Где sigaction() прям 100% полезен?
Мы с вами еще не говорили подробно о сигналах реального времени, но при работе с ними вам прям станет ясна необходимость sigaction(). Дело в том, что через настройку перехватчика мы cможем принимать дополнительные аргументы - пользовательские данные. Использовать для этого нам нужно флаг SA_SIGINFO:
Если флаг указан, обработчик должен присваиваться полю sa_sigaction и принимать расширенный интерфейс для пользовательских данных:
LinuxCamp
Сегодня поговорим про вызов, который активно рекомендуется к использованию в качестве альтернативы примитивному signal(). Несмотря на то, что sigaction() сложнее в использовании, взамен он предоставляет большую гибкость:
#include <signal.h>
// Возвращает 0 при успешном завершении, –1 при ошибке
int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact);
Аргумент sig означает сигнал, который мы хотим перехватить. Значением может быть любой экземпляр, кроме SIGKILL или SIGSTOP.
Аргумент act — указатель на структуру, определяющую условия перехвата. Если нам необходимо получить актуальное значения, без изменений, можно указать NULL.
Аргумент oldact — это указатель на структуру такого же типа. Используется для возврата информации о предыдущих правилах перехвата. Если нам это неинтересно, можно также задать значение NULL.
Структура, на которую указывают аргументы act и oldact, выглядит следующим образом:
struct sigaction
{
/* Адрес обработчика */
void (*sa_handler)(int);
/* Адрес расширенного обработчика */
void (*sa_sigaction)(int, siginfo_t *, void *);
/* Сигналы, блокируемые во время вызова обработчика */
sigset_t sa_mask;
/* Флаги, контролирующие активацию обработчика */
int sa_flags;
/* Устарел и не должен использоваться */
void (*sa_restorer)(void);
};
Поле sa_handler соотносится с аргументом handler, передаваемым функции signal(). В данном поле указывается либо адрес обработчика либо одна из констант — SIG_IGN (сигнал будет проигнорирован) или SIG_DFL (процесс ответит дефолтным поведением на сигнал).
Поле sa_mask определяет список сигналов, блокируемых во время активации обработчика. После старта его выполнения, все сигналы из набора автоматически добавляются в сигнальную маску. После того, как произойдет возврат из обработчика, они автоматически удалятся.
Сделано это для того, чтобы никакой из сигналов не прервал выполнение обработчика.
Поле sa_flags — битовая маска, устанавливающая разные параметры, контролирующие обработку сигнала. Биты могут быть объединены в этом поле битовой операцией ИЛИ (|). Более детально ознакомиться с флагами можете в мануале.
Простой пример использования вызова sigaction():
#include <signal.h>
#include <stdio.h>
void handle_signal(int sig) {
printf("Caught signal %d\n", sig);
}
int main() {
struct sigaction sa;
sa.sa_handler = handle_signal;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
while (1);
}
И да, как видите, я проинициализировал почти все поля структуры. Если вы работаете на C, не забывайте, что это не C++ и тут нет конструкторов, деструкторов, дефолтной инициализации. Если явно не указать значения переменным, они могут спокойно заполниться мусором, что приведет к UB (undefined behavior).
Где sigaction() прям 100% полезен?
Мы с вами еще не говорили подробно о сигналах реального времени, но при работе с ними вам прям станет ясна необходимость sigaction(). Дело в том, что через настройку перехватчика мы cможем принимать дополнительные аргументы - пользовательские данные. Использовать для этого нам нужно флаг SA_SIGINFO:
sa.sa_flags = SA_SIGINFO;
Если флаг указан, обработчик должен присваиваться полю sa_sigaction и принимать расширенный интерфейс для пользовательских данных:
void handler(int sig, siginfo_t *info, void *context);
sa.sa_sigaction = handler;
LinuxCamp
🔥19👍12❤🔥6