乐趣区

关于linux编程:Linux系统编程四信号

一. 信号概述

1.1 中断

中断就是字面的意思,譬如正在打游戏,手机响了,这时后中断游戏,去接手机,回来再打游戏,这就是中断。

1.2 什么是信号

信号是软件中断,是在软件档次上对中断机制的一种模仿,是一种异步通信的形式。信号是 Linux 过程间通信的最古老的形式,也是最罕用的通信形式。

1.3 信号机制

过程 A 给过程 B 发送信号,过程 B 收到信号之前执行本人的代码,收到信号后,不论执行到程序的什么地位,都要暂停运行,去解决信号,处理完毕后再继续执行,是一种异步模式。

1.4 信号状态

三种状态,产生、未决和递达。
1 产生
a) 按键产生,如:Ctrl+c、Ctrl+z
b)零碎调用产生,如:kill、raise、abort
3)软件条件产生,如:定时器 alarm
4)硬件异样产生,如:非法拜访内存 (段谬误)、除 0(浮点数例外)、内存对齐出错(总线谬误)
5) 命令产生,如:kill 命令
2 未决
没有被解决, 产生和递达之间的状态。次要因为阻塞 (屏蔽) 导致该状态
3 递达
递送并且达到过程, 信号被解决了

1.5 查看信号

1)kill -l

(base) zhaow@zhaow-610:~$ 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
//  信号编号)信号名字    

2)man 7 signal

SIGNAL(7)                  Linux Programmer's Manual                 SIGNAL(7)

NAME
       signal - overview of signals

DESCRIPTION
       Linux  supports both POSIX reliable signals (hereinafter "standard sig‐
       nals") and POSIX real-time signals.

......
Signal     Value     Action   Comment
       ──────────────────────────────────────────────────────────────────────
       SIGHUP        1       Term    Hangup detected on controlling terminal
                                     or death of controlling process
       SIGINT        2       Term    Interrupt from keyboard
       SIGQUIT       3       Core    Quit from keyboard
       SIGILL        4       Core    Illegal Instruction
       SIGABRT       6       Core    Abort signal from abort(3)
       SIGFPE        8       Core    Floating-point exception
......

信号的四个因素:
1)信号的编号

应用 kill - l 命令能够查看以后零碎有哪些信号,不存在编号为 0 的信号。其中 1 -31 号信号称之为惯例信号(也叫一般信号或规范信号),34-64 称之为实时信号,驱动编程与硬件相干。

2)信号的名称
3)产生信号的事件
4)信号的默认解决动作

Term:终止过程
Ign:疏忽信号 (默认即时对该种信号疏忽操作)
Core:终止过程,生成 Core 文件。(查验死亡起因,用于 gdb 调试)
Stop:进行(暂停)过程
Cont:持续运行进

1.6 阻塞信号集和未决信号集

Linux 内核的过程管制块 PCB 是一个构造体,这个构造体外面蕴含了信号相干的信息,次要有阻塞信号集和未决信号集。

信号集 阐明
阻塞信号集 将某些信号退出汇合,对他们设置屏蔽,当屏蔽信号后,再收到该信号,该信号的解决将推后(解决产生在解除屏蔽后)。
未决信号集 信号产生后因为某些起因 (次要是阻塞) 不能到达。这类信号的汇合称之为未决信号集。在屏蔽解除前,信号始终处于未决状态。信号产生,未决信号集中形容该信号的位立即翻转为 1,示意信号处于未决状态。当信号被解决对应位翻转回为 0。

二. 信号相干函数

2.1 signal 函数

#include <signal.h>

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

函数作用:注册信号捕获函数
函数参数
    signum:信号编号
    handler:信号处理函数

代码示例

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

/*signal 函数测试 --- 注册信号处理函数 */

void sighandler(int signo){printf("singo = %d\n",signo);
}

