[APUE] 再读之信号

1 . 每个信号都是以SIG 开头的正整数
2. 信号发生条件 用户敲击终端,比如按下ctrl+c (SIGINT) 发给所有前台进程组, ctrl+z 发 SIGSTOP 挂起前台进程,ctrl+\ 发SIGQUIT 给进程。而 ctrl+D 只是发EOF给终端
硬件异常。 比如遇到除以0, 或者内存访问越界的情况。
kill 函数发相应的信号
软件条件发生。 比如向一个已经关闭的管道写数据会产生SIGPIPE,以及闹钟信号SIGALRM。或者网络上传来一个非规定波特率的数据,SIGURG

#include 
#include 

static void sig_alrm(int signo)
{
    printf("Caught alarm signal\n");
}


int main()
{
    if (signal(SIGALRM,sig_alrm)==SIG_ERR)
    {
        printf("Register SIGALRM failed\n");
        return -1;
    }
    //register one second alarm
    alarm(1);
    pause();
    printf("Exit main\n");
}
//输出
//Caught alarm signal
//Exit main
SIGPIPE的例子

#include 
#include 
#include 


static void sig_pipe(int signo)
{
    printf("Caught signal pipe signal\n");
}
int main()
{
    int fd[2];
    if(pipe(fd)<0)
    {
        printf("Create pipe failed\n");
        exit(-1);
    }
    pid_t pid;
    pid = fork();
    if (pid<0)
    {
        printf("fork error\n");
    }
    else if (pid>0)
    {
        close(fd[0]);
        // register pipe signal
        if(signal(SIGPIPE,sig_pipe)==SIG_ERR)
        {
            printf("register SIGPIPE error\n");
            sleep(1);
            exit(-1);
        }
        sleep(1);
        printf("Parent Prcess begin\n");
        //try to write a closed pipe
        if(write(fd[1],'c',1)!=1)
            printf("write pipe error\n");
        close(fd[1]);
        printf("Parent Prcess end\n");
    }
    else
    {
        printf("Child Prcess begin\n");
        //here close read and write channel
        close(fd[0]);
        close(fd[1]);
    }
}
输出

Child Prcess begin
Parent Prcess begin
Caught signal pipe signal
write pipe error
Parent Prcess end

Kill 函数的例子

#include 
#include 

static void sig_usr1(int signo)
{
    printf("Get signal user1 \n");
}
int main()
{
    if (signal(SIGUSR1, sig_usr1)==SIG_ERR)
    {
        printf("Register sig user1 failed\n ");
    }

    kill(getpid(),SIGUSR1);
    printf("End of main\n");
}
3. 对待信号的三种方式: 捕抓,忽视或者系统默认。 SIGKILL, SIGSTOP不可忽略。

4. Signal 函数 void(* signal(int signo, void(*func)(int)  )  ) (int), 好复杂。

typedef  void Sigfunc (int) 

然后再定义 Sigfunc* signal(int signo, Sigfunc*)

5. 程序后台启动时候shell 自动对SIGINT 和SIGQUIT 选择忽略

6. 进程创建时候子进程复制父进程的信号处理方式

#include 
#include 
#include 

void sig_quit(int signo)
{
    printf("caught SIGQUIT \n");
}

int main()
{

    if (signal(SIGQUIT,sig_quit)==SIG_ERR)
    {
        printf("Register sigquit error\n");
    }

    pid_t pid ;
    pid = fork();

    if (pid< 0)
    {
        printf("fork error\n");
        exit(-1);
    }
    else if(pid>0)
    {
        while(1)
            pause();
    }
    else
    {
        while(1)
            pause();
    }

}
输入ctrl+\  时候会得到两行,分别为子进程和父进程产生的

caught SIGQUIT 
caught SIGQUIT 

7. 中断的系统调用。

系统调用分为低速系统调用和其他系统调用。低速系统调用,信号发生时, 其不再执行。

低速系统调用包括:

a. pause 函数

b. 读某些文件,数据不存在;如果某进程为读打开FIFO,且此时没有为写的进程打开该FIFO

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

#define FIFO_NAME "/tmp/fifotest"
#define BUF_LEN  512

