The behavior of signal () depends on the compile options

Introduction

Since I was addicted to the implementation of signal (), I will leave the survey results as a memorandum. The survey environment is

background

I was refactoring legacy code that was created around the year I was born and modified and extended as the operating platform changed.

Some of them used the signal () system call. Looking at man of signal (), avoid using signal () because its behavior changes depending on the platform. , Sigaction () was used, so I modified it to use sigaction (), but it fits in here.

SIGNAL(2)                                    Linux Programmer's Manual

name
       signal -ANSI C signal manipulation
Description
       signal()Behavior depends on the UNIX version.
It also historically depends on the version of Linux.
Avoid using this system call and instead sigaction(2)to use.
       ………

Implementation

In conclusion, Linux signal () behaves differently depending on the compile options. Even if you call signal (), it is wrapped in glibc, and internally it calls the sigaction () system call. When calling this sigaction () system call, the value of sa_flags, which is an argument, differs depending on the compile option.

Implementation inside the Linux kernel

First, let's take a look at the internal implementation of the Linux kernel.

/*
 * For backwards compatibility.  Functionality superseded by sigaction.
 */
SYSCALL_DEFINE2(signal, int, sig, __sighandler_t, handler)
{
	struct k_sigaction new_sa, old_sa;
	int ret;

	new_sa.sa.sa_handler = handler;
	new_sa.sa.sa_flags = SA_ONESHOT | SA_NOMASK;
	sigemptyset(&new_sa.sa.sa_mask);

	ret = do_sigaction(sig, &new_sa, &old_sa);

	return ret ? ret : (unsigned long)old_sa.sa.sa_handler;
}

