28

In general, to kill processes we generate signals like SIGKILL,SIGTSTP etc.

But how is it known who ordered that particular signal, who has sent it to a particular process, and in general how do signals perform their operations? How do signals internally work?

  • The question is a bit hard to understand. I apologize and mean no disrespect. Do you want to know who might have ran a command that killed a process or do you want to know more about SIGKILL and SIGSTP? – pullsumo Jun 19 '13 at 21:45
  • @mistermister I want to know who might have ran a command that killed a process and how ? – Varun Chhangani Jun 19 '13 at 21:50
33

The 50,000 foot view is that:

  1. A signal is generated either by the kernel internally (for example, SIGSEGV when an invalid address is accessed, or SIGQUIT when you hit Ctrl+\), or by a program using the kill syscall (or several related ones).

  2. If it’s by one of the syscalls, then the kernel confirms the calling process has sufficient privileges to send the signal. If not, an error is returned (and the signal doesn't happen).

  3. If it’s one of two special signals, the kernel unconditionally acts on it, without any input from the target process. The two special signals are SIGKILL and SIGSTOP. All the stuff below about default actions, blocking signals, etc., are irrelevant for these two.

  4. Next, the kernel figures out what do with the signal:

    1. For each process, there is an action associated with each signal. There are a bunch of defaults, and programs can set different ones using sigaction, signal, etc. These include things like "ignore it completely", "kill the process", "kill the process with a core dump", "stop the process", etc.

    2. Programs can also turn off the delivery of signals ("blocked"), on a signal-by-signal basis. Then the signal stays pending until unblocked.

    3. Programs can request that instead of the kernel taking some action itself, it deliver the signal to the process either synchronously (with sigwait, et. al. or signalfd) or asynchronously (by interrupting whatever the process is doing, and calling a specified function).

There is a second set of signals called "real-time signals", which have no specific meaning, and also allow multiple signals to be queued (normal signals queue only one of each when the signal is blocked). These are used in multi-threaded programs for the threads to communicate to each other. Several are used in glibc's POSIX threads implementation, for example. They can also be used to communicate between different processes (for example, you could use several real-time signals to have a fooctl program send a message to the foo daemon).

For a non-50,000 foot view, try man 7 signal and also the kernel internals documentation (or source).

  • "The two special signals are SIGKILL and SIGSTOP" so what may SIGCONT be... – Hauke Laging Jun 19 '13 at 21:36
  • @HaukeLaging SIGCONT is the signal that undoes SIGSTOP. The documentation doesn't list it as special... So I'm not sure if technically a process can set it to ignore, then you'd not be able to resume it (only SIGKILL it). – derobert Jun 19 '13 at 21:40
22

Signal implementation is very complex and is kernel specific. In other words, different kernels will implement signals differently. A simplified explanation is as follows:

The CPU, based on a special register value, has an address in memory where it expects to find an "interrupt descriptor table" which is actually a vector table. There is one vector for every possible exception, like division by zero, or trap, like INT 3 (debug). When the CPU encounters the exception it saves the flags and the current instruction pointer on the stack and then jumps to the address specified by the relevant vector. In Linux this vector always points into the kernel, where there is an exception handler. The CPU is now done, and the Linux kernel takes over.

Note, that you can also trigger an exception from software. For example, the user presses CTRL-C, then this call goes to the kernel which calls its own exception handler. In general, there are different ways to get to the handler, but regardless the same basic thing happens: the context gets saved on the stack and the kernel's exception handler is jumped to.

The exception handler then decides what thread should receive the signal. If something like division-by-zero occurred, then it is easy: the thread that caused the exception gets the signal, but for other types of signals, the decision can be very complex and in some unusual cases a more or less random thread might get the signal.

To send the signal what the kernel does is first set a value indicating the type of signal, SIGHUP or whatever. This is just an integer. Every process has a "pending signal" memory area where this value is stored. Then the kernel creates a data structure with the signal information. This structure includes a signal "disposition" which may be default, ignore or handle. The kernel then calls its own function do_signal(). The next phase begins.

do_signal() first decides whether it will handle the signal. For example, if it is a kill, then do_signal() just kills the process, end of story. Otherwise, it looks at the disposition. If the disposition is default, then do_signal() handles the signal according to a default policy that depends on the signal. If the disposition is handle, then it means there is a function in the user program which is designed to handle the signal in question and the pointer to this function will be in the aforementioned data structure. In this case do_signal() calls another kernel function, handle_signal(), which then goes through the process of switching back to user mode and calling this function. The details of this handoff are extremely complex. This code in your program is usually linked automatically into your program when you use the functions in signal.h.

By examining the pending signal value appropriately the kernel can determine if the process is handling all signals, and will take appropriate action if it is not, which might be putting the process to sleep or killing it or other action, depending on the signal.

14

Though this question has been answered, let me post a detailed flow of events in Linux kernel.
This is copied entirely from Linux posts: Linux Signals - Internals at the “Linux posts” blog at sklinuxblog.blogspot.in.

Signal User Space C Program

Let’s start with writing a simple signal user space C program:

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

/* Handler function */
void handler(int sig) {
    printf("Receive signal: %u\n", sig);
};

int main(void) {
    struct sigaction sig_a;

    /* Initialize the signal handler structure */
    sig_a.sa_handler = handler;
    sigemptyset(&sig_a.sa_mask);
    sig_a.sa_flags = 0;

    /* Assign a new handler function to the SIGINT signal */
    sigaction(SIGINT, &sig_a, NULL);

    /* Block and wait until a signal arrives */
    while (1) {
            sigsuspend(&sig_a.sa_mask);
            printf("loop\n");
    }
    return 0;
};

This code assigns a new handler for SIGINT signal. SIGINT can be sent to the running process using Ctrl+C key combination. When Ctrl+C is pressed then the asynchronous signal SIGINT is sent to the task. It is also equivalent to sending the kill -INT <pid> command in other terminal.

If you do a kill -l (that’s a lowercase L, which stands for “list”) you will come to know the various signals which can be sent to a running process.

[root@linux ~]# kill -l
 1) SIGHUP        2) SIGINT        3) SIGQUIT       4) SIGILL        5) SIGTRAP
 6) SIGABRT       7) SIGBUS        8) SIGFPE        9) SIGKILL      10) SIGUSR1
