[linux环境编程] 信号的基本概念与操作函数

[linux环境编程] 信号的基本概念与操作函数

一、基本的概念

1、中断的基本概念

  中断是指在CPU正常运行期间,由于内外部事件或由程序预先安排的事件引起的CPU暂时停止正在运行的程序,转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再返回去继续运行被暂时中断的程序。

  而在Linux中通常分为外部中断(又叫硬件中断)和内部中断(又叫异常)。
  硬中断:来自硬件设备的中断
  软中断:来自其它程序的中断

2、信号的基本概念

  信号是软件中断,提供了一种处理异步事件的方法,可以把他看作是进程与进程、内核与进程通信的一种方式。

3、信号的分类

  信号分为不可靠信号与可靠信号,编号由1~31、34~64(31、32空缺)。

  在早期Unix版本中信号都是不可靠的,其编号在1~31之间,这些信号是建立在早期的信号机制上的,其信号可能会丢失。为了尽量避免这种情况,一个事件发生可能会产生多次信号。不可靠信号不支持排除,在接收信号的时候可能会丢失,如果一个发给一个进程多次,它可能只接收到一次,其它的可能就丢失了。 早期版本的Linux中,进程在处理这种信号的时候,哪怕设置的信号处理函数,当信号处理函数执行完毕后,会再次恢复成默认的信号处理方式。

  之后经过信号机机制的更改,设计了34~64编号的可靠信号。可靠信号相比不可靠信号而言,它支持排除,并且不会丢失。即使可靠信号被递送到当前要阻塞的信号集,被屏蔽的可靠信号也会按照顺序“排队”,处于未决状态而不是丢失。被解除限制后会按照顺序依次接收。

  如果将不可靠信号比作是“女朋友要你送礼物”的话(发出多次信号,你最终可能只会执行一次),那么可靠信号就是“老板给你安排任务”(你可以暂时不做,但最终还是要有序完成)。其中无论是可靠信号还是不可靠信号都是通过一些同样的信号处理函数进行处理,并无区别。

  不可靠信号如下:

信号 解释 产生条件 默认动作
SIGHUP(1) 连接断开信号 如果终端接口检测一个连接断开,则将此信号发送给与该终端相关的控制进程(会话首进程) 终止
SIGINT(2) 终端中断符信号 用户按中断键(Ctrl+C),产生此信号,并送至前台进程组的所有进程 终止
SIGQUIT(3) 终端退出符信号 用户按退出键(Ctrl+),产生此信号,并送至前台进程组的所有进程 终止+core
SIGILL(4) 非法硬件指令信号 进程执行了一条非法硬件指令 终止+core
SIGTRAP(5) 硬件故障信号 指示一个实现定义的硬件故障。常用于调试 终止+core
SIGABRT(6) 异常终止信号 调用abort函数,产生此信号 终止+core
SIGBUS(7) 总线错误信号 指示一个实现定义的硬件故障,常用于内存故障 终止+core
SIGFPE(8) 算术异常信号 表示一个算术运算异常,例如除以0、浮点溢出等 终止+core
SIGKILL(9) 终止信号 不能被捕获或忽略。常用于杀死进程 终止
SIGUSR1(10) 用户定义信号 用户定义信号,用于应用程序 终止
SIGSEGV(11) 段错误信号 试图访问未分配的内存,或向没有写权限的内存写入数据 终止+core
SIGUSR2(12) 用户定义信号 用户定义信号,用于应用程序 终止
SIGPIPE(13) 管道异常信号 写管道时读进程已终止,或写SOCK_STREAM类型套接字时连接已断开,均产生此信号 终止
SIGALRM(14) 闹钟信号 以alarm函数设置的计时器到期,或以setitimer函数设置的间隔时间到期,均产生此信号 终止
SIGTERM(15) 终止信号 由kill命令发送的系统默认终止信号 终止
SIGSTKFLT(16) 数协器栈故障信号 表示数学协处理器发生栈故障 终止
SIGCHLD(17) 子进程状态改变信号 在一个进程终止或停止时,将此信号发送给其父进程 忽略
SIGCONT(18) 使停止的进程继续 向处于停止状态的进程发送此信号,令其继续运行 继续/忽略
SIGSTOP(19) 停止信号 不能被捕获或忽略。停止一个进程 停止进程
SIGTSTP(20) 终端停止符信号 用户按停止键(Ctrl+Z),产生此信号,并送至前台进程组的所有进程 停止进程
SIGTTIN(21) 后台读控制终端信号 后台进程组中的进程试图读其控制终端,产生此信号 停止
SIGTTOU(22) 后台写控制终端信号 后台进程组中的进程试图写其控制终端,产生此信号 停止
SIGURG(23) 紧急情况信号 有紧急情况发生,或从网络上接收到带外数据,产生此信号 忽略
SIGXCPU(24) 超过CPU限制信号 进程超过了其软CPU时间限制,产生此信号 终止+core
SIGXFSZ(25) 超过文件长度限制信号 进程超过了其软文件长度限制,产生此信号 终止+core
SIGVTALRM(26) 虚拟闹钟信号 以setitimer函数设置的虚拟间隔时间到期,产生此信号 终止
SIGPROF(27) 虚拟梗概闹钟信号 以setitimer函数设置的虚拟梗概统计间隔时间到期,产生此信号 终止
SIGWINCH(28) 终端窗口大小改变信号 以ioctl函数更改窗口大小,产生此信号 忽略
SIGIO(29) 异步I/O信号 指示一个异步I/O事件 终止
SIGPWR(30) 电源失效信号 电源失效,产生此信号 终止
SIGSYS(31) 非法系统调用异常 指示一个无效的系统调用 终止+core

