apue第10章 信号

1、引言

什么是信号:在计算机科学中,信号(英语:Signals)是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制,用来提醒进程一个事件已经发生。当一个信号发送给一个进程,操作系统中断了进程正常的控制流程,此时,任何非原子操作都将被中断。如果进程定义了信号的处理函数,那么它将被执行,否则就执行默认的处理函数。

2、信号

在头文件中,信号名都被定义为正整数常量。不存在值为0的有特殊的应用
产生信号的条件:

  • 在一个运行的程序的控制终端键入特定的组合键可以向它发送某些信号:
    • Ctrl-C发送INT信号(SIGINT);默认情况下,这会导致进程终止。
    • Ctrl-Z发送TSTP信号(SIGTSTP);默认情况下,这会导致进程挂起。
    • Ctrl-\发送QUIT信号(SIGQUIT);默认情况下,这会导致进程终止并且将内存中的信息转储到硬盘(核心转储)。
  • kill()系统调用会在权限允许的情况下向进程发送特定的信号,类似地,kill命令允许用户向进程发送信号。raise(3)库函数可以将特定信号发送给当前进程。
  • 像除数为零、段错误这些异常也会产生信号(这里分别是SIGFPESIGSEGV,默认都会导致进程终止和核心转储).
  • 内核可以向进程发送信号以告知它一个事件发生了。例如当进程将数据写入一个已经被关闭的管道是将会收到SIGPIPE信号,默认情况下会使进程关闭。

信号是异步处理的经典实例。产生的信号事件对于进程而言是随机出现的,当出现该信号时,可以告诉内核按照3种方式之一进行处理。

  • 忽略这个信号。但是有两个信号是无法被截获并处理的:SIGKILL和SIGSTOP。
  • 捕获这个信号。通知内核在某种信号发生时,调用一个用户函数。
  • 执行系统默认动作。

3、函数signal

signal函数

#include 
void (*signal(int signo , void(*func)(int)))(int));
//返回值:若成功,返回以前的信号处理配置;若出错,返回SIG_ERR
  • signo参数是信号名
  • func值是常量SIG_IGN(忽略此信号)、SIG_DFL(系统默认动作)、或当接到此信号后要调用的函数的地址.如果为指定函数的地址,则在发生信号时,调用该函数,称之为信号处理函数或信号捕获函数。

以下是一个简单的信号处理函数,它捕获两个用户定义的信号并打印信号编号。

#include 
#include 
#include 
#include 
#include 

static void sig_usr(int);

int main()
{
    if (signal(SIGUSR1,sig_usr) == SIG_ERR)
    {
        printf("can't catch SIGNUSR1\n");
    }
    if (signal(SIGUSR2, sig_usr) == SIG_ERR)
    {
        printf("can't catch SIGUSR2\n");
    }

    for (; ;)
        pause();
}

static void sig_usr(int signo)
{
    if (signo == SIGUSR1)
        printf("received SIGUSR1\n");
    else if(signo == SIGUSR2)
        printf("received SIGUSR2\n");
    else
    {
        printf("reveived signal %d\n", signo);
    }
}

输出:&为在后台启动进程

ubuntu@VM-188-113-ubuntu:~/Code/apue/ch10Signal$ ./SignalProcess &
[1] 829
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch10Signal$ kill -USR1 829
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch10Signal$ received SIGUSR1
kill -USR2 829
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch10Signal$ received SIGUSR2
kill 829
[1]+  Terminated              ./SignalProcess
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch10Signal$ 
  • kill发送信号给进程,调用信号处理函数,打印出语句。

4、不可靠信号

早期的UNIX版本中,信号是不可靠的

5、中断系统调用

6、可重入函数

当捕捉到信号时,需要对其进行处理。进程正在执行的正常指令就会被信号处理程序临时中断,当信号处理程序返回时,会继续执行进程中的指令。此时可能破坏原来进程。
若一个程序或子程序可以“安全的被并行执行(Parallel computing)”,则称其为可重入(reentrantre-entrant)的。即当该子程序正在运行时,可以再次进入并执行它(并行执行时,个别的执行结果,都符合设计时的预期)。可重入概念是在单线程操作系统的时代提出的。一个子程序的重入,可能由于自身原因,如执行了jmp或者call,类似于子程序的递归调用;或者由于硬件中断,UNIX系统的signal的处理,即子程序被中断处理程序或者signal处理程序调用。重入的子程序,按照后进先出线性序依次执行。

若一个函数是可重入的,则该函数:
- 不能含有静态(全局)非常量数据。
- 不能返回静态(全局)非常量数据的地址。
- 只能处理由调用者提供的数据。
- 不能依赖于单实例模式资源的锁。
- 不能调用(call)不可重入的函数(有呼叫(call)到的函数需满足前述条件)。

