浅墨浓香

想要天亮进城,就得天黑赶路。

导航

第8章 信号(2)_信号的发送

Posted on 2017-02-07 18:17  浅墨浓香  阅读(355)  评论(0编辑  收藏  举报

2. 信号发送的函数

2.1 安装信号处理函数:signal

(1)signal函数

头文件

#include <signal.h>

函数

void (*signal(int signo, void(*func)(int)))(int);

返回值

若成功则返回先前的信号处理函数指针,出错则返回SIG_ERR

参数

signo:要登记的信号值(如SIGCHLD)

func:

  ①信号处理函数指针;

  ②如果设为SIG_IGN,表示忽略信号

  ③如果设置为SIG_DFL:采用系统默认的方式处理信号,执行默认操作。

功能

向内核登记信号处理函数

备注

signal是个系统函数,它有两个参数signo和func。返回值为先前的信号处理函数指针。

②typedef的方式

  typedef void(*sighandler_t)(int);

  sighandler_t signal(int signum, sighandler_t handler);

【编程实验】捕获信号(如SIGUSR1、SIGSTOP、ctrl-z、ctrl-c等)

//signal_catch.c

#include <signal.h>
#include <stdlib.h>
#include <stdio.h>

/*演示信号的捕获*/

//定义信号处理函数
//signo:进程捕获到的信号
void sig_handler(int signo)
{
    //输出进程ID和信号值
    printf("%d, %d ocurred\n", getpid(), signo);
}

int main(void)
{
    //向内核登记信号处理函数(用于捕获ctrl-z和ctrl-c信号)
    //1.忽略ctrl-Z信号
    // if(signal(SIGTSTP, SIG_IGN) == SIG_ERR){
    //2.ctrl-z的默认处理
    // if(signal(SIGTSTP, SIG_DFL) == SIG_ERR){
    //3.捕获ctrl-z信号
    if(signal(SIGTSTP, sig_handler) == SIG_ERR){
        perror("signal sigtstp error");
    }
    
    //捕获ctrl-c信号
    if(signal(SIGINT, sig_handler) == SIG_ERR){
        perror("signal sigint error");
    }

    //捕获SIGUSR1信号(可在另一终端中发送kill -SIGUSR1 进程号
    //来观察,默认是被忽略的)
    if(signal(SIGUSR1, sig_handler) == SIG_ERR){
        perror("signal sigusr1 error");
    }

    //捕获SIGUSR2信号(默认被忽略的)
    if(signal(SIGUSR2, sig_handler) == SIG_ERR){
        perror("signal sigusr2 error");
    }
    
    //捕获/忽略SIGKILL或SIGSTOP信号都会提示无效参数,
    //以下代码不能起作用!
    //1. 捕获SIGKILL,会提示错误
    //if(signal(SIGKILL, sig_handler) == SIG_ERR){
    //2. 忽略SIGKILL,会提示错误,也无法起作用
    if(signal(SIGKILL, SIG_IGN) == SIG_ERR){
        perror("signal sigkill error");
    }

    int i = 0;
    while(i<30){
        //不停输出信息,在这过程中,可在另一个终端给该进程发送
        //各种信号(如SIGUSR1,SIGKILL等),也可按ctrl-c或ctrl-z
        //观察信号被捕获的情况。
        printf("%d out %d \n", getpid(), i++);
        sleep(1);
    }

    return 0;
}
/*输出结果:
 1852 out 0 
 1852 out 1 
 1852 out 2 
 ^C1852, 2 ocurred
 1852 out 3 
 1852 out 4 
 1852 out 5 
 1852 out 6 
 1852 out 7 
 ^Z1852, 20 ocurred
 1852 out 8 
 1852 out 9 
 ...
 */

(2)SIGCHLD信号

  子进程状态发生变化(子进程结束)产生该信号,父进程需要使用wait函数来等待子进程结束并回收它。

  ②避免僵尸进程

【编程实验】避免僵尸进程

//sig_child.c

#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>

void sig_handler(int signo)
{
    //子进程结束时,输出提示信息
    printf("child process deaded, signo: %d\n", signo);
    //当父进程捕获到SIGCHLD信号后要调用wait来回收子进程
    //否则,子进程会成为僵尸进程。
    if(SIGCHLD == signo)
        wait(0);
}

void out(int n)
{
    int i = 0;
    for(i=0; i<n; i++){
        printf("%d out %d \n",getpid(), i);
        sleep(1);
    }
}

int main(void)
{
    //在父进程中捕获子进程结束的信号
    if(signal(SIGCHLD, sig_handler) == SIG_ERR){
        perror("signal sigchld error");
    }

    pid_t pid = fork();
    if(pid < 0){
        perror("fork error");
        exit(1);
    }else if(pid > 0){ //parent process
        out(20);
    }else{  //child process
        out(10); //让子进程先结束
    }
    return 0;
}

2.1 信号的发送

(1)信号的发送

  ①除了内核和超级用户,并不是每个进程都可以向其他进程发送信号。

  ②一般的进程只能向具有相同uid和gid的进程发送信号,或向相同进程组中的其他进程发送信号。

  ③常用发送信号的函数有kill、raise、alarm、setitimer和abort

(2)kill和raise函数

头文件

#include <signal.h>

函数

int kill(pid_t pid, int signo);//向指向进程发送一个信号

int raise(int signo);  //向进程本身发送一个信号

返回值

若成功返回0,出错返回-1

参数

pid:接受信号进程的pid

  ①pid>0:将信号发给进程ID为pid的进程

  ②pid==0:将信号发给与发送进程同一进程组的所有进程

  ③pid<0:将该信号发送给进程ID等于pid的绝对值

  ④pid==-1:将该信号发送给发送进程有权限向他们发送信号的系统上所有进程。

signo:要发送的信量值

功能

向指定进程或本身发送一个信号。

备注

①kill函数将信号发送给进程或进程组。0为空信号,常用来检测特定的进程是否存在

②raise相当于kill(getpid(), sig);

【编程实验】信号的发送

//signal_send.c

#include <signal.h>
#include <stdlib.h>
#include <stdio.h>

/*演示信号的发送与捕获*/

//定义信号处理函数
//signo:进程捕获到的信号
void sig_handler(int signo)
{
    //输出进程ID和信号值
    printf("%d, %d ocurred\n", getpid(), signo);
}

int main(void)
{
    //向内核登记信号处理函数
    //捕获SIGUSR1信号
    if(signal(SIGUSR1, sig_handler) == SIG_ERR){
        perror("signal sigusr1 error");
    }

    //捕获SIGUSR2信号(默认被忽略的)
    if(signal(SIGUSR2, sig_handler) == SIG_ERR){
        perror("signal sigusr2 error");
    }
    
    int i = 0;
    while(i<10){
        //观察信号被捕获的情况。
        printf("%d out %d \n", getpid(), i++);

        /*进程自杀
        if(i == 5){
            kill(getpid(), SIGKILL);
        }
        */
        sleep(1);
    }

    //向进程自己发送SIGUSR1和SIGUSR2信号
    raise(SIGUSR1);
    kill(getpid(), SIGUSR2);

    return 0;
}
/*输出结果:
2049 out 0 
2049 out 1 
2049 out 2 
2049 out 3 
2049 out 4 
2049 out 5 
2049 out 6 
2049 out 7 
2049 out 8 
2049 out 9 
2049, 10 ocurred
2049, 12 ocurred
*/

(3)alarm函数

头文件

#include <unistd.h>

函数

unsigned int alarm(unsigned int seconds);

返回值

返回0或以前设置的定时器时间余留秒数

参数

seconds为0表示取消以前设置的定时器

功能

定时器

备注

①alarm函数可以设置定时器,当定时器超时,产生SIGALRM信号。

②信号是由内核产生的,在指定的seconds秒之后,给进程本身发送一个SIGALRM信号。

③alarm是一次性的定时器,每次触发后,要重新设置才能周期性的触发。

【编程实验】alarm定时器

//signal_alarm.c

#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>

void sig_handler(int signo)
{
    if(signo == SIGALRM){
        printf("alarm clock timeout\n");
        //由于alarm是一次性的,要重新设置定时器以达到周期性的目的。
        alarm(5);
    }
}

void out_data(void)
{
    int i = 1;
    while(i <= 20){
        double d = drand48();
        printf("%-10d:%lf\n", i++, d);
        if(i == 15)
            alarm(0);  //取消定时器
        sleep(1);
    }
}

int main(void)
{
    //注册信号处理函数
    if(signal(SIGALRM, sig_handler) == SIG_ERR){
        perror("signal sigalrm error");   
    }

    //设置定时器
    alarm(5);
    printf("begin running main\n");
    out_data();
    printf("end running main\n");
 
    return 0;
}

(4)gettitimer/setitimer函数

头文件

#include <unistd.h>

函数

int getitimer(int which, struct itimerval *value); //获取定时器状态

int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue); //设置定时器

