#Linux中的GCC编程# 信号

信号SIGNAL

信号出现在早期的Unix中,早期的信号模型是不可靠的。BSD和System V分别对早期信号进行扩展,但是互不兼容。POSIX统一了上述的两种模型,由此提供了可靠的信号模型。

1、信号的基本概念

  • 用于进程之间的通信,可以中断进程的运行过程,改变处理流程
进程1 向 进程2 发送一个信号,进程2 停下当前工作,转而处理信号,执行相应的工作
  • 信号是软件中断
  • 信号是异步事件
进程1 发信号 给进程2,进程1 不用等待进程2回应,会继续执行自己的事情。
- 不可预见
- 信号有自己的名称和编号,如 SIGINT
- 信号和异常处理机制
查看linux系统中的信号的名称和编号,使用kill -l命令。

kill-l

信号无优先级。
1~31 非实时信号,发送的信号可能会丢失,不支持信号排队。(linux操作系统中使用到,记录在/usr/include/bits/signum.h文件中,每个信号都有一定的解释)
34~64 实时信号,支持信号排队,发送的多个实时信号都会被接收。
  • 信号发生的来源
硬件来源(由硬件驱动)
1. 按下键盘。CTRL+C CTRL+Z等等。
2. 者其他的一些硬件故障,由硬件驱动产生。

软件来源(由内核产生)
1. 常见的发送信号的系统函数:kill(),raise(),alarm(),settimer()等。
2. 一些非法运算等操作。除0,浮点数运算错误等等。
3. 软件设置条件(如gdb)。

2、信号的三个处理方法

2.1. 忽略信号 signal(signo,SIG_IGN)

SIGKILL和SIGSTOP永远不能被忽略,且不能被捕获。设置了也没用。
可以用于忽略硬件异常信号。
进程启动时,SIGUSR1和SIGUSR2两个信号默认被忽略。登记一下,可以被捕获处理。

2.2. 执行默认操作 signal(signo,SIG_DEF)

每个信号都有自己的默认动作,大部分信号默认动作是终止进程。

2.3. 捕获信号 signal(signo,func)

告诉内核出现信号时,调用自己的处理函数。signal 用于向内核登记信号和处理函数。
SIGKILL和SIGSTOP不能被捕获,且不能被忽略,只能执行默认操作。

2.4 signal函数

查看signal函数,使用man signal命令。
man signal

#include <signal.h>
void (*signal(   int signo,   void (*func)(int)  ))(int);

功能:向内核登记安装信号处理函数。
返回:成功返回信号处理函数指针,错误返回SIG_ERR
参数:signo 要登记的信号值,func 信号处理函数指针 或者SIG_IGN忽略信号  或者SIG_DEF执行默认操作。

2.5 参考例子

对SIGTSTP和SIGINT 进行捕获登记

#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
//定义信号处理函数
//signo:进程捕获到的信号
void sig_handler(int signo)
{
    printf("%d,%d occured\n",getpid(),signo);
}

int main(void)
{
    //向内核登记信号处理函数以及信号值 
    if(signal(SIGTSTP,sig_handler) == SIG_ERR){
        perror("signal sigtstp error");
    }
    if(signal(SIGINT,sig_handler) == SIG_ERR){
        perror("signal sigint error");
    }

    int i=0;
    while(i<30)
    {
        printf("%d out %d\n",getpid(),i++);
        sleep(1);//1秒
    }

}

编译运行之后,键盘组合键 CTRL+C CTRL+Z 不再能够退出打断。

kshine@kshine-virtual-machine:~/桌面$ gcc test.c -o bin
kshine@kshine-virtual-machine:~/桌面$ ./bin
6116 out 0
6116 out 1
6116 out 2
6116 out 3
6116 out 4
6116 out 5
6116 out 6
6116 out 7
6116 out 8
6116 out 9
6116 out 10
^C6116,2 occured    //CTRL+C 终止
6116 out 11
6116 out 12
6116 out 13
6116 out 14
^C6116,2 occured   //CTRL+C 终止
6116 out 15
6116 out 16
6116 out 17
6116 out 18
^Z6116,20 occured   //CTRL+Z 暂停
6116 out 19
6116 out 20
6116 out 21
6116 out 22
6116 out 23
6116 out 24
6116 out 25
^C6116,2 occured  //CTRL+C 终止
6116 out 26
6116 out 27
6116 out 28
6116 out 29

2.6 信号还可以怎么使用

可以使用 kill -SIGCONT  进程号 ,来使得一个进程恢复运行。
也可以使用 kill -SIGSTP 进程号,来暂停 一个进程。和CTRL+Z功能一样。
signal(SIGINT,SIG_IGN); //可以用来忽略CTRL+C信号
signal(SIGINT,SIG_DFL); //CTRL+C信号按照默认的方式,用来终止进程

2.7 SIGCHLD信号

在学习进程时讲到,为了避免僵尸进程,有两种方式关闭子进程。一是父进程使用wait() 阻塞等待子进程结束并回收。二是父进程结束,由1号进程领养子进程并回收。
这里我们还可以使用SIGCHILD信号来及时回收子进程。这样可以让父进程继续运行,不用等待子进程结束。

