linux系統程式設計之訊號(三):訊號安裝、signal、kill,arise講解

mickole發表於2013-07-15

一,訊號安裝

如果程式要處理某一訊號,那麼就要在程式中安裝該訊號。安裝訊號主要用來確定訊號值及程式針對該訊號值的動作之間的對映關係,即程式將要處理哪個訊號;該訊號被傳遞給程式時,將執行何種操作。

linux主要有兩個函式實現訊號的安裝:signal()、sigaction()。其中signal()只有兩個引數,不支援訊號傳遞資訊,主要是用於前32種非實時訊號的安裝;而sigaction()是較新的函式(由兩個系統呼叫實現:sys_signal以及sys_rt_sigaction),有三個引數,支援訊號傳遞資訊,主要用來與 sigqueue() 系統呼叫配合使用,當然,sigaction()同樣支援非實時訊號的安裝。sigaction()優於signal()主要體現在支援訊號帶有引數。

二,signal()用法

#include <signal.h>

typedef void (*__sighandler_t) (int);

#define SIG_ERR ((__sighandler_t) -1)

#define SIG_DFL ((__sighandler_t) 0)

#define SIG_IGN ((__sighandler_t) 1)

void (*signal(int signum, void (*handler))(int)))(int);

 

如果該函式原型不容易理解的話,可以參考下面的分解方式來理解:

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler));

第一個引數指定訊號的值,第二個引數指定針對前面訊號值的處理,可以忽略該訊號(引數設為SIG_IGN);可以採用系統預設方式處理訊號(引數設為SIG_DFL);也可以自己實現處理方式(引數指定一個函式地址)。

如果signal()呼叫成功,返回最後一次也就是上一次為安裝訊號signum而呼叫signal()時的handler值;失敗則返回SIG_ERR。

傳遞給訊號處理例程的整數引數是訊號值,這樣可以使得一個訊號處理例程處理多個訊號。

man幫助說明:

DESCRIPTION
       The behavior of signal() varies across Unix versions, and has also var-
       ied historically across different versions of Linux.   Avoid  its  use:
       use sigaction(2) instead.  See Portability below.

       signal() sets the disposition of the signal signum to handler, which is
       either SIG_IGN, SIG_DFL, or the address of a  programmer-defined  func-
       tion (a "signal handler").

       If  the signal signum is delivered to the process, then one of the fol-
       lowing happens:

       *  If the disposition is set to SIG_IGN, then the signal is ignored.

       *  If the disposition is set to SIG_DFL, then the default action  asso-
          ciated with the signal (see signal(7)) occurs.

       *  If  the disposition is set to a function, then first either the dis-
          position is reset to SIG_DFL, or the signal is blocked  (see  Porta-
          bility  below), and then handler is called with argument signum.  If
          invocation of the handler caused the signal to be blocked, then  the

          signal is unblocked upon return from the handler.

       The signals SIGKILL and SIGSTOP cannot be caught or ignored.

RETURN VALUE
       signal()  returns  the previous value of the signal handler, or SIG_ERR
       on error.

示例程式:

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

void sig_handler(int signo);
int main(void)
{
    printf("mian is waiting for a signal\n");
    if(signal(SIGINT,sig_handler) == SIG_ERR){
        perror("signal errror");
        exit(EXIT_FAILURE);
    }
    for(; ;);//有時間讓我們傳送訊號


    return 0;
}

void sig_handler(int signo)
{
    printf("catch the signal SIGINT %d\n",signo);
}

結果:

QQ截圖20130715095740

可知我們捕獲了SIGINT訊號,每當我們按下ctrl+c或利用kill傳送SIGINT訊號時,執行我們安裝的訊號處理函式,當我們按下:ctrl+\或kill –SIGQUIT pid傳送SIGQUIT訊號時,程式退出,那是因為程式對SIGQUIT訊號的預設處理動作是退出程式。

現在我們來獲得程式的最後一次為安裝訊號時所指定的處理函式:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>

void sig_handler(int signo);
int main(void)
{
    printf("main is waiting for a signal\n");
    __sighandler_t prehandler;
    prehandler = signal(SIGINT,sig_handler);
    if(prehandler == SIG_ERR){
        perror("signal errror");
        exit(EXIT_FAILURE);
    }

    printf("the previous value of the signal handler is %d\n",(int)prehandler);
    //for(; ;);//有時間讓我們傳送訊號


    return 0;
}

void sig_handler(int signo)
{
    printf("catch the signal SIGINT %d\n",signo);
}

結果:

QQ截圖20130715101300

為0,由前面的巨集定義:#define SIG_DFL ((__sighandler_t) 0),可知處理動作為SIG_DFL,而SIGINT預設的處理動作就是終止程式

示例:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>

void sig_handler(int signo);
int main(void)
{
    printf("main is waiting for a signal\n");
    __sighandler_t prehandler;
    prehandler = signal(SIGINT,SIG_DFL);
    if(prehandler == SIG_ERR){
        perror("signal errror");
        exit(EXIT_FAILURE);
    }

    for(; ;);//有時間讓我們傳送訊號


    return 0;
}

結果:

QQ截圖20130715101641

當按下ctrl+c時傳送SIGINT訊號給程式,然後程式終止

三,kill()傳送訊號

傳送訊號的主要函式有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。

這裡我們先將kill函式使用:

#include <sys/types.h>

#include <signal.h>

int kill(pid_t pid,int signo)

該系統呼叫可以用來向任何程式或程式組傳送任何訊號。引數pid的值為訊號的接收程式

  • pid>0 程式ID為pid的程式
  • pid=0 同一個程式組的程式
  • pid<0 pid!=-1 程式組ID為 -pid的所有程式
  • pid=-1 除傳送給每一個呼叫程式有許可權傳送的程式除自身及1(init)程式外

