Распространенное утверждение, которое я слышал на протяжении многих лет, звучит так: «macOS и Linux в основном одинаковы»… на самом деле это не так. Снаружи оба выдают себя за UNIX. Но внутри реализации технологий различаются, даже несмотря на то, что POSIX пытается стандартизировать игровое поле для операционных систем UNIX.

Возьмем, к примеру, сигналы. Сигнал обычно используется в команде kill для управления выполнением процессов:

kill -SIGKILL firefox

Если вы не знакомы с SIGKILL, вы, вероятно, видели или использовали:

kill -9 firefox

при этом вы не понимаете, что «9» — это стандартное значение для SIGKILL (которое является универсальным для всех платформ UNIX). Если вы писали код C, SIGKILL и его значение «9» определены во включаемом файле signal.h:

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
    pid_t pid = atoi(argv[1]);
    int sig = atoi(argv[2]);
kill(pid, sig);
    printf("Signal %d passed to kill pid %d\n", sig, pid);
}
[user@fedora-virtualfedora playground]$ ps -e | grep vim
 33848 pts/0    00:00:00 vim
[user@fedora-virtualfedora playground]$ ./signal-example 33848 9
Signal 9 passed to kill pid 33848
(other terminal running vim)
Killed

SIGKILL — это один из многих сигналов, определенных в ‹signal.h›, которые могут использоваться программистами для управления процессом выполнения.

До этого момента этот пример с SIGKILL прекрасно работал на macOS и Linux. Так что, если вы хотите посмотреть на все различные сигналы и их значения, доступные программисту в macOS и Linux? Вот тут становится немного интересно.

Для macOS это довольно просто. Файл signal.h, используемый операционной системой, можно найти на opensource.apple.com:

https://opensource.apple.com/source/xnu/xnu-344/bsd/sys/signal.h

в котором первые 32 сигнала и их значения совпадают с тем, что можно найти в FreeBSD:

https://github.com/freebsd/freebsd/blob/master/sys/sys/signal.h

Файл с именем signal.h, содержащий все сигналы, доступные в операционной системе, не обязательно подходит для дистрибутивов Linux.

Например, возьмем RHEL 7.6 и Fedora 30, два дистрибутива, управляемых Red Hat. Если мы просмотрим /usr/include/signal.h в любом из этих дистрибутивов, вы не найдете какие-либо определения сигналов. На секунду вам может показаться, что файл signal.h даже не подходит для включения в программы на C, пока вы не увидите этот заголовок комментария вверху файла:

/*
 * ISO C99 Standard: 7.14 Signal handling <signal.h>
 */

Итак, где вы можете найти эти фактические определения сигналов и назначения значений в Linux для этих двух дистрибутивов?

Рядом с этим комментарием в signal.h находится ‹bits/signum.h›, который можно найти в /usr/include/bits/signum.h. Если в RHEL 7.6 мы откроем этот файл, мы обнаружим то, что ожидаем:

#ifdef  _SIGNAL_H
/* Fake signal functions.  */
#define SIG_ERR ((__sighandler_t) -1)           /* Error return.  */
#define SIG_DFL ((__sighandler_t) 0)            /* Default action.  */
#define SIG_IGN ((__sighandler_t) 1)            /* Ignore signal.  */
#ifdef __USE_UNIX98
# define SIG_HOLD       ((__sighandler_t) 2)    /* Add signal to hold mask.  */
#endif
/* Signals.  */
#define SIGHUP          1       /* Hangup (POSIX).  */
#define SIGINT          2       /* Interrupt (ANSI).  */
#define SIGQUIT         3       /* Quit (POSIX).  */
#define SIGILL          4       /* Illegal instruction (ANSI).  */
#define SIGTRAP         5       /* Trace trap (POSIX).  */
#define SIGABRT         6       /* Abort (ANSI).  */
#define SIGIOT          6       /* IOT trap (4.2 BSD).  */
#define SIGBUS          7       /* BUS error (4.2 BSD).  */
#define SIGFPE          8       /* Floating-point exception (ANSI). */
#define SIGKILL         9       /* Kill, unblockable (POSIX).  */
.
.
.

Но это неверно для Fedora 30:

#ifndef _BITS_SIGNUM_H
#define _BITS_SIGNUM_H 1
#ifndef _SIGNAL_H
#error "Never include <bits/signum.h> directly; use <signal.h> instead."
#endif
#include <bits/signum-generic.h>
/* Adjustments and additions to the signal number constants for
   most Linux systems.  */
#define SIGSTKFLT   16  /* Stack fault (obsolete).  */
#define SIGPWR      30  /* Power failure imminent.  */
#undef  SIGBUS
#define SIGBUS       7
#undef  SIGUSR1
#define SIGUSR1     10
#undef  SIGUSR2
.
.
.