int main(){signal(SIGPIPE,sighandler);
    // 没给读端的管道写数据产生 SIGPIPE 信号。int fd[2];
    int ret = pipe(fd);
    if(ret < 0){perror("pipe");
        return -1;
    }

    close(fd[0]);

    write(fd[1],"hello",strlen("hello")); 
    // 信号产生后,内核调用注册的 sighandler
    return 0;
    
}

2.2 kill 函数


#include <sys/types.h>
#include <signal.h>
​
int kill(pid_t pid, int sig);
性能:给指定过程发送指定信号(不肯定杀死)
​
参数:pid : 取值有 4 种状况 :
        pid > 0:  将信号传送给过程 ID 为 pid 的过程。pid = 0 :  将信号传送给以后过程所在过程组中的所有过程。pid = -1 : 将信号传送给零碎内所有的过程。pid < -1 : 将信号传给指定过程组的所有过程。这个过程组号等于 pid 的绝对值。sig : 信号的编号,这里能够填数字编号,也能够填信号的宏定义,能够通过命令 kill - l("l" 为字母)进行相应查看。不举荐间接应用数字,应应用宏名,因为不同操作系统信号编号可能不同,但名称统一。​
返回值:胜利:0
    失败:-1

代码示例

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

int main(){kill(getpid(),SIGKILL); // 给以后过程发送信号 SIGKILL
    printf("helloworld\n");
    return 0;
}

2.3 raise 函数

#include <signal.h>
​
int raise(int sig);
性能:给以后过程发送指定信号(本人给本人发),等价于 kill(getpid(), sig)
参数:sig:信号编号
返回值:胜利:0
    失败:非 0 值

代码示例

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

// 信号处理函数
void sighandler(int signo)
{printf("signo==[%d]\n", signo);
}


int main(){
    // 注册信号处理函数
    signal(SIGINT,sighandler);

    // 给以后过程发送 SIGINT 信号
    raise(SIGINT);
    return 0;

}

2.4 abort

#include <stdlib.h>
​
void abort(void);
性能:给本人发送异样终止信号 6) SIGABRT,并产生 core 文件,等价于 kill(getpid(), SIGABRT);
​
参数:无
​
返回值:无
​

代码示例

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

// 信号处理函数
void sighandler(int signo)
{printf("signo==[%d]\n", signo);
}


int main(){
    // 注册信号处理函数
    signal(SIGINT,sighandler);

    // 给以后过程发送 SIGINT 信号
    raise(SIGINT);
    // abort 给本人发送异样终止信号 6) SIGABRT,并产生 core 文件,等价于 kill(getpid(), SIGABRT);
    abort();
    return 0;

}

2.5 alarm 函数

#include <unistd.h>
​
unsigned int alarm(unsigned int seconds);
性能:设置定时器(闹钟)。在指定 seconds 后,内核会给以后过程发送 14)SIGALRM 信号。过程收到该信号,默认动作终止。每个过程都有且只有惟一的一个定时器。勾销定时器 alarm(0),返回旧闹钟余下秒数。参数:seconds:指定的工夫,以秒为单位
返回值:返回 0 或残余的秒数

代码示例

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

int main(){int n = alarm(5);
    printf("n = %d\n",n);
    sleep(1);
    n = alarm(5);
    printf("n = %d\n",n);
    return 0;
}

2.6 setitimer 函数

#include <sys/time.h>
​
int setitimer(int which,  const struct itimerval *new_value, struct itimerval *old_value);
性能:设置定时器(闹钟)。可代替 alarm 函数。精度微秒 us,能够实现周期定时。参数:which:指定定时形式
        a) 天然定时:ITIMER_REAL → 14)SIGALRM 计算天然工夫
        b) 虚拟空间计时(用户空间):ITIMER_VIRTUAL → 26)SIGVTALRM  只计算过程占用 cpu 的工夫
        c) 运行时计时(用户 + 内核):ITIMER_PROF → 27)SIGPROF 计算占用 cpu 及执行零碎调用的工夫
    new_value:struct itimerval, 负责设定 timeout 工夫
        struct itimerval {
            struct timerval it_interval; // 闹钟触发周期
            struct timerval it_value;    // 闹钟触发工夫
        };
        struct timeval {
            long tv_sec;            // 秒
            long tv_usec;           // 微秒
        }
        itimerval.it_value:设定第一次执行 function 所提早的秒数 
        itimerval.it_interval:设定当前每几秒执行 function