4、信号的来源

  信号的来源主要分为硬件来源与软件来源。

    硬件来源:
    键盘:Ctrl+c 终端中断信号、Ctrl+z 终端暂停信号、Ctrl+/终端退出信号
    驱动:硬件设备被激活、使用、失效
    内存:非法访问内存
  软件来源:
    命令:kill -信号 进程号、killall -信号 程序名(普通用户只能给自己的进程发信号,超级用户可以能任意进程发送信号)
    函数:kill/raise/alarm/setitimer/sigqueue

5、信号的处理方式

  忽略信号:大多数信号都是这种处理方式。
  捕捉信号:当某种信号产生时,用户可以捕捉该信号,去调用一个用户自行设计的函数,以达到用户所期望的处理效果。
  默认操作:系统对每个信号都有默认操作,种类有:忽略、继续、终止、终止+core、停止进程。要注意的是,系统对大多数信号的默认操作是终止该进程

  值得注意的是,SIGKILL(9)与SIGSTOP(19)无法被忽略和捕捉,因为他们向内核和超级用户提供了终止或停止进程的可靠方法。

  core文件是一种二进制文件,需要一些高度工具才能解析出来。在Ubuntu系统下,默认不产生core,若要生成core文件则需要使用命令设置:ulimit -c unlimited。以下是core文件的处理方式:
  1、执行gcc -g code.c 生成带调试信息的可执行文件;
  2、运行可执行文件产生core文件;
  3、执行gdb ./a.out core 后程序会停止在产生错误的位置。

6、信号集

  信号集是一种数据类型,一共由128个二进制位组成,每个二进制位表示一个信号,代表的是一系列信号的集合,一般用于信号的屏蔽。

  值得注意的是,由于C语言编译程序将不赋初值的局部变量和外部变量都初始化为0,而这是否与给定系统上信号集的实现相对应却并不清楚,所以在所有应用程序使用信号集之前要对该信号集调用sigemptyset(清空信号集)或sigfillset(填充信号集)一次。

7、子进程的信号处理

  因为子进程在被fork创建时会复制父进程的数据空间、堆和栈,信号捕捉函数的地址在子进程中是有意义的,所以通过fork创建的子进程会继承父进程的信号处理方式。而通过vfork+exec创建的子进程所运行的新程序会将原先从父进程复制来的数据覆盖掉,因此在子进程中会父进程将原先设置为要捕捉的信号都更改为默认动作。

二、信号的捕获和处理

1、信号捕获 signal