多“用户/对象/进程优先级”以及多进程,一般会使得对可重入代码的控制变得复杂。同时,IO代码通常不是可重入的,因为他们依赖于像磁盘这样共享的、单独的(类似编程中的静态(Static)、全域(Global))资源。

如果函数是可重入的,说明该函数是异步信号安全的(async-signal safe)

  • 由于一个线程只有一个errno,在调用信号处理函数时,需要保存当前errno,调用完成后恢复原来的errno.

以下的例子中嗲用一个非可重入函数getpwnam

#include 
#include 
#include 
#include 
#include 
#include 

static void my_alarm(int signo)
{
    struct passwd* rootptr;

    printf("in signal handle\n");
    if ((rootptr = getpwnam("root")) == NULL )
    {
        printf("getpwnam(root) error\n");
    }

    alarm(1);

}


int main()
{
    struct passwd* ptr;

    signal(SIGALRM, my_alarm);
    alarm(1);
    for(;;)
    {
        if ((ptr = getpwnam("ubuntu")) == NULL)
            printf("getpwnam error\n");
        if (strcmp(ptr->pw_name, "ubuntu") != 0)
            printf("return value corrupted!, pw_name = %s\n", ptr->pw_name);
    }

    exit(0);
}
  • 由于调用了非可重入函数,结果是不可预知的。

7、SIGCLD语义

参考:http://blog.csdn.net/caianye/article/details/6453774

  • 现今的Unixs系统(包括Linux)都提供了可靠的信号机制.
  • Linux(RH7.2,kernel 2.4.7)上对SIGCLD的处理是: #define SIGCLD SIGCHLD.
  • 现在的程序大都使用SIGCHLD(POSIX也是采用的该信号),而不用SIGCLD.

8、可靠信号术语和语义

  • 当一个信号产生时,内核通常在进程表中以某种形式设置一个标志。
  • 当对信号采取了这种动作时,我们说向进程递送了一个信号。在信号产生(generation)和递送(delivery)之间的时间间隔内,称信号是未决的(pending),直到进程对此信号解除阻塞,或者将对此信号的动作更改为忽略。
  • 进程调用sigpending函数来判定哪些信号是设置为阻塞并处于未决状态的。
  • 如果在进程解除对某个信号的阻塞之前,这种信号发生了多次,那么将如何呢?POSIX.1允许系统递送信号一次或多次。如果递送该信号多次,则称这些信号进行了排队。但是除非支持POSIX.1实时扩展,否则大多数UNIX并不对信号排队,而只是递送这种信号一次。
  • 如果由多个信号要递送给一个进程,那将如何呢?POSIX.1并没有规定这些信号的递送顺序。但是POSIX.1基础部分建议:在其他信号之前递送与进程当前状态有关的信号。
  • 每一个进程都有一个信号屏蔽字 (signal mask),它规定了当前要阻塞递送到该进程的信号集。进程调用sigprocmask函数来检测和更改其当前信号屏蔽字。

9、函数killraise

11信号集

信号集是一个能表示多个信号的数据类型。如果定义sigset_t set;,那么set即为一个信号集合。

#include 
int sigemptyset(sigset_t *set);         //初始化:清空
int sigfillset(sigset_t *set);          //初始化:填满
int sigaddset(sigset_t *set, int signo);    //增
int sigdelset(sigset_t *set, int signo);    //删
//4个函数返回值:若成功,返回0;若出错,返回-1
int sigismember(const sigset_t *set, int signo);//成员
//返回值:若真,返回1;若假,返回0

以下程序测试信号集:

/*
 * 测试信号集
 * */

#include 
#include 
#include 
#include 
#include 

int main()
{
    sigset_t sigset;

    sigfillset(&sigset);    //填充所用信号

    if (sigismember(&sigset, SIGINT))
        printf("SIGINT exist int sigal set!\n");
    if (sigismember(&sigset,SIGTERM))
        printf("SIGTERM exits in signal_set!\n");
    if (sigismember(&sigset,SIGABRT))
        printf("SIGABRT exist in signal_set!\n");
    if (sigdelset(&sigset,SIGINT) < 0)
        perror("del error\n");
    else
        printf("SIGINT has been removed!\n");

    if (sigismember(&sigset,SIGINT))
        printf("SIGINT exist in signal_set!\n");
    else
        printf("SIGINT not exist in signal_set!\n");

    exit(0);
}

输出:

SIGINT exist int sigal set!
SIGTERM exits in signal_set!
SIGABRT exist in signal_set!
SIGINT has been removed!
SIGINT not exist in signal_set!