​
    old_value:寄存旧的 timeout 值,个别指定为 NULL
返回值:胜利:0
    失败:-1

代码示例

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

// 信号处理函数
void sighandler(int signo)
{printf("signo==[%d]\n", signo);
}

int main(){signal(SIGALRM,sighandler);

    struct itimerval tm;
    // 周期性工夫赋值
    tm.it_interval.tv_sec = 1;
    tm.it_interval.tv_usec = 0;
    // 第一次登程的工夫
    tm.it_value.tv_sec = 3;
    tm.it_value.tv_usec = 0;

    setitimer(ITIMER_REAL,&tm,NULL);

    while (1)
    {sleep(1);
    }
    

    return 0;
}

三 信号集函数

3.1 信号集

为了不便对多个信号进行解决,一个用户过程经常须要对多个信号做出解决,在 Linux 零碎中引入了信号集。
信号集是一个能示意多个信号的数据类型,sigset_t set,set 即一个信号集。
因为信号集属于内核的一块区域,用户不能间接操作内核空间,为此,内核提供了一些信号集相干的接口函数,应用这些函数用户就能够实现对信号集的相干操作。

函数 函数阐明 参数与返回值
int sigemptyset(sigset_t *set); 函数阐明:将某个信号集清 0 函数返回值:胜利:0;失败:-1,设置 errno
int sigfillset(sigset_t *set); 函数阐明:将某个信号集置 1 函数返回值:胜利:0;失败:-1,设置 errno
int sigaddset(sigset_t *set, int signum); 函数阐明:将某个信号退出信号汇合中 函数返回值:胜利:0;失败:-1,设置 errno
int sigdelset(sigset_t *set, int signum); 函数阐明:将某信号从信号清出信号集 函数返回值:胜利:0;失败:-1,设置 errno
int sigismember(const sigset_t *set, int signum); 函数阐明:判断某个信号是否在信号集中 函数返回值:在:1;不在:0;出错:-1,设置 errno

代码示例

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


int main(){
    // 定义个信号集变量
    sigset_t set;
    int ret = 0;

    // 初始化信号集内容,也就是清空信号集
    sigemptyset(&set);

    // 判断 SIGINT 是否在信号集 set 中,在返回 1,不在返回 0
    ret = sigismember(&set,SIGINT);
    if(ret == 0){printf("SIGINT not in set\n");
    }

    // 将 SIGINT 和 SIGQUIT 增加到信号集 set
    sigaddset(&set,SIGINT);
    sigaddset(&set,SIGQUIT);

    // 判断 SIGINT 是否在信号集 set 中,在返回 1,不在返回 0
    ret = sigismember(&set,SIGINT);
    if(ret == 1){printf("SIGINT  in set\n");
    } 
    // 把 SIGQUIT 从信号集 set 移除
    sigdelset(&set, SIGQUIT); 

    // 判断 SIGQUI 是否在信号集 set 中,在返回 1,不在返回 0
    ret = sigismember(&set, SIGQUIT);
    if (ret == 0)
    {printf("SIGQUIT not in set \n");
    }
    return 0;
}

3.2 sigprocmask 函数

用来屏蔽信号、解除屏蔽也应用该函数。其本质,读取或批改过程管制块中的信号屏蔽字(阻塞信号集)。