It should be noted that the flag specified in sa_flags is (SA_ONESHOT | SA_NOMASK). SA_ONESHOT means that sa_handler returns to SIG_DFL when the process receives a signal. SA_NOMASK means that when a process receives a signal and executes a handler, it receives the same signal again (that is, does not mask it). This is the behavior of System V signal (). (Unix has two major systems, System V and BSD, and it seems that the implementation is different in some places. I don't know in detail because I was the first person to touch Linux (OS other than Windows) in 2012. )

Therefore, if you are using Linux kernel ver.5.5.5, you may think that the specification of signal () is an implementation of System V, but as mentioned above, when using glibc, signal () in the kernel is Not called.

glibc implementation

When calling a system call from C language, it basically calls the glibc wrapper function. Take a look at signal.h (/usr/include/signal.h), which is included when calling signal ().

/usr/include/signal.h


/* Set the handler for the signal SIG to HANDLER, returning the old
   handler, or SIG_ERR on error.
   By default `signal' has the BSD semantic.  */
#ifdef __USE_MISC
extern __sighandler_t signal (int __sig, __sighandler_t __handler)
     __THROW;
#else
/* Make sure the used `signal' implementation is the SVID version. */
# ifdef __REDIRECT_NTH
extern __sighandler_t __REDIRECT_NTH (signal,
				      (int __sig, __sighandler_t __handler),
				      __sysv_signal);
# else
#  define signal __sysv_signal
# endif
#endif

It differs in the following three ways.

  1. If \ _ \ _USE \ _MISC is defined
  2. If \ _ \ _USE \ _MISC is not defined and \ _ \ _ REDIRECT \ _NTH is defined
  3. If neither \ _ \ _ USE \ _MISC nor \ _ \ _ REDIRECT \ _NTH is defined

I will explain each.

If \ _ \ _USE_MISC is defined

By default, \ _ \ _USE \ _MISC is defined. You can see this by looking at /usr/include/features.h. The explanation of here was easy to understand.

/usr/include/signal.h


extern __sighandler_t signal (int __sig, __sighandler_t __handler)
     __THROW;

signal () is declared extern and the entity is in glibc. The implementation is as follows.

c:glibc-2.28/sysdeps/posix/signal.c


sigset_t _sigintr attribute_hidden;		/* Set by siginterrupt.  */

__sighandler_t
__bsd_signal (int sig, __sighandler_t handler)
{
  struct sigaction act, oact;

<Omitted>

  act.sa_handler = handler;
  __sigemptyset (&act.sa_mask);
  __sigaddset (&act.sa_mask, sig);
  act.sa_flags = __sigismember (&_sigintr, sig) ? 0 : SA_RESTART;
  if (__sigaction (sig, &act, &oact) < 0)
    return SIG_ERR;

  return oact.sa_handler;
}
weak_alias (__bsd_signal, signal)

It is weak_alias (\ _ \ _ bsd \ _signal, signal), and when signal () is called, \ _ \ _ bsd \ _signal () is called. This is BSD behavior, as you can see from the name \ _ \ _ bsd \ _signal (). This is consistent with the description "By default` signal'has the BSD semantic. "In the comment on the first line of /usr/include/signal.h.

What we pay attention to this time is the value specified for act.sa_flags. I think \ _ \ _ sigisempty () will return 0 (Is this anything other than 0?), So act.sa_flags is always SA_RESTART. In SA_RESTART, the handler is automatically re-registered after the process receives the signal and completes the handler processing. If a signal is received during system call execution, processing will continue, and if it was in the wait state, it will be in the wait state again, and no error will be returned by EINTR. As an exception, in the system call of socket family and msg family, when a signal is received, an error is returned by EINTR.

If \ _ \ _USE \ _MISC is not defined and \ _ \ _ REDIRECT \ _NTH is defined

Whether \ _ \ _USE \ _MISC is defined

Before explaining the implementation of signal (), I will introduce the case where \ _ \ _USE \ _MISC is not defined.

For example, adding the -ansi option makes \ _ \ _USE \ _MISC undefined. You can check it with the following command.

$ echo "#include <signal.h>" | gcc -ansi -dM -E - | grep __USE_
#define __USE_FORTIFY_LEVEL 0
#define __bos(ptr) __builtin_object_size (ptr, __USE_FORTIFY_LEVEL > 1)

By the way, __USE_MISC is not defined even if -D_XOPEN_SOURCE is specified as an option.

$ echo "#include <signal.h>" | gcc  -D_XOPEN_SOURCE -dM -E - | grep __USE_
#define __USE_FORTIFY_LEVEL 0
#define __USE_ISOC11 1
#define __USE_ISOC95 1
#define __USE_ISOC99 1
#define __USE_XOPEN 1
#define __USE_POSIX2 1
#define __USE_POSIX 1
#define __bos(ptr) __builtin_object_size (ptr, __USE_FORTIFY_LEVEL > 1)
#define __USE_POSIX_IMPLICITLY 1

On the other hand, if you do not specify -ansi or -D_XOPEN_SOURCE as an option as shown below, that is, by default, you can see that __USE_MISC is defined.

$ echo "#include <features.h>" | gcc -dM -E - | grep __USE_
#define __USE_FORTIFY_LEVEL 0
#define __USE_ISOC11 1
#define __USE_ISOC95 1
#define __USE_ISOC99 1
#define __USE_XOPEN2K 1
#define __USE_POSIX199506 1
#define __USE_POSIX2 1
#define __USE_XOPEN2K8 1
#define __USE_MISC 1
#define __USE_POSIX 1
#define __bos(ptr) __builtin_object_size (ptr, __USE_FORTIFY_LEVEL > 1)
#define __USE_POSIX199309 1
#define __USE_POSIX_IMPLICITLY 1
#define __USE_ATFILE 1

Note that \ _ \ _REDIRECT \ _NTH is defined regardless of the presence or absence of -ansi or -D_XOPEN_SOURCE.

Implementation of signal ()

The declaration of signal () when \ _ \ _USE \ _MISC is not defined and \ _ \ _ REDIRECT \ _NTH is defined is as follows.

/usr/include/signal.h


extern __sighandler_t __REDIRECT_NTH (signal,
				      (int __sig, __sighandler_t __handler),
				      __sysv_signal);

\ _ \ _REDIRECT \ _NTH is a macro that tells the compiler to use the symbol __sysv_signal instead of the symbol signal It seems to be -nth-do-in-unistd-h), so when you call signal (), glibc's __sysv_signal () is called.

The implementation of __sysv_signal () is as follows.

