Обработка сигналов в операционных системах семейства Unix на Golang

Сигналы в операционных системах семейства Unix  —  это программные прерывания, которые отправляются программе для указания на возникновение какого-то важного события. Это могут быть разные события: от запросов пользователей до ошибок некорректного доступа к памяти. Некоторые сигналы, например сигнал прерывания, свидетельствуют о том, что пользователь отправил программе указание выполнить что-то, но сделал это не в обычном потоке управления.

Работа с сигналами операционной системы важна в различных ситуациях. Например, бывает нужно, чтобы сервер нормально завершил работу в случае получения им сигнала SIGTERM или чтобы инструмент командной строки остановил обработку входных данных в случае получения им сигнала SIGINT. Вот как с помощью каналов обрабатываются сигналы на Go:

Изображение автора: сигналы ОС

В этой статье мы рассмотрим, как с помощью применяемого в Golang пакета os/signal обрабатываются сигналы в операционных системах семейства Unix.

Пакет «os/signal»

Пакет os/signal позволяет настроить поведение программы на Golang при получении определенных типов сигналов в операционных системах семейства Unix. Большинство программ на Linux/Unix благополучно завершится после получения kill (сигнала уничтожения). Но бывает нужно, чтобы с помощью программы сначала был перехвачен сигнал, выполнено резервное копирование, сброшены данные на диск и т. д. В этом случае перед завершением следует использовать пакет os/signal.

Типы сигналов

При рассмотрении сигналов сделаем акцент на асинхронных сигналах. К их возникновению ошибки программы не приводят. Эти сигналы отправляются ядром или какой-то другой программой.

  • Сигнал SIGHUP отправляется при потере программой своего управляющего терминала.
  • Сигнал SIGINT отправляется при введении пользователем в управляющем терминале символа прерывания, по умолчанию это ^C (Control-C).
  • Сигнал SIGQUIT отправляется при введении пользователем в управляющем терминале символа выхода, по умолчанию это ^\ (Control-Backslash).
  • SIGTERM  —  это общий сигнал, используемый для завершения программы.

Вот простой пример на Golang того, как перехватывать самые распространенные сигналы уничтожения/завершения в операционных системах семейства Unix (внимание: для лучшего понимания читайте комментарии к коду):

package main

import (
	"fmt"
	"os"
	"os/signal"
	"syscall"
)

func main() {
	signalChanel := make(chan os.Signal, 1)
	signal.Notify(signalChanel,
		syscall.SIGHUP,
		syscall.SIGINT,
		syscall.SIGTERM,
		syscall.SIGQUIT)

	exit_chan := make(chan int)
	go func() {
		for {
			s := <-signalChanel
			switch s {
			// kill -SIGHUP XXXX [XXXX - идентификатор процесса для программы]
			case syscall.SIGHUP:
				fmt.Println("Signal hang up triggered.")

				// kill -SIGINT XXXX или Ctrl+c  [XXXX - идентификатор процесса для программы]
			case syscall.SIGINT:
				fmt.Println("Signal interrupt triggered.")

				// kill -SIGTERM XXXX [XXXX - идентификатор процесса для программы]
			case syscall.SIGTERM:
				fmt.Println("Signal terminte triggered.")
				exit_chan <- 0

				// kill -SIGQUIT XXXX [XXXX - идентификатор процесса для программы]
			case syscall.SIGQUIT:
				fmt.Println("Signal quit triggered.")
				exit_chan <- 0

			default:
				fmt.Println("Unknown signal.")
				exit_chan <- 1
			}
		}
	}()
	exitCode := <-exit_chan
	os.Exit(exitCode)
}

/* Вывод

➜  ~ kill -SIGINT 451740
	 Terminal 2 - Output - "Signal interrupt triggered."

➜  ~ kill -SIGHUP 451740
	 Terminal 2 - Output - "Signal hang up triggered."

➜  ~ kill -SIGTERM 451740
     Terminal 2 - Output - "Signal terminte triggered."

➜  ~  kill -SIGQUIT 451846
	 Terminal 2 - Output - "Signal quit triggered."

*/

Копируем эту программу на локальный компьютер и запускаем. Дадим ей название «signal-controller. go». Вот что получилось при ее выполнении на моем компьютере с Ubuntu:

  • Terminal 1 — go build signal-controller.go
  • Terminal 1 — ./signal-controller

На моем компьютере идентификатор процесса запускаемого двоичного файла был 451575.

  • Terminal 2 — kill -SIGINT 451575
Terminal 1 - Output - "Signal interrupt triggered."
  • Terminal 2 — kill -SIGHUP 451575
Terminal 1 - Output - "Signal hang up triggered."
  • Terminal 2 — kill -SIGTERM 451575
Terminal 1 - Output - "Signal terminte triggered."
  • Terminal 2 — kill -SIGQUIT 451575
Terminal 1 - Output - "Signal quit triggered."

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

Заключение

Сигналы в операционных системах семейства Unix легко обрабатываются на Golang. Здесь в этом помогает пакет os/signal. Для чтения сигналов ​нужно использовать канал типа os.Signal. Кроме того, есть возможность реализовать код для обработки всех типов таких сигналов, получаемых программой.

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Radhakishan Surwase: Handling Unix Signals In Golang

Предыдущая статьяПонятие об умных указателях Rust
Следующая статьяКак преобразовать функции JavaScript в генераторы, эффективно использующие память