#include <signal.h>
​
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
性能:查看或批改信号阻塞集,依据 how 指定的办法对过程的阻塞汇合进行批改,新的信号阻塞集由 set 指定,而原先的信号阻塞汇合由 oldset 保留。​
参数:how : 信号阻塞汇合的批改办法,有 3 种状况:SIG_BLOCK:向信号阻塞汇合中增加 set 信号集,新的信号掩码是 set 和旧信号掩码的并集。相当于 mask = mask|set。SIG_UNBLOCK:从信号阻塞汇合中删除 set 信号集,从以后信号掩码中去除 set 中的信号。相当于 mask = mask & ~ set。SIG_SETMASK:将信号阻塞汇合设为 set 信号集,相当于原来信号阻塞集的内容清空,而后依照 set 中的信号从新设置信号阻塞集。相当于 mask = set。set : 要操作的信号集地址。若 set 为 NULL,则不扭转信号阻塞汇合,函数只把以后信号阻塞汇合保留到 oldset 中。oldset : 保留原先信号阻塞集地址
​
返回值:胜利:0,失败:-1,失败时错误代码只可能是 EINVAL,示意参数 how 不非法。

3.3 sigpending 函数

#include <signal.h>
​
int sigpending(sigset_t *set);
性能:读取以后过程的未决信号集
参数:set:未决信号集
返回值:胜利:0
    失败:-1

代码示例

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

// 信号处理函数
void sighandler(int signo)
{printf("signo==[%d]\n", signo);
}


int main(){
    // 注册 SIGINT 和 SIGQUIT 的信号处理函数
    signal(SIGINT, sighandler);
    signal(SIGQUIT, sighandler);

    // 定义个信号集变量
    sigset_t myset,old;
    // 初始化信号集
    sigemptyset(&myset);

    // 增加信号集到阻塞中
    sigaddset(&myset, SIGINT);
    sigaddset(&myset, SIGQUIT);
    sigaddset(&myset, SIGKILL);

    // 自定义信号集设置到内核中的阻塞信号集
    sigprocmask(SIG_BLOCK, &myset, &old);

    sigset_t pend;
    int i = 0;
    while (1)
    {
        // 读取内核中的未决信号集
        sigpending(&pend);

        for(int i=1; i< 32;i++){if (sigismember(&pend, i))
            {printf("1");
            }
            else if (sigismember(&pend, i) == 0)
            {printf("0");
            }
            printf("\n");
        }
        printf("\n");
        sleep(5);
        i++;

        if(i<5){sigprocmask(SIG_SETMASK, &old, NULL);
        
        }


    }
    
    return 0;
}

四. 信号捕获

一个过程收到一个信号的时候,能够用如下办法进行解决:
1)执行零碎默认动作:对大多数信号来说,零碎默认动作是用来终止该过程。
2)疏忽此信号(抛弃)
3)执行自定义信号处理函数(捕捉)

SIGKILL 和 SIGSTOP 不能更改信号的解决形式,因为它们向用户提供了一种使过程终止的牢靠办法。

信号捕获函数罕用的有两个 signal 函数和 sigaction 函数。
signal 函数上述已有阐明。

4.1 sigaction 函数

#include <signal.h>
​
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
性能:查看或批改指定信号的设置(或同时执行这两种操作)。​
参数:signum:要操作的信号。act:要设置的对信号的新解决形式(传入参数)。oldact:原来对信号的解决形式(传出参数)。​
    如果 act 指针非空,则要扭转指定信号的解决形式(设置),如果 oldact 指针非空,则零碎将此前指定信号的解决形式存入 oldact。​
返回值:胜利:0
    失败:-1


struct sigaction {void  (*sa_handler)(int);    // 旧的信号处理函数
       void  (*sa_sigaction)(int, siginfo_t *, void *); // 新的信号处理函数
       sigset_t  sa_mask; // 信号处理函数执行期间须要阻塞的信号
       int      sa_flags; // 通常为 0,示意应用默认标识
       void     (*sa_restorer)(void);
};

代码示例

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

// 信号处理函数
void sighandler(int signo)
{printf("signo==[%d]\n", signo);
    sleep(4);
}

