12 min read

Categories

Tags

Signals are a limited form of inter-process communication used in Unix, Unix-like, and other POSIX-compliant operating systems. A signal is an asynchronous notification sent to a process or to a specific thread within the same process in order to notify it of an event that occurred. Signals have been around since the 1970s Bell Labs Unix and have been more recently specified in the POSIX standard.

Table of Contents

  1. Kill System Call
  2. Post Signal
  3. Signal Delivery

Kill System Call

kill - send signal to a process

int kill(pid_t pid, int sig);

The kill() system call can be used to send any signal to any process group or process.

If pid is greater than zero:
The sig signal is sent to the process whose ID is equal to pid.

If pid is zero:
The sig signal is sent to all processes whose group ID is equal to the process group ID of the sender, and for which the process has permission; this is a variant of killpg(2).

If pid is -1:
If the user has super-user privileges, the signal is sent to all processes excluding system processes (with P_SYSTEM flag set), process with ID 1 (usually init(8)), and the process sending the signal. If the user is not the super user, the signal is sent to all processes with the same uid as the user excluding the process sending the signal. No error is returned if any process could be signaled.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
Argument to Kill system call (kern_sig.c)
struct kill_args {
     int pid;
     int signum;
};

If process id is greater than 0 then signal has to be send to that particular process else it is either broadcast
signal or own process group etc.

// If process id  > 0
if (uap->pid > 0) {
     Find the process
     if ((p = pfind(uap->pid)) == NULL) {
          If its a zombie process
          if ((p = zpfind(uap->pid)) == NULL) {
               return (ESRCH);
          }
     }
}

Determine if we can send the signal to process

error = p_cansignal(td, p, uap->signum);

In Function p_cansignal(). This function will determine if thread can send signal to processes
cr_cansignal(struct ucred *cred, struct proc *proc, int signum)
{
     Check if process to whom signal is to be send is in Jail semantics

     Check the creds i.e. otherUids, otherGids

     Allow only following signals, for other signals needs special privs
          case 0:
          case SIGKILL:
          case SIGINT:
          case SIGTERM:
          case SIGALRM:
          case SIGSTOP:
          case SIGTTIN:
          case SIGTTOU:
          case SIGTSTP:
          case SIGHUP:
          case SIGUSR1:
          case SIGUSR2:
               break;
          default:
               Need priv

     More priv checks

     Finally yes you can send signal
}


center-aligned-image



Post Signal

If we are allowed to send signal to other process then send the signal