int main()
{
    int res;
    extern int errno;
    res = mkfifo(FIFO_NAME, O_CREAT| O_EXCL);
    if ((res<0) && (errno!= EEXIST))
    {
        printf("We get an error %d, reason %s\n", errno, strerror(errno));
    }

    int fd = open(FIFO_NAME,O_RDONLY,0);
    char buf[BUF_LEN];
    int n;

    if ((n=read(fd, buf,BUF_LEN))<0)
    {
        printf("read error %d, reason %s\n", errno, strerror(errno));
    }
    buf[n]= '\0';
    printf("read data from fifo: %s\n", buf);

}
上面代码将一直阻塞,一直等到有其他程序写fifo为止。

下面代码将写相应的fifo,运行下面代码可使得上面读fifo的进程激活,并输出内容。

#include 
#include 
#include 
#include 
#include 

#define FIFO_NAME "/tmp/fifotest"

int main()
{
    int fd;
    fd = open(FIFO_NAME, O_WRONLY,0 );

    char* buf = "Greeting from fifo message\n";

    if(write(fd, buf, strlen(buf))!= strlen(buf))
    {
        printf("Write error. errno= %d, reason is : %s\n", errno, strerror(errno));
    }

}



c. 写某些文件,不能立即接受这些数据;打开文件时; 某种ioctl函数;某些进程间通信函数。

8. 可再入函数

典型的不可再入函数包括 a)malloc, free b) 操作静态区内容的函数, 比如getpwnam c)标准IO 函数

9. 信号未决

信号未决指的是信号产生,却还没有传递到进程的状态。

如果一个信号发生多次,那最终进程接收信号时,只会被传递一次。

10. Kill ,raise函数

Kill函数参数: a) pid>0, 则发送给对应pid进程。 b) pid == 0 , 发送信号给所在进程的进程组所有进程 c) pid< 0, 发送给绝对值等于PID的进程组中所有进程

#include 
#include 

static void sig_usr1(int signo)
{
    printf("Get signal user1 \n");
}

int main()
{
    if (signal(SIGUSR1, sig_usr1)==SIG_ERR)
    {
        printf("Register sig user1 failed\n ");
    }

    kill(getpid(),SIGUSR1);
    printf("Test kill pid > 0 senario done\n");

    pid_t pid;
    if ((pid=fork())<0)
    {
        printf("Fork error\n ");
    }

    else if (pid> 0)
    {
        printf("pid = %d, group id = %d\n", getpid(), getpgrp());
        while(1)
            pause();
    }
    else
    {
        printf("pid = %d, group id = %d\n", getpid(), getpgrp());
        kill(0,SIGUSR1);
    }
}
测试了kill pid>0 和pid=0的情况

输出为:

Get signal user1 
Test kill pid > 0 senario done
pid = 28220, group id = 28220
pid = 28221, group id = 28220
Get signal user1 
Get signal user1 

11. alarm 和pause 函数

alarm 提示系统多长时间向进程发送SIGALRM信号. alarm 函数后注册的闹钟会把前面注册的给覆盖掉。比方说下面的例子,printf两次之间应该只是相隔一秒钟时间。

time now is 1440655538
Caught alarm signal
time now is 1440655539

#include 
#include 
#include 

static void sig_alrm(int signo)
{
    printf("Caught alarm signal\n");
}