10函数sigprocmask

函数sigprocmask可以检测或更该或同时进行更该进程信号屏蔽字。

#include 
int sigprocmask(int how, const siget_t *restrict set,siget_t *restrict oset);
//返回值:若成功,返回0;若出错,返回-1
  • oset是非空指针,那么进程的当前信号屏蔽字通过oset返回
  • set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。
    • SIG_BLOCK 当前信号屏蔽字并上set指向的信号屏蔽字
    • SIG_UNBLOCK 当前信号屏蔽字去掉set
    • SIG_SETMASK set替换当前信号屏蔽字
  • 如果set为空,那么how的参数没有意义

13函数sigpending

sigpending函数返回在送往进程的时候被阻塞挂起的信号集合。这个信号集合通过参数set返回.

#include 
int sigpending(sigset_t *set);
//返回值:若成功,返回0;若出错,返回-1
#include 
#include 
#include 
#include 

//捕获函数
void sig_quit(int signo)
{
    printf("caught SIGQUIT\n");
    if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)
        printf("Can't reset SIGQUIT\n");
}

int main()
{
    sigset_t newmask, oldmask, pendmask;
//捕获SIGQUIT信号
    if (signal(SIGQUIT, sig_quit) == SIG_ERR)
        printf("Can't catch SIGQUIT.\n");

    sigemptyset(&newmask);
    sigaddset(&newmask, SIGQUIT);
    if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
        printf("SIG_BLOCK error\n");

    sleep(5);
//信号捕获后被阻塞,因此会被sigpending函数返回
    if (sigpending(&pendmask) < 0)
        printf("sigpending error\n");
    if (sigismember(&pendmask, SIGQUIT))
        printf("\nSIQGUIT pending\n");
//将信号屏蔽字设置为oldmask
    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        printf("SIG_SETMASK ERROR\n");
    printf("SIGQUIT unblocked \n");
    sleep(5);
    exit(0);
}

程序输出:

ubuntu@VM-188-113-ubuntu:~/Code/apue/ch10Signal$ ./a.out 
^\^\^\^\^\

SIQGUIT pending
caught SIGQUIT
SIGQUIT unblocked 
^\Quit

14函数sigaction

函数sigaction的功能是检查或修改指定信号相关联的处理动作,取代了早期的signal函数.

#include 
int sigaction(int signo, 
const struct sigaction *restrict act ,
struct sigaction *restrict oact);
//返回值:若成功,返回0;若出错,返回-1
  • signo是要检测或修改其具体动作的信号编号
  • act一个函数指针,如果不为空,则指定收到该信号后的行为
  • oact为非空,系统由oact指针返回该信号的上一个动作

