What are they?
An asynchronous IPC mechanism supported in UNIX and further variants. Signal is general term used, where a process can signal another process, or keyboard generates interrupt and hence signals the kernel of availability of data, or a kernel signals a process of execution violence (eg: NULL pointer deference, divide by zero etc), or a shell controlling the execution of the commands.
To get the currently supported signals in the system, execute “kill -l”
Linux supports 31 non-realtime and 31 realtime signals as of now.
When a signal is delivered, process can choose to ignore it or handle them by a suitable handler, or allow the kernel to handle it. Every signal has a default action defined, and kernel performs that when it handles.
Signals are sent from terminal to a process, from process to another process or kernel to process. The signals sent from the kernel to process are hardware traps, which occurred while executing some instruction of the process. Eg: SIGILL is sent when an illegal instruction is executed. SIGABRT when you execute the abort instruction.
Eg:
Signal Name ---- Default Action Comment
----------------------------------------------------------------
SIGHUP --- Abort Hangup terminal or process
SIGINT --- Abort Keyboard interrupt (usually Ctrl-C)
SIGKILL ---- Abort Forced process termination
SIGUSR1 ---- Abort Process specific
SIGSEGV ---- Dump Invalid memory reference
Understanding some signals:
SIGHUP: This is received when the process which started the executing process, has terminated. An example is, the shell which executed this process, is no more. At that time SIGHUP is sent to all the processes which were started by it. We can bypass this by using nohup command while executing, which will not kill the process even if the terminal exits.
SIGINT: On press of ^C in terminal, this is generated, and default action is to kill that process.
SIGQUIT: Generated with ^\ where controlling terminal informs the process to quit normally. Again nohup can be used to ignore this.
SIGILL: The execute machine code aka opcode was not recognised by the processor,and it traps the kernel.
SIGTRAP: Used by the debugging utility to get the control back during program execution to themselves.
SIGBUS: An address alignment issue happened. Eg: Improper address is given on the bus
SIGKILL: Unless the system is unstable, this signal will terminate the reciever.
SIGSEGV: Raised when the fage fault raised by processor, couldnt be serviced by kernel. The reason is invalid region of memory is being accessed.
SIGUSR1, SIGUSR2: Signals for user processors to define their actions.
SIGALRM: This is used by the system alarm to inform the process of its firing.
SIGCHLD: The forked processes terminated using exit, and parent gets informed through this.
SIGCONT: Used by debuggers to inform the process to continue.
SIGTSTP: ^Z
SIGURG: Urgent data is now available on socket.
Where it is implemented?
------------------------------
Every task has signal support in its task_struct datastructure.
Struct task_struct{
…
struct signal_struct *signal;
struct sighand_struct *sighand;
sigset_t blocked, real_blocked;
sigset_t saved_sigmask; /* restored if set_restore_sigmask() was used */
struct sigpending pending;
…
}
• First variable is used to store the access properties. A task can send a signal to another task in same process group, or with another task with same uid and gid. Only task with super-user privilege can send signal to any other task. Some fatal signals can be sent to an entire group, and one process in that group processes it stopping others. All such information is stored in this.
• The second parameter defines the handlers. We can have upto 64 handlers, one for each signal.
• Next parameter defines the blocked signals (0-31) and real-time signals (32-63).
• Signal masks are used if you choose to ignore some signals. So this is stored in the next parameter.
• Any pending signals are accounted in the next variable
Operation states of signals:
--------------------------------
1. Signal Receiving/Handling
Every time the kernel returns to user space (from an interrupt/exception or system call), then it first checks if there are any non-blocked pending signals for this task. If so, then it will call do_signal(). Inside this function we check if we have any signal to be processed by repeatedly calling qeueue_signal(). Any signal which is not performed with default action yet, will be caught now.
handle_signal() will be invoked on all the dequeued signals obtained above. We have a complication here. We are executing in kernel space. Signal handlers are there in user space. Signal handlers themselves can make system calls, to make us come back to kernel space. How should we handle this? We setup frames for the signals on the user stack for this. We use put_user() to put it to user space. On an ia32 architecture, we make the SP point to this frame, IP point to the handler, AX to contain the signal number. Then we load the user data and code segments to CS and DS which means, the execution starts in user space.
If the task state is TASK_INTERRUPTIBLE, then it will be put back to run-queue after its state changed to TASK_RUNNING. If it is executing any slow system call like read() or write(), then we set a flag SA_RESTART before executing the signal handler, to denote that system call needs to be re-started after signal is handled.
2. Signal Sending
A signal is sent because of one of the events like send_sig_info() or kill_proc_info() inside kernel. Former is sent in case of exceptions, and later is for the terminal events etc. There is an info segment in these function, which differentiates whether kernel sent this signal or user space sent it.
3. Signal Pending
Because the task might be ignoring signals (TASK_UNINTERRUPTIBLE), the sent signal is not yet consumed. This will be saved in the pending variable discussed above. The problem is, if you send multiple signals of same type, then the signal handler will be executed only once. The reason is that any action that can be performed on the task, for a signal type, will have unique effect. And hence we cannot apply same action on it again. Eg: SIGKILL would kill task, but if you have delivered 100's of SIGKILL, then the very first time we processed it, the task is no more. So what is the advantage of storing all 100 signals of same type?
============================================================
User Space programming :
Signals are used in user space with the calls like signal(), sigaction(), sigaddset(), sigemptyset(), sigdelset(), kill() etc.
Lets see a simple example of signals:
Lets see another example on how to block some signals:
To register/recieve a signal:
struct sigaction mysig_act;
mysig_act.sa_flags = SA_SIGINFO;
mysig_act.sa_sigaction = (void *)mysig_handler;
if(sigaction (
printf("Sigaction returned error = %d\n", errno);
exit(0);
}
struct sigaction {
void (*sa_handler)(int); /* func pointer */
void (*sa_sigaction)(int, siginfo_t *, void *); /*func pointer */
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
}
Or we can use signal(SIGNO, handler);
Sending signals:
int kill ( pid_t process_id, int signal_number );
RSS Feed
Twitter
Orkut