Applying C - Signals
Written by Harry Fairhead   
Monday, 09 March 2020
Article Index
Applying C - Signals
Controlling Signals
Sending Signals

Signals are just software interrupts but this is just the start of the story.This extract is from my  book on using C in an IoT context.

Now available as a paperback or ebook from Amazon.

Applying C For The IoT With Linux

  1. C,IoT, POSIX & LINUX
  2. Kernel Mode, User Mode & Syscall
  3. Execution, Permissions & Systemd
    Extract Running Programs With Systemd
  4. Signals & Exceptions
    Extract  Signals
  5. Integer Arithmetic
    Extract: Basic Arithmetic As Bit Operations
    Extract: BCD Arithmetic  ***NEW
  6. Fixed Point
    Extract: Simple Fixed Point Arithmetic
  7. Floating Point 
  8. File Descriptors
    Extract: Simple File Descriptors 
    Extract: Pipes 
  9. The Pseudo-File System
    Extract: The Pseudo File System
    Extract: Memory Mapped Files 
  10. Graphics
    Extract: framebuffer
  11. Sockets
    Extract: Sockets The Client
    Extract: Socket Server
  12. Threading
    Extract:  Pthreads
    Extract:  Condition Variables
    Extract:  Deadline Scheduling
  13. Cores Atomics & Memory Management
    Extract: Applying C - Cores 
  14. Interupts & Polling
    Extract: Interrupts & Polling 
  15. Assembler
    Extract: Assembler

Also see the companion book: Fundamental C

<ASIN:1871962609>

<ASIN:1871962617>

ACcover

Signals are the POSIX way of implementing software interrupts. They are used to inform user-space programs that something has happened and they are also used to terminate a program. Going beyond this they can be generated by user-mode programs to signal to other programs. That is, they can be used as a simple form of inter-process communication (IPC). A signal causes the user program to abandon its normal execution and transfers control to another location. This can be thought of as an exception. There is no exception handling defined within the C language. C has no try-catch type of construct, but there is a standard facility for implementing the same sort of behavior - the longjmp - which deserves to be better known.

Signal Basics

There are a number of system-generated signals, which are mostly the result of hardware interrupts that occur in the kernel, which are converted into signals and delivered to the user-mode program that caused them.

There are a many different signals and variations on the standards. The table below list the most commonly encountered.

Signal

Value

Action

Comment

SIGINT

 2

Term

Interrupt from keyboard Ctrl-C

SIGQUIT

 3

Core

Quit from keyboard Ctrl-\

SIGABRT

 6

Core

Abort signal from the abort function

SIGFPE

 8

Core

Floating-point exception

SIGKILL

 9

Term

Kill signal

SIGSEGV

11

Core

Invalid memory reference

SIGTERM

15

Term

Termination signal

SIGSTOP

23

Stop

Stop process

SIGCONT

25

Cont

Continue if stopped

The Action column indicates the default action performed in response to the signal. There are four possibilities:

Term = terminate process

Core = terminate and perform a core dump

Stop = stop process

Cont = continue process if stopped

While these actions are the default behaviors, a process can block a signal, ignore a signal, or handle it with a custom function. A signal that is set to be ignored is lost, but a signal that is blocked remains pending until it is unblocked. If a signal occurs again while it is blocked, only a single instance of the signal is retained.

For obvious reasons SIGKILL and SIGSTOP cannot be ignored, blocked or handled. SIGABRT can be blocked and it can be handled, but it still terminates the process when the handler returns unless you arrange for it not to - see later.

Notice that there are multiple ways terminate a program.

There are two ways to issue a terminate signal to a running process using the kill command line program.

The SIGKILL signal terminates the process immediately and as it cannot be be handled, blocked or ignored it is the signal of last resort. Notice the process gets no chance to do a cleanup operation. You can send SIGKILL to a process using the command:

kill -SIGKILL pid or kill -9 pid

The SIGTERM signal terminates the process but it can be handled, blocked or ignored and so the process has time to clean up and end gracefully. You can send this to a process using the command:

kill -SIGTERM pid or just kill pid

as SIGTERM is the default.

There are two ways to terminate the foreground process using the terminal.

The SIGINT signal terminates the process but it can be handled, blocked or ignored and so the process has time to clean up and end gracefully. You send this signal to the foreground process by pressing Ctrl-C.

The SIGQUIT signal behaves like SIGINT, but with the addition of a core dump. You can send the signal to the foreground process by pressing Ctrl-\.

SIGSTOP and SIGCONT are used by the kernel to start and stop processes. SIGSTOP cannot be handled, blocked or ignored, and SIGCONT is used to restart the process.

Finally, it is worth taking note of the way SIGABRT behaves. This signal is generated by some machine errors or by calling the abort function. It can be handled, but what happens is that first SIGABRT is automatically unblocked. That is, it always interrupts the program without delay. If there is a handler, it gets to run. If there is no handler, the process is terminated. If the handler returns, it is disabled and SIGABRT is sent a second time, so terminating the process. So the handler gets to run, but the process is always aborted. This is not always what you want and how to modify this behavior is described later.

Dealing with Async

It is important that you realize that signals occur asynchronously. Your program can be interrupted at any time by a signal. If you have a handler set then, unless you take steps to block signals, it too can be interrupted. When a handler returns, your program restarts from where it was. The exception to this is if your program was in the middle of a syscall, when what happens depends on the syscall. Either the call fails with an error or the call is restarted.

All of this means that, even if you are writing a single-threaded program, if you are using signals you have to take account of possible race conditions, which action will be carried out first, and asynchronous execution. For example, if you have a global variable that is used by both your program and a signal handler, then you have the same problems that you would have if you were sharing the variable between two threads. If the program is in the middle of updating the shared variable and a signal interrupts it, then its state is not certain and when the handler updates it and returns, the result is probably not what you expected. The situation is even worse if the handler itself can be interrupted by another signal. To stop this happening always block signals within a handler.

Sharing resources between the program and a signal handler is the same as sharing resources between threads, see Chapter 12.

If you don't make use of a handler for any signal, then you can mostly ignore this problem as by default signals bring the process to an end and all you need to worry about is the state of any open resources that you don't get a chance to gracefully close.

You can also ignore the problem if you choose to handle signals using one of the synchronous approaches. In this case, your program isn't interrupted as it simply either waits for a signal or polls to see if there is a signal to process.



Last Updated ( Monday, 09 March 2020 )