[以下参考:http://www.cnblogs.com/wblyuyang/archive/2012/11/13/2768923.html]
sigaction结构体如下:

struct sigaction{
    void (*sa_handler)(int);/* addr of signal handler,*/
                            /*or SIG_IGN, OR SIG_DFL*/
    sigset_t sa_mask;       /*additional signals to block*/
    int sa_flags;           /*signal options, Figure 10.16*/
    /*alternate handler*/
    void (sa_sigaction)(int, siginfo_t *, void *);
}
  • sa_handler 是一个函数指针,其含义与 signal 函数中的信号处理函数类似。
  • sa_sigaction是另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。当 sa_flags 成员的值包含了 SA_SIGINFO 标志时,系统将使用 sa_sigaction 函数作为信号处理函数,否则使用 sa_handler 作为信号处理函数。注意:在某些系统中,成员 sa_handler 与 sa_sigaction 被放在联合体中,因此使用时不要同时设置。
  • sa_mask:成员用来指定在信号处理函数执行期间需要被屏蔽的信号,特别是当某个信号被处理时,它自身会被自动放入进程的信号掩码,因此在信号处理函数执行期间这个信号不会再度发生。
  • sa_flags:成员用于指定信号处理的行为,它可以是以下值的“按位或”组合。
    • SA_RESTART:使被信号打断的系统调用自动重新发起。
    • SA_ONSTACK:系统将在调用[[sigalstack|sigalstack]]替代信号栈上运行信号句柄;否则使用用户栈来交付信号。
    • SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。
    • SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程。
    • SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
    • SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
    • SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数。

使用的例子:

#include 
#include 
#include 
#include 
#include 

static void sig_usr(int signum)
{
    if (signum == SIGUSR1)
        printf("SIGUSR1 received.\n");
    else if (signum == SIGUSR2)
        printf("SIGUSR2 received.\n");
    else
        printf("signal %d received.\n", signum);

}

int main(void)
{
    char buf[512];
    int n;
    struct sigaction sa_usr;
    sa_usr.sa_flags = 0;
    sa_usr.sa_handler = sig_usr;
    sigaction(SIGUSR1, &sa_usr, NULL);
    sigaction(SIGUSR2, &sa_usr, NULL);
    printf("My PID is %d\n", getpid());

    while(1)
    {
        if ((n = read(STDIN_FILENO, buf, 511)) == -1)
        {
            if (errno == EINTR)
                printf("read is interrupted by sianal\n");
            else
            {
                buf[n] = '\0';
                printf("%d bytes read:%s\n", n, buf);
            }
        }
    }

    exit(0);
}
  • 在这个例程中使用 sigaction 函数为 SIGUSR1 和 SIGUSR2 信号注册了处理函数,然后从标准输入读入字符
  • 程序运行后输出自己的PID:My PID is 23252
  • 另一个终端输入:kill -USR1 23252,程序显示如下:
SIGUSR1 received.
read is interrupted by sianal
  • siginfo_t结构包含了信号产生原因的有关信息,该结构如下
struct siginfo {
    int      si_signo;        /* signal number */
    int      si_errno;        /* if nonzero, errno value from  */
    int      si_code;         /* additional info (depends on signal) */
    pid_t    si_pid;          /* sending process ID */
    uid_t    si_uid;          /* sending process real user ID */
    void    *si_addr;         /* address that caused the fault */
    int      si_status;       /* exit value or signal number */
    long     si_band;         /* band number for SIGPOLL */
    /* possibly other fields also */
};
  • 信号是SIGCHLD(子程序相关异常),则将设置si_pid、si_status和si_uid字段
  • 信号是SIGILL或SIGSEGV,则si_addr包含造成故障的根源地址,尽管该地址可能并不准确。
  • 信号是SIGPOLL,那么si_band字段将包含STREAMS消息的优先级(priority band),该消息产生POLL_IN、POLL_OUT或POLL_MSG事件。
  • si_errno字段包含错误编号,它对应于引发信号产生的条件,并由实现定义。

以下是signal函数的实现

Sigfunc* signal(int signo, Sigfunc func)
{
    struct sigaction act, oact;
    act.sa_handler = func;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;

    if (signo ==SIGALRM)
    {
#ifdef SA_INTERRUPT
        act.sa_flags |= SA_INTERRUPT;
#endif
    }
    else
        act.sa_flags |= SA_RESTART;

    if (sigaction(signo, &act, &oact) < 0)
       return SIG_ERR;
    return oact.sa_handler;
}

必须用sigemptyset函数初始化act结构的sa_mask成员。不能保证:act.sa_mask = 0;会做同样的事情。
对除SIGALRM以外的所有信号,我们都有尝试设置SA_RESTART标志,于是被这些信号中断的系统调用都能自动重启动。不希望重启动由SIGALRM信号中断的系统调用的原因是:我们希望对I/O操作可以设置时间限制。

15函数sigsetjmpsiglongjmp

  • 在信号处理程序中经常调用longjmp函数以返回到程序的主循环中。
  • 调用longjmp,当捕捉一个信号时,进入信号捕捉函数,此时当前信号被自动加到进程的信号屏蔽字中。这样后来产生的这种信号中断不能被捕获。
  • 为了解决这个问题,定义了两个新的函数sigsetjmpsiglongjmp。如果需要在信号处理程序中进行非局部转移,可以使用这两个函数.
#include 
int sigsetjmp(sigjmp_buf env, int savemask);
//返回值:若直接调用,返回0;若从siglongjmp调用返回,则返回非0
void siglongjmp(sigjmp_buf env, int val);
  • 如果savemask非0,则sigsetjmp在env中保存进程的当前信号屏蔽字。
  • 调用siglongjmp时,如果带非0savemasksigsetjmp调用已经保存了env,则siglongjmp从其中恢复保存的信号屏蔽字。
#include 
#include 
#include 
#include 
#include 
#include
#include 
#include

void pr_mask(const char* str)
{
    sigset_t sigset;
    int errno_save;

    errno_save = errno;     //保存的errno
    if (sigprocmask(0, NULL, &sigset) < 0)
    {
        printf("sigprocmask error!\n");
        exit(1);
    }
    else
    {
        printf("%s", str);
        if (sigismember(&sigset, SIGINT))
            printf("SIGINT");
        if (sigismember(&sigset, SIGQUIT))
            printf(" SIGQUIT");
        if (sigismember(&sigset, SIGUSR1))
            printf(" SIGUSR1");
         if (sigismember(&sigset, SIGUSR2))
            printf(" SIGUSR2");

        printf("\n");

    }
}

static void sig_usr1(int);
static void                  sig_alrm(int);
static sigjmp_buf            jmpbuf;
static volatile sig_atomic_t canjump;
int main(void)
{
    if(signal(SIGUSR1,sig_usr1)==SIG_ERR)
        printf("singnal SIGUSR1 error");
    if(signal(SIGALRM,sig_alrm)==SIG_ERR)
        printf("signal SIGALRM error");
    pr_mask("staring main : \n");
    if(sigsetjmp(jmpbuf,1))
    {
        pr_mask("ending main: \n");
        exit(0);
    }
    canjump=1;
    for(;;)
    {
        pause(); //终止保持信号接收
    }
}

static void sig_usr1(int siigno)
{
    time_t startime;
    if(canjump==0)
    {
        return ;
    }
    pr_mask("staring sig_usr1 function\n");
    alarm (3);
    startime=time(NULL);
    for(;;)
    {
        if(time(NULL)>startime +5)
            break;
    }
    pr_mask("finishing the function sig_usr1\n");
    canjump =0;
    siglongjmp(jmpbuf,1);//返回主函数同前一例中的longjmp作用类似

}
static void sig_alrm(int signo )
{
    pr_mask("int the function sig_alrm\n");
}

输出:

ubuntu@VM-188-113-ubuntu:~/Code/apue/ch10Signal$ ./a.out &
[1] 9387
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch10Signal$ staring main : 

kill -USR1 9387
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch10Signal$ staring sig_usr1 function
 SIGUSR1
int the function sig_alrm
 SIGUSR1
finishing the function sig_usr1
 SIGUSR1
ending main: 

//按回车输出
[1]+  Done                    ./a.out
  • 在信号处理程序中调用siglongjmp就应该使用这种技术:仅在调用sigsetjmp之后才将变量canjump设置为非0,在信号处理中检测此变量,仅为非0时才调用setlongjmp.这样防止sigsetjmp没有初始化jmpbuf就调用信号处理程序。
  • sig_atomic_t这种类型变量不会中断。

16函数sigsuspend

  • 该函数的作用是保护代码临界区,使其不被特定信号中断
  • 在一个原子操作中先恢复信号屏蔽字,然后使进程休眠

sigsuspend的函数如下:

#include 
int sigsuspend(const sigset_t *sigmask);
//返回值:-1,并将errno设置为EINTR
  • 进程的信号屏蔽字由sigmask指向的值。
  • 在捕捉到一个信号或发生了一个终止该进程的信号之前,该进程挂起。
  • 如果捕捉到一个信号而且从该信号处理程序返回,则sigsuspend返回,并且该进程的信号屏蔽字设置为调用sigsuspend之前的值。

一个保护代码临界区的例子:

#include 
#include 
#include 
#include 
#include 
#include 

void pr_mask(const char* str)
{
    sigset_t sigset;
    int errno_save;

    errno_save = errno;     //保存的errno
    if (sigprocmask(0, NULL, &sigset) < 0)
    {
        printf("sigprocmask error!\n");
        exit(1);
    }
    else
    {
        printf("%s", str);
        if (sigismember(&sigset, SIGINT))
            printf("SIGINT");
        if (sigismember(&sigset, SIGQUIT))
            printf(" SIGQUIT");
        if (sigismember(&sigset, SIGUSR1))
            printf(" SIGUSR1");
         if (sigismember(&sigset, SIGUSR2))
            printf(" SIGUSR2");

        printf("\n");

    }
}

static void sig_int(int signo)
{
    pr_mask("\nin sig_int: ");
}

int main()
{
    sigset_t newmask, oldmask, waitmask;

    pr_mask("program start: ");

    if (signal(SIGINT, sig_int) == SIG_ERR)
        printf("signal(SIGINT) error.\n");
    sigemptyset(&waitmask);
    sigaddset(&waitmask, SIGUSR1);
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGINT);

    //Block SIGINT and save current signal mask.
    if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
        printf("SIG_BLOCK ERROR.\n");

    pr_mask("in critical region: ");
//SIGINT信号被阻塞,将信号屏蔽恢复为原来的状态
    if (sigsuspend(&waitmask) != -1)
        printf("sigsuspend error.\n");

    pr_mask("after return from sigsuspend:");

    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        printf("SIG_SETMASK ERROR.\n");

    pr_mask("program exit: ");

    exit(0);
}
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch10Signal$ ./a.out 
program start: 
in critical region: SIGINT
^C
in sig_int: SIGINT SIGUSR1
after return from sigsuspend:SIGINT
program exit: 
  • sigsuspend返回时,它将信号屏蔽字设置为调用它之前的值
    在本例中:由于在调用sigsuspend时,SIGUSR1信号加到了进程信号屏蔽字中,所以当运行该信号时,我们得知该信号屏蔽字已经改变了。

