Linux环境编程之信号

    信号的概念

    信号是软件中断。它即可以作为进程间通信的一种机制,更重要的是,信号总是中断一个进程的正常运行,它更多地被用于处理一些非正常情况。信号是异步的,进程并不知道信号什么时候到达。进程既可以处理信号,也可以发送信号给特定进程。每个信号都有一个名字,这些名字都以SIG开头。例如:SIGABRT是进程异常终止信号。

 

    信号的来源

    硬件异常产生信号:除数为0、无效的存储访问等等。这些条件通常由硬件检测到,并将其通知内核。然后内核为该条件发生时正在运行的进程产生适当的信号。软件产生异常信号,可以用kill、raise、alarm、setitimer和 sigqueue产生信号。

 

    信号的种类

    不可靠的信号:Linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,因此,把那些建立在早期机制上的信号叫做“不可靠信号”,信号值小于SIGRTMIN的叫不可靠信号。每次信号处理后,该信号对应的处理函数会恢复到默认值。
信号可能丢失。

    可靠信号:信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题 。实时信号与非实时信号:Linux目前定义了64种信号(将来可能会扩展),前面32种为非实时信号,后32种为实时信号。非实时信号都不支持排队,都是不可靠信号,实时信号都支持排队,都是可靠信号。

 

    Linux系统信号

    linux下有很多信号,具体参见《UNIX环境高级编程》P235

 

   信号处理流程:见下图,可以看出进程1与内核的交互。

  

 

    信号术语

    信号的产生:当引发信号的事件发生时,为进程产生一个信号(或向一个进程发送一个信号)。信号产生时,内核会在进程表中设置一位标识。信号的递送(delivery):当进程对信号采取动作(执行信号处理函数或忽略)时称为递送。信号产生和递送之间的时间间隔内称信号是未决的(pending)。信号递送阻塞(block):进程可指定对某个信号采用递送阻塞。如果此时信号的处理时默认或者捕捉的,该信号就会处于决的状态,直到进程解除对该信号的递送阻塞或者处理方式改为忽略。如果信号的处理方式是忽略该信号,那么该信号永远不会处于递送或者递送未决状态

 

    信号的使用

    进程可以从三个方面使用信号:指定当收到信号时进程的处理函数(信号处理)。阻塞一个信号(也就是推迟它的发生),比如处于一段临界代码,执行完临界代码后在启用这个信号。向另外一个进程发送信号。

 

    信号的处理

    忽略此信号:大多数信号都可使用这种方式进行处理,但有两种信号却不能被忽略。它们是:SIGKILL和SIGSTOP。这两种信号不能被忽略的原因时:它们向超级用户提供一种进程终止或或停止的可靠方法;捕捉信号:为了做到这一点要通知内核在某种信号发生时,调用一个用户函数。在用户函数中,可执行用户希望对这种事件进行的处理。执行系统默认动作:对大多数信号的系统默认动作是终止该进程。

 

    信号注册函数:signal

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

参数signum是信号名称,可以用kill –l列出所有的信号(有整数表示)。
参数handler是信号处理方式,a)SIG_IGN忽略此信号,b)SIG_DFL以系统默认方式处理此信号,c)用户自定义的函数地址,当信号递送时linux内核会调用此函数。

失败:返回SIG_ERR,成功:返回前一次注册的信号处理函数。

 

    信号等待函数:pause

    pause会使调用进程进入睡眠状态,直到有信号递送。如果你对某个信号采取了SIG_IGN,那么pause是不会被唤醒的。 
    pause返回值为-1,errno设置成EINTR。

 

    特殊信号

    SIGKILL 和 SIGSTOP 既不能忽略也不能被捕捉,它们只能按照默认方式处理。
    SIGKILL 收到该信号的进程会立即终止。
    SIGSTOP 用来停止一个进程。
    让内核和超级用户有一种确定方式可以杀死或者停止某个进程。下面代码是SIG_KILL的演示代码,主函数中执行将SIG_KILL信号注册给了kill_stop_handler函数。

 

    子进程信号处理

    子进程会继承父进程的信号处理方式,直到子进程调用exec函数。子进程调用exec函数后,exec将父进程中设置为捕捉的信号变为默认处理方式,其余不变。例如在父进程中把SIGTERM设置为捕捉,SIGINT设置为忽略。子进程执行exec。   

    下面是term_int.c的代码

   

 

    信号集

    目前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);
    把信号signum从信号集set中清除 int sigdelset(sigset_t *set, int signum);
    判断某个信号是否在信号集set中 int sigismember(const sigset_t *set, int signum);

 

    信号注册函数:sigaction

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

    第一个signum指定需要处理的信号(除SIGKILL和SIGSTOP)。第二个参数act设定信号的处理方式,act可以为NULL。之前设定的信号处理方式会保存到第三个参数oldact,oldact为NULL时不保存。