Now, it can happen that in multiprocessor env other process may be running or may not. So, we post the signal and when the process come to life it will receive the signal and act appropriately.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
//Post signal
psignal(p, uap->signum);
Function tdsignal(struct proc *p, struct thread *td, int sig, ksiginfo_t *ksi)
{
     Determine the property of signal
     //Signal property table is in kern_sig.c
     // #define     SA_KILL          0x01          /* terminates process by default */
     // #define     SA_CORE          0x02          /* ditto and coredumps */
     // #define     SA_STOP          0x04          /* suspend process */
     // #define     SA_TTYSTOP     0x08          /* ditto, from tty */
     // #define     SA_IGNORE     0x10          /* ignore by default */
     // #define     SA_CONT          0x20          /* continue if suspended */
     // #define     SA_CANTMASK     0x40          /* non-maskable, catchable */
     // #define     SA_PROC          0x80          /* deliverable to any thread */
     //static int sigproptbl[NSIG]

     Post Signal to thread in a process
     td = sigtd(p, sig, prop);
     Function sigtd(struct proc *p, int sig, int prop) {
          Check if we are current process
          For each thread in process check if thread is registered for signal handling

          If we cant find any thread to post signal then just pick the first thread in process
          return the thread pointer
     }

     So far, we have signal to post and the thread pointer where to post
     There is a signal queue. So get the pointer to signal queue.
     There are 2 signal queues
          1. Signal queue for process
          2. Signal queue for thread

     if (SIGISMEMBER(td->td_sigmask, sig))
               sigqueue = &p->p_sigqueue;
     else
               sigqueue = &td->td_sigqueue;

     Check if the signal we will be sending is in our ignore list. If it is in our ignore list then do not
     process further and drop it

     We will now check the preferences/flags marked in the target thread regarding signals
     It can happen that thread have masked all the signals right now i.e. thread may be processing some signal
     and want all other signals to hold until it finishes processing
     if (SIGISMEMBER(td->td_sigmask, sig))
          action = SIG_HOLD;

     If signal is register to be catch
     else if (SIGISMEMBER(ps->ps_sigcatch, sig))
         action = SIG_CATCH;

     Check the property of Signal
     If property of signal is to continue then go through signal queue and delete the signal which will stop
     the execution
     if (prop & SA_CONT)
          sigqueue_delete_stopmask_proc(p);
     else if (prop & SA_STOP) {
          // Stop running the process

     Finally, Add the signal to the signal queue
     ret = sigqueue_add(sigqueue, sig, ksi);

     Notify thread that Signal has been posted
     signotify(td);
     In function signotify() we check that what are all the pending signals for the thread and for which
     signal handler have not masked that means target thread signal handler wants to accept that signal.
     Then we enable the flags so that the target thread can to running state if suspended and when awake
     knows that some signal is posted for the thread
     td->td_flags |= TDF_NEEDSIGCHK | TDF_ASTPENDING;

     ...

     If target process is in sleep state or sleep interruptible state then wake it up
     tdsigwakeup(td, sig, action, intrval);
               Function tdsigwakeup()
               {
                    bump up the thread priority
                    sched_prio(td, PUSER);

                    If thread is in the sleep queue and interruptible
                    then wakeup the thread
                    wakeup_swapper = sleepq_abort(td, intrval);
                                  Function sleepq_abort(struct thread *td, int intrval) {
                                         ...
                                         ...
                                        Enable thread flags
                                        td->td_intrval = intrval;
                                        td->td_flags |= TDF_SLEEPABORT;
                                        ...
                                        get the thread wait channel
                                        wchan = td->td_wchan;

                                        Sleep queue of wait channel where thread resides currently when sleeping
                                        sq = sleepq_lookup(wchan);

                                        //Wake up the thread
                                        return (sleepq_resume_thread(sq, td, 0));
                                                             Whole idea is that there is a hash of all the wait channel
We have already for the wait channel and now there are all the processes
waiting on this wait channel

//Removes a thread from a sleep queue and makes it runnable
Function sleepq_resume_thread(struct sleepqueue *sq, struct thread *td, int pri) {
     ...
     ...
     Remove process/thread from wait channel
     TAILQ_REMOVE(&sq->sq_blocked[td->td_sqqueue], td, td_slpq);
     ...
     ...

     Clear the sleeping flag and set it runnable
     if (TD_IS_SLEEPING(td)) {
          TD_CLR_SLEEPING(td);
          return (setrunnable(td));
                              Function setrunnable(struct thread *td)
          {

               ...
               ...

               sched_wakeup(td);
                    Function sched_wakeup(struct thread *td) {    ====> sched_ule.c
                         This function let schedular know that thread needs to resume
                         ...
                         ...
                         sched_add()
                    }
          } // end setrunnable
     } // end sleepq_resume_thread

} // end sleepq_resume_thread

                                   } // end sleepq_abort
               } //end tdsigwakeup()
} // end tdsignal


center-aligned-image




Signal Delivery

So far, in above processing we have loaded the target process to to the run queue and eventually our process run time slice will over and now another process will run.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
The switch of process will happen at
Function mi_switch
// The machine independent parts of context switching.

Function mi_switch(int flags, struct thread *newtd)
{
     ...
     ...
     // Schedular come into the picture to switch the thread
     sched_switch(td, newtd, flags);
               Function sched_switch(struct thread *td, struct thread *newtd, int flags)
               {
                    Save information related to thread before context switching
                    cpuid = PCPU_GET(cpuid);
                    tdq = TDQ_CPU(cpuid);
                    ts = td->td_sched;
                    mtx = td->td_lock;
                    ts->ts_rltick = ticks;
                    td->td_lastcpu = td->td_oncpu;
                    td->td_oncpu = NOCPU;
                    td->td_flags &= ~TDF_NEEDRESCHED;
                    td->td_owepreempt = 0;
                    tdq->tdq_switchcnt++;
                    ...
                    ...

                    choose new thread to run
                    newtd = choosethread();
                         Function choosethread() {
                              ...
                              td = sched_choose();
                                   Function sched_choose() {
                                        Choose next high priority thread to run from the same CPU
                                        tdq = TDQ_SELF();
                                        td = tdq_choose(tdq);
                                             Function tdq_choose(struct tdq *tdq) {
                                                  Pick the thread from realtime thread queue
                                                  td = runq_choose(&tdq->tdq_realtime);

                                                  If its NULL, then pick from Timshare queue
                                                  td = runq_choose_from(&tdq->tdq_timeshare, tdq->tdq_ridx);

                                                  If its NULL, then pick from Idle queue
                                                  td = runq_choose(&tdq->tdq_idle);
                                                            In function runq_choose we simply go to queue and get the
                                                            first item from the queue

                                             } //end tdq_choose

                                        ...
                                        return the thread

                                   } //end sched_choose
                         } //end choosethread

                    So, newtd is the newthread we choose to run
                    This function will switch the threads and newtd will be running after this function call
                    This function is a assembly level code
                    cpu_switch(td, newtd, mtx);

               } //end sched_switch
} //end mi_switch

Now we have target thread running.
When we were posting the signal we enabled the flag TDF_ASTPENDING in function
     signotify(td);
     In function signotify() we check that what are all the pending signals for the thread and for which signal
     handler have not masked that means target thread signal handler wants to accept that signal.
     Then we enable the flags so that the target thread can to running state if suspended and when awake
     knows that some signal is posted for the thread

     td->td_flags |= TDF_NEEDSIGCHK | TDF_ASTPENDING;

Since, this flag is enable when the target process comes into like it will check this flag and if it is enable
then it will call function AST (Asynchronous software trap)


center-aligned-image


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
Function ast(struct trapframe *framep)
{
     ...
     ...
     ...
     Check if any signals are posted
     if (flags & TDF_NEEDSIGCHK) {
          PROC_LOCK(p);
          mtx_lock(&p->p_sigacts->ps_mtx);

          This function will get the signal posted and which is unmasked
          We will process all the pending signals one-by-one
          while ((sig = cursig(td)) != 0)
               postsig(sig);
               mtx_unlock(&p->p_sigacts->ps_mtx);
               PROC_UNLOCK(p);

                    Function cursig(struct thread *td) {
                         ...

                         Return if there is signal to process or 0 when nothing to process
                         return (SIGPENDING(td) ? issignal(td) : 0);
                                   Function issignal(td) {
                                        Find any pending signal
                                        sigpending = td->td_sigqueue.sq_signals;

                                        Get first pending signal
                                        sig = sig_ffs(&sigpending);

                                        Handle special cases of SIGSTOP or SIGCONT

                                        Get properties of signal
                                        prop = sigprop(sig);

                                        Handle other cases i.e. Default Signal, Ignore Signal etc

                                        return signal
                                   } //end of issignal(td)
                    } //end of cursig(struct thread *td)


                    // This function take action for specified signal
                    Function postsig(sig) {
                         register struct proc *p = td->td_proc;
                         ...
                         ...
                         Get signal action
                         action = ps->ps_sigact[_SIG_IDX(sig)];

                         Now we will check action and process accordingly
                         //Default action which is kill the process
                         if (action == SIG_DFL) {
                              sigexit(td, sig);

                                   Function sigexit(td, sig) {
                                        ...
                                        exit1(td, W_EXITCODE(0, sig));
                                   }
                         } else {
                              ...
                              get signal mask
                              returnmask = td->td_sigmask;
                              ...
                              // Send signal to signal handler
                              // In process structure
                              // struct sysentvec *p_sysent;     /* (b) Syscall dispatch info. */
                              (*p->p_sysent->sv_sendsig)(action, &ksi, &returnmask);

                                        // i386/machdep.c
                                        Function sendsig(sig_t catcher, ksiginfo_t *ksi, sigset_t *mask) {
                                             struct sigframe sf;
                                             ...
                                             When stack is context switched it will save the stack frame,
					     register contents etc
                                             Get the stack frame pointer of thread
                                             regs = td->td_frame;
                                             ...

                                             Make a copy of stack frame because we will be changing the
					     frame to handle the signal.
                                             Once the signal is handled and the processing is done this
					     stack frame will be brought back.
                                             bcopy(regs, &sf.sf_uc.uc_mcontext.mc_fs, sizeof(*regs));

                                             Allocate space for signal handler context and put that in stack frame
                                             sp = (char *)regs->tf_esp - sizeof(struct sigframe);
                                             ...

                                             Build argument list for signal handler
                                             sf.sf_signum = sig;

                                             Pointer to signal context
                                             sf.sf_ucontext = (register_t)&sfp->sf_uc;

                                             We are in kernel space currently, we will be going to user space soon
                                             and we want the signal catcher code to be executed in user space
                                             We are assigning the address of catcher function to be called from user space
                                             sf.sf_ahu.sf_action = (__siginfohandler_t *)catcher;
                                             ...
                                             ...

                                             Copy the signal frame to user's stack
                                             if (copyout(&sf, sfp, sizeof(*sfp)) != 0) {
                                                  sigexit(td, SIGILL);
                                             }

                                             Stack frame pointer we got above "regs" will copy sigtramp() to
					     instruction pointer. So, after returning from here we think
					     we will return from ast() and go to user code back but
                                             instead now we will be going to trampoline code.

                                             regs->tf_eip = PS_STRINGS - szfreebsd4_sigcode;
                                        } // end sendsig
                         }
                    } //end postsig()
     }
} //end of ast()

In i386/locore.s code for signal trampoline in user space
NON_GPROF_ENTRY(sigcode)
     calll     *SIGF_HANDLER(%esp)
     leal     SIGF_UC(%esp),%eax     /* get ucontext */
     pushl     %eax
...
...
     movl $SYS_sigreturn,%eax

Function sigreturn()
Function sigreturn(td, uap)
{
     regs = td->td_frame;
     ...
     ...
     lot of snaity checking
     ...
     ...
     bcopy(&ucp->uc_mcontext.mc_fs, regs, sizeof(*regs));
     ...

}