以下例子是sigsuspend的另一种应用是等待一个信号处理程序设置一个全局变量。

#include 
#include 
#include 
#include 

volatile sig_atomic_t quitflag;

static void sig_int(int signo)
{
    if (signo == SIGINT)
        printf("\ninterrupt\n");
    else if(signo == SIGQUIT)
        quitflag = 1;
}

int main(void)
{
    sigset_t newmask, oldmask, zeromask;

    if(signal(SIGINT, sig_int) == SIG_ERR)
       printf("signal (SIGINT) error!\n");
    if (signal(SIGQUIT, sig_int) == SIG_ERR)
        printf("signal(SIGIQUIT) error.\n");

    sigemptyset(&zeromask);
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGQUIT);

    while(quitflag == 0)
        sigsuspend(&zeromask);
        //SIGQUIT 信号被捕获
    quitflag = 0;

    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        printf("SIG_SETMASK error!\n");

    exit(0);
}

程序输出:

ubuntu@VM-188-113-ubuntu:~/Code/apue/ch10Signal$ ./a.out 
^C
interrupt
^C
interrupt
^\ubuntu@VM-188-113-ubuntu:~/Code/apue/ch10Signal$

17函数abort

abort函数的功能是使程序异常终止

#include 
void abort(void);
//此函数不返回值
  • 此函数将SIGABRT信号发送给调用进程(进程不应该忽略此信号)
  • POSIX.1要求如果abrot调用终止进程并关闭所有流和冲洗。

