- Linux signals
- Description
- Finding system-specific signals
- Signals in Linux
- Signals not supported by Linux
- Sending signals from the keyboard
- Real-time signals
- Examples: sending signals
- Related commands
- Sending signal to Processes
- Contents
- kill — send a signal to a process
- kill command Examples
- killall — kill processes by name
- pkill — kill process
- All about Linux signals
- What is covered here
- What is signaled in Linux
- Signal handlers
- Traditional signal() is deprecated
- The recommended way of setting signal actions: sigaction
- Example use of sigaction()
- SA_SIGINFO handler
- Compiler optimization and data in signal handler
- Atomic Type
- Signal-safe functions
- Alternative method of handling signals: signalfd()
- Handling SIGCHLD
- Handling SIGBUS
- Handling SIGSEGV
- Handling SIGABRT
- What happens when a process receives a signal?
- Default actions
- Interrupting system calls
- What is interrupted?
- Simple example of signal aware code
- Data transferring and signals
- Blocking signals
- How to block signals
- Preventing race conditions.
- Waiting for a signal
- Other functions to wait for a signal
- Sending signals
- Sending signal from keyboard
- Sending signals to yourself
- Sending data along with signal — sigqueue()
- Real-time signals
- Signals and fork()
- Signals and threads
- Which thread receives the signal?
- Signal handlers
- sigwaitinfo()/sigtimedwait() and process-directed signals
- Real-time signals
- Other uses of signals
Linux signals
On Unix-like operating systems such as Linux, signals are software interrupts. They provide a way for the user (or a process) to directly communicate with a process.
Software may be programmed to respond intelligently to a wide array of signals, and certain signals cause processes to behave in a standardized, predefined way at the kernel level.
Description
Process signals were developed as part of UNIX in the 1970s. They are used on all modern Unix-like operating systems, including Linux, BSD, and macOS X.
When a signal is sent to a process, the operating system interrupts the normal flow of the process execution and delivers the notification. If the process has previously registered a way to handle that particular signal, that routine is executed, otherwise the system executes the default signal handler.
Signals can be sent with the kill command, which is named for its default signal (SIGKILL) that instructs the OS to forcefully terminate a process before doing anything else.
Signal names are commonly abbreviated without their SIG prefix, e.g., «KILL», including in the command arguments of kill.
Finding system-specific signals
Signals are defined in the system library signal.h. To view the signals used by your operating system, open a terminal and run man signal or man 7 signal.
Signals in Linux
Signal | Number | Description | Standard |
---|---|---|---|
SIGHUP | 1 | The HUP signal is sent to a process when its controlling terminal is closed. It was originally designed to notify a serial line drop (HUP stands for «Hang Up»). In modern systems, this signal usually indicates the controlling pseudo or virtual terminal is closed. | POSIX |
SIGINT | 2 | The INT signal is sent to a process by its controlling terminal when a user wants to interrupt the process. This signal is often initiated by pressing Ctrl + C , but on some systems, the «delete» character or «break» key can be used. | ANSI |
SIGQUIT | 3 | The QUIT signal is sent to a process by its controlling terminal when the user requests that the process perform a core dump. | POSIX |
SIGILL | 4 | Illegal instruction. The ILL signal is sent to a process when it attempts to execute a malformed, unknown, or privileged instruction. | ANSI |
SIGTRAP | 5 | Trace trap. The TRAP signal is sent to a process when a condition arises that a debugger is tracing — for example, when a particular function is executed, or when a particular variable changes value. | POSIX |
SIGABRT, SIGIOT | 6 | Abort process. ABRT is usually sent by the process itself, when it calls the abort() system call to signal an abnormal termination, but it can be sent from any process like any other signal. SIGIOT is a synonym for SIGABRT. (IOT stands for input/output trap, a signal which originated on the PDP-11.) | 4.2 BSD |
SIGBUS | 7 | The BUS signal is sent to a process when it causes a bus error, such as an incorrect memory access alignment or non-existent physical address. In Linux, this signal maps to SIGUNUSED, because memory access errors of this kind are not possible. | 4.2 BSD |
SIGFPE | 8 | Floating point exception. The FPE signal is sent to a process when it executes erroneous arithmetic operations, such as division by zero. | ANSI |
SIGKILL | 9 | Forcefully terminate a process. With STOP, this is one of two signals which cannot be intercepted, ignored, or handled by the process itself. | POSIX |
SIGUSR1 | 10 | User-defined signal 1. This is one of two signals designated for custom user signal handling. | POSIX |
SIGSEGV | 11 | The SEGV signal is sent to a process when it makes an invalid virtual memory reference, or segmentation fault, i.e., when it performs a segmentation violation. | |
SIGUSR2 | 12 | User-defined signal 2. This is one of two signals designated for custom user signal handling. | POSIX |
SIGPIPE | 13 | The PIPE signal is sent to a process when it attempts to write to a pipe without a process connected to the other end. | POSIX |
SIGALRM | 14 | The ALRM signal notifies a process that the time interval specified in a call to the alarm() system function has expired. | POSIX |
SIGTERM | 15 | The TERM signal is sent to a process to request its termination. Unlike the KILL signal, it can be caught and interpreted or ignored by the process. This signal allows the process to perform nice termination releasing resources and saving state if appropriate. It should be noted that SIGINT is nearly identical to SIGTERM. | ANSI |
SIGSTKFLT | 16 | Stack fault. Maps to SIGUNUSED in Linux. | |
SIGCHLD | 17 | The CHLD signal is sent to a process when a child process terminates, is interrupted, or resumes after being interrupted. One common usage of the signal is to instruct the operating system to clean up the resources used by a child process after its termination without an explicit call to the wait system call. | POSIX |
SIGCONT | 18 | Continue executing after stopped, e.g., by STOP | POSIX |
SIGSTOP | 19 | The STOP signal instructs the operating system to stop a process for later resumption. This is one of two signals, along with KILL that cannot be intercepted, ignored, or handled by the process itself. | POSIX |
SIGTSTP | 20 | The TSTP signal is sent to a process by its controlling terminal to request it to stop temporarily. It is commonly initiated by the user pressing Ctrl + Z . Unlike SIGSTOP, this process can register a signal handler for or ignore the signal. | POSIX |
SIGTTIN | 21 | The TTIN signal is sent to a process when it attempts to read from the tty while in the background. This signal can be received only by processes under job control. Daemons do not have controlling terminals and should never receive this signal. | POSIX |
SIGTTOU | 22 | TTOU signal is sent to a process when it attempts to write from the tty while in the background. The compliment to TTIN. | POSIX |
SIGURG | 23 | The URG signal is sent to a process when a socket has urgent or out-of-band data available to read. | 4.2 BSD |
SIGXCPU | 24 | The XCPU signal is sent to a process when it has used up the CPU for a duration that exceeds a certain predetermined user-settable value. The arrival of an XCPU signal provides the receiving process a chance to quickly save any intermediate results and to exit gracefully, before it is terminated by the operating system using the SIGKILL signal. | 4.2 BSD |
SIGXFSZ | 25 | The XFSZ signal is sent to a process when it grows a file larger than the maximum allowed size. | 4.2 BSD |
SIGVTALRM | 26 | Virtual alarm clock. May be sent by the alarm() system call. By default, this signal kills the process, but it’s intended for use with process-specific signal handling. | 4.2 BSD |
SIGPROF | 27 | Profiling alarm clock. Indicates expiration of a timer that measures CPU time used by the current process («user» time), and CPU time expended on behalf of the process by the system («system» time). These times may be used to implement code profiling facilities. By default, this signal terminates the process, but it’s intended for use with process-specific signal handling. | 4.2 BSD |
SIGWINCH | 28 | Window change. The WINCH signal is sent to a process when its controlling terminal changes size, for instance if you resize it in your window manager. | 4.3 BSD, Sun |
SIGIO, SIGPOLL | 29 | Input/output is now possible. SIGPOLL is a synonym for SIGIO, and in Linux its behavior is identical to SIGURG. | 4.2 BSD |
SIGPWR, SIGLOST | 30 | Power failure. The PWR signal is sent to a process when the system detects a power failure. SIGLOST is a synonym for SIGPWR. | System V |
SIGUNUSED, SIGSYS | 31 | Unused signal. This signal is provided for compatibility reasons, for example when porting software from an operating system with different or unsupported signals in Linux. In Linux, SIGSYS is a synonym for SIGUNUSED. | System V r4 |
Signals not supported by Linux
The following signals may be used by other systems, such as BSD, but are interpreted as SIGUNUSED in Linux.
SIGEMT | The EMT signal is sent to a process when an emulator trap occurs. Unused in Linux. |
SIGINFO | The INFO signal is sent to a process when a status request is received from the controlling terminal. Unused in Linux |
SIGLOST | The LOST signal is sent to a process when a file lock is lost. Unused in Linux. |
SIGSYS | The SYS signal is sent to a process when it passes a bad argument to a system call. Unused in Linux. |
Sending signals from the keyboard
Signals may be sent from the keyboard. Several standard defaults are listed below. Default key combinations for sending interrupt signals can be defined with the stty command.
Ctrl-C | Send SIGINT (Interrupt). By default, this causes a process to terminate. |
Ctrl-Z | Send SIGTSTP (Suspend). By default, this causes a process to suspend all operation. |
Ctrl-\ | Send SIGQUIT (Quit). By default, this causes a process to terminate immediately and dump the core. |
Ctrl-T | Send SIGINFO (Info). By default, this causes the operating system to display information about the command. Not supported on all systems. |
Real-time signals
Real-time signals are a set of signals with no predefined purpose, for programmers to use as they want in their software. Two signal names, SIGRTMIN and SIGRTMAX, define the minimum and maximum signal numbers of the real-time signals. For example, the programmer may use the signal number as SIGRTMIN+3 to refer to the fourth real-time signal number.
Examples: sending signals
The kill command sends signals to processes. Your shell may have a built-in version of kill, which supersedes the version installed at /bin/kill. The two versions have slightly different options, but basic functions are the same. The following examples may run using either version of kill.
The process to be signaled is referred to by PID (process ID). If you’re not sure of the process ID, you can find it with the ps command, for example ps -aux.
Send the KILL signal to the process with PID 1234.
Kill three processes: PIDs 123, 456, and 789.
Send signal number 15 (TERM) to processes 1234 and 5678.
Same as the previous command.
List all available signals. Example output:
The special process ID -1 refers to all processes other than kill and the system root process. This command attempts to kill (-9) every possible process (-1) on the system. For more information, see the documentation of kill, linked below.
Related commands
kill — End a process.
stty — Change terminal line settings.
Источник
Sending signal to Processes
You can send various signals to commands / process and shell scripts using the, pkill command, kill command, and killall command.
Contents
kill — send a signal to a process
The default signal for kill is TERM. To list available signals, enter:
kill command Examples
The kill command can send all of the above signals to commands and process. However, commands only give response if they are programmed to recognize those signals. Particularly useful signals include:
- SIGHUP (1) — Hangup detected on controlling terminal or death of controlling process.
- SIGINT (2) — Interrupt from keyboard.
- SIGKILL (9) — Kill signal i.e. kill running process.
- SIGSTOP (19) — Stop process.
- SIGCONT (18) — Continue process if stopped.
To send a kill signal to PID # 1234 use:
killall — kill processes by name
killall sends a signal to all processes running any of the specified commands . If no signal name is specified, SIGTERM is sent. To terminate all firefox process (child and parent), enter:
To send a KILL signal to firefox, enter:
pkill — kill process
The pkill command is another command with additional options to kill process by its name, user name, group name, terminal, UID, EUID, and GID. It will send the specified signal (by default SIGTERM) to each process instead of listing them on stdout. To send a kill signal to php-cgi process, enter:
The above example will kill all users php-cgi process. However, -u option will kill only processes whose effective user ID is set to vivek:
Make sshd reread its configuration file, enter:
Источник
All about Linux signals
In most cases if you want to handle a signal in your application you write a simple signal handler like:
void handler ( int sig )
and use the signal(2) system function to run it when a signal is delivered to the process. This is the simplest case, but signals are more interesting than that! Information contained in this article is useful for example when you are writing a daemon and must handle interrupting your program properly without interrupting the current operation or the whole program.
What is covered here
What is signaled in Linux
For a complete list of signals see the signal(7) manual page.
Signal handlers
Traditional signal() is deprecated
The recommended way of setting signal actions: sigaction
As you can see you don’t pass the pointer to the signal handler directly, but instead a struct sigaction object. It’s defined as:
For a detailed description of this structure’s fields see the sigaction(2) manual page. Most important fields are:
- sa_handler — This is the pointer to your handler function that has the same prototype as a handler for signal(2).
- sa_sigaction — This is an alternative way to run the signal handler. It has two additional arguments beside the signal number where the siginfo_t * is the more interesting. It provides more information about the received signal, I will describe it later.
- sa_mask allows you to explicitly set signals that are blocked during the execution of the handler. In addition if you don’t use the SA_NODEFER flag the signal which triggered will be also blocked.
- sa_flags allow to modify the behavior of the signal handling process. For the detailed description of this field, see the manual page. To use the sa_sigaction handler you must use SA_SIGINFO flag here.
What is the difference between signal(2) and sigaction(2) if you don’t use any additional feature the later one provides? The answer is: portability and no race conditions. The issue with resetting the signal handler after it’s called doesn’t affect sigaction(2), because the default behavior is not to reset the handler and blocking the signal during it’s execution. So there is no race and this behavior is documented in the POSIX specification. Another difference is that with signal(2) some system calls are automatically restarted and with sigaction(2) they’re not by default.
Example use of sigaction()
In this example we use the three arguments version of signal handler for SIGTERM . Without setting the SA_SIGINFO flag we would use a traditional one argument version of the handler and pass the pointer to it by the sa_handler field. It would be a replacement for signal(2). You can try to run it and do kill PID to see what happens.
In the signal handler we read two fields from the siginfo_t * siginfo parameter to read the sender’s PID and UID. This structure has more fields, I’ll describe them later.
The sleep(3) function is used in a loop because it’s interrupted when the signal arrives and must be called again.
SA_SIGINFO handler
We’ll see more examples of use of siginfo_t later.
Compiler optimization and data in signal handler
What it does? It depends on compiler optimization settings. Without optimization it executes a loop that ends when the process receives SIGTERM or other sgnal that terminates the process and was not handler. When you compile it with the -O3 gcc flag it will not exit after receiving SIGTERM . Why? because whe while loop is optimized in such way that the exit_flag variable is loaded into a processor register once and not read from the memory in the loop. The compiler isn’t aware that the loop is not the only place where the program accesses this variable while running the loop. In such cases — modifying a variable in a signal handler that is also accessed in some other parts of the program you must remember to instruct the compiler to always access this variable in memory when reading or writing them. You should use the volatile keyword in the variable declaration:
Atomic Type
- It doesn’t work like a mutex: it’s guaranteed that read or write of this type translates into an uninterruptible operation but code such as:
Isn’t safe: there is read and update in the if operation but only single reads and single writes are atomic.
- Don’t try to use this type in a multi-threaded program as a type that can be used without a mutex. It’s only intended for signal handlers and has nothing to do with mutexes!
- You don’t need to worry if data are modified or read in a signal handler are also modified or read in the program if it happens only in parts where the signal is blocked. Later I’ll show how to block signals. But you will still need the volatile keyword.
Signal-safe functions
Alternative method of handling signals: signalfd()
First we must block the signals we want to handle with signalfd(2) using sigprocmask(2). This function will be described later. Then we call signalfd(2) to create a file descriptor that will be used to read incoming signals. At this point in case of SIGTERM or SIGINT delivered to your program it will not be interrupted, no handler will be called. It will be queued and you can read information about it from the sfd descriptor. You must supply a buffer large enough to read the struct signalfd_siginfo object that will be filled with information similar to the previously described siginfo_t . The difference is that the fields are named a bit different (like ssi_signo instead of si_signo ). What is interesting is that the sfd descriptor behaves and can be used just like any other file descriptor, in particular you can:
- Use it in select(2), poll(2) and similar functions.
- Make it non-blocking.
- Create many of them, each handling different signals to return different descriptors as ready by select(2) for every different signal.
- After fork() the file descriptor is not closed, so the child process can read signals that were send to the parent.
This is perfect to be used in a single-process server with the main loop executes a function like poll(2) to handle many connections. It simplifies signal handling because the signal descriptor can be added to the poll’s array of descriptors and handled like any other of them, without asynchronous actions. You handle the signal when you are ready for that because your program is not interrupted.
Handling SIGCHLD
This way whenever a child exits it will be cleaned-up but information which process was that, why it exited and its exit status is forgotten. You could make the handler more intelligent but remember to not use any function that is not listed as signal-safe.
You must remember that if you make child processes SIGCHLD must have a handler. The behavior of ignoring this signal is undefined, so at least a handler that doesn’t do anything is required.
Handling SIGBUS
You must keep in mind the list of signal-safe functions: In this example we never actually return from the signal handler. The stack is cleaned up, but program is restarted in completely different place, so if you’ve had, for example, a mutex locked during the operation like:
After longjmp(3) the mutex is still held although in every other situation the mutex is released.
So handling SIGBUS is possible but very tricky and can introduce bugs that are very hard to debug. The program’s code also becomes ugly.
Handling SIGSEGV
Exhausting stack space is one of the causes of segmentation fault. In this case running a signal handler is not possible because it requires space on the stack. To allow handling SIGSEGV in such condition the sigaltstack(2) function exists that sets alternative stack to be used by signal handlers.
Handling SIGABRT
What happens when a process receives a signal?
Default actions
For a complete list of default actions see the signal(7) manual page.
Interrupting system calls
What is interrupted?
Simple example of signal aware code
This example works, but if you try it and send few signals during sleep you can see that it may sleep different amount of time. This is because sleep(3) takes the argument and returns the value with 1s resolution so it can’t be precise telling you how long it need to sleep after interruption.
Data transferring and signals
This program reads from it’s standard input and copies the data to the standard output. Additionally, when SIGUSR1 is received it prints to stderr how many bytes has been already read and written. It installs a signal handler which sets a global flag to 1 if called. Whatever the program does at the moment it receives the signal, the numbers are immediately printed. It works because read(2) and write(2) functions are interrupted by signals even during operation. In case of those functions two things might happen:
- When read(2) waits for data or write(2) waits for stdout to put some data and no data were yet transfered in the call and SIGUSR1 arrives those functions exit with return value of -1. You can distinguish this situation from other errors by reading the value of the errno variable. If it’s EINTR it means that the function was interrupted without any data transfered and we can call the function again with the same parameters.
- Another case is that some data were transfered but the function was interrupted before it finished. In this case the functions don’t return an error but a value less that the supplied data size (or buffer size). Neither the return value nor the errno variable tells us that the function was interrupted by a signal, if we want to distinguish this case we need to set some flag in the signal handler (as we do in this example). To continue after interruption we need to call the function again keeping in mind that some data were consumed or read adn we must restart from the right point. In our example only the write(2) must be properly restarted, we use the written variable to track how many bytes were actually written and properly call write(2) again if there are data left in the buffer.
Remember that not all system calls behave exactly the same way, consult their manual page to make sure.
Reading the sigaction(2) manual page you can think that setting the SA_RESTART flag is simpler that handling system call interruption. The documentation says that setting it will make certain system calls automatically restartable across signals. It’s not specified which calls are restarted. This flag is mainly used for compatibility with older systems, don’t use it.
Blocking signals
How to block signals
This program will sleep for 10 seconds and will ignore the SIGTERM signal during the sleep. It works this way because we’ve block the signal with sigprocmask(2). The signal is not ignored, it’s blocked, it means that are queued by the kernel and delivered when we unblock the signal. This is different than ignoring the signal with signal(2). First sigprocmask(2) is more complicated, it operates in a set of signals represented by sigset_t , not on one signal. The SIG_BLOCK parameter tells that the the signals in set are to be blocked (in addition to the already blocked signals). The SIG_SETMASK tells that the signals in set are to be blocked, and signals that are not present in the set are to be unblocked. The third parameter, if not NULL, is written with the current signal mask. This allows to restore the mask after modifying the process’ signal mask. We do it in this example. The first sleep(3) function is executed with SIGTERM blocked, if the signal arrives at this moment, it’s queued. When we restore the original signal mask, we unblock SIGTERM and it’s delivered, the signal handler is called.
See the sigprocmask(2) manual on how to use this function and sigsetops(3) on how to manipulate signal sets.
Preventing race conditions.
Let’s say it’s an example of a network daemon that accepts connections using select(2) and accept(2). It can use select(2) because it listens on multiple interfaces or waits also for some events other than incoming connections. We want to be able to cleanly shut it down with a signal like SIGTERM (remove the PID file, wait for pending connections to finish etc.). To do this we have a handler for the signal defined which sets global flag and relay on the fact that select(2) will be interrupted when the signal arrives at the moment we are just waiting for some events. If the main loop in the program looks similarly as the above code everything works. almost. There is a specific case in which the signal will not interrupt the program even if it does nothing at all at the moment. When it arrives between checking the while condition and executing select(2). The select(2) function will not be interrupted (because signal was handled) and will sleep until some file descriptor it monitors will be ready.
This is where the sigprocmask(2) and other «new» functions are useful. Let’s see an improved version:
What’s the difference between select(2) and pselect(2)? The most important one is that the later takes an additional argument of type sigset_t with set of signals that are unblocked during the execution of the system call. The idea is that the signals are blocked, then global variables/flags that are changed in signal handlers are read and then pselect(2) runs. There is no race because pselect(2) unblocks the signals atomically. See the example: the exit_request flag is checked while the signal is blocked, so there is no race here that would lead to executing pselect(2) just after the signal arrives. In fact, in this example we block the signal all the time and the only place where it can be delivered to the program is the pselect(2) execution. In real world you may block the signals only for the part of the program that contains the flag check and the pselect(2) call to allow interruption in other places in the program.
Another difference not related to the signals is that select(2)’s timeout parameter is of type struct timeval * and pselect(2)’s is const struct timespec * . See the pselect(2) manual page for more information.
If you like poll(2) there is analogous ppoll(2) functions, but in contrast of pselect(2) ppoll(2) is not a standard POSIX function.
Waiting for a signal
The proper solution is to use a dedicated function to wait for a signal: see an example of using sigtimedwait().
This program creates a child process that sleeps few seconds (in a real world application this process would do something like execve(2)) and waits for it to finish. We want to implement a timeout after which the process is killed. The waitpid(2) function does not have a timeout parameter, but we use the SIGCHLD signal that is sent when the child process exits. One solution would be to have a handler for this signal and a loop with sleep(3) in it. The sleep(3) will be interrupted by the SIGCHLD signal or will sleep for the whole time which means the timeout occurred. Such a loop would have a race because the signal could arrive not in the sleep(3), but somewhere else like just before the sleep(3). To solve this we use the sigtimedwait(2) function that allows us to wait for a signal without any race. We can do this because we block the SIGCHLD signal before fork(2) and then call sigtimedwait(2) which atomically unblock the signal and wait for it. If the signal arrives it block it again and returns. It can also take a timeout parameter so it will not sleep forever. So without any trick we can wait for the signal safely.
One drawback is that if sigtimedwait(2) is interrupted by another signal it returns with an error and doesn’t tell us how much time elapsed, so we don’t know how to properly restart it. The proper solution is to wait for all signals we expect at this point in hte program or block other signals. There is another small bug i the program: when we kill the process, SIGCHLD is sent and we don’t handle it anywhere. We should unblock the signal before waitpid(2) and have a handler for it.
Other functions to wait for a signal
- sigsuspend(2) — waits for any signal. It takes a signal mask of signals that are atomically unblocked, co it doesn’t introduce race conditions.
- sigwaitinfo(2) — like sigtimedwait(2), but without the timeout parameter.
- pause(2) — simple function taking no argument. Just waits for any signal. Don’t use it, you will introduce a race condition similar to the described previously, use sigsuspend(2).
Sending signals
Sending signal from keyboard
Sending signals to yourself
Sending data along with signal — sigqueue()
Real-time signals
Whats the difference between RT signals and standard signals? There are couple:
- More than one RT signal can be queued for the process if it has the signal blocked while someone sends it. In standard signals only one of a given type is queued, the rest is ignored.
- Order of delivery of RT signal is guaranteed to be the same as the sending order.
- PID and UID of sending process is written to si_pid and si_uid fields of siginfo_t . For more information see section about Real time signals in signal(7).
Signals and fork()
Signals and threads
With Native POSIX Threads Library things get more interesting. Since this is the POSIX compliant implementation the behavior described here also applies to other POSIX systems.
Which thread receives the signal?
As you can see there is a process-wide signal queue and a per-thread queues.
Signal handlers
sigwaitinfo()/sigtimedwait() and process-directed signals
Real-time signals
Other uses of signals
It’s possible to be notified of I/O availability by a signal. It’s an alternative to functions like select(2). It’s done by setting the O_ASYNC flag on the file descriptor. If you do so and if I/O is available (as select(2) would consider it) a signal is sent to the process. By default it’s SIGIO , but using Real-time signals is more practical and you can set up the file descriptor using fcntl(2) so that you get more information in siginfo_t structure. See the links at the bottom of this article for more information. There is now a better way to do it on Linux: epoll(7) and similar mechanisms are available on other systems.
The dnotify mechanism uses similar technique: you are notified about file system actions using signals related to file descriptors of monitored directories or files. The recommended way of monitoring files is now inotify.
Источник