linux c信号(3)-高级特性

2018-06-08 20:14:50

core dump文件

coredump 文件 内含进程终止时内存映像的一个文件,把它加载到调试器中,即可查明信号到达时程序代码和数据的状态。 引发core dump文件除了程序中调用 abort()外,还可以在命令行键入退出符(通常为 ctrl + \),从而产生 SIGQUIT 信号时产生。

[root@izj6cfw9yi1iqoik31tqbgz tmp]# ulimit -c unlimited
[root@izj6cfw9yi1iqoik31tqbgz tmp]# sleep 30
^\Quit (core dumped)
[root@izj6cfw9yi1iqoik31tqbgz tmp]# ll
-rw------- 1 root root 380928 Jun  8 17:10 core.2318

ulimit -c unlimited 来取消对core dump文件的文件大小任何限制。

/proc/PID/coredump_filter 里存储着掩码,可以对文件的内存映射类型施以进程级控制。后续文章将会讲到内存映射。

/proc/sys/kernel/core_pattern 为生成的core 文件的命名格式。

SIGKILL 和 SIGSTOP

这两个信号一个是终止进程,一个是停止一个进程。行为均无法改变。也就是说不能改变着2个信号的行为,也不能阻塞它们。当进程接收到这2个信号时,肯定能终止或停止进程。

SIGCONT 和停止信号

可使用SIGCONT信号来使某些(因接收 SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU)处于停止状态的进程得以继续运行。由于这些信号具有独特母的,所以内核对它们的处理方式将有别于其他信号。

当一个进程处于停止状态,那么收到SIGCONT 信号肯定会使其恢复运行。不管该进程是否阻塞了该进程。如果被阻塞了,且建立了处理器函数,那么进程恢复后,只有取消了对SIGCONT的阻塞后,进程才会去调用相应的处理器函数。

硬件产生的信号

硬件异常可以产生 SIGBUS、SIGFPE、SIGILL、SIGSEGV信号。SUSv3 规定,在硬件异常的情况下,如果进程从此类信号的处理器函数中返回,亦或进程忽略或阻塞了此类信号,那么进程的行为未定义。

原因如下。从信号处理器中返回:

1.假设机器语言指令产生了上述信号之一,并因此而调用了信号处理器函数。当从处理器函数正常返回后,程序会尝试从其中断处恢复执行。可当初引发信号产生的恰恰正是这条指令,所以信号会再次“光临”。故事的结局通常是,程序进入无限循环,重复调用信号处理器函数。

2.忽略信号:忽略因硬件而产生的信号于情理不合,试想算术异常之后,程序应当如何继续执行呢?无法明确。当由于硬件异常而产生上述信号之一时, Linux 会强制传递信号,即使程序已经请求忽略此类信号。

3.阻塞信号。与上一种情况一样,阻塞因硬件而产生的信号也不合情理:不清楚程序随后应当如何继续执行。始于 Linux 2.6 ,如果信号遭到阻塞,那么该信号总是会立刻杀死进程,即使进程己经为此信号安装了处理器函数。

信号传递的顺序

如果进程使用了sigprocmask() 解除了对多个信号的阻塞,那么所有这些信号会立即传递给进程,就目前而言,linux内核会按照信号编号升序来传递信号。


实时信号

实时信号意在弥补对标准信号的诸多限制。其优势如下:

1.实时信号的信号范围有所扩大,可应用于自定义目的,而标准信号中可提供随意使用的只有SIGUSR1和 SIGUSR2。
2.实时信号为队列化管理,同一信号发送多次将会多次传递给进程。
3.发送信号可以伴随数据(一整形或者指针值)
4.不同实时信号同时处于等待状态时,那么率先传递较小编号的信号。如果排队的是同一类型的信号,那么信号的传递顺序会按照发送来的的顺序。

发送实时信号

#define _POSIX_C_SOURCE 199309
#include <signal.h>

union sigval {
    int sival_int; /* Integer value for accompanying data */
    void *sival_ptr; /* 很少用到 */
};

int sigqueue(pid_t pid, int sig, const union sigval value);