int main()
{
    if (signal(SIGALRM,sig_alrm)==SIG_ERR)
    {
        printf("Register SIGALRM failed\n");
        return -1;
    }
    //register one second alarm
    printf("time now is %d\n", time(NULL));
    alarm(2);
    alarm(1);
    pause();
    printf("time now is %d\n", time(NULL));

pause函数则阻塞进程,直到进程得到信号。

用alarm, pause 和setjmp 设置的sleep 函数。缺点是当和其他信号发生冲突时,会有些问题。

#include 
#include 
#include 
static jmp_buf buf;
static void sig_alrm(int signo)
{
    longjmp(buf,1);
}

int sleep2(unsigned int seconds)
{
    if (signal(SIGALRM, sig_alrm)==SIG_ERR)
    {
        printf("Register SIGALRM failed\n");
    }

    alarm(seconds);
    if(!setjmp(buf))
    {
        printf("set jump first return\n");
        pause();
    }
    else
    {
        printf("Set jump returned\n");
    }

    return seconds;
}
int main()
{
    sleep2(1);
}

下面代码,在5秒总之内发生SIGINT 信号,将导致sig_int后面的语句无法执行完成。

#include 
#include 
#include 
static jmp_buf buf;
static void sig_alrm(int signo)
{
    longjmp(buf,1);
}

int sleep2(unsigned int seconds)
{
    if (signal(SIGALRM, sig_alrm)==SIG_ERR)
    {
        printf("Register SIGALRM failed\n");
    }

    alarm(seconds);
    if(!setjmp(buf))
    {
        printf("set jump first return\n");
        pause();
    }
    else
    {
        printf("Set jump returned\n");
    }

    return seconds;
}
static void sig_int(int signo)
{
    printf("Sum is 0\n");
    volatile int sum;
    int i;
    for(i=0;i<2000000000;i++)
        sum += i*i;
    printf("Sum is %d\n", sum);
}
int main()
{
    if(signal(SIGINT,sig_int)==SIG_ERR)
    {
        printf("Register SIGINT failed\n");
    }
    sleep2(5);
}

12 信号集sigset_t

int sigemptyset(sigset_t * )

int sigfillset(sigset_t *)

int sigdelset(sigset_t* ,int signo)

int sigaddset(sigset_t* ,int signo)

int sigismember(const sigset_t *, int signo)

int sigprocmask(int how, const sig_set* new, sig_set* old)

sigprocmask是个好函数,一个函数既可以获取当前信号屏蔽字,也可以设置信号屏蔽。

how 等于SIG_BLOCK, SIG_UNBLOCK,SIG_SETMASK。前面为取并,第二个为取交,最后一个直接设置。 打印当前屏蔽字的demo

#include 
#include 
#include 
#include 

void printmask()
{
    int result;
    sigset_t new,old;

    if (sigprocmask(SIG_BLOCK,NULL, &old)<0)
    {
        printf("Sigprocmast failed. errno = %d, error is: %s\n", errno, strerror(errno));
        return;
    }

    if (sigismember( &old,SIGALRM))
        printf("sig_alrm has been in signal mask\n");
    printf("End of print mask\n");
}

int main()
{
    sigset_t new,old;
    if(sigemptyset(&new)<0 || sigemptyset(&old)<0 || sigaddset(&new, SIGALRM)<-1)
    {
        printf("sigemtpy set error. errno = %d, error is : %s\n",errno,strerror(errno));
        return -1;
    }

    if (sigprocmask(SIG_BLOCK,&new, &old)<-1)
    {
        printf("sigprocmask set error. errno = %d, error is : %s\n",errno,strerror(errno));
        return -1;
    }
    printmask();
}

int sigpending(sigset_t*) 正确返回0,未决信号存储在参数中。下面代码执行后,中间按两次ctrl + c,得到结果

SIGINT is in pending status
Caught sig int 

#include 
#include 
#include 
#include 

static void sig_int(int signo)
{
    printf("Caught sig int \n");
}

int main()
{
    sigset_t new,old;
    sigset_t pending;
    if (signal(SIGINT,sig_int) ==SIG_ERR)
    {
        printf("signal error\n");
        return;
    }
    if(sigemptyset(&new)<0 || sigemptyset(&old)<0 || sigemptyset(&pending)<0 || sigaddset(&new, SIGINT)<-1)
    {
        printf("sigemtpy or  sigemptyset error. errno = %d, error is : %s\n",errno,strerror(errno));
        return -1;
    }

    if (sigprocmask(SIG_BLOCK,&new, &old)<-1)
    {
        printf("sigprocmask set error. errno = %d, error is : %s\n",errno,strerror(errno));
        return -1;
    }
    sleep(5);

    if (sigpending(&pending)<-1)
    {
        printf("sigpending error. errno = %d, error is : %s\n",errno,strerror(errno));
        return -1;
    }
    if (sigismember(&pending, SIGINT))
        printf("SIGINT is in pending status\n");

    if (sigprocmask(SIG_SETMASK,&old, NULL)<-1)
    {
        printf("sigprocmask set error. errno = %d, error is : %s\n",errno,strerror(errno));
        return -1;
    }
}

13. sigaction 函数

int sigaction(int signo, const sigaction* act, sigaction* oact)

struct sigaction

{

void (*sighandler)(); //signal process function

sigset_t mask; //mask 为信号屏蔽字,在sighandler 函数返回之前,会屏蔽信号。返回后再解除屏蔽,这样做的目的是保证sighandler的执行不会受到影响。

int sa_flags;

};

14. sigsetjmp, setjmp, setlongjmp 和longjmp

siglongjmp 从信号函数跳出后会恢复信号屏蔽字,而longjmp从信号函数跳出后则不会恢复信号。

下面代码,当用sig开头函数时,每次向进程发送SIGUSR1信号时都会输出一次:

Get signal usr1
Returned from sigsetjmp

而不用sig开头函数时,只有第一次向进程发送SIGUSR1才会输出:

Get signal usr1
Returned from sigsetjmp

#include 
#include 
#include 
#include 
#include 
#include 
static jmp_buf buf;

static void sig_usr(int signo)
{
    printf("Get signal usr1\n");
    //siglongjmp(buf,1);
    longjmp(buf,1);
}

int main()
{
    if (signal(SIGUSR1,sig_usr)==SIG_ERR)
    {
        printf("Register singal error %d. reason:%s\n", errno, strerror(errno));
        exit(-1);
    }

    //if(!sigsetjmp(buf,1))
    if(!setjmp(buf))
    {
        printf("sigsetjmp buf\n");
    }
    else
    {
        printf("Returned from sigsetjmp\n");
    }
    while(1)
        pause();
}

15. sigsuspend 函数

 解决pause会丢失信号,从而造成进程永远阻塞问题。sigsuspend相当于unblock 信号mask 和pause()的原子操作。如下程序kill 信号SIGINT将立即唤醒进程。
#include 
#include 
#include 
#include 
#include 
static void sig_usr1(int signo)
{
    printf("Get signal\n");
}
int main()
{
    printf("Begin main()\n");
    if (signal(SIGUSR1,sig_usr1)==SIG_ERR ||signal(SIGINT,sig_usr1)==SIG_ERR   )
    {
        printf("Register sig_usr1 failed. errno=%d, error = %s", errno,strerror(errno));
        return -1;
    }
    sigset_t oldset, newset, pendmask;
    if(sigemptyset(&oldset)<0 || sigemptyset(&newset)<0 ||sigemptyset(&pendmask)<0  ||  \
        sigaddset(&newset,SIGUSR1)<0 ||sigaddset(&newset,SIGINT)<0 || sigaddset(&pendmask,SIGUSR1)<0)
    {
        printf("sigemtpyset or sigaddset error. errno=%d, error = %s", errno,strerror(errno));
        return -1;
    }
    if (sigprocmask(SIG_BLOCK,&newset,&oldset)<0)
    {
        printf("sigprocmask error. errno=%d, error = %s", errno,strerror(errno));
        return -1;
    }
    printf("Critical section\n");
    sleep(5);
    sigsuspend(&pendmask);
    printf("Awake by signal");
    if (sigprocmask(SIG_SETMASK,&oldset,NULL)<0)
    {
        printf("sigprocmask error. errno=%d, error = %s", errno,strerror(errno));
        return -1;
    }
}

16. abort函数

书上的abort函数有些啰嗦,不懂后面哪一段代码有什么必要。懂的同学可以评论下,让我知道。

#include 
#include 
#include 
#include 
#include 
#include 
void mabort(void)
{
    struct sigaction action;
    sigset_t set,oldset;
    if(sigaction(SIGABRT,NULL,&action)<0)
    {
        printf("sigaction error:%d, reason: %s\n", errno,strerror(errno));
        exit(-1);
    }
    if(action.sa_handler==SIG_IGN)
    {
        action.sa_handler == SIG_DFL;
        sigaction(SIGABRT,&action,NULL);
    }

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

    if(sigfillset(&set)<0 || sigdelset(&set,SIGABRT)<0)
    {
        printf("sigfillset error:%d, reason: %s\n", errno,strerror(errno));
        exit(-1);
    }

    if(sigprocmask(SIG_SETMASK,&set,NULL)<0)
    {
        printf("sigaction error:%d, reason: %s\n", errno,strerror(errno));
        exit(-1);
    }
    kill(getpid(),SIGABRT);
    printf("we are here\n");
	    printf("we are here\n");                                                                                                                                  
    fflush(NULL);
}
int main()        
{                                                                                                                                                             
    printf("we are in main\n");
    mabort();
    printf("we are end main\n");
    return;
}
        

17 system 函数

POSIX 2.0 要求system 函数忽略SIGINT , SIGQUIT,并且阻塞SIGCHLD。测试代码如下:

#include 
#include 
#include 
#include 
#include 
int mysystem(const char* cmd)
{
    struct sigaction ignore,saveintptr, savequitptr;
    ignore.sa_handler = SIG_IGN;
    sigemptyset(&ignore.sa_mask);
    pid_t pid;
    int status;
    //ignore sigint and sigquit
    if (sigaction(SIGINT,&ignore,&saveintptr)<0 || sigaction(SIGQUIT,&ignore, &savequitptr)< 0 )
    {
        printf("sigaction failed. errno: %d, reason: %s",errno, strerror(errno));
        return -1;
    }
    sigset_t childmask,savemask;

    if (sigemptyset(&childmask)<0 || sigaddset(&childmask,SIGCHLD)<0)
    {
        printf("sigemtpyset failed. errno: %d, reason: %s",errno, strerror(errno));
        return -1;
    }

    if (sigprocmask(SIG_BLOCK,&childmask,&savemask)<0)
    {
        //printf("sigaction failed. errno: %d, reason: %s",errno, strerror(errno));

        return -1;
    }
    if((pid=fork())<0)
    {
        printf("fork error. errno: %d, reason: %s",errno, strerror(errno));
        return -1;
    }
    else if(pid==0)
    {
        //child
        sleep(5);
        sigaction(SIGINT,&saveintptr,NULL);
        sigaction(SIGQUIT,&savequitptr,NULL);
        sigprocmask(SIG_SETMASK,&savemask,NULL);
        execl("/bin/sh","sh","-c", cmd, (char*)0);
        //why not print this, because, execl with no return if succeed
        printf("returning child %d\n", pid);
        fflush(NULL);
        _exit(127);
    }
    else
    {
        //here should be blocked, just while in case of error
        while(waitpid(pid, &status,0) <0)
        {
            printf("wait pid is %d\n", pid);
            //why we use EINTR, EINTR is for slow system, which get signal, here in case of execl for slow function
            if(errno!=EINTR)
            {
                status =-1;
                break;
            }
        }
        //waitpid(pid, &status,0) ;
        printf("waiting %d\n", pid);
    }
    sigaction(SIGINT,&saveintptr,NULL);
    sigaction(SIGQUIT,&savequitptr,NULL);
    sigprocmask(SIG_SETMASK,&savemask,NULL);
    return status;
}

int main()
{
    int status = mysystem("ls -lt");
    printf("status is %d\n", status);
}
18 sleep 函数实现

#include 
#include 

static void sig_alrm()
{
    return;
}
unsigned int msleep(unsigned int nsecs)
{

    sigset_t pendmask,oldmask,susmask;
    struct sigaction action,oldaction;
    action.sa_handler = sig_alrm;
    if(sigemptyset(&action.sa_mask)<0)
        return -1;
    if(sigaction(SIGALRM,&action,&oldaction)<0)
        return -1;

    sigemptyset(&pendmask);
    sigaddset(&pendmask, SIGALRM);
    sigprocmask(SIG_BLOCK,&pendmask,&oldmask);
    alarm(nsecs);

    susmask = oldmask;
    sigdelset(&susmask,SIGALRM);
    sigsuspend(&susmask);
    int uslept = alarm(0);
    sigaction(SIGALRM,&oldaction,NULL);
    sigprocmask(SIG_BLOCK,&oldmask,NULL);

    return uslept;
}

int main()
{
    msleep(5);
}




你可能感兴趣的:([APUE] 再读之信号)