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) andlmkd(Low Memory Killer Daemon) useSIGKILLto forcefully terminate processes to free up RAM. - SIGTERM (15): The termination signal. It politely asks a process to terminate. Android's
initsendsSIGTERMto services when shutting them down, waiting for a timeout before escalating toSIGKILL. - 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 likeSIGABRT,SIGBUS,SIGFPE). Thedebuggerddaemon catches these usingptraceand 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