欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

linux信号(signal)详解.md

程序员文章站 2022-07-12 11:17:49
...

linux信号(signal)详解

信号(signal)可以看作是进程级别的中断。(这样说主要是为了区别内核级别的硬件中断软件中断)。它允许进程和内核中断其他进程。信号的功能当然就是发送通知事件了。

我们可以使用man 7 signal命令查看关于信号的详细信息。用命令kill -l直接查看所有的信号的number。

在版本的慢慢迭代下,linux的信号分为了两种:

  1. 标准信号:信号值为1~31.如我们最常用的SIGINT的信号值为2。标准信号大多数都有自己的含义。比如SIGBUS表示总线错误,它由内核发送给进程。每个信号都有自己默认处理动作。下面是几种默认的处理动作:
    1. Term:终止一个进程。
    2. Ign :进程忽略此信号。
    3. Core:终止进程并转储内存.(转储内存(dumping core):指把代码和数据内存段的映像写到磁盘上.)。
    4. Stop:暂停(挂起)进程。
    5. Cont:继续运行一个当前被挂起的进程。
  2. 实时信号:信号值为32~64(准确来说的范围应该SIGRTMIN~SIGRTMAX,)。由于版本迭代、移植方便等的原因,实时信号值的表示应该用SIGRTMIN+n(SIGRTMIN+n <= SIGRTMAX).和标准信号不同,系统没有指定每个实时信号具体的含义(默认动作为终止进程),一般由用户通过函数sigaction()signal()设置自定义的处理动作。

标准信号与实时信号相比,标准信号不支持排队,所以信号可能会丢失。实时信号支持排队,信号不会丢失。什么是信号不排队?不排队信号为什么可能会丢失?简单说就像RTOS中的事件,一个事件用一个位表示,当这个事件已经发生但未处理之前,如果再发生此事件,就只能丢弃了(其实信号发生了2次,系统只能记录一次)。在这点上大家可以把标准信号实时信号比作RTOS中的事件信号量.

信号处理过程

我们必须要清楚的是信号的处理函数是在用户空间处理的(因为它是在用户空间编写的函数)。下面是信号处理的过程。

  1. 先假设一个进程运行在用户态。此时由于调用了系统调用或者被中断而陷入了内核态。
  2. 内核态中处理完相关任务后在返回用户态前,会检查是否有需要处理的信号。
  3. 如果有需要处理的信号,且没有被阻塞。那么内核就在用户栈上创建一个层,然后跳入到用户态,执行信号的回调处理函数。
  4. 执行完处理函数后,会再次进入内核态。
  5. 在内核态中再次检测信号等工作。最后恢复原先程序的中断执行点,恢复到用户态(之前进程运行状态)。

信号处理过程图linux信号(signal)详解.md
在使用信号时,可以选择执行信号的默认动作。或者忽略此信号。或者设置信号的回调函数。

SIGKILLSIGSTOP信号不能被捕获、阻止或忽略。

设置信号处理动作的函数如下:

  1. sighandler_t signal(int signum, sighandler_t handler)
    1. 描述:设置信号的处理动作。
    2. handler为用户自定义的回调函数指针,
    3. handler = SIG_IGN:设置为忽略此信号。
    4. handler = SIG_DFL:恢复为系统默认动作。
  2. int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact)
    1. 描述:设置信号的处理动作。
    2. act:指定新的信号处理方式。详见man手册。
    3. oldact如果不为NULL,将保存先前的信号信息到oldact.