1        #include <signal.h>
2
3        typedef void (*sighandler_t)(int);
4
5        sighandler_t signal(int signum, sighandler_t handler);

  其功能是向内核注册一个信号处理函数,捕捉指定信号触发指定函数。

  signum:信号的编号,可以直接写数字,也可以使用系统提供的宏(SIG_IGN 忽略信号、SIG_DFL 恢复信号默认的处理方式)
  handler:函数指针,即指定函数的函数名
  返回值  :之前信号的处理方式

  在有些早期Unix系统中,向内核注册的信号处理函数在执行一次后会被恢复成默认的处理方式,如果想继续使用该信号处理函数,就得在每次的处理函数结束时再次注册。

2、信号发送 kill & raise

1        #include <sys/types.h>
2        #include <signal.h>
3
4        int kill(pid_t pid, int sig);
5
6
7        #include <signal.h>
8
9        int raise(int sig);

  int kill(pid_t pid, int sig);
  功能:向指定的进程发送信号
  pid:1、pid大于 0时,pid为接收信号的进程号。
      2、pid等于 0时,信号将发送给所有与调用该函数的用户属同一个使用组的进程。
      3、pid等于-1时,信号将发送给所有调用该函数的进程有权给其发送信号的进程,除了进程1(init)。
      4、pid小于-1时,信号将发送给以pid绝对值为组ID的所有进程。
  sig:将要发送的信号,0表示空信号,不会向进程发送信号,但是会测试是否能向pid发送信号,这样可以检测一个进程是否存在,返回-1表示进程不存在,errno为ESRCH。但这种测试并非原子操作,所以这种测试并无多大价值。
  返回值:-1,说明进程不存在

  int raise(int sig);
  功能:向自己发送信号

3、闹钟与休眠 alarm & pause

1        #include <unistd.h>
2
3        unsigned int alarm(unsigned int seconds);
4
5
6        #include <unistd.h>
7
8        int pause(void);

  alarm函数是一个闹钟函数,当时间超过所设置的seconds时会向程序发送一个SIGARLM信号。返回值为0或以前设置的闹钟时间的余留秒数。需要注意的是,SIGALRM信号的默认处理方式是直接退出。

  pause函数从功能上来讲它相当于没有时间限制的sleep函数,进程调用了pause函数后会进程睡眠状态,直到有不被忽略的信号把它叫醒。当信号来临后,先执行信号处理函数,信号处理函数结束后pause再返回。pause函数要么不返回(一直睡眠),要么返回-1,并且修改全局变量errno的值。

4、信号集处理函数

 1        #include <signal.h>
 2
 3        int sigemptyset(sigset_t *set);
 4
 5        int sigfillset(sigset_t *set);
 6
 7        int sigaddset(sigset_t *set, int signum);
 8
 9        int sigdelset(sigset_t *set, int signum);
10
11        int sigismember(const sigset_t *set, int signum);

  清空/填充信号集:sigemptyset/sigfillset、添加/删除信号集:sigdelset/sigdelset、查询信号集成员中是否包含指定信号:sigismember

 5、信号屏蔽 sigprocmack

  每个进程都有一个信号掩码(signal mask),它就是一个信号集,里面包含了进程所屏蔽的信号。

 1        #include <signal.h>
 2
 3        int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
 4
 5
 6        SIG_BLOCK
 7               The set of blocked signals is the union of the current  set  and
 8               the set argument.
 9
10        SIG_UNBLOCK
11               The  signals  in set are removed from the current set of blocked
12               signals.  It is permissible to attempt to unblock a signal which
13               is not blocked.
14
15        SIG_SETMASK
16               The set of blocked signals is set to the argument set.

  功能:设置进程的信号掩码(信号屏蔽码)
  how:修改信号掩码的方式
    SIG_BLOCK:向信号掩码中添加信号
    SIG_UNBLOCK:从信号掩码中删除信号
    SIG_SETMASK:用新的信号集替换旧的信号掩码
  newset:新添加、删除、替换的信号集,也可以为空
  oldset:获取旧的信号掩码
  当newset为空时,就是在备份信号掩码

  当进程执行一些敏感操作时不希望被打扰,例如需要原子操作时,此时需要向屏蔽信号。屏蔽信号的目的不是为了不接收信号,而是延时接收,当处理完要做的事情后,应该把屏蔽的信号还原。当信号屏蔽时发生的信号会记录一次,这个信号设置为末决状态,当信号屏蔽结束后,会再发送一次。
  不可靠信号在信号屏蔽期间无论信号发生多少次,信号解除屏蔽后,只发送一次。
  可靠信号在信号屏蔽期间发生的信号会排队记录,在信号解除屏蔽后逐个处理。
  在执行处理函数时,会默认把当前处理的信号屏蔽掉,执行完成后再恢复。