//成功返回0,失败-1

sigqueue 用来发送实时信号,kill()、killpg()、raise()调用也能发送实时信号,但是排不排队由具体实现决定,linux下是排队的。

一旦触及对排队信号的数量限制,sigqueue()调用会失败,errno置为EAGAIN。

处理实时信号

可以像标准信号一样,使用常规(1个参数)信号处理器来处理实时信号。也可以用带有3个参数的信号处理器函数来处理实时信号。

一旦采用了第二种方式,第二个参数是一个siginfo_t 结构。对于一个实时信号而言,会在siginfo_t 结构中设置如下字段

1.si_signo 字段,其值与传递给信号处理器函数的第一个参数相同。
2.si_code 字段,表示信号来源,由sigqueue() 发送的实时信号来说,该值总是SI_QUEUE。
3.si_value 字段,为进程于 sigqueue() 带过来的额外参数,sigval union。
4.si_pid 和 si_uid,分别为信号发送进程的进程id,实际用户id。

例子

send.c 用来发送信号,可带3-4个参数,分别为 进程号、信号数字编号(参考 linux c信号(1)-基本概念)、数据、发送的信号数(默认为1)

#include <signal.h>
#include <sys/types.h>  /* Type definitions used by many programs */
#include <stdio.h>      /* Standard I/O functions */
#include <stdlib.h>     /* Prototypes of commonly used library functions,
                           plus EXIT_SUCCESS and EXIT_FAILURE constants */
#include <unistd.h>     /* Prototypes for many system calls */
#include <errno.h>      /* Declares errno and defines error constants */
#include <string.h>     /* Commonly used string-handling functions */

int
main(int argc, char *argv[])
{
    int sig, numSigs, j, sigData;
    union sigval sv;

    if (argc < 4 || strcmp(argv[1], "--help") == 0){
        printf("%s pid sig-num data [num-sigs]\n", argv[0]);
        return 1;
    }

    printf("%s: PID is %ld, UID is %ld\n", argv[0],
            (long) getpid(), (long) getuid());

    sig = atoi(argv[2]);
    sigData = atoi(argv[3]);
    numSigs = (argc > 4) ? atoi(argv[4]) : 1;

    for (j = 0; j < numSigs; j++) {
        sv.sival_int = sigData + j;
        if (sigqueue(atoi(argv[1]), sig, sv) == -1)
            perror("sigqueue");
    }

    return 0;
}

main.c 用来接收信号。可带2个参数,程序休眠时间(为了让信号排队)、信号处理器函数处理间隔时间。

#define _GNU_SOURCE
#include <string.h>
#include <signal.h>
#include <sys/types.h>  /* Type definitions used by many programs */
#include <stdio.h>      /* Standard I/O functions */
#include <stdlib.h>     /* Prototypes of commonly used library functions,
                           plus EXIT_SUCCESS and EXIT_FAILURE constants */
#include <unistd.h>     /* Prototypes for many system calls */
#include <errno.h>      /* Declares errno and defines error constants */
#include <string.h>     /* Commonly used string-handling functions */

static volatile int handlerSleepTime;
static volatile int sigCnt = 0;         /* Number of signals received */
static volatile int allDone = 0;

static void
siginfoHandler(int sig, siginfo_t *si, void *ucontext)
{
    //如果是 SIGINT or SIGTERM 信号则标记结束。
    if (sig == SIGINT || sig == SIGTERM) {
        allDone = 1;
        return;
    }

    sigCnt++;
    printf("caught signal %d\n", sig);

    printf("    si_signo=%d, si_code=%d (%s), ", si->si_signo, si->si_code,
            (si->si_code == SI_USER) ? "SI_USER" :
            (si->si_code == SI_QUEUE) ? "SI_QUEUE" : "other");
    printf("si_value=%d\n", si->si_value.sival_int);
    printf("    si_pid=%ld, si_uid=%ld\n",
            (long) si->si_pid, (long) si->si_uid);

    sleep(handlerSleepTime);
}

