信号

信号

概念

  1. 信号的产生:

    当引发信号的事件发生时,为进程产生一个信号(或向一个进程发送一个信号)。信号产生时,内核会在进程表中设置一位标识。

  2. 信号的递送(delivery):

    当进程对信号采取动作(执行信号处理函数或忽略)时称为递送。

    信号产生和递送之间的时间间隔内称信号是未决的(pending)。

  3. 信号递送阻塞(block):

    进程可指定对某个信号采用递送阻塞,如果此时信号的处理是系统默认动作或者捕捉该信号,则该信号就会处于未决的状态,直到进程解除对该信号的递送阻塞或者处理方式改为忽略。

在这里插入图片描述
来了一个信号,信号产生标记变为1,此时检测信号屏蔽字是否为0;为0可以递送,递送走之后,信号产生标记变为0,信号屏蔽字变为1;当捕捉函数接收递送的信号并结束,信号屏蔽字变为0。(类似于接电话,接通第一个电话的时候,接不了第二个电话)
如果信号的处理方式是忽略该信号,那么该信号永远不会处于递送或者递送未决状态


  1. 非实时信号: 1~31(不支持排队,信号同一时间太多,会丢失,不可靠信号)

    信号名称编号信号说明默认处理
    SIGHUP1终端关闭时产生这个信号进程终止
    SIGINT2终端输入了中断字符ctrl+c进程终止
    SIGKILL9用来立即结束程序的运行。本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。进程终止
    SIGSEGV11试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据,默认打印出segment fault。进程终止并且产生core文件
    SIGPIPE13往管道写时,读者已经不在了,或者往一个已断开数据流socket写数据。发送的进程会收到进程终止
    SIGALRM14时钟定时信号, 计算的是实际的时间或时钟时间。alarm 函数使用该信号。进程终止
    SIGTERM15有kill函数调用产生。进程终止
    SIGCHLD17子进程停止或者终止时,父进程会收到该信号。忽略该信号
    SIGSTOP19用来停止一个进程。本信号不能忽略,也不能被捕捉进程暂停执行
  2. 实时信号:34~64(支持排队,可靠信号)(克服了信号丢失的问题)

在这里插入图片描述

core 文件(调试用)

在这里插入图片描述

  1. 限制产生的core文件的大小不能超过1024kb: ulimit -c 1024
  2. 禁止产生core文件:ulimit -c 0
  3. 调试找错误命令: gdb -c core.5032 a.out

信号的使用

进程可以从三个方面使用信号:

  1. 指定当收到信号时进程的处理函数(信号处理)。

  2. 阻塞一个信号(也就是推迟它的递送),比如处于一段临界代码,执行完临界代码后在启用这个信号。

  3. 向另外一个进程发送信号(进程间通讯)。

信号的处理

signal函数用来通知内核如何处理某个特定信号(忽略、捕捉、默认处理)。

#include <signal.h>
//typedef简化函数声明
typedef void (*sighandler_t)(int);//定义一个新函数指针类型sighandler_t
sighandler_t signal(int signum, sighandler_t handler);//函数返回值是函数指针

void (*signal(int signo, void (*handler)(int signo)))(int); 
//signo:要处理的信号
//handler:处理的方式(是系统默认还是忽略还是捕获)。
//signal把信号做为参数传递给handler信号处理函数,并开始执行它,接着 signal函数返回函数指针,指向信号处理函数。
/*int (*p)();
这是一个函数指针, p所指向的函数是一个不带任何参数, 并且返回值为int的一个函数
int (*fun())();
这个式子与上面式子的区别在于用fun()代替了p,而fun()是一个函数,所以说就可以看成是fun()这个函数执行之后,它的返回值是一个函数指针,这个函数指针(其实就是上面的p)所指向的函数是一个不带任何参数,并且返回值为int的一个函数.
void (*signal(int signo, void (*handler)(int)))(int);就可以看成是signal()函数(它自己是带两个参数,一个为整型,一个为函数指针的函数),而这个signal()函数的返回值也为一个函数指针,这个函数指针指向一个带一个整型参数,并且返回值为void的一个函数.
*/

进程在接收到一个信号时,通常可以进行三种处理:

  1. 忽略此信号:大多数信号都可使用这种方式进行处理.

    但有两种信号不能被忽略:SIGKILLSIGSTOP。(它们向超级用户提供一种进程终止或或停止的可靠方法)

signal(SIGINT, SIG_IGN)//忽略SIGINT信号
  1. 捕捉信号:为了做到这一点要通知内核在某种信号发生时,调用一个用户函数。在用户函数中,可执行用户希望对这种事件进行的处理。
#include <stdio.h>
#include <signal.h>