abort的实现

#include 
#include 
#include 
#include 

void abort()
{
    sigset_t mask;
    struct sigaction action;

    sigaction(SIGABRT, NULL, &action);
    if (action.sa_handler == SIG_IGN)
    {
        action.sa_handler = SIG_DFL;
        sigaction(SIGABRT, &action, NULL);
    }

    if (action.sa_handler == SIG_DEL)
        fflush(NULL);

    sigfillset(&mask);
    sigdelset(&mask, SIGABRT);

    sigprocmask(SIG_SETMASK, &mask, NULL);
    kill(getpid(), SIGABRT);

    fflush(NULL);
    action.sa_handler = SIG_DEL;
    sigaction(SIGABRT, &mask, NULL);
    sigprocmask(SIG_SETMASK, &mask, NULL);
    kill(getpid(), SIGABRT);
    exit(1);
}

18函数system

参考:http://www.cnblogs.com/mickole/p/3187974.html

system()函数调用/bin/sh -c command执行特定的命令,阻塞当前进程直到command命令执行完毕.

函数原型:int system(const char *command);
函数返回值:如果无法启动shell运行命令,system将返回127;出现不能执行system调用的其他错误时返回-1。如果system能够顺利执行,返回那个命令的退出码。
system函数执行时,会调用fork、execve、waitpid等函数。

Linxu下的system函数源码:

int system(const char * cmdstring)
 {
     pid_t pid;
     int status;
     if(cmdstring == NULL){        
          return (1);
     }
     if((pid = fork())<0){
             status = -1;
     }
     else if(pid == 0){
         execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
         _exit(127); //子进程正常执行则不会执行此语句
        }
     else{
             while(waitpid(pid, &status, 0) < 0){
                 if(errno != EINTER){
                     status = -1;
                     break;
                 }
             }
         }
         return status;
 }

system()会调用fork()产生子进程,由子进程来调用/bin/sh-c string来执行参数string字符串所代表的命令,此命>令执行完后随即返回原调用的进程。在调用system()期间SIGCHLD 信号会被暂时搁置,SIGINT和SIGQUIT 信号则会被忽略。
返回值

  • =-1:出现错误
  • =0:调用成功但是没有出现子进程
  • >0:成功退出的子进程的id
    如果system()在调用/bin/sh时失败则返回127,其他失败原因返回-1。若参数string为空指针(NULL),则返回非零值。如果system()调用成功则最后会返回执行shell命令后的返回值,但是此返回值也有可能为 system()调用/bin/sh失败所返回的127,因此最好能再检查errno 来确认执行成功。

system函数对返回值的处理有三个阶段:

阶段1:创建子进程等准备工作。如果失败,返回-1。
阶段2:调用/bin/sh拉起shell脚本,如果拉起失败或者shell未正常执行结束(参见备注1),原因值被写入到status的低8~15比特位中。system的man中只说明了会写了127这个值,但实测发现还会写126等值。
阶段3:如果shell脚本正常执行结束,将shell返回值填到status的低8~15比特位中。