int
main(int argc, char *argv[])
{
    struct sigaction sa;
    int sig;
    sigset_t prevMask, blockMask;

    if (argc > 1 && strcmp(argv[1], "--help") == 0){
        printf("%s [block-time [handler-sleep-time]]\n", argv[0]);
        return 1;
    }
        

    printf("%s: PID is %ld\n", argv[0], (long) getpid());

    handlerSleepTime = (argc > 2) ? atoi(argv[2]) : 1;


    sa.sa_sigaction = siginfoHandler;
    sa.sa_flags = SA_SIGINFO;
    
    //调用信号处理函数时,屏蔽所有信号。
    sigfillset(&sa.sa_mask);

    //设置1-31标准信号处置
    for (sig = 1; sig < NSIG; sig++)
        if (sig != SIGTSTP && sig != SIGQUIT)
            sigaction(sig, &sa, NULL);

    //阻塞除 SIGINT SIGTERM信号的全部信号并进入休眠
    //实时信号此时发送过来时将会排队
    //休眠结束解除阻塞
    if (argc > 1) {
        //设置所有信号集
        sigfillset(&blockMask);
        
        //除去2个
        sigdelset(&blockMask, SIGINT);
        sigdelset(&blockMask, SIGTERM);

        //设置将blockMask信号集里的信号屏蔽阻塞
        //并把之前屏蔽信号集存入prevMask以供后面复原。
        if (sigprocmask(SIG_SETMASK, &blockMask, &prevMask) == -1)
            perror("sigprocmask");

        printf("%s: signals blocked - sleeping %s seconds\n", argv[0], argv[1]);
        sleep(atoi(argv[1]));
        printf("%s: sleep complete\n\n", argv[0]);

        //复原
        if (sigprocmask(SIG_SETMASK, &prevMask, NULL) == -1)
            perror("sigprocmask");
    }

    while (!allDone)                    /* Wait for incoming signals */
        pause();

    printf("Caught %d signals\n", sigCnt);
    return 0;
}
[root@izj6cfw9yi1iqoik31tqbgz c]# gcc send.c -o send_sig
[root@izj6cfw9yi1iqoik31tqbgz c]# gcc main.c -o recv_sig
[root@izj6cfw9yi1iqoik31tqbgz c]# ./recv_sig 30 5
./recv_sig: PID is 2509
./recv_sig: signals blocked - sleeping 30 seconds
#这里睡眠30秒
./recv_sig: sleep complete
caught signal 13
    si_signo=13, si_code=-1 (SI_QUEUE), si_value=10
    si_pid=2510, si_uid=0
#这里间隔5秒
caught signal 14
    si_signo=14, si_code=-1 (SI_QUEUE), si_value=11
    si_pid=2511, si_uid=0

#kill产生
caught signal 14
    si_signo=14, si_code=0 (SI_USER), si_value=11
    si_pid=2276, si_uid=0
#这里发送的2个信号将排队,因为接收者正在休眠并阻塞了这2个信号。
[root@izj6cfw9yi1iqoik31tqbgz c]# ./send_sig 2509 13 10 1
./send_sig: PID is 2510, UID is 0
[root@izj6cfw9yi1iqoik31tqbgz c]# ./send_sig 2509 14 11 1
./send_sig: PID is 2511, UID is 0
[root@izj6cfw9yi1iqoik31tqbgz c]# kill -14 2509

使用掩码来等待信号:sigsuspend()

对信号编程时偶尔会遇到如下情况:

1.临时阻塞一个信号,以防止在处理关键代码时被此信号打断
2.解除对信号的阻塞,然后暂停执行,直至有信号到达

可以尝试用以下方法。

sigset_t prevMask, intMask;
struct sigaction sa;
sigemptyset(&intMask);
sigaddset(&intMask, SIGINT);
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = handler;
if (sigaction(SIGINT, &sa, NULL) == -1)
    perror("sigaction");

//阻塞 SIGINT
if (sigprocmask(SIG_BLOCK, &intMask, &prevMask) == -1)
    perror("sigprocmask - SIG_BLOCK");