信号的触发

  1. kill命令发送信号
    1. 描述:我们可以在shell使用此命令发送信号。
    2. eg:kill -9 111表示发送信号9(SIGKILL)给进程111。
    3. 负的pid表示向pid的进程组发送次信号。进程组中的每个进程都会收到此信号。如kill -9 -111.
  2. 使用键盘按建发送信号
    1. ctrl+c:发送信号SIGINT给前台进程组中的每个进程。用来终止进程。
    2. ctrl+z发送信号SIGTSTP给前台进程组中的每个进程。用来挂起进程。
  3. 用标准函数发送信号
    1. int kill(pid_t pid, int sig):
      1. 描述:向指定进程、指定进程组的所有成员或系统上的所有进程发送信号。
      2. pid>0:向pid进程发送信号sig.
      3. pid=0:向调用进程所在组中的每个进程都发送信号sig.
      4. pid=-1:将sig发送给调用进程有权发送信号的每个进程,进程1(init)除外。
      5. pid<-1:向|pid|进程组的所有进程发送信号sig.
      6. sig:要发送的信号。如果sig为0,则不发送信号,但仍然执行错误检查;这可用于检查进程ID或进程组ID是否存在。
    2. int killpg(int pgrp,int sig):
      1. 描述:向指定进程组的所有成员发送一个信号。
      2. pgrp>1:向指定进程组发送信号.
      3. pgrp=0:向调用进程组发送信号。
    3. raise(): 向调用线程发送一个信号。单线程时相当于kill(getpid(),sig),多线程时相当于pthread_kill(pthread_self(),sig).
    4. int pthread_kill(pthread_t thread, int sig): 向指定线程发送一个信号。
    5. int sigqueue(pid_t pid, int sig, const union sigval value): 向指定的进程发送实时信号和相关数据。
    6. unsigned int alarm(unsigned int seconds)
      1. 描述:在seconds秒之后,向调用进程发送SIGALRM信号。

等待信号捕捉

下面的系统调用将挂起调用进程或线程,直到捕获到信号(或未处理的信号终止进程).

  1. int pause(void): 挂起执行,直到捕捉到任何信号。
  2. int sigsuspend(const sigset_t *mask): 临时更改信号掩码并挂起执行,直到捕获到一个未掩码的信号。

下面是一个最简单信号使用例程

#include "stdlib.h"
#include "stdio.h"
#include "signal.h"
#include "fcntl.h"
#include "sys/types.h"
#include "unistd.h"

void signal_handler(int num)
{
    printf("this is signal(%d) test.\n",num);
    exit(0);
}

int main(void)
{
    pid_t thisPid;

    signal(SIGINT,signal_handler);
    thisPid = getpid();
    printf("pid %d\n",thisPid);
    while (1);
    return 0;
}

其他与信号相关知识

以后再补充这里面的知识吧。

  1. linux线程与信号
    1. 多线程共享所在进程的信号处理函数
    2. posix函数kill()/sigqueue()必须面向进程。而不是进程下某个特定的线程。
  2. 阻塞和解除阻塞信号
    1. 隐式阻塞机制
    2. 显式阻塞机制
      1。 进程中的每个线程都有一个独立的信号掩码,它指示线程当前阻塞的信号集。线程可以使用pthread_sigmask()操作它的信号掩码。
  3. 同步接受信号
    1. 不是通过信号处理程序异步捕获信号.可以同步地接受信号,也就是说,在发送信号之前一直执行阻塞,此时内核将向调用者返回关于信号的信息。
    2. sigwaitinfo(),sigtimedwait(),sigwait() 这三个函数将挂起执行,直到传递指定集合中的一个信号。每个调用都返回关于所传递信号的信息。
      前一直执行阻塞,此时内核将向调用者返回关于信号的信息。
    3. sigwaitinfo(),sigtimedwait(),sigwait() 这三个函数将挂起执行,直到传递指定集合中的一个信号。每个调用都返回关于所传递信号的信息。
    4. signalfd()返回一个文件描述符,该文件描述符可用于读取关于发送给调用者的信号的信息。每次从这个文件描述符中读取(2)都会阻塞,直到signalfd(2)调用中指定的集合中的一个信号被传递给调用者。read(2)返回的缓冲区包含一个描述信号的结构。

关于技术交流

此处后的文字已经和题目内容无关,可以不看。
qq群:825695030
微信公众号:嵌入式的日常
如果上面的文章对你有用,欢迎打赏、点赞、评论。linux信号(signal)详解.md

相关标签: 初学linux