备注1:
只要能够调用到/bin/sh,并且执行shell过程中没有被其他信号异常中断,都算正常结束。
比如:不管shell脚本中返回什么原因值,是0还是非0,都算正常执行结束。即使shell脚本不存在或没有执行权限,也都算正常执行结束。
如果shell脚本执行过程中被强制kill掉等情况则算异常结束。

如何判断阶段2中,shell脚本子进程是否正常执行结束呢?系统提供了宏:WIFEXITED(status)。如果WIFEXITED(status)为真,则说明正常结束。
如何取得阶段3中的shell返回值?你可以直接通过右移8bit来实现,但安全的做法是使用系统提供的宏:WEXITSTATUS(status)。

由于我们一般在shell脚本中会通过返回值判断本脚本是否正常执行,如果成功返回0,失败返回正数。
所以综上,判断一个system函数调用shell脚本是否正常结束的方法应该是如下3个条件同时成立:
(1)-1 != status
(2)WIFEXITED(status)为真
(3)0 == WEXITSTATUS(status)
注意:
根据以上分析,当shell脚本不存在、没有执行权限等场景下时,以上前2个条件仍会成立,此时WEXITSTATUS(status)为127,126等数值。
所以,我们在shell脚本中不能将127,126等数值定义为返回值,否则无法区分中是shell的返回值,还是调用shell脚本异常的原因值。shell脚本中的返回值最好多1开始递增。

例子:

#include 
#include 
#include 

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

int main()
{
    int status;
    status = system("ls -l|wc -l");
    if (status == -1)
    {
            EXIT_ERR("system error");
    }
    else
    {
        if (WIFEXITED(status))
        {
            if (WEXITSTATUS(status)==0)
                printf("run command successful\n");
            else
                printf("run command fail and exit code is %d\n", WEXITSTATUS(status));

        }
        else
        {
            printf("exit status = %d\n", WEXITSTATUS(status));
        }
    }

    return 0;
}
16
run command successful

函数sleep, nanosleepclock_nanosleep

sleep函数的原型为:

#include 
unsigned int sleep(unsigned int seconds);
//返回值:0或为休眠完的秒数

此函数使调用进程挂起直到满足下面两个条件之一。

  1. 已经过了seconds所指定的墙上时钟时间。
  2. 调用进程捕捉到一个信号并从信号处理程序返回。

nanosleep函数与sleep函数类似,但提供了纳秒级的精度

#include 
int nanosleep(const struct timespec *reqtp, struct timespec *remtp);
//返回值:若休眠到要求的时间,返回0;若出错,返回-1

reqpt参数用秒和纳秒指定需要休眠的时间长度。如果某个信号中断了休眠间隔,程序并没有终止,remtp参数指向的timespec结构就会被设置为未休眠完的时间长度。如果对为休眠完的时间并不感兴趣,可以把该参数置为NULL;

随着多个系统时钟的引入,需要使用相对于特定时钟的延迟时间来挂起调用线程。clock_nanosleep函数提供了这种功能

#include 
int clock_nanosleep(clockid_t clock_id, int flags,
                    const struct timespec *reqtp, 
                    struct timespec *remtp);
//返回值:若休眠要求的时间,返回0;如出错,返回错误号
  • clock_id参数指定了计算延迟时间基于的时钟
  • flags参数用于控制延迟是相对的还是绝对的。flags为0时表示休眠时间是相对的(希望休眠的时间长度),如果flags值设置为TIMER_ABSTIME,表示休眠时间是绝对的(希望休眠到时钟到达某个特定的时间)。
  • reqpt与remtp参数与nanosleep函数中相同。

20、函数sigqueue

参考:http://www.cnblogs.com/mickole/p/3191804.html

在POSIX.1的实时扩展中,一些系统开始对信号排队的支持。
使用排队信号必须做以下几个操作

  • sigaction函数安装信号处理程序时指定SA_SIGINFO标志
  • sigaction结构体中的成员中提供信号处理程序
  • 使用sigqueue发送信号。

函数原型:

#include 
#include 
int sigqueue(pid_t pid, int sig, const union sigval val)
调用成功返回 0;否则,返回 -1
  • sigqueue()是比较新的发送信号系统调用,主要是针对实时信号提出的(当然也支持前32种),支持信号带有参数,与函数sigaction()配合使用。
  • sigqueue的第一个参数是指定接收信号的进程ID,第二个参数确定即将发送的信号,第三个参数是一个联合数据结构union sigval,指定了信号传递的参数