//在这里做一些重要工作保证不被SIGINT打断

//解除 SIGINT 阻塞
if (sigprocmask(SIG_SETMASK, &prevMask, NULL) == -1)
    perror("sigprocmask - SIG_SETMASK");

//如果此时SIGINT到达,bug就出现了。

pause(); /* Wait for SIGINT */

要避免这一问题,需要将解除信号阻塞和挂起2个动作封装成一个原子操作。这正是sigsuspend() 调用目的所在。

#include <signal.h>

int sigsuspend(const sigset_t *mask);

//(Normally) returns –1 with errno set to EINTR

sissuspend() 系统调用将以mask 所指向的信号集来替换信号掩码,然后挂起进程的执行,直到其捕获到信号,并从处理器函数返回,返回后,信号掩码恢复为调用前的值。类似于不可中断方式执行如下操作

sigprocmask(SIG_SETMASK, &mask, &prevMask); /* 设置新掩码 */
pause();
sigprocmask(SIG_SETMASK, &prevMask, NULL); /* 恢复老掩码 */

例子

#define _GNU_SOURCE     /* Get strsignal() declaration from <string.h> */
#include <string.h>
#include <signal.h>
#include <time.h>
#include <sys/types.h>  /* Type definitions used by many programs */
#include <stdio.h>      /* Standard I/O functions */
#include <stdlib.h>     /* Prototypes of commonly used library functions,
                           plus EXIT_SUCCESS and EXIT_FAILURE constants */
#include <unistd.h>     /* Prototypes for many system calls */
#include <errno.h>      /* Declares errno and defines error constants */
#include <string.h>     /* Commonly used string-handling functions */

int printSigMask(FILE *of, const char *msg);
int printPendingSigs(FILE *of, const char *msg);
void printSigset(FILE *of, const char *ldr, const sigset_t *mask);

static volatile sig_atomic_t gotSigquit = 0;

//打印信号集信号
void printSigset(FILE *of, const char *prefix, const sigset_t *sigset)
{
    int sig, cnt;

    cnt = 0;
    for (sig = 1; sig < NSIG; sig++) {
        if (sigismember(sigset, sig)) {
            cnt++;
            fprintf(of, "%s%d (%s)\n", prefix, sig, strsignal(sig));
        }
    }

    if (cnt == 0)
        fprintf(of, "%s<empty signal set>\n", prefix);
}

//打印屏蔽的信号
int printSigMask(FILE *of, const char *msg)
{
    sigset_t currMask;

    if (msg != NULL)
        fprintf(of, "%s", msg);

    if (sigprocmask(SIG_BLOCK, NULL, &currMask) == -1)
        return -1;

    printSigset(of, "\t\t", &currMask);

    return 0;
}

//打印等待中的信号
int printPendingSigs(FILE *of, const char *msg)
{
    sigset_t pendingSigs;

    if (msg != NULL)
        fprintf(of, "%s", msg);

    if (sigpending(&pendingSigs) == -1)
        return -1;

    printSigset(of, "\t\t", &pendingSigs);

    return 0;
}

static void handler(int sig)
{
    printf("Caught signal %d (%s)\n", sig, strsignal(sig));
                                        /* UNSAFE (see Section 21.1.2) */
    if (sig == SIGQUIT)
        gotSigquit = 1;
}