6、获取未决信号 sigpending

1      #include <signal.h>
2
3      int sigpending(sigset_t *set); 

  功能:获取末决状态的信号

7、信号处理 sigaction 

 1        #include <signal.h>
 2
 3        int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
 4
 5
 6         The sigaction structure is defined as something like:
 7
 8            struct sigaction {
 9                void     (*sa_handler)(int); // 信号处理函数指针
10                void     (*sa_sigaction)(int, siginfo_t *, void *); // 信号处理函数指针 需要使用sigqueue发送信号
11                sigset_t   sa_mask; // 信号屏蔽码
12                int        sa_flags;
13                void     (*sa_restorer)(void);
14            };

  功能:设置或获取信号处理方式
  SA_NOCLDSTOP:忽略SIGCHLD信号
  SA_NODEFER/SA_NOMASK:在处理信号时不屏蔽信号
  SA_RESETHAND:处理完信号后,恢复系统默认处理方式
  SA_RESTART:当信号处理函数中断的系统调用,则重启系统调用。
  SA_SIGINFO:用sa_sigaction处理信号

8、信号排队 sigqueue

1        #include <signal.h>
2
3        int sigqueue(pid_t pid, int sig, const union sigval value);
4
5
6            union sigval {
7                int   sival_int;
8                void *sival_ptr;
9            };

  sigqueue函数只能把信号发给单个进程,可以使用value参数向信号处理程序传递整数和指针,除此之外,sigqueue函数与kill函数相似。需要留意的是,信号不能无限排队,其个数受到QUEUE_MAX的限制。

原文地址:https://www.cnblogs.com/usingnamespace-caoliu/p/9399161.html

时间: 2024-09-27 18:03:56

[linux环境编程] 信号的基本概念与操作函数的相关文章

Linux环境编程之信号(一):信号基本概述

引言 假如在后台运行一个可执行程序./a.out,如果想终止该程序,通常会按下Ctrl-C,从而产生一个中断,其实这个过程的实现就是通过信号完成的.信号是软件中断,它提供了一种处理异步事件的方法. (一) 每个信号都有一个名字,这些名字都以三个字符SIG开头.例如SIGALARM是闹钟信号,当由alarm函数设置的计时器超时后产生此信号.Linux除支持31种不同信号外,还支持应用程序额外定义信号.信号定义在<bits/signum.h>中,也可以通过命令kill -l查看. (二)信号的产生

Linux环境编程之信号(三):一些信号函数

(一)kill和raise函数 kill函数将信号发送给进程或进程组.raise函数则允许进程自身发送信号. #include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig); int raise(int  signo);  //返回值:若成功则返回0,若出错则返回-1. 参数:pid参数有4种情况:1.pid > 0 将信号发送给进程为pid的进程.2.pid == 0 将该信号发送给与发送进程属

Linux环境编程之信号(二):不可靠信号、中断的系统调用、可重入函数

(一)不可靠信号 对前面说的信号,是不可靠的,不可靠指的是信号可能会丢失:一个信号发生了,但进程却可能一直不知道这一点.另外,进程对信号的控制能力有限,只能捕捉信号或忽略它.有时用户希望通知内核阻塞一个信号:不要忽略它,在其发生时记住它,然后在进程做好准备时再通知它.这种阻塞信号的能力并不具备. 之前的版本中村咋一个问题:在进程每次接到信号对其进行处理时,随即将该信号动作复位为默认值.另一个问题是,在进程不希望某种信号发生时,它不能关闭该信号.进程能做的一切就是忽略该信号. (二)中断的系统调用