#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
//定义信号处理函数
//signo:进程捕获到的信号
void sig_handler(int signo)
{
    printf("child processs dead, signo:%d\n",signo);
    //当子进程结束时,会产生SIGCHID信号。父进程捕获并回收(wait)子进程资源。
    wait(0);
}
void out(int n)
{
    int i;
    for(i=0;i<n;i++)
    {
        printf("%d out %d\n",getpid(),i);
        sleep(2);
    }


}
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){//父进程
        out(100);
    }else if(pid==0){//子进程
        out(10);

    }

}

运行结果:

kshine@kshine-virtual-machine:~/桌面$ gcc test.c -o bin
kshine@kshine-virtual-machine:~/桌面$ ./bin 
9078 out 0
9079 out 0
9078 out 1
9079 out 1
9078 out 2
9079 out 2
9078 out 3
9079 out 3
9078 out 4
9079 out 4
9078 out 5
9079 out 5
9078 out 6
9079 out 6
9078 out 7
9079 out 7
9078 out 8
9079 out 8
9078 out 9
9079 out 9
9078 out 10
child processs dead, signo:17      
9078 out 11
9078 out 12
9078 out 13
9078 out 14
9078 out 15
^C
kshine@kshine-virtual-machine:~/桌面$ 

3. 信号发送

3.1 基本概念

  • 除了内核和超级用户root,并不是所有进程都可以向其他进程发送信号。
  • 一般的进程 只能想相同的 uid(用户ID,同一个用户创建的) 和gid(用户组ID,属于同一个用户组) 的进程发送信号,或者向相同进程组的其他进程发送信号。
3.1.1 发送信号的函数
  • kill() 向其他进程或者自己发送信号 ,可以使用man 2 kill 查看函数介绍。
#include <sys/types.h>
#include <signal.h>
/*
sig 可以为0 空信号,用来检测特定的信号是否存在。
pid 进程编号。
(1)pid>0 进程ID
(2)pid ==0,将信号发送给同一进程组的所有进程。
(3)pid < 0 将信号发送给进程组ID等于pid的绝对值的所有进程
(4)pid==-1 将信号发送给 发送进程有权限发送信号的系统上的所有进程。
*/
int kill(pid_t pid, int sig);
  • raise() 只能向自己发送信号
#include <signal.h>
int raise(int sig);

参考例子:

#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
//定义信号处理函数
//signo:进程捕获到的信号
void sig_handler(int signo)
{
    printf("signo:%d\n",signo);
}

int main(void)
{
    //向内核登记信号处理函数以及信号值
    if(signal(SIGUSR1,sig_handler) == SIG_ERR){
        perror("signal sigusr1 error");
    }
    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);
    }
    //向进程自己发送信号
    raise(SIGUSR1);
    kill(getpid(),SIGUSR2);

}
kshine@kshine-virtual-machine:~/桌面$ gcc test.c -o bin
kshine@kshine-virtual-machine:~/桌面$ ./bin 
9567 out 0
9567 out 1
9567 out 2
9567 out 3
9567 out 4
9567 out 5
9567 out 6
9567 out 7
9567 out 8
9567 out 9
signo:10
signo:12
kshine@kshine-virtual-machine:~/桌面$ 
  • alarm() 定时信号
    设置定时器,当定时器超时,产生SIGALRM信号。
    不是周期性的定时信号,是一次性的信号。
    精确度只到秒,要想使用更高的精度可以使用 ualarm()函数。
#include <unistd.h>
/*
seconds ==0 时,可以取消以前设置的定时器。
*/
unsigned int alarm(unsigned int seconds);

参考例子:

#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <math.h>
//定义信号处理函数
//signo:进程捕获到的信号
void sig_handler(int signo)
{
    if(signo == SIGALRM){
	printf("clock time out\n");
        //重新设置定时器(周期性定时)
        alarm(5);
    }
    
}
void out(void)
{
    int i=0;
    while(i<20)
   {
      double d = drand48();
      printf("%-10d:%lf\n",i++,d);
      //if(i==16) 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();
    printf("end running main\n");
    return 0;
}

运行结果:

kshine@kshine-virtual-machine:~/桌面$ gcc test.c -o bin
kshine@kshine-virtual-machine:~/桌面$ ./bin 
begin running main
0         :0.000000
1         :0.000985
2         :0.041631
3         :0.176643
4         :0.364602
clock time out
5         :0.091331
6         :0.092298
7         :0.487217
8         :0.526750
9         :0.454433
clock time out
10        :0.233178
11        :0.831292
12        :0.931731
13        :0.568060
14        :0.556094
clock time out
15        :0.050832
16        :0.767051
17        :0.018915
18        :0.252360
19        :0.298197
clock time out
end running main
kshine@kshine-virtual-machine:~/桌面$ 
  • settimer()
  • abort() 异常终止 产生SIGABRT

4. 高阶提升

4.1 信号的可靠性

  1. 不可靠信号问题一
    发生信号时,关联动作中,再次绑定该信号。
  • 进入关联动作关联动作中再次调用signal绑定之间的时间内,信号可能会丢失,即同样的信号不会被捕获
  • 导致进程终止
  1. 不可靠信号问题二
    无法暂停阻塞信号。

4.2 函数可重入性

4.3 信号集

4.4 信号屏蔽

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值