int main(int argc, char *argv[])
{
    int loopNum;
    time_t startTime;
    sigset_t origMask, blockMask;
    struct sigaction sa;

    //打印信号屏蔽的初始值
    printSigMask(stdout, "Initial signal mask is:\n");

    //设置信号屏蔽为 SIGINT SIGQUIT信号
    //返回之前的信号屏蔽到origMask
    sigemptyset(&blockMask);
    sigaddset(&blockMask, SIGINT);
    sigaddset(&blockMask, SIGQUIT);
    if (sigprocmask(SIG_BLOCK, &blockMask, &origMask) == -1)
        perror("sigprocmask - SIG_BLOCK");

    //改变信号SIGINT SIGQUIT处理方式
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sa.sa_handler = handler;
    if (sigaction(SIGINT, &sa, NULL) == -1)
        perror("sigaction");
    if (sigaction(SIGQUIT, &sa, NULL) == -1)
        perror("sigaction");

    for (loopNum = 1; !gotSigquit; loopNum++) {
        printf("=== LOOP %d\n", loopNum);

        /* Simulate a critical section by delaying a few seconds */

        printSigMask(stdout, "Starting critical section, signal mask is:\n");
        for (startTime = time(NULL); time(NULL) < startTime + 4; )
            continue;                   /* Run for a few seconds elapsed time */
        
        //此时还未解除屏蔽
        //所以在上面的4s执行中接收到信号会进入等待信号列
        printPendingSigs(stdout,
                "Before sigsuspend() - pending signals:\n");
        
        //设置信号屏蔽为初始值(不屏蔽)
        //也就是解除信号 SIGINT SIGQUIT的屏蔽
        //并等待信号(如果尚未信号处于等待状态)
        if (sigsuspend(&origMask) == -1 && errno != EINTR)
            perror("sigsuspend");
    }

    //设置信号屏蔽
    if (sigprocmask(SIG_SETMASK, &origMask, NULL) == -1)
        perror("sigprocmask - SIG_SETMASK");

    printSigMask(stdout, "=== Exited loop\nRestored signal mask to:\n");

    /* Do other processing... */

    return 0;
}

以同步方式等待信号

同步方式等待信号会相对简单易控一点。

#include <signal.h>

int sigwaitinfo(const sigset_t *set, siginfo_t *info);

//成功返回信号编号,失败-1

int sigtimedwait(const sigset_t *set, siginfo_t *info, const struct timespec *timeout);

//可以指定超时时间,如果调用超时而又没有收到信号返回-1
//并设置errno为EAGAIN

sigwaitinfo() 系统调用挂起进程的执行,直至set指向的信号集中的某一信号到达。如果有信号处于等待状态,则立马返回。info 如果不为NULL,则会指向经过初始化处理的 siginfo_t 结构。

sigwaitinfo() 接受信号的顺序和排队特性与信号处理器所捕获的信号相同。标准信号不排队,实时信号按照低编号优先。 

除了不需要编写信号处理器,它的速度也更快一点。

例子

#define _GNU_SOURCE
#include <string.h>
#include <signal.h>
#include <time.h>
#include <sys/types.h>  /* Type definitions used by many programs */
#include <stdio.h>      /* Standard I/O functions */
#include <stdlib.h>     /* Prototypes of commonly used library functions,
                           plus EXIT_SUCCESS and EXIT_FAILURE constants */
#include <unistd.h>     /* Prototypes for many system calls */
#include <errno.h>      /* Declares errno and defines error constants */
#include <string.h>     /* Commonly used string-handling functions */

