LinuxCamp | DevOps
Обо мне: C/C++/Linux эксперт. Говорим про разработку, Linux, DevOps, сети и администрирование. Админ (реклама): @XoDefender Чат: @linuxcamp_chat Менеджер: @Spiral_Yuri Биржа: https://telega.in/c/linuxcamp_tg РКН: https://clck.ru/3RWA3C
نمایش بیشتر📈 تحلیل کانال تلگرام LinuxCamp | DevOps
کانال LinuxCamp | DevOps (@linuxcamp_tg) در بخش زبانی روسی بازیگری فعال است. در حال حاضر جامعه شامل 13 932 مشترک است و جایگاه 9 149 را در دسته فناوری و برنامهها و رتبه 47 289 را در منطقه روسيا دارد.
📊 شاخصهای مخاطب و پویایی
از زمان ایجاد در невідомо، پروژه رشد سریعی داشته و 13 932 مشترک جذب کرده است.
بر اساس آخرین دادهها در تاریخ 08 ژوئیه, 2026، کانال فعالیت پایداری دارد. در ۳۰ روز گذشته تغییر اعضا برابر -100 و در ۲۴ ساعت گذشته برابر -6 بوده و همچنان دسترسی گستردهای حفظ شده است.
- وضعیت تأیید: تأیید نشده
- نرخ تعامل (ER): میانگین تعامل مخاطب 28.83% است و در ۲۴ ساعت نخست پس از انتشار، محتوا معمولاً 11.52% واکنش نسبت به کل مشترکان کسب میکند.
- دسترسی پستها: هر پست به طور میانگین 4 023 بازدید دریافت میکند. در اولین روز معمولاً 1 608 بازدید جمعآوری میشود.
- واکنشها و تعامل: مخاطبان بهطور فعال حمایت میکنند؛ میانگین واکنش به هر پست 23 است.
- علایق موضوعی: محتوا بر موضوعات کلیدی مانند linuxcamp, ядро, linux, диск, docker تمرکز دارد.
📝 توضیح و سیاست محتوایی
نویسنده این فضا را محل بیان دیدگاههای شخصی توصیف میکند:
“Обо мне: C/C++/Linux эксперт. Говорим про разработку, Linux, DevOps, сети и администрирование.
Админ (реклама): @XoDefender
Чат: @linuxcamp_chat
Менеджер: @Spiral_Yuri
Биржа: https://telega.in/c/linuxcamp_tg
РКН: https://clck.ru/3RWA3C”
به لطف بهروزرسانیهای پرتکرار (آخرین داده در تاریخ 09 ژوئیه, 2026)، کانال همواره بهروز و دارای دسترسی بالاست. تحلیلها نشان میدهد مخاطبان بهطور فعال با محتوا تعامل دارند و آن را به نقطه اثرگذاری مهم در دسته فناوری و برنامهها تبدیل کردهاند.
$ sleep 10 & sleep 20 & sleep 30 &
Чтобы выяснить, за какими заданиями следит оболочка, используется команда jobs:
$ jobs
[1] Running sleep 10 &
[2]- Running sleep 20 &
[3]+ Running sleep 30 &
[1] - номер задания, который используется для идентификации. Полезен, когда необходимо "ткнуть пальцем" в определенный элемент, используя: fg, bg, kill;
"" (отсутствие знака) - обозначает то, что задание не является ни текущим ни предыдущим, но находится в списке.
"+" указывает на текущее задание, по отношению к которому будут выполнены команды, если не указывать номер. "-" указывает на предыдущее задание;
Running - статус задачи. Также возможны: Stopped, Done, Terminated, Killed. Статусы могут меняться в ответ на отправленный сигнал (SIGKILL, SIGSTOP и т.д.);
Теперь выведем второе задание на передний план. Для этого будем использовать команду fg с указанием номера:
$ fg %2
sleep 20
Если в параметры ничего не вносить, команда выполнится для последнего задания (со знаком +).
Для того, чтобы перенести выполнение в фон и не блокировать терминал, используется bg.
Для примера запустим задание переднего плана, остановим его выполнение "Ctrl + Z" и отправим его в фоновый режим:
$ nm-applet
^Z
[1]+ Stopped nm-applet
$ bg
[1]+ nm-applet &
$ jobs
[1]+ Running nm-applet
К целевому заданию также можно обратиться по номеру:
$ jobs
[1]- Stopped sleep 200
[2]+ Running sleep 100 &
$ bg %1
[1]+ sleep 200 &
С заданиями "kill %n" можно использовать для принудительного завершения:
$ kill %1
[1]+ Terminated sleep 200
По умолчанию отправляется сигнал SIGTERM, при желании, можно указать любой другой:
$ kill -9 %1
[1]+ Killed sleep 200
🐧 LinuxCamp
$ wc -c file.txt &
[1] 74931
$
[1] - номер, под которым закрепилось задание (job);
74931 - PID процесса;
Если команда завершится успешно, оболочка выведет сообщение:
[1]+ Done wc -c file.txt
Если где-то произошла ошибка и команда завершилась аварийно, вы увидите сообщение о выходе с кодом возврата:
[1]+ Exit 1 wc -c file.txt
Амперсанд "&" является оператором списка - вы можете вывести в фон сразу ряд команд:
$ nm-applet & wc -c file.txt &
[1] 28839
[2] 28840
Чем является задание?
Вы можете подумать, что под заданием подразумевается 1 процесс - немного не так.
Задание — это единица работы оболочки. Простые команды, конвейеры и условные списки — примеры заданий, которые можно запускать из командной строки.
Задание — это больше, чем просто процесс Linux. Задание может состоять из одного или нескольких процессов:
[4]+ Running nm-applet && wc -c file.txt &
Например, конвейер из шести программ представляет собой одно задание, включающее 6 процессов:
$ cat file.txt | grep "word" | sort | uniq | tr 'a-z' 'A-Z' | wc -l > result.txt &
[5]+ Running cat file.txt | grep --color=auto "word" ... &
В оболочке может быть запущено одновременно несколько заданий, каждый экземпляр имеет свой ID.
🐧 LinuxCampНазвание: Зеркала Исполнитель: Лепс Острые углы, нервы, суетаСтоит задача распределить файлы в подкаталоги по исполнителям. Ну, для начала, можно найти все файлы песен исполнителя с помощью grep:
$ grep -l "Лепс" *.txt
song1.txt
song2.txt
Затем переместить каждый файл в необходимый каталог:
$ mkdir leps
$ mv song1.txt leps
$ mv song2.txt leps
Чет неудобно, так? Было бы неплохо сказать оболочке: «Перемести все файлы, содержащие строку Лепс, в каталог leps».
Для этого нужно забрать то, что нам выдала команда "grep -l" и передать результат в качестве списка аргументов для mv:
$ mv $(grep -l "Лепс" *.txt) leps
Синтаксис "$(команда)" выполняет выражение в круглых скобках и заменяет вставку его выводом.
Таким образом, "grep -l" подменяется, подходящими под условие именнами файлов. Результат использования $() в примере выше аналогичен:
$ mv song1.txt song2.txt leps
В сценариях оболочки подстановка бывает полезна для сохранения вывода команды в переменной:
переменная=$(команда)Например, напишем простенький скрипт, который поможет нам узнать, содержит ли текущая директория файлы:
status=$(ls ./ 2>/dev/null | wc -l)
if [ "$status" -gt 0 ]; then
echo "В директории есть $status файлов"
else
echo "Директория пуста или не существует"
fi
Выдаем права на выполнение, запускаем и проверяем:
$ chmod +x script.sh
$ ./script.sh
В директории есть 66 файлов
Для чего бывает полезно брать "$()" в кавычки?
Есть несколько основных кейсов, когда следует использовать "$()" вместо $().
Во-первых, если вывод команды содержит пробелы, он будет разбит на отдельные слова.
Представим, что стоит задача определить для текущей директории специальные права.
Есть момент - имя каталога состоит из нескольких слов, разделенных пробелами "photo and video":
$ pwd
$ /home/xoadmin/photo and video
$ chmod 777 $(pwd)
После подстановки оболочка интерпретирует это как попытку применить chmod к трём разным объектам: "/home/xoadmin/photo", "and", и "video". Это, вероятно, приведёт к ошибке:
chmod: cannot access '/home/xoadmin/photo': No such file or directory
chmod: cannot access 'and': No such file or directory
chmod: cannot access 'video': No such file or directory
Чтобы вывод pwd воспринимался, как единое целое, следует заключить оператор в кавычки:
$ chmod 777 "$(pwd)"
Во-вторых, без кавычек, символы перевода строки могут быть удалены при выводе через echo:
$ echo $(ls)
adduser.conf alsa alternatives apache2 apg.conf apparmor
Если требуется сохранить четкую структуру текста, содержащего спец символы, следует использовать "$()":
$ echo "$(ls)"
adduser.conf
alsa
alternatives
🐧 LinuxCamp
$ cd dir
$ touch new.txt
Запуск второй команды зависит от успешного выполнения первой. Если каталог не существует, то нет смысла и команду запускать.
Оболочка позволяет сделать эту зависимость явной, объединив команды оператором && (И):
$ cd dir && touch new.txt
Если вы используете систему контроля версий Git, то, вероятно, знакомы со следующей последовательностью команд фиксации и внесения изменений:
$ git add . && git commit -m"fixed a bug" && git push
Если какая-либо из команд завершится ошибкой, остальные вы не запустите. Поэтому эта цепочка хорошо работает в формате условного списка.
Так же, как оператор && запускает вторую команду только в случае успеха первой, оператор || (ИЛИ) запускает вторую команду только в случае сбоя первой.
Например, следующая команда пытается войти в каталог и, если это не удается, создает его:
$ cd dir || mkdir dir
Вы часто будете видеть этот оператор в сценариях оболочки. В частности, для выхода в случае возникновения ошибки:
cd dir || exit 1
Операторы && и || можно использовать вместе, чтобы настраивать более сложные действия:
$ cd dir || mkdir dir && cd dir || echo "I failed"
Последовательность предполагает попытку войти в каталог dir, если это не удается, создать каталог и войти в него, а если и это не удается, вывести ошибку.
Безусловные списки
Команды в списке не обязательно должны зависеть друг от друга. Если вы разделяете команды точкой с запятой, они просто выполняются по порядку.
Вот пример команды, которая спит течение двух часов, а затем создает резервную копию важных файлов:
$ sleep 7200; cp -a ~/files /mnt/backup_drive
Безусловные списки дают те же результаты, что и ввод команд по отдельности.
Единственное существенное различие - коды возврата. В таком списке коды отдельных команд отбрасываются, кроме последней.
Только код возврата последней команды в списке присваивается переменной оболочки "?":
$ mv file1 file2; mv file2 file3; mv file3 file4
# Код возврата команды «mv file3 file4»
$ echo $?
0
LinuxCamp 🐧
$ echo My name is $USER and my files are in $HOME
My name is xoadmin and my files are in /home/xoadmin
$ echo ch*ter9
chapter9
Когда вы выводите на экран значение переменной с помощью команды echo, вы можете подумать, что сама команда проверяет переменную и выводит ее значение.
На самом деле это не так, команда ничего не знает о переменных. Она просто выводит на экран любые аргументы, которые вы ей передаете. Значения для HOME и USER вычисляет оболочка перед запуском команды.
Вы можете определить или изменить переменную в любое время, используя следующий синтаксис:
name=value
Например, если вы часто работаете с каталогом "Projects" внутри домашней директории, вы можете присвоить его имя переменной:
$ work=$HOME/Projects
И использовать его как удобное сокращение при работе с cd:
$ cd $work
$ pwd
/home/xoadmin/Projects
Вы можете передавать $work любой команде, ожидающей имя каталога, в качестве аргумента:
$ cp myfile $work
$ ls $work
Myfile
Вообще, этот принцип важно понять: оболочка вычисляет переменные, шаблоны и другие конструкции перед выполнением команды.
Шаблоны vs переменные
Предположим, вы хотите перенести ряд файлов с расширением ".txt" из одного каталога в другой.
Вот два способа сделать это, но один работает, а другой нет:
# Метод 1
$ mv dir1/*.txt dir2
# Метод 2
$ FILES="file1.txt file1.txt"
$ mv dir1/$FILES dir2
Метод 1 работает, потому что шаблон соответствует всему пути к файлу — имя каталога dir1 является частью совпадений:
$ echo dir1/*.txt
dir1/file1.txt dir1/file2.txt
Таким образом, метод 1 работает так, как если бы вы набрали следующую команду:
$ mv dir1/file1.txt dir1/file2.txt dir2
В методе 2 используются переменные, имеющие только свои буквальные значения, и нет специального инструмента для вычисления путей к файлам:
$ echo dir1/$FILES
dir1/file1.txt file2.txt
Следовательно, метод 2 работает так, как если бы вы набрали следующую команду:
$ mv dir1/file1.txt file2.txt dir2
/bin/mv: cannot stat 'file2.txt': No such file or directory
Linux++ | IT-ОбразованиеЕсли вы все еще путаетесь в том, что такое оболочка, предлагаю обратиться к моей последней сводке постов.И так, команда может включать в себя символ групповых операций * для одновременной ссылки на несколько файлов:
$ ls *.py
data.py main.py user_interface.py
Этот символ обрабатывается командной оболочкой, а не программой ls. Оболочка заменяет выражение "*.py" списком имен подходящих файлов еще до запуска программы ls.
Иными словами, ls не увидит символ групповой операции. С точки зрения утилиты, вы ввели команду следующего вида:
$ ls data.py main.py user_interface.py
Символ * соответствует любой последовательности из символов в путях к файлам или каталогам.
Например, гораздо проще и быстрее, вместо прямого перечисления:
$ grep Linux chapter1 chapter2 chapter3 chapter4 chapter5...
Использовать базовый шаблон:
$ grep Linux chapter*
Остается за кадром, как командная оболочка (не программа grep) преобразует шаблон "chapter*" в список подходящих имен файлов. И только после этого оболочка запускает grep.
Если шаблон не соответствует ни одному файлу, оболочка передает его в качестве аргумента команды:
$ grep Linux chapter*
grep: chapter*: No such file or directory
Еще бывает полезно использовать знак ?, который соответствует любому единичному символу. Например, вы можете выполнить поиск слова Linux в главах с 1 по 9, используя ? для поиска совпадений:
$ grep Linux chapter?
Для поиска в главах с 10 по 99 придется использовать "??":
$ grep Linux chapter??
Менее известно использование скобок [] для запроса у оболочки соответствия одному из символов набора. Например, вы можете искать только в первых 3 главах:
$ grep Linux chapter[123]
$ grep Linux chapter[1-3]
Любые символы, не только цифры, могут быть помещены в квадратные скобки для сопоставления.
Например, следующая команда заставит оболочку искать имена файлов, начинающиеся с заглавной буквы, содержащие символ _ и заканчивающиеся символом @:
$ ls [A-Z]*_*@
Linux++ | IT-Образование
$ ls /usr/lib | grep nma
libnma.so.0
libnma.so.0.0.0
В результате мы видим не все содержимое директории, а только то, с чем grep нашел пересечение по шаблону.
Команды обычно и не знают, что являются частью конвейера: ls считает, что выводит данные на дисплей, хотя на самом деле ее вывод был перенаправлен на grep, который верит, что читает данные с клавиатуры, когда на самом деле получает вывод ls:
ls (STDOUT) -> grep (STDIN) -> grep (STDOUT)Тут уже оболочка, на своем уровне, выполняет всю магию и использует системный вызов "pipe()" для перенаправления:
#include <unistd.h>
int pipe(int pipefd[2]);
Важно понимать, что любая утилита, которая читает стандартный ввод или записывает вывод, может участвовать в создании конвейера и быть полезна для решения комплексной задачи.
Допустим, мы хотим узнать, сколько подкаталогов находится в "/usr/lib". Нет простой команды для получения ответа, поэтому создадим конвейер.
Начнем с простого вывода содержимого директории. Обратите внимание, что команда "ls –l" помечает каталоги буквой "d" в начале строки:
$ ls -l /usr/lib
drwxrwxr-x ... 4kstogram
drwxr-xr-x ... NetworkManager
Используем cut, чтобы вывести первый столбец:
$ ls -l /usr/lib | cut -c1
d
d
d
-
-
-
Затем используем grep, чтобы оставить только строки, содержащие букву "d":
$ ls -l /usr/lib | cut -c1 | grep d
d
d
d
Наконец, подсчитаем строки с помощью команды wc и получим ответ, созданный конвейером из четырех команд:
$ ls -l /usr/lib | cut -c1 | grep d | wc -l
145
Результат: директория "/usr/lib" содержит 145 подкаталогов.
Что мы сделали? Превратили небольшую горстку команд в набор комбинируемых инструментов. Как говорится, целое всегда есть нечто большее, чем сумма его частей.
Linux++ | IT-Образование
$ tr 'a-z' 'A-Z'
hello
HELLO
Команда не получает файл в качестве аргумента. Чтобы все-таки скормить ей статический поток данных, можно использовать то самое перенаправление STDIN:
$ cat animals.txt
1 retriever
2 badger
$ tr 'a-z' 'A-Z' < animals.txt
1 RETRIEVER
2 BADGER
Оператор '<<' heredoc
Оператор '<<' дает возможность перенаправлять многострочный поток данных (массив строк) на вход программе:
$ cat << EOF
> Сurrent user: $(whoami)
> Status: sysadmin
> EOF
Сurrent user: parallels
Status: sysadmin
Может быть удобно, если нужно передать текстовые данные прямо в команду без использования файлов - допустим, в скриптах оболочки для задач автоматизации.
Как работает "heredoc"
После оператора указывается метка (произвольное слово), которая обозначает начало и конец текста.
Все строки между метками считаются входными данными для команды. Когда оболочка повторно встречает слово, которое шло сразу за '<<', ввод завершается:
$ cat << EOF > config.conf
[settings]
host=localhost
port=8080
debug=true
EOF
В примере выше мы отправили команде cat на вход (STDIN) массив строк, вывод (STDOUT) которых перенаправили в конфиг.
Еще одним полезным примером может быть передача SQL-запросов в БД через ручной ввод:
$ mysql -u root -p << EOF
CREATE DATABASE my_database;
USE my_database;
...
EOF
Linux++ | IT-Образование
$ date > date.txt
$ cat date.txt
Sat Dec 14 20:50:46 MSK 2024
Перенаправление вывода с использованием '>' создаст новый файл или полностью перезапишет содержимое существующего.
Использование '>>' позволяет также создать новый файл при отсутствии и добавить данные в его конец - без полной перезаписи:
$ date >> date.txt
$ cat date.txt
Sat Dec 14 20:50:46 MSK 2024
Sat Dec 14 20:51:40 MSK 2024
Подчеркну, что перенаправляется только STDOUT. Если программа писала ошибки в поток STDERR, они так и выведутся в терминал:
$ find /etc -type f > ~/results.txt
find: ‘/etc/ssl/private’: Permission denied
Давайте дополнительно обработаем и STDERR - для этого нам явно нужно указать номер целевого дескриптора "2>":
$ find /etc -type f > ~/results.txt 2> ~/errors.txt
$ cat ~/errors.txt
find: ‘/etc/ssl/private’: Permission denied
Также бывает полезно подчистить вывод какой-нибудь команды от всех ошибок. Для этого нам нужно перенаправить STDERR в "/dev/null":
$ find /etc -type f 2> /dev/null
Все, что попадает в "/dev/null" исчезает на веки вечные. Такой себе "black hole".
И, наконец, если нужно, чтобы всё попало в один файл, можно перенаправить оба потока в одно и то же место:
$ ls > /tmp/lsdata 2>&1
$ ls &> /tmp/lsdata
Выражение "2>&1" означает «отправлять stderr туда, куда направляется stdout».
Linux++ | IT-Образование
$ cd /proc/551981/fd
$ ls
0 1 103 2 43
В примере выше мы зашли в виртуальную директорию процесса 551981 (PID), в которой хранятся дескрипторы открытых файлов. По ним, как раз, происходит чтение и запись информации.
STDIN
Данный поток используется процессом для получения информации извне. Когда мы запрашиваем какой-то ввод от пользователя, данные попадают в stdin, после чего считываются из него программой.
Давайте запустим команду cat:
$ cat
Hello bro
Hello bro
В примере выше cat ожидает от пользователя ввод, который впоследствии считывается с потока stdin (по 0 дескриптору) и направляется в поток stdout для вывода в терминал. Как результат, мы видим дубликат нашего текста.
Никто нам также не запрещает закрыть дескриптор, отвечающий за stdin - в таком случае программа просто не сможет получить данные.
STDOUT
Поток stdout (дескриптор 1) отвечает за вывод информации программой. По умолчанию все, что в него попадает, выводится в терминал:
$ echo $USER
parallels
STDERR
Еще один поток вывода информации (дескриптор 2), который отвечает за отображение ошибок. Если программа не смогла сделать все как надо — она пишет именно в него. Например, когда rm пытается удалить несуществующий файл:
$ rm example.txt
rm: example.txt: No such file or directory
Если вы вдруг не различили stdout от stderr либо хотите убедиться в том, что программа завершилась с ошибкой, можете воспользоваться следующей командой для получения кода возврата:
$ rm example.txt
rm: example.txt: No such file or directory
$ echo $?
1
Значение 1 говорит о том, что в программе были ошибки, 0 - все хорошо. Такой прием бывает полезен для написания скриптов автоматизации - нам иногда следует понимать, нормально ли прога отработала.
Linux++ | IT-Образование
#include <signal.h>
// Возвращают 0 при успешном завершении, –1 при ошибке
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t, *set);
Для инициализации набора сигналов должна быть использована одна из функций выше. Это необходимо потому, что C за вас ничего не определит. Если проигнорировать данный шаг, можно спокойно аварийно положить приложение, т.к. сет заполнится мусором.
После инициализации отдельные сигналы могут быть добавлены в набор с помощью функции sigaddset() и удалены — с помощью sigdelset():
#include <signal.h>
int sigaddset(sigset_t *set, int sig);
int sigdelset(sigset_t *set, int sig);
Функция sigismember() проверяет, является ли сигнал членом набора:
#include <signal.h>
int sigismember(sigset_t *set, int sig);
В GNU-библиотеке C реализованы три нестандартные функции, которые дополняют те, что были описаны выше:
#define _GNU_SOURCE
#include <signal.h>
int sigandset(sigset_t *set, sigset_t *left, sigset_t *right);
int sigorset(sigset_t *set, sigset_t *left, sigset_t *right);
int sigisemptyset(const sigset_t *set);
1) sigandset() — выполняет логическое И (пересечение) над двумя наборами left и right, помещает результат в dest;
2) sigorset() — выполняет логическое ИЛИ (объединение) над двумя наборами left и right, помещает результат в dest;
3) sigisemptyset() — возвращает 1, если в set нет ни одного сигнала, 0 в противном случае;
Linux++ | IT-Образование
#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;
Linux++ | IT-Образование
#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");
}
Сигналы не ставятся в очередь
Набор ожидающих сигналов — это лишь маска, она показывает факт возникновения того или иного сигнала, но не их количество.
Иначе говоря, если один и тот же сигнал был сгенерирован несколько раз (будучи заблокированным), то он записывается в набор, а затем доставляется, но лишь ОДНАЖДЫ.
Linux++ | IT-Образование
#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);
Linux++ | IT-Образование
#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).
Linux++ | IT-Образование