Sinno是訊號值,當為0時(即空訊號),實際不傳送任何訊號,但照常進行錯誤檢查,因此,可用於檢查目標程式是否存在,以及當前程式是否具有向目標傳送訊號的許可權(root許可權的程式可以向任何程式傳送訊號,非root許可權的程式只能向屬於同一個session或者同一個使用者的程式傳送訊號)。

Kill()最常用於pid>0時的訊號傳送。該呼叫執行成功時,返回值為0;錯誤時,返回-1,並設定相應的錯誤程式碼errno。下面是一些可能返回的錯誤程式碼:

EINVAL:指定的訊號sig無效。

ESRCH:引數pid指定的程式或程式組不存在。注意,在程式表項中存在的程式,可能是一個還沒有被wait收回,但已經終止執行的僵死程式。

EPERM: 程式沒有權力將這個訊號傳送到指定接收訊號的程式。因為,一個程式被允許將訊號傳送到程式pid時,必須擁有root權力,或者是發出呼叫的程式的UID 或EUID與指定接收的程式的UID或儲存使用者ID(savedset-user-ID)相同。如果引數pid小於-1,即該訊號傳送給一個組,則該錯誤表示組中有成員程式不能接收該訊號。

man幫助說明:

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

       If pid is positive, then signal sig is sent to the process with the  ID
       specified by pid.

       If pid equals 0, then sig is sent to every process in the process group
       of the calling process.

       If pid equals -1, then sig is sent to every process for which the call-
       ing  process  has  permission  to  send  signals,  except for process 1
       (init),
but see below.

       If pid is less than -1, then sig is sent to every process in  the  pro-
       cess group whose ID is -pid.

       If  sig  is 0, then no signal is sent, but error checking is still per-
       formed; this can be used to check for the existence of a process ID  or
       process group ID.

       For  a  process  to  have permission to send a signal it must either be
       privileged (under Linux: have the CAP_KILL capability), or the real  or
       effective  user  ID of the sending process must equal the real or saved
       set-user-ID of the target process.  In the case of SIGCONT it  suffices
       when the sending and receiving processes belong to the same session.

RETURN VALUE
       On success (at least one signal was sent), zero is returned.  On error,
       -1 is returned, and errno is set appropriately.

ERRORS
       EINVAL An invalid signal was specified.

       EPERM  The process does not have permission to send the signal  to  any
              of the target processes.

       ESRCH  The  pid or process group does not exist.  Note that an existing
              process might be a zombie, a  process  which  already  committed
              termination, but has not yet been wait(2)ed for.

示例程式:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>


#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

void handler(int sig);
int main(int argc, char *argv[])
{
    if (signal(SIGUSR1, handler) == SIG_ERR)
        ERR_EXIT("signal error");
    pid_t pid = fork();
    if (pid == -1)
        ERR_EXIT("fork error");

    if (pid == 0)
    {
        sleep(1);
        kill(getppid(), SIGUSR1);
        exit(EXIT_SUCCESS);
    }

    int n = 5;
    do
    {
        printf("the number of  seconds  left to sleep is %d s\n",n);
        n = sleep(n);
    } while (n > 0);
    return 0;
}

void handler(int sig)
{
    printf("recv a sig=%d\n", sig);
}

結果:

QQ截圖20130715103450
以上程式裡有子程式給父程式傳送SIGUSR1訊號,父程式收到訊號後,睡眠被中斷,然後去執行訊號處理函式,返回後繼續睡眠剩餘的時間後退出程式。

現在利用kill給與給定pid同組所有程式傳送訊號:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>


#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

void handler(int sig);
int main(int argc, char *argv[])
{
    if (signal(SIGUSR1, handler) == SIG_ERR)
        ERR_EXIT("signal error");
    pid_t pid = fork();
    if (pid == -1)
        ERR_EXIT("fork error");

    if (pid == 0)
    {
        pid = getpgrp();
        kill(-pid, SIGUSR1);
        //kilpg(getpgrp(), SIGUSR1);
        exit(EXIT_SUCCESS);
    }

    int n = 5;
    do
    {
        n = sleep(n);
    } while (n > 0);
    return 0;
}

void handler(int sig)
{
   printf("recv a sig=%d\n", sig);
}

結果:

QQ截圖20130715104018

可知收到進行了兩次訊號處理函式的執行:因為當前所屬組中只有父子兩個程式,從上可知有兩種方式給組程式傳送訊號:kill和killpg

四,arise函式

#include <signal.h>

int raise(int signo)

向程式本身傳送訊號,引數為即將傳送的訊號值。呼叫成功返回 0;否則,返回 -1。

man幫助說明:


DESCRIPTION
       The  raise()  function sends a signal to the calling process or thread.
       In a single-threaded program it is equivalent to

           kill(getpid(), sig);

       In a multithreaded program it is equivalent to

           pthread_kill(pthread_self(), sig);

       If the signal causes a handler to be called, raise() will  only  return
       after the signal handler has returned.

RETURN VALUE
       raise() returns 0 on success, and non-zero for failure.

示例程式:

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

void sig_handler(int signo);
int main(void)
{
    printf("mian is waiting for a signal\n");
    if(signal(SIGINT,sig_handler) == SIG_ERR){
        perror("signal errror");
        exit(EXIT_FAILURE);
    }
    printf("useing raise to send a signal to himself\n");
    raise(SIGINT);
    sleep(1);
    printf("useing kill to send a signal to himself\n");
    kill(getpid(),SIGINT);
    
    return 0;
}

void sig_handler(int signo)
{
    printf("catch the signal SIGINT %d\n",signo);
}

結果:

QQ截圖20130715105250

可知兩種方式都可以給自身傳送訊號。

相關文章