AOSP Expert & Production Engineering
3 min read

Signals

POSIX Signals Overview

In Unix-like systems, a signal is an asynchronous notification sent to a process or to a specific thread within the same process to notify it of an event. When a signal is sent, the operating system interrupts the target process's normal flow of execution to deliver the signal. If the process has registered a custom signal handler, that routine is executed. Otherwise, the default signal action is executed.

In Android (AOSP), signals play a crucial role in process lifecycle management, IPC, and debugging. The init process, for example, relies heavily on signals to manage spawned daemon processes.

Signal Handlers

A process can define how it reacts to specific signals using the sigaction system call. The older signal function is generally avoided in production code due to portability and behavioral inconsistencies.

#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void my_signal_handler(int signum) {
    // Note: printf is not async-signal-safe. Used here for simplicity.
    printf("Received signal: %d\n", signum);
}

int main() {
    struct sigaction sa;
    sa.sa_handler = my_signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    if (sigaction(SIGTERM, &sa, NULL) == -1) {
        perror("Error setting up sigaction");
        return 1;
    }

    while(1) {
        sleep(1);
    }
    return 0;
}

Signal Safety and Async-Signal-Safe Functions

A critical constraint of signal handlers is that they can be invoked at any point during program execution. This means they might interrupt a function that is currently modifying global data structures.

If a signal handler attempts to acquire a lock that the interrupted thread currently holds, it will result in a deadlock. Thus, only a specific set of system calls and library functions are guaranteed to be async-signal-safe.

Functions like malloc, free, and printf are not async-signal-safe because they manage internal locks for heap allocation and I/O streams. In AOSP development, violating signal safety is a common cause of mysterious deadlocks and crashes.

Safe functions typically include write, read, _exit, and kill.

void safe_signal_handler(int signum) {
    const char msg[] = "Received signal\n";
    write(STDOUT_FILENO, msg, sizeof(msg) - 1);
}

Android Signal Usage: SIGKILL, SIGTERM, SIGSEGV

Android utilizes signals to manage the application lifecycle strictly.

  • SIGKILL (9): This signal cannot be caught, blocked, or ignored. The Android ActivityManagerService (AMS) and lmkd (Low Memory Killer Daemon) use SIGKILL to forcefully terminate processes to free up RAM.
  • SIGTERM (15): The termination signal. It politely asks a process to terminate. Android's init sends SIGTERM to services when shutting them down, waiting for a timeout before escalating to SIGKILL.
  • SIGSEGV (11): Segmentation fault. Sent when a process makes an invalid memory reference. In AOSP, when a native crash occurs, the kernel sends SIGSEGV (or similar signals like SIGABRT, SIGBUS, SIGFPE). The debuggerd daemon catches these using ptrace and signal handlers to generate a tombstone (crash dump) which can be found in /data/tombstones.

To view signals being sent in an Android system, you can sometimes trace them using strace via adb shell:

adb shell
su
strace -p <pid> -e trace=signal