sysdeps/posix/sysv_signal.c


/* Set the handler for the signal SIG to HANDLER,
   returning the old handler, or SIG_ERR on error.  */
__sighandler_t
__sysv_signal (int sig, __sighandler_t handler)
{
  struct sigaction act, oact;

<Omitted>

  act.sa_handler = handler;
  __sigemptyset (&act.sa_mask);
  act.sa_flags = SA_ONESHOT | SA_NOMASK | SA_INTERRUPT;
  act.sa_flags &= ~SA_RESTART;
  if (__sigaction (sig, &act, &oact) < 0)
    return SIG_ERR;

  return oact.sa_handler;
}

weak_alias (__sysv_signal, sysv_signal)

Look at the value of sa_flags every time. SA_ONESHOT and SA_NOMASK are the same as signal () inside the kernel, but are also flagged as SA_INTERRUPT. This SA_INTERRUPT is not explained by man of sigaction, and when I look at glibc, it says / * Historical no-op. * /, So can I ignore it? I've carefully defeated the SA_RESTART bit, but I'm not sure. Is there an environment that has the same bits as SA_INTERRUPT? In the first place, SA_RESTART is a flag unique to the BSD extension, so I wonder if it is carefully defeated because it does not exist in the implementation of system v.

If neither \ _ \ _USE \ _MISC nor \ _ \ _REDIRECT \ _NTH is defined

Since it is declared as follows in /usr/include/signal.h, you can see that __sysv_signal () is called when signal () is called regardless of the presence or absence of \ _ \ _REDIRECT \ _NTH. Does the use of \ _ \ _REDIRECT \ _NTH affect anything? I wasn't sure.

/usr/include/signal.h


#  define signal __sysv_signal

Digression

When I was investigating the source code of glibc, the function with the same name was implemented in multiple places, and I was not sure which one was called. For the time being, I was looking at the following source code in consideration of my own environment.

Summary

Since the behavior of signal () changes depending on the compile option, the behavior changed when all the modifications to signal ()-> sigaction () were modified in the same way by brain death.

--Do not use signal () as it is less portable as in man. --When changing signal () to sigaction (), be aware of what the existing behavior of your application is.

We hope this article helps those who maintain and refactor their legacy code (and of course others).

Recommended Posts

The behavior of signal () depends on the compile options
A note on the default behavior of collate_fn in PyTorch
About the behavior of yield_per of SqlAlchemy
Check the behavior of destructor in Python
I checked the options of copyMakeBorder of OpenCV
Investigate the effect of outliers on correlation
About the behavior of enable_backprop of Chainer v2
Post the subject of Gmail on twitter
Display the graph of tensorBoard on jupyter
Hook to Shared Library on Linux to interrupt the behavior of existing binaries
Change the order of PostgreSQL on Heroku
In Python, change the behavior of the method depending on how it is called
About the behavior of copy, deepcopy and numpy.copy
See the behavior of drunkenness with reinforcement learning
[2020July] Check the UDID of the iPad on Linux
Use the latest version of PyCharm on Ubuntu
Calculate the probability of outliers on a boxplot
About the behavior of Model.get_or_create () of peewee in Python
At the time of python update on ubuntu
Visualize the behavior of the sorting algorithm with matplotlib
Change the resolution of Ubuntu running on VirtualBox
About the behavior of Queue during parallel processing
[AWS S3] Confirmation of the existence of folders on S3
Behavior of multiprocessing.pool.Pool.map
rsync Behavior changes depending on the presence or absence of the slash in the copy source
Install the latest version of CMake on Ubuntu 18.04.4 LTS
Maybe I overestimated the impact of ShellShock on CGI
Try to estimate the number of likes on Twitter
Drawing on Jupyter using the plot function of pandas
Looking back on the transition of the Qiita Advent calendar
Tweet the triple forecast of the boat race on Twitter
Difference in results depending on the argument of multiprocess.Process
Scroll Japanese on the LED of RaspberryPi Sense HAT
A Study on Visualization of the Scope of Prediction Models
Create a shape on the trajectory of an object
Announcing the availability of Java 11 LTS on Amazon Linux 2