返回0成功,返回-1失败。

 struct sigaction {
                  void     (*sa_handler)(int);
                  void     (*sa_sigaction)(int, siginfo_t *, void *);
                  sigset_t     sa_mask;
                  int     sa_flags;
                  void     (*sa_restorer)(void);     /*  obsolete */
              }
sa_handler和signal函数的第二个参数类型一样,当信号递送给进程时会调用这个sa_handler.
sa_sigaction也是信号处理的函数指针,它只会在sa_flags包含SA_SIGINFO时才会被调用。否则linux内核默认调用sa_handler。siginfo_t 包含了信号产生的原因。不用同时赋值给sa_handler和sa_sigaction,因为它们可能是一个union。
以下代码是该函数的实例代码

 

    sa_mask信号屏蔽字,当执行sa_handler信号处理函数时,sa_mask指定的信号会被阻塞,直到该信号处理函数返回。当前信号处理函数对应的信号会被自动加入到sa_mask中。sa_flags用来改变信号处理时的行为。当sa_flags包含SA_RESTART时,被中断的系统调用在信号处理完后会被自动启动。

 

    signal函数的默认值

    当用signal函数注册信号捕捉函数时,signal函数设置哪些默认值。可用sigaction来观察。

 

    信号和系统调用

    由于信号是异步的,它会在程序的任何地方发生。由此程序正常的执行路径被打破,去执行信号处理函数。
一般情况下,当进程正在执行某个系统调用,那么在该系统调用返回前信号是不会被递送的。但慢速系统调用除外,如读写终端、网络、磁盘,以及wait和pause。这些系统调用都会返回-1,errno置为EINTR当系统调用被中断时,我们可以选择使用循环再次调用,或者设置重新启动该系统调用(SA_RESTART)。

  

    信号发送函数:kill      

    kill函数可以向某个进程或者进程组发送特定的信号。

参数
pid > 0    将信号发送给进程ID为pid的进程;
pid == 0    将信号发送给起进程组ID等于发送进程的进程组ID,而且发送进程有许可权向其发送信号的所有进程;
pid < 0    将信号发送给其进程组ID等于pid绝对值,而且发送进程有许可权向其发送信号的所有进程。
pid == -1     将信号发送给除进程0以外的所有进程,但发送进程必须有许可权。       

成功返回0,失败返回-1,errno会被设置。
有发送信号许可权的基本规则是:1)超级用户可以将信号发送给任意进程;2)发送者的实际或有效用户ID必须等于接收者实际或有效用户ID;当signum为0时,则kill仍执行正常的错误检测,但不发送信号。这常被用来确定一个特定进程是否存在。如果向一个并不存在的进程发送空信号,则kill返回-1,errno则被设置为ESRCH。

 

    信号发送函数:raise

raise函数向调用进程发送一个信号,相当于kill(getpid(),signum)

成功返回0,失败返回非0。


    信号发送函数:sigqueue

    sigqueue 向一个特定进程pid发送信号sig,发送前也需要检查权限。
第三个参数只有在注册信号处理函数时,在sa_flags中包含SA_SIGINFO,系统才会把value传递给信号处理函数,在siginfo_t 中的si_value中。
      union sigval {
                int   sival_int;
               void *sival_ptr;
           };

成功返回0,失败返回-1,errno会被设置。

 

    信号屏蔽字

每个进程都会有一个信号屏蔽字,它规定了当前那些信号可以递送,哪些信号需要阻塞。
当程序执行敏感任务时(比如更新数据库),不希望外界信号中断程序的运行。在这个阶段并不是简单地忽略信号,而是阻塞这些信号,当进程处理完关键任务后,还会处理这些信号。

    信号屏蔽:sigprocmask

   



sigprocmask可以检测和修改进程的信号屏蔽字。oldset非空时,当前信号屏蔽字会保存到oldset中。如果set非空,那么参数how指示如何修改当前屏蔽字。
how有三个值:
    SIG_BLOCK    被阻塞的信号是当前屏蔽字加上第二个参数包含的信号
     SIG_UNBLOCK  第二个参数包含的信号从当前屏蔽字中移除
     SIG_SETMASK  当前屏蔽字被完全设置为第二个参数的值。  

 

    信号屏蔽:sigpending

 

 

    sigpending返回当前处于阻塞递送的信号,既信号已经产生,但还没有地送给进程处理。成功返回0,失败返回-1

 

    信号屏蔽问题

    在sigpending中,如果我们在解除某个信号调用sleep函数,那么未被递送的信号会首先被处理。导致pause无法被唤醒。
需要把解除信号和等待信号递送做成一个原子操作,这样就不会产生上述问题。

    信号屏蔽:sigsuspend

    #include <signal.h>
    int sigsuspend(const sigset_t *mask);

    sigsuspend会把当前信号屏蔽字设置为mask,然后挂起调用进程直到有信号被递送。
总是返回-1,因为它是被信号中断后返回的!

 

 

 

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值