LinuxCamp | DevOps – Telegram
LinuxCamp | DevOps
14.2K subscribers
194 photos
7 videos
300 links
Обо мне: C/C++/Linux эксперт. Говорим про разработку, Linux, DevOps, сети и администрирование.

Админ (реклама): @XoDefender
Чат: @linuxcamp_chat

Менеджер: @Spiral_Yuri
Биржа: https://telega.in/c/linuxcamp_tg

№ 6327102672
Download Telegram
Во, отличная шпаргалка, которая будет полезным дополнением к 2 предыдущим постам:

1) разница между chmod и chown;
2) файл групп /etc/group;

Много раз слышал от знакомых "Ой, вот помню, что 777 дает всем права на все, дальше хз...".

Та я и сам, когда лень вспоминать степени 2 и что-то там считать, обычно проставляю права через "буковки". Теперь, может, и другие комбинации в голове отложатся)

LinuxCamp
👍43🔥17🤯2
Старость - не радость!

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

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

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

Но за всем не уследишь. На днях, что вы думаете, нашел очередную проблему в модуле ядра - драйвере i915... По итогу, уже 2 очень неприятных графических бага решаются просто поднятием версии до плюс-минус актуальной.

Больше шансов столкнуться с проблемами у тех пользователей, которые сидят на старых ядрах и новом железе. Какие-то шестеренки linux kernel могут очень тяжело прокручиваться на вашем 144Гц мониторе или, к примеру, intel I9 последнего поколения.

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

Linux++ | IT-Образование
🔥27👍9❤‍🔥5👎1
Модуль ядра — ключ к расширению возможностей Linux

Как вы знаете, или, может быть, слышали, ядро 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" и отображает его содержимое в хорошо отформатированном списке:


$ 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❤‍🔥41
Сокращаем команды: Мощь псевдонимов в Linux

Команда 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🔥1743
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, выявил проблему и устранил её, изменив менее десятка строк кода.

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 и научились сокращать вызовы команд с длинным перечнем аргументов до пары символов.

Сегодня мы обратимся к моему опыту и рассмотрим ТОП команд, которые удобно использовать под псевдонимами.

Быстрый поиск команды по истории


$ 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👌71
Оболочка - не терминал: что это и зачем нужно?

До того как значки и окна заполонили экраны наших мониторов, для взаимодействия с системой использовался командный интерпретатор (оболочка).

Оболочка — это специальная программа, которая предоставляет пользователю интерфейс для взаимодействия с ядром ОС.


$ 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🔥116❤‍🔥2
Командные оболочки: от классики до инноваций

Наверняка многие знают оболочки 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-Образование
👍489❤‍🔥7🔥2
Понимание типов оболочек: interactive, non-interactive, login, non-login

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

Оболочки "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/":


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🔥721
Сигналы в Linux: что это и зачем нужно?

Предположим, что есть две программы. Одна программа должна передать другой большое количество данных - через файл.

Работает эта история таким образом, что одна программа открывает файл (на запись) и передает данные, другая открывает его же (на чтение) и получает инфу.

Проблема заключается в том, что вторая программа должна как-то узнать о том, что пришло время считывать данные.

Как реализовать процесс уведомления? Ну, можно через сигналы)

Сигнал — это механизм оповещения процесса о том, что произошло некое событие.

Иногда они описываются, как программные прерывания, т.к. приостанавливают нормальное выполнение программы.

Один процесс может отправить сигнал другому с помощью системного вызова 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❤‍🔥61🍌1
Перехват и обработка сигналов

Обработчик сигнала — функция, написанная программистом и выполняющая нужные действия при получении сигнала.

Например, для оболочки может быть определен обработчик сигнала 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🔥102❤‍🔥2
👋 Приветственный пост

Рад всех видеть на моем канале! Го знакомиться, меня зовут Кирилл. Я - действующий программист, занимаюсь прикладной разработкой системного софта под Линукс.

Моя задача - в увлекательном формате делиться опытом и погружать вас в мир системной разработки и администрирования!

Разнообразие материала позволит найти тут свое место как абсолютным новичкам, так и до дыр обученным профессионалам. Будет интересно, полезно и, главное, по делу. Добро пожаловать в наше сообщество!

📱 ITCamp
🔈 LinuxCamp | Chat

Навигация по контенту

План по разделяемым библиотекам
Итоги квартала (август 2024)
Итоги квартала (ноябрь 2024)
Итоги квартала (февраль 2025)
Итоги квартала (август 2025)

LinuxCamp | #info
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5142🔥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
👍50🔥1613❤‍🔥6
Отправка сигналов: kill()

Один процесс может отправить сигнал другому с помощью системного вызова 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❤‍🔥43
Отправка сигнала самому себе: raise()

Иногда полезной практикой является отправка процессом сигнала самому себе. Эту задачу выполняет функция стандартной библиотеки (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❤‍🔥64🔥2🤣21
Защита от незваных гостей: сигнальная маска

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

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

Как же нам определить эту чудо-маску и замутить пару сигналов?) Использовать системный вызов 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❤‍🔥521
Работа с ожидающими сигналами

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

Если процесс получает сигнал, который в данный момент подлежит блокированию - входит в сигнальную маску, то он добавляется в набор ожидающих сигналов. Когда сигнал будет разблокирован, он 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() сложнее в использовании, взамен он предоставляет большую гибкость:


#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