Нам нужно перейти к файлу /usr/include/bits/signum-generic.h в Fedora 30, чтобы получить то, что мы ищем:

/* We define here all the signal names listed in POSIX (1003.1-2008);
   as of 1003.1-2013, no additional signals have been added by POSIX.
   We also define here signal names that historically exist in every
   real-world POSIX variant (e.g. SIGWINCH).
Signals in the 1-15 range are defined with their historical numbers.
   For other signals, we use the BSD numbers.
   There are two unallocated signal numbers in the 1-31 range: 7 and 29.
   Signal number 0 is reserved for use as kill(pid, 0), to test whether
   a process exists without sending it a signal.  */
/* ISO C99 signals.  */
#define SIGINT      2   /* Interactive attention signal.  */
#define SIGILL      4   /* Illegal instruction.  */
#define SIGABRT     6   /* Abnormal termination.  */
#define SIGFPE      8   /* Erroneous arithmetic operation.  */
#define SIGSEGV     11  /* Invalid access to storage.  */
#define SIGTERM     15  /* Termination request.  */
/* Historical signals specified by POSIX. */
#define SIGHUP      1   /* Hangup.  */
#define SIGQUIT     3   /* Quit.  */
#define SIGTRAP     5   /* Trace/breakpoint trap.  */
#define SIGKILL     9   /* Killed.  */
.
.
.

Обратите внимание, что в последнем выпуске Red Hat RHEL 8.1 определения сигналов снова перемещены в /usr/include/asm/signal.h (но, по крайней мере, файл называется signal.h 😛).

Различия на этом не заканчиваются. Некоторые из определенных значений для этих сигналов в дистрибутивах Linux отличаются от того, что вы найдете для macOS. И это ключевой момент, который следует понимать в зависимости от того, насколько специфичен ваш интерес к решениям для кодирования, совместимым с платформой NIX.

Вернитесь к /usr/include/bits/signum-generic.h в Fedora 30, и сигнал определяет:

#define SIGSTOP     17  /* Stop, unblockable.  */
#define SIGTSTP     18  /* Keyboard stop.  */
#define SIGCONT     19  /* Continue.  */

Эти сигналы можно использовать для элементарного управления выполнением процесса путем приостановки выполнения процесса (SIGSTOP, 17), чтобы решить, следует ли разрешить процессу возобновить выполнение позже (SIGCONT, 19).

В /usr/include/bits/signum.h в Fedora 30 значения этих сигналов становятся неопределенными и им переназначаются новые значения:

#undef  SIGCONT
#define SIGCONT     18
#undef  SIGSTOP
#define SIGSTOP     19
#undef  SIGTSTP
#define SIGTSTP     20

Когда вы используете signal.h в программе C на Fedora 30, именно эти значения в /usr/include/bits/signum.h используются дистрибутивом и не то, что вы найдете в /usr/include/bits/signum-generic.h в Fedora 30 или signal.h в macOS.

Для удовольствия давайте попробуем поведение, используя ту же программу C, показанную ранее в Fedora 30, используя номер «19» в разных дистрибутивах Linux.

user@fedora-virtualfedora playground]$ ps -e | grep vim
 37552 pts/4    00:00:00 vim
[user@fedora-virtualfedora playground]$ ./signal-example 37552 19
Signal 19 passed to kill pid 37552
(other terminal running vim)
[1]+  Stopped                 vim

а теперь RHEL 8.1:

[user@rhel81 playground]$ ps -e | grep vim
  4097 pts/2    00:00:00 vim
[user@rhel81 playground]$ ./signal-example 4097 19
Signal 19 passed to kill pid 4097
(other terminal running vim)
[1]+  Stopped                 vim

Примечание. Fedora 30 действует точно так же, как RHEL 8.1, несмотря на то, что Fedora 30 начинается с определений сигналов, которые имеют те же значения, что и в macOS и в ее файле signal.h:

#define	SIGSTOP	17	/* sendable stop signal not from tty */
#define	SIGTSTP	18	/* stop signal from tty */
#define	SIGCONT	19	/* continue a stopped process */

Если мы возьмем тот же пример кода и запустим его на macOS, то получим другой результат:

mymac:signal-example user$ ps -e | grep vim
97588 ttys004    0:00.05 vim
97608 ttys005    0:00.00 grep vim
mymac:signal-example user$ ./signal-example 97588 19
JF-EN-C02V905BHTDF:signal-example user$ ps -e | grep vim
97588 ttys004    0:00.05 vim
97620 ttys005    0:00.00 grep vim
(other terminal running vim)
(nothing happens or is printed to the terminal)

Внешний вид может быть обманчивым в мире POSIX стандартов UNIX, пока вы не начнете копать глубже, почему что-то ведет себя немного по-разному в операционных системах NIX, таких как macOS и Linux.