Skip to content

Latest commit

 

History

History
196 lines (155 loc) · 6.03 KB

signals.md

File metadata and controls

196 lines (155 loc) · 6.03 KB

Signals

Signals are a communication mechanism between a process and the operating system. When a running program encounters a significant error, the OS sends a signal to that process. Some processes may have a signal handler which does some important tasks before the process leaves the CPU.

Signals and interrupts serve similar purposes, but they differ in their origin and handling mechanisms. Interrupts originate from the processor and are managed by the operating system's kernel. In contrast, signals are generated by the kernel and are managed by the individual process to which they are sent.

Types of Signals

There are various types of signals, each corresponding to different events.

SIGILL

This signal in Unix-like operating systems is sent to a process when it attempts to execute an illegal, malformed, unknown, or privileged instruction.

SIGSEGV

The signal is generated when process tries to access memory location which is not allocated to it.

Segmentation fault

The following are some typical causes of a segmentation fault:

  1. Attempting to access a nonexistent memory address (outside process's address space)
  2. Attempting to access memory the program does not have rights to (such as kernel structures in process context)
  3. Attempting to write read-only memory (such as code segment)

When you access an array index, C and C++ don't do bound checking. Segmentation faults only happen when you try to read or write to a page that was not allocated (or try to do something on a page which isn't permitted, e.g. trying to write to a read-only page), but since pages are usually pretty big (multiples of a few kilobytes), it often leaves you with lots of room to overflow.

int main() {
  // Define an array with a small size
  int array[5] = {0, 1, 2, 3, 4};

  std::cout << "Original array values:\n";
  for (int i = 0; i < 5; i++) {
    std::cout << "array[" << i << "] = " << array[i] << "\n";
  }

  // Overflowing the array
  // Writing beyond the bounds of the array
  std::cout << "\nWriting beyond the bounds of the array...\n";
  for (int i = 0; i < 100;
       i++) { // This loop intentionally goes well beyond the array's bounds
    array[i] = i;
  }
  return 0;
}

By setting the followings flag you can find the issue:

set(CMAKE_CXX_FLAGS "-fsanitize=address ${CMAKE_CXX_FLAGS}")
set(CMAKE_CXX_FLAGS "-fno-omit-frame-pointer ${CMAKE_CXX_FLAGS}")

SIGABRT

If an error itself is detected by the program then this signal is generated using call to abort(). This signal is also used by standard library to report an internal error. assert() function in c++ also uses abort() to generate this signal.

SIGFPE

Error that occurred like division by zero, floating point error.

SIGBUS

This signal is also produced when an invalid memory is accessed. It may seem to be same like SIGSEGV but in SIGSEGV, the memory location referenced is valid but in case of SIGBUS, memory referenced does not exist i.e de-referencing a memory location out of memory space.

SIGSYS

This signal is sent to process when an invalid argument is passed to a system call.

SIGTRAP

This signal is send to process when an exception is occurred. This is requested by the debugger to get informed. For example, if a variable changes its value then this will trigger it.

SIGTERM

The SIGTERM signal is a generic signal used to cause program termination. Unlike SIGKILL, this signal can be blocked, handled, and ignored. It is the normal way to politely ask a program to terminate. The shell command kill generates SIGTERM by default.

Setting Signal Handler

create the signal handler function:

void signalHandler(int signum) {
    std::cout << "signal (" << signum << ") received.\n";
    // Cleanup and close up logic
    exit(signum);
}

setting the handler for a specific signal:

int main() {
    // Register signal SIGINT ( or any of SIGILL,SIGINT,SIGSEGV,SIGTERM,SIGABRT,SIGFPE) and signal handler  
    signal(SIGINT, signalHandler);
}

Examples

SIGINT_Handler

In the following example signal that is being handled is SIGINT. This signal is typically sent when the user presses Ctrl+C in the terminal. The SIGINT_Handler function in the example is specifically set up to catch and handle the SIGINT signal.

#include <csignal>
#include <iostream>


// Signal handler function
void SIGINT_Handler(int signum) {
    std::cout << "Interrupt signal (" << signum << ") received.\n";
    // Cleanup and close up logic
    exit(signum);
}

int main() {
    // Register signal SIGINT and signal handler  
    signal(SIGINT, SIGINT_Handler);

    while(1) {
       std::cout << "Waiting for signal..." << std::endl;
       sleep(1);
    }

    return 0;
}

SIGSEGV_Handler

#include <csignal>
#include <cstdlib> // for  exit()
#include <iostream>
#include <unistd.h> // getpid()

void SIGSEGV_Handler(int signum) {
  std::cout << "oh my god! segmenation fault happened" << std::endl;
  printf("Process %d got signal %d\n", getpid(), signum);
  // kill(getpid(), signum);
  exit(signum);
}

int main() {
  signal(SIGSEGV, SIGSEGV_Handler);
  int *p;
  *p = 10;
}

Raising a Signal

The <csignal> header file declared the function raise() to handle a particular signal. Syntax:

int raise ( int signal_ )

complete example:

sig_atomic_t s_value = 0;
void SIGTERM_Handler(int signum) { s_value = signum; }

int main() {
  signal(SIGTERM, SIGTERM_Handler);
  std::cout << "Before called Signal = " << s_value << std::endl;
  raise(SIGTERM);
  std::cout << "After called Signal = " << s_value << std::endl;
}

code