返回值

成功调用返回0,错误返回-1

参数

①which:

  ITIMER_REAL:按实际时间计时,计时到达将给进程发送SIGALRM信号。

  ITIMER_VIRTUAL:仅当进程执行时才进行计时。计时到达将发送SIGVALRM。

  ITMIER_PROF:当进程执行时和系统为该进程执行动作时都计时。与定时器经常用来统计进程在用户态和内核态花费的时间。计时到达将发送SIGPROF信号给进程。

②value:用来指时定时器的时间,其结构如下:

struct itimerval{
    struct timeval it_interval; //下一次的取值,相当于以后每次触发的时间间隔
    struct timeval it_value;    //本次的设定值,相当于调用setitimer以后,将于多久后第1次触发。
}

③timeval结构体

struct timeval{
    long tv_sev; //
    long tv_usec; //微秒,1秒=1000000微秒
}

功能

获取/设置定时器状态。

备注

①定时器将it_value递减到0时,产生一个信号,并将it_value的值设定为it_interval的值,然后重新开始计时。

②it_value设定为0时,计时器停止,或者当它计时到期,而it_interval为0时停止。

③ovalue不为空,则其中保留的是上次调用设定的值。

【编程实验】周期性定时器

//signal_setitimer.c

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

/*周期性定时器*/