void fun(int sig)
{
	printf("sig:%", sig);//输出SIGINT的编号2
}
int main()
{
	signal(SIGINT, fun);
	while(1);
}
  1. 执行系统默认动作:对大多数信号的系统默认动作是终止该进程。
signal(SIGINT, SIG_DFL)//系统默认方式处理SIGINT

信号的继承

  • 子进程会继承父进程的信号处理方式,直到子进程调用exec函数。
  #include <stdio.h>  
  #include <signal.h>
  #include <unistd.h>  
     
  void handler1()
  {
      printf("hello world");
  }
  void handler2()
  {
      printf("nice time");
  }
  
  int main() 
  {  
      signal(SIGINT, handler1);
      pid_t pid = fork();
      if (pid == 0)
      {
          //子进程会继承父进程的信号处理方式
          //收到SIGINT子进程输出hello world
          getchar();
      }
      //收到SIGINT父进程输出hello world
      getchar();
  
      return 0;  
  }
int main() 
{  
    signal(SIGINT, handler1);
    pid_t pid = fork();
    if (pid == 0)
    {
		signal(SIGINT, handler2);
        //收到SIGINT信号子进程输出nice time
        getchar();
    }
    //收到SIGINT父进程输出hello world
    getchar();

    return 0;  
}
  • 子进程调用exec函数后,exec将父进程中设置为捕捉的信号变为默认处理方式,其余不变(SIG_IGN)。例如在父进程中把SIGTERM设置为捕捉,SIGINT设置为忽略。子进程执行exec和不执行exec的区别。

    int main() 
        {  
            signal(SIGINT, handler1);
            pid_t pid = fork();
            if (pid == 0)
            {
                //收到SIGINT信号子进程结束(变为默认处理方式)
        		execl("b.out");
            }
            //收到SIGINT父进程输出hello world
            getchar();
        
            return 0;  
        }
    
    int main() 
    {  
        signal(SIGINT, SIG_IGN);
        pid_t pid = fork();
        if (pid == 0)
        {
            //收到SIGINT信号子进程忽略信号
            //因为b.out里面没有handler自定义函数
    		execl("b.out");
        }
        //收到SIGINT父进程忽略信号
        getchar();
    
        return 0;  
    }
    

    信号集

  • 目前Linux系统定义了64个信号,以后也可能进一步扩展。所以用类型sigset_t来表示所有的信号。

  • 一般情况下,sigset_t中的一个比特位表示一种信号。

  • 对信号集的操作需要专用的操作函数。

  • 信号0是系统保留,不被使用的。

  • 信号集操作函数有以下5个

#include <signal.h>

//把所有信号集的比特位置为0
int sigemptyset(sigset_t *set); 

//把所有信号集的比特位设为1
int sigfillset(sigset_t *set);

//把信号signum加入信号集set中
int sigaddset(sigset_t *set, int signum);sigaddset(&set, SIGINT);

//把信号signum从信号集set中清除 
int sigdelset(sigset_t *set, int signum);

//判断某个信号是否在信号集set中 
int sigismember(const sigset_t *set, int signum);
  • 使用例子:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

int main()
{
	sigset_t sigset;
	sigemptyset(&sigset);
	sigaddset(&sigset, 2);
	sigaddset(&sigset, 5);
	sigaddset(&sigset, 17);
	sigaddset(&sigset, 22);

	int i;
	for (i = 1; i <= 64; i++)
	{
		if (i == 32 || i == 33)
		  continue;
		if (sigismember(&sigset, i))
		{
			printf("i:%d\n", i);//打印出信号集里的所有信号
		}
	}

}

sigaction

Linux中signal注册函数最终也是调用sigaction来实现,保留signal主要是为了兼容。

声明:

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

参数:

  • signum:指定需要处理的信号(除SIGKILL和SIGSTOP)。

  • act:设定信号的处理方式,act可以为NULL。

  • oldact:之前设定的信号处理方式会保存到第三个参数oldact,oldact为NULL时不保存。

返回0成功,返回-1失败。

参数结构体定义:

struct sigaction {

    void  (*sa_handler)(int); //不可靠信号信号处理方式
    /*sa_handler和signal函数的第二个参数类型(函数指针)一样,当信号递送给进程时会调
    用这个sa_handler.
    */

    void  (*sa_sigaction)(int, siginfo_t *, void *); //可靠信号的信号处理方式
    /*sa_sigaction也是信号处理的函数指针,它只会在sa_flags包含SA_SIGINFO时才会被
    调用。否则linux内核默认调用sa_handler。siginfo_t 包含了信号产生的原因。不用同
    时赋值给sa_handler和sa_sigaction,因为它们可能是一个union。
    */