int
main(int argc, char *argv[])
{
    int sig;
    siginfo_t si;
    sigset_t allSigs;

    if (argc > 1 && strcmp(argv[1], "--help") == 0){
        printf("%s [delay-secs]\n", argv[0]);
        return 1;
    }
        

    printf("%s: PID is %ld\n", argv[0], (long) getpid());

    //阻塞全部信号
    sigfillset(&allSigs);
    if (sigprocmask(SIG_SETMASK, &allSigs, NULL) == -1)
        perror("sigprocmask");
    printf("%s: signals blocked\n", argv[0]);

    //延时几秒
    if (argc > 1) {
        printf("%s: about to delay %s seconds\n", argv[0], argv[1]);
        sleep(atoi(argv[1]));
        printf("%s: finished delay\n", argv[0]);
    }

    for (;;) {
        //捕获所有信号
        sig = sigwaitinfo(&allSigs, &si);
        if (sig == -1)
            perror("sigwaitinfo");

        if (sig == SIGINT || sig == SIGTERM)
            return 0;

        printf("got signal: %d (%s)\n", sig, strsignal(sig));
        printf("    si_signo=%d, si_code=%d (%s), si_value=%d\n",
                si.si_signo, si.si_code,
                (si.si_code == SI_USER) ? "SI_USER" :
                    (si.si_code == SI_QUEUE) ? "SI_QUEUE" : "other",
                si.si_value.sival_int);
        printf("    si_pid=%ld, si_uid=%ld\n",
                (long) si.si_pid, (long) si.si_uid);
    }
}
^C[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out 30
./a.out: PID is 2717
./a.out: signals blocked
./a.out: about to delay 30 seconds


./a.out: finished delay
got signal: 11 (Segmentation fault)
    si_signo=11, si_code=0 (SI_USER), si_value=-915527394
    si_pid=2473, si_uid=0
#实时信号升序排队
got signal: 42 (Real-time signal 8)
    si_signo=42, si_code=-1 (SI_QUEUE), si_value=1
    si_pid=2719, si_uid=0
got signal: 43 (Real-time signal 9)
    si_signo=43, si_code=-1 (SI_QUEUE), si_value=1
    si_pid=2718, si_uid=0
[root@izj6cfw9yi1iqoik31tqbgz c]# ./send_sig 2717 43 1 1
./send_sig: PID is 2718, UID is 0
[root@izj6cfw9yi1iqoik31tqbgz c]# ./send_sig 2717 42 1 1
./send_sig: PID is 2719, UID is 0

[root@izj6cfw9yi1iqoik31tqbgz c]# kill -11 2717


总结

信号讲解到此结束,如果有疑问可以给我留言。


备注
1.编译器版本gcc4.8,运行环境centos7 64位
2.原文地址http://www.freecls.com/a/2712/4f

©著作权归作者所有
收藏
推荐阅读
  • err
    linux c信号(2)-处理函数

    一般而言信号处理函数设计的越简单越好。其中的一个重要原因在于,这将降低引发竞争条件的风险。下面是信号处理函数的两种常见设计。1.信号处理器函数设置全局性标记变量并退出。...

  • err
    linux c信号(1)-基本概念

    信号是事件发生时对进程的通知机制。有时也称之为软件中断。信号与硬件中断的相似之处在于打断了程序执行的正常流程,大多数情况下,无法预测信号到达的精确时间。一个(具有合适权限...

  • err
    linux c监控文件事件

    某些应用程序需要对文件或目录进行监控,来侦测其发生特定事件。例如当文件加入或移出一目录,图形化文件管理器应能判定此目录是否在其当前显示之列,而守护进程可能也要监控自己的配...

  • err
    linux c目录与链接

    相关系统调用:link(),unlink(),rename(),symlink(),readlink(),mkdir(),rmdir(),remove(),opendir...

  • err
    linux c检查修改文件属性

    相关系统调用:utime(),utimes(),chown(),lchown(),fchown(),access(),umask(),chmod()修改文件时间修改文件的...

  • nginx模块 ngx_http_headers_module

    ngx_http_headers_module 模块是用来增加 Expires 和 Cache-control,或者是任意的响应头。Syntax: add_header name value [alw...

  • nginx模块 ngx_http_gunzip_module、ngx_http_gzip_module、ngx_http_gzip_static_module

    ngx_http_gunzip_module 模块将文件解压缩后并在响应头加上 "Content-Encoding: gzip" 返回给客户端。为了解决客户端不支持gzip压缩。编译的时候带上 --w...

  • nginx模块 ngx_http_flv_module、ngx_http_mp4_module

    ngx_http_flv_module模块提供了对 flv 视频的伪流支持。编译的时候带上 --with-http_flv_module。它会根据指定的 start 参数来指定跳过多少字节,并在返回数...

  • nginx模块 ngx_http_fastcgi_module

    ngx_http_fastcgi_module 模块使得nginx可以与 fastcgi 服务器通信。比如目前要使得 nginx 支持 php 就得使用 fastcgi技术,在服务器上装上 nginx...

  • nginx模块 ngx_http_autoindex_module

    ngx_http_autoindex_module 模块可以将uri以 / 结尾时,列出里面的文件和目录。Syntax: autoindex on | off; Default: autoindex ...

简介
天降大任于斯人也,必先苦其心志。