Linux环境编程之共享内存区(一):共享内存区简介

Spark生态圈,也就是BDAS(伯克利数据分析栈),是伯克利APMLab实验室精心打造的,力图在算法(Algorithms).机器(Machines).人(People)之间通过大规模集成,来展现大数据应用的一个平台,其核心引擎就是Spark,其计算基础是弹性分布式数据集,也就是RDD.通过Spark生态圈,AMPLab运用大数据.云计算.通信等各种资源,以及各种灵活的技术方案,对海量不透明的数据进行甄别并转化为有用的信息,以供人们更好的理解世界.Spark生态圈已经涉及到机器学习.数据挖掘.

Linux环境编程之共享内存区(二):Posix共享内存区

现在将共享内存区的概念扩展到将无亲缘关系进程间共享的内存区包括在内.Posix提供了两种在无亲缘关系进程间共享内存区的方法: 1.内存映射文件:由open函数打开,由mmap函数把得到的描述符映射到当前进程地址空间中的一个文件.(上一节就是这种技术) 2.共享内存区对象:由shm_open打开一个Posix名字(也许是在文件系统中的一个路径名),所返回的描述符由mmap函数映射到当前进程的地址空间.(本节内容) Posix共享内存区涉及以下两个步骤要求: 1.指定一个名字参数调用shm_open

Linux环境编程之文件I/O(四):文件I/O的数据结构

(一) Linux系统支持不同进程间共享打开的文件.内核使用三种数据结构表示打开的文件:进程表项.文件表项.v节点表. 1.进程表项:每个进程在进程表中都有一个记录项,记录项中年包含有一张打开文件描述符表,可将其视为一个矢量,每个描述符占用一项.与每个文件描述符相关联的是: a.文件描述符标志 b.指向一个文件表项的指针 2.内核为所有打开文件维持一张文件表.每个文件表项包含: a.文件状态标志,如读写.添加.同步和非阻塞等. b.当前文件偏移量 c.指向该文件v节点表项的指针 3.每个打开的文

Linux环境编程之进程(一):main函数调用、进程终止以及命令行参数和环境表

(一)main函数调用 main函数作为程序运行时的入口函数,它是如何被调用的呢?首先必须清楚一点,main函数也是一个函数,它只有被调用才能够执行.其实,在执行可执行程序时,在调用main函数之前,内核会先调用一个特殊的启动例程,将此启动例程作为可执行程序的起始地址.启动例程是如何作为可执行程序的起始地址的?这是由链接编译器设置的,而链接编译器则是由C编译器(如gcc编译器)调用的.启动例程作为可执行程序的起始地址主要做哪些工作呢?启动例程从内核取得命令行参数和环境变量值,以此来为main函数

Linux环境编程之文件I/O(七):目录文件及操作

什么是数据结构? 数据结构是相互之间存在一种或多种特定关系的数据元素的集合. 还有一些概念(数据.数据元素.数据项.数据对象.数据类型...) 传统上,我们把数据结构分为逻辑结构和物理结构. 逻辑结构:是指数据对象中数据元素之间的相互关系,也是我们今后最需要关注和讨论的问题. 物理结构:是指数据的逻辑结构在计算机中的存储形式. 逻辑结构分为以下四种: 1.集合:集合结构中的数据元素除了同属于一个集合外,之间没有任何关系. 2.线性结构:元素之间一对一. 3.树形结构:一对多. 4.图形结构:多对

Linux环境编程之文件I/O(五):fcntl函数

引言: 对于一个普通的文件,我们可以想到的对它的操作有,读取文件的内容.写数据到文件中,这些都是前面提到的read.write函数的作用.除此之外,还可以获取文件的其他性质,并对这些性质进行修改,比如文件的描述符.文件描述符标记.文件状态标志等等.这些对文件性质的修改就由fcntl函数完成. 函数介绍: #include <unistd.h> #include <fcntl.h> int fcntl(int fd, int cmd, ... /* arg */ ); 参数: fd: