Unix环境高级编程信号续

Posted AlanTu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unix环境高级编程信号续相关的知识,希望对你有一定的参考价值。

1、signal函数

  Unix系统的信号机制最简单的接口是signal函数,函数原型如下:

  #include <signal.h>
  typedef void (*sighandler_t)(int);
  sighandler_t signal(int signum, sighandler_t handler);

signum表示信号名称,handler取值常量SIG_IGN(忽略此信号)、常量SIG_DFL(执行默认动作)或者接到此信号后要调用的函数的地址(调用信号处理程序)。

写个程序练习一下signal函数的使用,程序的功能是捕获子进程退出。程序如下:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <sys/wait.h>
 4 #include <unistd.h>
 5 #include <sys/types.h>
 6 #include <errno.h>
 7 #include <signal.h>
 8 //信号处理程序
 9 void sig_child(int signo)
10 {
11     int status;
12     if(waitpid(-1,&status,0) != -1)
13         printf("child process exit.status= %d\\n",status);
14     else
15     {
16         perror("waitpid() error");
17         exit(-1);
18     }
19 }
20 
21 int main()
22 {
23     pid_t  pid;
24     //创建子进程退出信号
25     signal(SIGCHLD,sig_child);
26     if((pid=fork()) == -1)
27         perror("fork() error");
28     else if(pid == 0)
29     {
30         printf("I am child process.\\n");
31         exit(0);
32     }
33     else
34     {
35         sleep(3);  //让子进程先执行
36         printf("I am parent process.\\n");
37     }
38     exit(0);
39 }

程序执行结果如下:

2、中断的系统调用

  如果进程在执行一个低速系统调用而阻塞期间捕获到一个信号,则该系统调用就被中断不再继续执行。系统调用返回出错,将errno的值设置为EINTR。当一个信号发生时,进程捕捉到,意味着已经发生了某种事情,可以唤醒阻塞系统调用进行相关操作。低速系统调用是可能使进程永远阻塞的一类系统调用。与被中断的系统调用相关的问题是必须显示的处理出错返回。例如在进行多操作时候,多的过程被中断了,中断结束后希望重新启动读操作。代码如下:

复制代码
1 again:
2     if((n=read(fd,buf,BUFSIZE))<0)
3    {
4        if(errno == EINTR)
5           goto again;
6        /*handle other errnors*/
7     }
复制代码

自动重新启动的系统调用包括:ioctl、read、readv、write、writew、wait和waitpid。
3、可重入函数

  进程捕捉到信号并对其进行处理,进程正在执行的指令序列就被信号处理程序临时中断,首先执行该信号处理程序中的指令,如果从信号处理程序返回,则继续在捕捉到信号时进程正在执行的正常指令中返回。在信号处理程序中,不能判断捕捉到信号时进程在何处执行,这样不能保证在中断处理结束后能够正确返回到进程的执行指令中。为了保证进程在处理完中断后能够正确返回,需要保证调用的是可重入的函数。不可重入函数包括:(1)使用静态数据结构,(2)调用malloc或free,(3)标准I/O函数。信号处理程序中调用一个不可重入的函数,则结果是不可预测的。例如getpwnam函数是个不可重入的,因为其结果存放在静态存储单元中,信号处理程序调用后,返回给正常调用者的信息可能是被返回给信号处理程序的信息覆盖。程序如下:

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <signal.h>
#include <pwd.h>
#include <string.h>

static void my_alarm(int signo)
{
    struct passwd *rootptr;
    printf("in signal handler.\\n");
    if ((rootptr = getpwnam("root")) == NULL)
    {
        perror("getpwnam() error");
        exit(-1);
    }
    printf("return value:,pw_name = %s\\n",rootptr->pw_name);
    alarm(1);
}

int main()
{

    struct passwd  *ptr;
    signal(SIGALRM,my_alarm);
    alarm(1);
    while(1)
    {
        if((ptr = getpwnam("anker")) == NULL)
        {
             perror("getpwnam() error");
            exit(-1);
        }
        if(strcmp(ptr->pw_name,"anker") != 0)
            printf("return value corrupted!,pw_name = %s\\n",ptr->pw_name);
        else
            printf("return value not corrupted!,pw_name = %s\\n",ptr->pw_name);

    }
}
复制代码

编译执行程序,没有发现出现什么异常,此处有些不明白,日后过来想想。
4、kill和raise函数

  kill函数将信号发送给进程或者进程组,raise函数则允许进程向自身发送信号。进程将信号发送给其他进程需要权限,超级用户可以将信号发送给任一进程。调用kill产生的信号时不被阻塞的。函数原型如下:

  #include <sys/types.h>
  #include <signal.h>
  int kill(pid_t pid, int sig);
  int raise(int sig);    //等价于kill(getpid(),signo)

5、alarm和pause函数

  使用alarm函数设置计时器,在将来某个指定时间该计时器会超时,产生SIGALRM信号,默认动作时终止调用alarm函数的进程,每个进程只能有一个闹钟时钟,如果在调用alarm之前已设置过闹钟时间,则任何以前的闹钟时间都被新值所代替当在调用alarm()前已经设置了一个闹钟,那么我们可以调用alarm(0)来取消此闹钟,并返回剩余时间。puase函数使调用进程挂起直到捕捉到一个信号。只有执行了一个信号处理程序并从其返回时,pause才返回-1,并将errno设置为EINTR。函数原型如下:

  #include <unistd.h>
  unsigned int alarm(unsigned int seconds);  //返回0或者以前设置的闹钟时间的余留秒数
  int pause(void);

写个程序练习以上函数,程序如下:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <sys/wait.h>
 4 #include <unistd.h>
 5 #include <sys/types.h>
 6 #include <errno.h>
 7 #include <signal.h>
 8 static void sig_kill(int signo)
 9 {
10     printf("Received from kill().\\n");
11 }
12 static void sig_alarm(int signo)
13 {
14     printf("Receiver form aralm().\\n");
15 }
16 int main()
17 {
18 
19     signal(SIGHUP,sig_kill);
20     signal(SIGALRM,sig_alarm);
21 
22     printf("kill() is called.\\n");
23     kill(getpid(),SIGHUP);
24     printf("alarm() is called.\\n");
25     alarm(3);
26     printf("pause() is called.\\n");
27     pause();
28     printf("raise() is called.\\n");
29     raise(SIGHUP);
30     exit(0);
31 }

 程序执行结果如下:

alarm只设定一个闹钟,时间到达并执行其注册函数之后,闹钟便失效。如果想循环设置闹钟,需在其注册函数中在调用alarm函数。写个程序可以设置一个循环闹钟,使得程序每个多少秒执行以下,执行完成之后开始计时,时间都接着执行,循环指定主程序结束。程序如下:

 1 #include <unistd.h>
 2 #include <signal.h>
 3 #include <stdio.h>
 4 void sig_alarm(int signo)
 5 {
 6     printf("Alarming.\\n");
 7     signal(SIGALRM, sig_alarm); //让内核做好准备,一旦接受到SIGALARM信号,就执行sig_alarm
 8     alarm(5);
 9 }
10 void main()
11 {
12     int i;
13     signal(SIGALRM, sig_alarm);//让内核做好准备,一旦接受到SIGALARM信号,就执行sig_alarm
14     alarm(5);
15     for(i=1;i<21;i++)
16     {
17         printf("sleep %d ...\\n",i);
18         sleep(1);
19     }
20 }

程序执行结果如下:

 6、信号集

  通过信号集(signal set)表示多个信号,这样方便操作多个信号。信号集的数据类型为sigset_t,信号集操作函数如下: 

  #include <signal.h>
  int sigemptyset(sigset_t *set);  //初始化由set指向的信号集,清除其中所有信号
  int sigfillset(sigset_t *set);    //初始化由set指向的信号集,使其包括所有信号
  int sigaddset(sigset_t *set, int signum);   //添加一个指定的信号
  int sigdelset(sigset_t *set, int signum);   //删除一个指定信号
  int sigismember(const sigset_t *set, int signum);   //判断signum是否在set所指向的信号集中

  一个进程的信号屏蔽字规定了当前阻塞而不能递送给该进程的信号集,调用sigprocmask函数可以检测或更改其信号屏蔽字。函数原型如下:

  int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

  how的取值为:SIG_BLOCK、SIG_UNBLOCK、SIG_SETMASK

  sigpending函数返回信号集,其中的各个信号对于调用进程是阻塞的而不能传递。函数原型为:int sigpending(sigset_t *set)。

写个程序进行信号屏蔽测试,程序如下:

 1 #include <sys/wait.h>
 2 #include <unistd.h>
 3 #include <sys/types.h>
 4 #include <errno.h>
 5 #include <signal.h>
 6 #include <stdio.h>
 7 #include <stdlib.h>
 8 
 9 static void sig_quit(int signo);
10 
11 int main()
12 {
13     sigset_t    newmask,oldmask,pendmask;
14     if(signal(SIGQUIT,sig_quit) == SIG_ERR)
15     {
16         perror("signal() error");
17         exit(-1);
18     }
19     sigemptyset(&newmask);
20     //添加一个退出信号
21     sigaddset(&newmask,SIGQUIT);
22     //将newmask信号机设置为阻塞,原信号集保存在oldmask中
23     if(sigprocmask(SIG_BLOCK,&newmask,&oldmask) == -1)
24     {
25         perror("sigprocmask() error");
26         exit(-1);
27     }
28     sleep(5);
29     //获取阻塞的信号集
30     if(sigpending(&pendmask) == -1)
31     {
32         perror("sigpending() error");
33         exit(-1);
34     }
35     //判断SIGQUIT是否是阻塞的
36     if(sigismember(&pendmask,SIGQUIT))
37         printf("\\nSIGQUIT is pending.\\n");
38     //回复原理的信号集
39     if(sigprocmask(SIG_SETMASK,&oldmask,NULL) == -1)
40     {
41         perror("sigprocmask() error");
42         exit(-1);
43     }
44     printf("SITQUIT unblocked\\n");
45     sleep(5);
46     exit(0);
47 }
48 
49 static void sig_quit(int signo)
50 {
51     printf("caught SIGQUIT.\\n");
52     if(signal(SIGQUIT,SIG_DFL) == SIG_ERR)
53     {
54         perror("signal() error");
55         exit(-1);
56     }
57 }

 程序执行结果如下:在中断上键入Ctlr+\\退出字符。

第二次运行时候,在进程休眠的时候产生多次SIGQUIT信号,但是解除了该信号的阻塞后,只会向进程发送一个SIGQUIT,系统没有对信号进行排队。

 

 

 

  接着昨天学习Unix信号机制,信号内容挺多了,发了两天的时间才了解各大概,日后遇到问题需要多看几遍,掌握核心应用。

7、sigaction函数

sigaction函数的功能是检查或修改与指定信号相关联的处理动作或同时执行这两种操作,可以用sigaction函数实现signal函数。函数原型及结构参数如下:

  int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

  struct sigaction {
      void     (*sa_handler)(int);
      void     (*sa_sigaction)(int, siginfo_t *, void *);
      sigset_t   sa_mask;
      int        sa_flags;
      void     (*sa_restorer)(void);
  };

现用sigaction函数实现signal函数,被信号中断的系统调用都能够重启。程序如下:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <sys/types.h>
 5 #include <signal.h>
 6 
 7 typedef void Sigfunc(int);
 8 
 9 Sigfunc* mysignal(int signo,Sigfunc *func)
10 {
11     struct sigaction    act,oact;
12     act.sa_handler = func;  //设置中断处理程序
13     sigemptyset(&act.sa_mask);  //初始化信号集
14     act.sa_flags = 0;
15     if(signo == SIGALRM)   //将SIGALRM信号设置为系统调用不会自动重启
16     {
17     #ifdef SA_INTERRUPT
18         act.sa_flags |= SA_INTERRUPT;
19     #endif
20     }
21     else   //其余信号设置为系统调用自动重启
22     {
23     #ifdef  SA_RESTART
24         act.sa_flags |= SA_RESTART;
25     #endif
26     }
27     if(sigaction(signo,&act,&oact)<0)
28         return (SIG_ERR);
29     return (oact.sa_handler);
30 }
31 
32 static void sig_func(int signo)
33 {
34     printf("Recevied a SIGALRM signal.\\n");
35 }
36 
37 int main()
38 {
39     printf("Starting.\\n");
40     mysignal(SIGALRM,sig_func);
41     alarm(2);
42     pause();  //等待信号出现
43     exit(0);
44 }

程序执行结果如下:

8、sigsetjmp和siglongjmp函数

 这两个函数是对非局部转移的setjmp和longjmp函数的改进,在信号处理程序中进行非局部转移时应当使用这两个函数。函数原型如下:  

   int sigsetjmp(sigjmp_buf env, int savesigs);
   void siglongjmp(sigjmp_buf env, int val);

在sigsetjmp函数中,如果savesigs非0,则sigsetjmp在env中保存进程的当前信号屏蔽字,调用siglongjmp从其中恢复保存的信号屏蔽字。

写个程序练习一下,程序如下:

 

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <sys/types.h>
 5 #include <errno.h>
 6 #include <signal.h>
 7 
 8 static void printcharacter(const char* str);
 9 static void sig_usr(int signo);
10 void TELL_WAIT(void);
11 void TELL_PARENT(pid_t pid);
12 void TELL_CHILD(pid_t pid);
13 void WAIT_PARENT(void);
14 void WAIT_CHILD(void);
15 static volatile sig_atomic_t sigflag;
16 static sigset_t newmask,oldmask,zeromask;
17 
18 int main()
19 {
20     pid_t   pid;
21     TELL_WAIT();
22     pid = fork();
23     switch(pid)
24     {
25     case -1:
26         perror("fork() error");
27         exit(-1);
28     case 0:   //让子进程优先执行
29         //WAIT_PARENT();
30         printcharacter("output from child prcess.\\n");
31         TELL_PARENT(getppid());
32         break;
33     default:
34         WAIT_CHILD();
35         printcharacter("output from parent prcess.\\n");
36         //TELL_CHILD(pid);
37     }
38     exit(0);
39 }
40 
41 static void printcharacter(const char* str)
42 {
43     const char *ptr;
44     setbuf(stdout,NULL);
45     for(ptr=str;*ptr!=\'\\0\';ptr++)
46         putc(*ptr,stdout);
47 }
48 static void sig_usr(int signo)  //信号处理程序
49 {
50     sigflag = 1;
51 }
52 void TELL_WAIT(void)
53 {
54     signal(SIGUSR1,sig_usr);
55     signal(SIGUSR2,sig_usr);
56     sigemptyset(&zeromask);
57     sigemptyset(&newmask);
58     //添加信号集
59     sigaddset(&newmask,SIGUSR1);
60     sigaddset(&newmask,SIGUSR2);
61     sigprocmask(SIG_BLOCK,&newmask,&oldmask);  //设置信号为阻塞
62 }
63 void TELL_PARENT(pid_t pid)
64 { 
65     kill(pid,SIGUSR2); //向子进程发送信号
66 }
67 void TELL_CHILD(pid_t pid)
68 {
69     kill(pid,SIGUSR1);//向父进程发送信号
70 }    
71 void WAIT_PARENT(void)
72 {
73     while(sigflag == 0)
74       sigsuspend(&zeromask);
75     sigflag = 0;
76     sigprocmask(SIG_SETMASK,&oldmask,NULL);
77 }
78 void WAIT_CHILD(void)
79 {
80     while(sigflag == 0)
81       sigsuspend(&zeromask); //阻塞进程
82     sigflag = 0;
83     sigprocmask(SIG_SETMASK,&oldmask,NULL);
84 }

 

程序执行结果如下:

 当调用一个信号处理程序时,被捕捉到的信号添加到进程的当前信号屏蔽字中,当从信号处理程序返回时,恢复原来的屏蔽字,siglongjmp恢复了有sigsetjmp保存的信号屏蔽字。

9、sigsuspend函数

  该函数在一个原子操作中先恢复信号屏蔽字,然后使进程休眠。函数原型如下:

  int sigsuspend(const sigset_t *mask); //返回-1,并将errno设置为EINTR

  将信号屏蔽字设置为mask指向的值,在捕捉到一个信号或发生一个会终止该进程的信号之前,该进程被挂起。如果捕捉到一个信号而且从该信号程序返回,则sigsuspend返回,并且该进程的信号屏蔽字设置为调用sigsuspend之前的值。该函数可以保护不希望由信号中断的代码临界区,写个程序,使用该函数保护临界区,使其不被特定信号中断,程序如下:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <sys/types.h>
 5 #include <signal.h>
 6 #include <errno.h>
 7 #include <setjmp.h>
 8 void pr_mask(const char *str);
 9 static void sig_int(int);
10 
11 int main()
12 {
13     sigset_t    newmask,oldmask,waitmask;
14     pr_mask("program start: ");
15     signal(SIGINT,sig_int);
16     sigemptyset(&waitmask);
17     sigaddset(&waitmask,SIGUSR1);
18     sigemptyset(&newmask);
19     sigaddset(&newmask,SIGINT);
20     sigprocmask(SIG_BLOCK,&newmask,&oldmask);
21     pr_mask("in critical region");
22     //修改进程屏蔽字,在捕捉信号之前,将进程挂起
23     sigsuspend(&waitmask);
24     pr_mask("after return form sigsuspend: ");
25     sigprocmask(SIG_SETMASK,&oldmask,NULL);
26     pr_mask("program exit: ");
27     exit(0);
28 }
29 
30 void pr_mask(const char *str)
31 {
32     sigset_t sigset;
33     int errno_save;
34     errno_save = errno;
35     if(sigprocmask(0,NULL,&sigset)<0)
36     {
37         perror("sigprocmask() error");
38         exit(-1);
39     }
40     printf("%s\\n",str);
41     if(sigismember(&sigset,SIGINT))
42         printf("SIGINT \\n");
43     if(sigismember(&sigset,SIGQUIT))
44         printf("SIGQUIT \\n");
45     if(sigismember

以上是关于Unix环境高级编程信号续的主要内容,如果未能解决你的问题,请参考以下文章

UNIX环境高级编程第十章上

Unix高级环境编程学习小结

Unix环境高级编程(十四)守护进程实现时间服务器

Unix环境高级编程书上的第一个例程如何运行(附代码)

《UNIX环境高级编程中文版》pdf

Unix环境高级编程线程