void sig_handler(int signo)
{
    switch(signo){
    case SIGALRM:
        printf("Catch a signal--SIGALRM(%d)\n", signo);
        break;
    case SIGVTALRM:
        printf("Catch a signal--SIGVTALRM(%d)\n", signo);
        break;
    }
}

int main(void)
{
    struct itimerval value1, value2, ovalue;

    signal(SIGALRM, sig_handler);
    signal(SIGVTALRM, sig_handler);

    printf("process id is %d\n", getpid());
    
    //按实际时间计时,超时发送SIGALRM信号
    value1.it_value.tv_sec = 5;   //it_value:调用settimer后,5秒以后第1次触发
    value1.it_value.tv_usec = 0;
    value1.it_interval.tv_sec = 1; //以后每次触发的时间间隔
    value1.it_interval.tv_usec = 0;

    setitimer(ITIMER_REAL, &value1, &ovalue);
    
    //仅当进程执行时才进行计时,超时发送SIGVTALRM信号
    value2.it_value.tv_sec = 0;
    value2.it_value.tv_usec = 500000; //0.5秒
    value2.it_interval.tv_sec = 0;
    value2.it_interval.tv_usec = 500000;

    setitimer(ITIMER_VIRTUAL, &value2, &ovalue);

    for(;;);

    return 0;
}
/*输出结果:
 process id is 2185
 Catch a signal--SIGVTALRM(26)
 Catch a signal--SIGVTALRM(26)
 Catch a signal--SIGVTALRM(26)
 Catch a signal--SIGVTALRM(26)
 Catch a signal--SIGVTALRM(26)
 Catch a signal--SIGVTALRM(26)
 Catch a signal--SIGVTALRM(26)
 Catch a signal--SIGVTALRM(26)
 Catch a signal--SIGVTALRM(26)
 Catch a signal--SIGALRM(14)
 Catch a signal--SIGVTALRM(26)
 Catch a signal--SIGVTALRM(26)
 Catch a signal--SIGALRM(14)
 Catch a signal--SIGVTALRM(26)
 ...
 */