11) SIGSEGV      12) SIGUSR2      13) SIGPIPE      14) SIGALRM      15) SIGTERM
16) SIGSTKFLT    17) SIGCHLD      18) SIGCONT      19) SIGSTOP      20) SIGTSTP
21) SIGTTIN      22) SIGTTOU      23) SIGURG       24) SIGXCPU      25) SIGXFSZ
26) SIGVTALRM    27) SIGPROF      28) SIGWINCH     29) SIGIO        30) SIGPWR
31) SIGSYS       34) SIGRTMIN     35) SIGRTMIN+1   36) SIGRTMIN+2   37) SIGRTMIN+3
38) SIGRTMIN+4   39) SIGRTMIN+5   40) SIGRTMIN+6   41) SIGRTMIN+7   42) SIGRTMIN+8
43) SIGRTMIN+9   44) SIGRTMIN+10  45) SIGRTMIN+11  46) SIGRTMIN+12  47) SIGRTMIN+13
48) SIGRTMIN+14  49) SIGRTMIN+15  50) SIGRTMAX-14  51) SIGRTMAX-13  52) SIGRTMAX-12
53) SIGRTMAX-11  54) SIGRTMAX-10  55) SIGRTMAX-9   56) SIGRTMAX-8   57) SIGRTMAX-7
58) SIGRTMAX-6   59) SIGRTMAX-5   60) SIGRTMAX-4   61) SIGRTMAX-3   62) SIGRTMAX-2
63) SIGRTMAX-1   64) SIGRTMAX

Also following key combination can be used to send particular signals:

  • Ctrl+C – sends SIGINT which default action is to terminate the application.
  • Ctrl+\  – sends SIGQUIT which default action is to terminate the application dumping core.
  • Ctrl+Z – sends SIGSTOP that suspends the program.

If you compile and run the above C program then you will get the following output:

[root@linux signal]# ./a.out
Receive signal: 2
loop
Receive signal: 2
loop
^CReceive signal: 2
loop

Even with Ctrl+C or kill -2 <pid> the process will not terminate. Instead it will execute the signal handler and return.

How the signal is sent to the process

If we see the internals of the signal sending to a process and put Jprobe with dump_stack at __send_signal function we will see following call trace:

May  5 16:18:37 linux kernel: dump_stack+0x19/0x1b
May  5 16:18:37 linux kernel: my_handler+0x29/0x30 (probe)
May  5 16:18:37 linux kernel: complete_signal+0x205/0x250
May  5 16:18:37 linux kernel: __send_signal+0x194/0x4b0
May  5 16:18:37 linux kernel: send_signal+0x3e/0x80
May  5 16:18:37 linux kernel: do_send_sig_info+0x52/0xa0
May  5 16:18:37 linux kernel: group_send_sig_info+0x46/0x50
May  5 16:18:37 linux kernel: __kill_pgrp_info+0x4d/0x80
May  5 16:18:37 linux kernel: kill_pgrp+0x35/0x50
May  5 16:18:37 linux kernel: n_tty_receive_char+0x42b/0xe30
May  5 16:18:37 linux kernel:  ? ftrace_ops_list_func+0x106/0x120
May  5 16:18:37 linux kernel: n_tty_receive_buf+0x1ac/0x470
May  5 16:18:37 linux kernel: flush_to_ldisc+0x109/0x160
May  5 16:18:37 linux kernel: process_one_work+0x17b/0x460
May  5 16:18:37 linux kernel: worker_thread+0x11b/0x400
May  5 16:18:37 linux kernel: rescuer_thread+0x400/0x400
May  5 16:18:37 linux kernel:  kthread+0xcf/0xe0
May  5 16:18:37 linux kernel:  kthread_create_on_node+0x140/0x140
May  5 16:18:37 linux kernel:  ret_from_fork+0x7c/0xb0
May  5 16:18:37 linux kernel: ? kthread_create_on_node+0x140/0x140