int main()
{
    // 注册信号处理函数
    struct sigaction act;
    act.sa_handler = sighandler;
    sigemptyset(&act.sa_mask);  // 在信号处理函数执行期间, 不阻塞任何信号
    sigaddset(&act.sa_mask, SIGQUIT);
    act.sa_flags = 0;
    sigaction(SIGINT, &act, NULL);

    
    signal(SIGQUIT, sighandler);    

    while(1)
    {sleep(10);
    }

    return 0;
}

4.2 内核实现信号捕获过程

如果信号的解决动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕获信号。
以如下例子阐明
1. 用户程序注册了 SIGQUIT 信号的处理函数 sighandler。
2. 以后正在执行 main 函数,这时产生中断或异样切换到内核态。
3. 在中断处理完毕后要返回用户态的 main 函数之前查看到有信号 SIGQUIT 递达。
4. 内核决定返回用户态后不是复原 main 函数的上下文继续执行,而是执行 sighandler 函数,sighandler 和 main 函数应用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的管制流程。
5.sighandler 函数返回后主动执行非凡的零碎调用 sigreturn 再次进入内核态。
如果没有新的信号要递达,这次再返回用户态就是复原 main 函数的上下文继续执行了

五.SIGCHLD 信号

5.1 产生 SIGCHLD 信号的条件

1) 子过程终止时

2) 子过程接管到 SIGSTOP 信号进行时

3) 子过程处在进行态,承受到 SIGCONT 后唤醒时

5.2 SIGCHLD 信号的作用

子过程退出后,内核会给它的父过程发送 SIGCHLD 信号,父过程收到这个信号后能够对子过程进行回收。
应用 SIGCHLD 信号实现对子过程的回收能够防止父过程阻塞期待而不能执行其余操作,只有当父过程收到 SIGCHLD 信号之后才去调用信号捕获函数实现对子过程的回收,未收到 SIGCHLD 信号之前能够解决其余操作。

5.3 防止僵尸过程

父过程通过 wait() 和 waitpid() 等函数期待子过程完结,然而,这会导致父过程挂起。
如果父过程要解决的事件很多,不可能挂起,能够通过 signal() 函数人为解决信号 SIGCHLD,只有有子过程退出主动调用指定好的回调函数,因为子过程完结后,父过程会收到该信号 SIGCHLD,能够在其回调函数里调用 wait() 或 waitpid() 回收。
代码示例

// 父过程应用 SICCHLD 信号实现对子过程的回收
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

void waitchild(int signo)
{
    pid_t wpid;

    // 回收子过程
    while(1)
    {wpid = waitpid(-1, NULL, WNOHANG);
        if(wpid>0)
        {printf("child is quit, wpid==[%d]\n", wpid);
        }
        else if(wpid==0)
        {printf("child is living, wpid==[%d]\n", wpid);
            break;
        }
        else if(wpid==-1)
        {printf("no child is living, wpid==[%d]\n", wpid);
            break;
        }
    }
}

int main()
{
    pid_t pid;

    signal(SIGCHLD,waitchild);

    pid = fork();
    if(pid < 0){perror("fork");
        exit(1);
    }else if(pid == 0){printf("child process [%d]\n",getpid());
        exit(0);
    }else{sleep(2);
        printf("father process [%d]\n",getpid());
        system("ps -ef | grep defunct"); // 查看有无僵尸过程
    }
    return 0;

    return 0;
}

5.4 父过程疏忽子过程,子过程完结后,由内核回收

用 signal(SIGCHLD, SIG_IGN)告诉内核,父过程疏忽此信号,那么子过程完结后,内核会回收,并不再给父过程发送信号。

// 父过程应用 SICCHLD 信号实现对子过程的回收
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

int main()
{
    pid_t pid;

    signal(SIGCHLD,SIG_IGN);

    pid = fork();
    if(pid < 0){perror("fork");
        exit(1);
    }else if(pid == 0){printf("child process [%d]\n",getpid());
        exit(0);
    }else{sleep(2);
        printf("father process [%d]\n",getpid());
        system("ps -ef | grep defunct");
    }
    return 0;

    return 0;
}
退出移动版