    sigset_t  sa_mask;//在处理信号的过程中,要屏蔽掉的信号
	/*
	信号屏蔽字,当执行sa_handler信号处理函数时,sa_mask指定的信号会被阻塞,直到该信
	号处理函数返回。当前信号处理函数对应的信号会被自动加入到sa_mask中。
	*/
    int  sa_flags;
	/*
	用来改变信号处理时的行为。当sa_flags包含SA_RESTART时,被中断的系统调用在信号处
	理完后会被自动启动。当值包含SA_RESETHAND时,对此信号的处理方式在此信号捕捉函数的
	入口处重置为SIG_DFL,对应于早期不可靠信号。
	Act.sa_flags = SA_RESTART;
	*/
    void  (*sa_restorer)(void);   /* obsolete */ 

}

信号阻塞

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

void fun()
{
    sleep(10);
    printf("I woke up");
}

int main()
{
    struct sigaction act;
    memset(&act, 0, sizeof(act));
    
    act.sa_handler = fun;
    //把信号集act.sa_mask比特位全部置为1;屏蔽掉所有的信号,信号处理函数在运行时,就不会被打断
    sigfillset(&act.sa_mask);
    act.sa_flags = SA_RESTART;

    sigaction(SIGHUP, &act, NULL);

    while (1);

    return 0;
}

可重入与不可重入函数

  • 由于信号处理函数可被中断,所以在处理函数中调用的函数都必须是可重入的

  • 可重入函数:指一个可以被多个任务调用的过程(函数),任务在调用时不必担心数据是否会出错。因为进程在收到信号后,就将跳转到信号处理函数去接着执行。如果信号处理函数中使用了不可重入函数,那么信号处理函数可能会修改原来进程中不应该被修改的数据,这样进程从信号处理函数中返回接着执行时,可能会出现不可预料的后果。不可再入函数在信号处理函数中被视为不安全函数

  • 满足下列条件的函数多数是不可重入的:

(1)使用静态的数据结构或者变量或者使用了全局的对象或变量的函数,如strtok(),gmtime(),getgrgid(),gethostbyname(),localtime()以及getpwnam()等等;

(2)实现时使用了标准I/O函数的。标准I / O库的很多实现都以不可再入方式使用全局数据结构。

tips:fun_r()可重入函数

发送信号

  • kill-------------kill函数可以向某个进程或者进程组发送特定的信号
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
  • raise----------函数向调用进程发送一个信号(给自己发送信号),相当于kill(getpid(),signum)
 #include <signal.h>
 int raise(int sig);

信号屏蔽字

sigprocmask

  • 每个进程都会有一个信号屏蔽字,它规定了当前那些信号可以递送,哪些信号需要阻塞。

  • 当程序执行敏感任务时(比如更新数据库),不希望外界信号中断程序的运行。在这个阶段并不是简单地忽略信号,而是阻塞这些信号,当进程处理完关键任务后,还会处理这些信号

  • sigprocmask可以检测和修改进程的信号屏蔽字。oldset非空时,当前信号屏蔽字会保存到oldset中。如果set非空,那么参数how指示如何修改当前屏蔽字。

      #include <signal.h>
      int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
      /*how有三个值:
           SIG_BLOCK    被阻塞的信号是当前屏蔽字加上第二个参数包含的信号
           SIG_UNBLOCK  第二个参数包含的信号从当前屏蔽字中移除
           SIG_SETMASK  当前屏蔽字被完全设置为第二个参数的值。
      */
    

sigpending

#include <signal.h>
int sigpending(sigset_t *set);
  • sigpending返回当前处于阻塞递送的信号,即信号已经产生,但还没有递送给进程处理。

  • 成功返回0,失败返回-1

  • 在sigpending中,如果我们在解除某个信号调用sleep函数,那么未被递送的信号会首先被处理。导致pause无法被唤醒。

  • 需要把解除信号和等待信号递送做成一个原子操作,这样就不会产生上述问题。

 #include <signal.h>
 int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
 /*how有三个值:
      SIG_BLOCK    被阻塞的信号是当前屏蔽字加上第二个参数包含的信号
      SIG_UNBLOCK  第二个参数包含的信号从当前屏蔽字中移除
      SIG_SETMASK  当前屏蔽字被完全设置为第二个参数的值。
 */
 
 eg:
     
 sigset_t set
 sigset_t set2
 //所有信号加到信号集set
 sigfillset(&set);    
 //设置屏障
 sigprocmask(SIG_SETMASK, &set, NULL);
 //敏感任务
 meeting();
 //返回挡在门外的信号集set2
 sigpending(&set2);
 //找出挡在门外的信号
 for 1 to 64 
     sigismember();
 //解除屏障
 sigemptyset(set); //信号集清空
 sigprocmask(SIG_SETMASK, &set, NULL);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值