So the major function calls for sending the signal goes like:

First shell send the Ctrl+C signal using n_tty_receive_char
n_tty_receive_char()
isig()
kill_pgrp()
__kill_pgrp_info()
group_send_sig_info() -- for each PID in group call this function
do_send_sig_info()
send_signal()
__send_signal() -- allocates a signal structure and add to task pending signals
complete_signal()
signal_wake_up()
signal_wake_up_state()  -- sets TIF_SIGPENDING in the task_struct flags. Then it wake up the thread to which signal was delivered.

Now everything is set up and necessary changes are done to the task_struct of the process.

Handling of signal

The signal is checked/handled by a process when it returns from system call or if the return from interrupt is done. The return from the system call is present in file entry_64.S.

The function int_signal function is called from entry_64.S which calls the function do_notify_resume().

Let’s check the function do_notify_resume(). This function checks if we have the TIF_SIGPENDING flag set in the task_struct:

 /* deal with pending signal delivery */
 if (thread_info_flags & _TIF_SIGPENDING)
  do_signal(regs);
do_signal calls handle_signal to call the signal specific handler
Signals are actually run in user mode in function:
__setup_rt_frame -- this sets up the instruction pointer to handler: regs->ip = (unsigned long) ksig->ka.sa.sa_handler;

SYSTEM calls and signals

“Slow” syscalls e.g. blocking read/write, put processes into waiting state: TASK_INTERRUPTIBLE or TASK_UNINTERRUPTIBLE.

A task in state TASK_INTERRUPTIBLE will be changed to the TASK_RUNNING state by a signal. TASK_RUNNING means a process can be scheduled.

If executed, its signal handler will be run before completion of “slow” syscall. The syscall does not complete by default.

If SA_RESTART flag set, syscall is restarted after signal handler finishes.

References

  • Thanks for making an effort to contribute to the site, but (1) if you’re going to copy material from another site (word for word, letter for letter, including the grammar and punctuation errors), you should say that you are doing so, much more clearly.  Listing the source as a “Reference”, while necessary, is not sufficient.  Unless you are the author of the blog (K_K = sk?), in which case you aren’t required to link to it — but, if you do, you must disclose (i.e., say) that it is yours.  … (Cont’d) – G-Man Jun 23 '17 at 22:04
  • (Cont’d) …  (2) Your source (the blog you copied from) is not very good.  It’s been four years since the question was asked; couldn’t you find a better reference to copy from?   (If you’re the original author, sorry.)  In addition to the aforementioned grammar and punctuation errors (and generally sloppy wording, and poor formatting), it’s wrong.  (2a) Ctrl+Z sends SIGTSTP, not SIGSTOP.   (SIGTSTP, like SIGTERM, can be caught; SIGSTOP, like SIGKILL, cannot.)  … (Cont’d) – G-Man Jun 23 '17 at 22:05
  • (Cont’d) …  (2b) The shell doesn’t send the Ctrl+C signal.  The shell has no role in sending signals (except when the user uses the kill command, which is a shell builtin). (2c) While semicolons after the closing } of a function are not, strictly speaking, errors, they are unnecessary and highly unorthodox. (3) Even if everything were correct, it wouldn’t be a very good answer to the question. (3a) The question, while somewhat unclear, seems to be focusing on how actors (users and process) initiate (i.e., send) signals. … (Cont’d) – G-Man Jun 23 '17 at 22:06
  • (Cont’d) …  The answer seems to focus on signals generated by the kernel (specifically, keyboard-generated signals) and how the recipient process reacts to signals. (3b) The question seems to be at the level of «Somebody killed my process — who did it, and how?»  The answer discusses the signal-handling API, kernel routines, kernel debugging (Jprobe?), kernel stack traces, and kernel data structures.  IMO, that is inappropriately low level — especially since it doesn’t provide any references where a reader could more learn more about these internal workings. – G-Man Jun 23 '17 at 22:06
  • It is my own blog .. my own traces .. thats what I want .. everyone shall know such detailed flow.. talking in air is of no sense.. even if after it violates this communities guidelines please get my answer removed through proper channel.. this is kernel internal answer not grammar internals. – K_K Jun 24 '17 at 1:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.