typedef union sigval {
               int  sival_int;
               void *sival_ptr;
}sigval_t;
  • sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。如果signo=0,将会执行错误检查,但实际上不发送任何信号,0值信号可用于检查pid的有效性以及当前进程是否有权限向目标进程发送信号。

例子:给自己发数据

//use sigqueue()

#include 
#include 
#include 
#include 

void sighandler(int signo, siginfo_t *info, void *ctx)
{
    //以下两种方式都能获得sigqueue发送的数据
    printf("receive the data by info->si_int: %d\n", info->si_int);
    printf("receive the data by info->si_value.sival_int:%d\n", info->si_value.sival_int);
}

int main()
{
    struct sigaction act;
    act.sa_sigaction = sighandler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_SIGINFO;  //信号传递开关
    if (sigaction(SIGINT, &act, NULL) == -1)
    {
        perror("sigaction error!");
        exit(1);
    }
    sleep(2);
    union sigval mysigval;
    mysigval.sival_int = 1000;
    if (sigqueue(getpid(), SIGINT, mysigval) == -1)
    {
        perror("sigqueue error!");
        exit(1);
    }

    return 0;
}

运行结果:

receive the data by info->si_int: 1000
receive the data by info->si_value.sival_int:1000

例子:发送和接受信号

发送

//send datat use sigqueue()

#include 
#include 
#include 
#include 

int main(int argc, char** argv)
{
    if (argc != 2)
    {
        fprintf(stderr, "usage:%s pid\n", argv[0]);
        exit(1);
    }
    pid_t pid = atoi(argv[1]);
    sleep(2);
    union sigval mysigval;
    mysigval.sival_int = 100;
    printf("sending SIGINT signal to %d ......\n", pid);
    if (sigqueue(pid, SIGINT, mysigval) == -1)
    {
        perror("sigqueue error!");
        exit(1);
    }

    return 0;
}

接受:

//receive data use sigqueue()

#include 
#include 
#include 
#include 

void sighandler(int signo, siginfo_t *info, void *ctx)
{
    //以下两种方式都能获得sigqueue发送的数据
    printf("receive the data by info->si_int: %d\n", info->si_int);
    printf("receive the data by info->si_value.sival_int:%d\n", info->si_value.sival_int);
}

int main()
{
    struct sigaction act;
    act.sa_sigaction = sighandler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_SIGINFO;  //信号传递开关
    if (sigaction(SIGINT, &act, NULL) == -1)
    {
        perror("sigaction error!");
        exit(1);
    }
    for (;;)
    {
        printf("waiting a SIGINT signal...\n");
        pause();
    }
    return 0;
}
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch10Signal$ ps -ef|grep rev|grep -v /usr | grep -v grep|awk '{print $2}'
21045
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch10Signal$ ./send 21045
sending SIGINT signal to 21045 ......
waiting a SIGINT signal...
receive the data by info->si_int: 100
receive the data by info->si_value.sival_int:100
waiting a SIGINT signal...

21、作业控制信号

6个作业控制信号如下:

  • SIGCHLD子进程已经停止或终止
  • SIGCONT如果进程停止,则使其继续运行
  • SIGSTOP停止信号
  • SIGTSTP交互式停止信号
  • SIGTIN后台进程组成员读控制终端
  • SIGTOU后天进程组成员写控制终端

说明:

  • 除了SIGCHLD以外,大多数应用程序都不处理这些信号,交互式shell会处理这些信号的所用工作。
  • 当键入挂起字符(Ctrl+Z)时,SIGTSTP被送至所有的前台进程组的进程
  • 当通知shell在前台或后台恢复一个运行的作业时,shell想该作业中的所有进程发送SIGCOUT信号。

22、信号名和管道

本节介绍如何在信号编号和信号名之间进行映射。

  • extern char* sys_siglist[]数组下标是信号编号,数组中的元素是指向信号名字符串的指针
  • psignal函数可以打印与信号编号对应的字符串
#include 
void psignal(int signo, const char *msg);
  • 字符串msg输出到标准错误文件,后面跟随一个冒号和一个空格,再后面对该信号的说明,最后是一个换行符。如果msg为NULL,只有信号说明部分输出到标准错误文件。
  • 如果在sigaciton信号处理程序中有siginfo结构,可以使用psiginfo函数打印信号信息。
#include 
void psiginfo(const siginfo_t *info, const char *msg); 

psignal类似

  • 如果只需要信号的字符描述部分,也不需要把它写到标准错误文件中,可以使用strsignal函数。
#include 
char *strsignal(int signo);
//返回值:指向描述该信号的字符串的指针

你可能感兴趣的:(UNIX环境高级编程)