Linux信号(一)

一、进程间通信机制(IPC)

在 Linux 系统上运行多个进程,一般进程间是独立运行的。但是有时候需要某些进程之间相互配合以完成预期目地,这就需要 IPC 机制来支持,以下总结一些进程间通信机制(IPC)的特点:
①信号,用来表示事件的发生,在进程通信中信号是唯一的异步通信机制
②管道,用于在进程间传递数据
③套接字,供同一台主机或者互联网上不同主机上运行的进程之间传递数据
④文件锁定,防止其它进程读取或者更新内容,允许某进程对文件的部分区域加以锁定
⑤消息队列,用以在进程间交换消息(消息包)
⑥信号量,用来同步进程动作
⑦共享内存,允许两个及以上进程共享一块内存,当一个进程改变了共享内存的内容时,其它进程会立即了解到这一变化

二、信号简介

信号一般被称为“软中断”,因为某些信号的到达将会导致进程中断(对应硬中断比如:拔电源、摔电脑等导致的程序运行中断)。信号总是由内核发出的,要想认识信号需要从以下过程去掌握。

1、信号的产生

硬件异常:硬件检测到错误(异常机器指令,如被零除、段错误,如指针的越界访问)通知内核,内核发出信号。

用户输入:比如用户输入 Ctrl + C 发出 SIGINT 信号,终止进程。

系统调用:程序中调用 kill() 函数,向指定进程发送指定信号。

软件事件:比如闹钟定时到期、子进程退出给父进程发送信号、文件描述符状态由阻塞变为非阻塞。

2、信号在目标进程中注册

在进程表中的表项中有一个软中断信号域,该域中每一位对应一个信号。内核给一个进程发送软中断信号的方法,是在进程所在的进程表项的信号域设置对应于该信号的位。如果信号发送给一个正在睡眠的进程,如果进程睡眠在可被中断的优先级上,则唤醒进程;否则仅设置进程表中信号域相应的位,而不唤醒进程。如果发送给一个处于可运行状态的进程,则只置相应的域即可。

信号在进程中的注册就是信号加入到未决信号集中,并且信号所携带的信息被保留到未决信号信息链的某个结构中,信号在进程的未决信号集中就表明进程知道这些信号存在,但还没有来得及处理,或者该信号被进程阻塞。

当实时信号传给一个进程时,不管该信号是否已经在进程中注册,都会被注册一次,信号不会丢失。因此,实时信号被称为“可靠信号”。同一个实时信号可能会在同一进程保存多次(多次插入未决信号链中)。即:所有诞生的实时信号都会在目标进程中注册。

当一个非实时信号发送给一个进程时,如果该信号已在进程中注册,则该信号将被丢弃,因此非实时信号又称“不可靠信号”。即同一非实时信号在同一进程的未决信号信息链中至多存在一个。

3、信号的执行和注销

内核处理信号是在该进程的上下文中,进程必须处于运行状态才能处理该进程的信号。当一个进程被CPU唤醒或者重新获得CPU时间片时,在其内核空间返回到用户空间时会检测是否有信号等待处理。如果存在未决信号等待处理,且该信号没有被进程阻塞,则在运行相应信号处理函数前,进程会把信号从未决信号集链表中卸掉。

对于非实时信号来说,由于未决信号信息链中最多只占有一个,因此,该信号会在进程未决集中删除。对于实时信号来说,只有等到该未决信号集中所有该信号处理完毕之后才删除该信号。

当所有未被屏蔽的信号处理完毕之后,就返回用户空间。对于未被屏蔽的信号,取消屏蔽之后,会在返回用户空间之前执行上述步骤。

进程不会在内核态运行时处理信号,进程在用户态下不会有未处理完的信号。

信号一般有以下几种处理方式:

忽略:①对于非实时信号,若某进程的未决信号集中该信号已经被注册,那么再次发送该信号就会被内核忽略;②对于其他一些信号(不是所有信号)可被用户捕获之后,由用户执行忽略操作。

终止该进程:例如该进程收到 SIGINT 信号。

产生核心转储文件并终止进程:比如 SIGABRT 信号

暂停进程执行

在之前进程暂停处恢复进程执行

系统默认处理

用户自定义处理

4、关于信号的小结

信号进程间通信的一种方法,信号由内核产生(一定是内核产生),也由内核传递给某一进程,在进程中会存在两个集合,分别是未决信号集和阻塞信号集,当有信号产生时,信号处于未决状态,信号被内核传达到对应进程,该进程会在未决信号集中设置该信号对应的位,等到该进程运行期间CPU由内核态转到用户态前会扫描该进程对应的未决信号集和阻塞信号集,并且把未阻塞的信号(信号发生了,但是对应阻塞信号集中没有该信号)一一处理之后注销。当然,对于阻塞了的信号,等到它们变为非阻塞的时候,该进程会在下一次有内核态转换到用户态之前处理掉没阻塞的所有信号。

5、附上所有信号(非实时信号)

SIGABRT 当进程调用abort()函数时,内核向进程发送该信号。默认该信号会终止进程并产生核心转储文件。

SIGALRM 调用alarm()或setitimer()而设置的定时器到时,内核向进程发送该信号。

SIGBUS 内存访问错误会产生该错误

SIGCHLD 当父进程的某一子进程终止,或因为调用了exit(),或被杀死时,内核向父进程发送该信号,当进程的某一子进程收到信号而停止或恢复时也会向进程发送该信号。

SIGCLD 同SIGCHLD

SIGCONT 将该信号发送给已停止的进程,进程将会恢复运行(在之后某个点恢复调度),当接收信号的进程当前不处于停止状态时默认忽略该信号。

SIGEMT Unix一般用该信号标识一个依赖于实现的硬件错误。

SIGFPE 特定类型的算术错误产生该信号,比如除以0。

SIGHUP 当终端断开时,将发送该信号给终端控制进程。

SIGILL 进程试图执行非法(格式不正确)的机器语言指令,系统将向进程发送该信号

SIGINFO Linux中,该信号与SIGPWR同,BSD系统中,键入Ctrl + T可以产生该信号,用于获取前台进程组的状态信息。

SIGINT 当用户键入终端 Ctrl + C 时,终端驱动程序将发送该信号给前台进程组,该信号默认操作是终止进程。

SIGIO 利用fcntl()系统调用可对特定类型的打开文件描述符发生I/O事件时产生该信号。

SIGIOT Linux中通SIGABRT 其它一些Unix中表示发生了由实现定义的硬件错误。

SIGKILL 此信号为必杀信号,处理器程序无法将它阻塞、忽略或者捕获

SIGLOST Linux中未用,Unix中用到。

SIGPIPE 某一进程试图向管道、FIFO、或套接字写入信息时,若这些设备无相应的读进程,可发生此信号。

SIGPOLL 信号由System V 派生而来,与Linux的SIGIO信号同义

SIGPROF settimer()调用所设置的性能分析定时器刚一过期,内核就将产生该信号。性能分析定时器用于记录进程所使用的CPU时间。与虚拟定时器不同,性能分析定时器对CPU时间计数时会将用户态和内核态都包含在内

SIGPWR 电源故障信号,当系统配备不间断电源(UPS)时,可以设置守护进程来监控电源发生故障时备用电池的剩余电量。如果电池电量即将耗尽(长时间停电),监控进程会将该信号发给init进程,init进程将快速有序关机。

SIGQUIT 用户输入Ctrl+\时,该信号发往前台进程组,默认该信号终止进程,并生成核心转储文件。当进程死循环,终止后调用gdb加载核心转储文件,用backtrace命令可以发现正在执行的是程序哪部分代码。

SIGSEGV 应用程序对内存引用无效时就产生该信号。“段错误”

SIGSTKFLT Linux未作使用,“协处理器栈错误”

SIGSTOP 必停信号,处理器无法将其阻塞、忽略或者捕获,总能停止进程。

SIGSYS 若进程发起的系统调用有误,那么将产生该信号。这意味着系统将进程执行的指令视为一个系统调用陷阱,但相关的系统调用编号是无效的。

SIGTERM 这是用来终止进程的标准信号,即kill,和killall指令发送的默认信号。一般应该默认优先使用该信号来终止进程,这样好的程序会在收到该信号后处理该信号并退出,而kill -9发送SUGKILL终止进程应该作为最后手段。

SIGTRAP 该信号用来实现断点调试功能及strace()所执行的跟踪系统调用功能。

SIGTSTP 作业控制的停止信号,用户键入Ctrl+Z时将发送该信号给前台控制组,使其停止运行

SIGTTIN 在作业控制shell下运行时,若后台进程组试图对终端进行read()操作,终端驱动程序将向该进程组发送此信号,该信号默认停止进程。

SIGTTOU 该信号目的与SIGTTIN信号类似,但针对的是后台作业的终端输出。在作业控制shell下运行时,若终端启用了TOSTOP(终端输出停止)选项(可能通过stty tostop命令)而某一后台进程组试图对终端进行write()操作,那么终端驱动程序将向该进程组发送SIGTTOU信号,该信号默认终止进程。

SIGUNUSED 该信号没有使用。

SIGURG 系统发送该信号给一个进程,表示套接字上存在紧急数据

SIGUSR1 该信号供程序员自定义使用,内核不会主动产生该信号。

SIGUSR2 同SIGUSR1,仅这两个信号完全供程序员使用,内核不会主动产生。

SIGVTALRM 调用settimer() 设置的虚拟定时器那一刹那,内核产生该信号,虚拟定时器记录的是进程在用户态所使用的的CPU时间。

SIGWINCH 在窗口环境下,当终端窗口尺寸发生变化时。会向前台发送该信号。vi、less之类的程序会在窗口尺寸调整后重新绘制输出。

SIGXCPU 当进程的CPU时间超过对应资源限制时,将发送该信号给进程。

SIGXFSZ 若进程因试图增大文件(调用write()或truncate())而突破对进程文件大小的资源限制时将发送此信号给进程。

小结信号表

两个信号SIGKILL、SIGSTOP是无法捕获、忽略的。

三、信号在程序中的使用

1、信号的产生

>kill
#include <signal.h>
int kill(pid_t pid, int sig);
pid 标识一个或多个目标:
pid > 0 发送pid表示的进程
pid = 0 发送信号给与调用进程同组的每一个进程,包括调用进程自身。
pid < -1 会向同组ID等于该pid绝对值的进程组内所有下属进程发送信号。
pid = -1 调用进程发送到它有权发送的每个目标进程,出去init(进程ID为1)和调用进程自身。如果特权级进程发起这一调用,那么会发送信号给系统中所有进程。该发送方式也称为广播信号。

如果无进程与制定pid相匹配,则kill()调用失败,将erron置为ESRCH。
sig参数表示表示要发送何种信号,sig为0则发送空信号,根据kill后errno检查进程是否存在。

进程发送信号需要一定权限,权限规则如下:

特权级(CAP_KILL)可以向任何进程发送信号

以root用户、用户组运行的init进程(进程号为1),仅能接受已安装了处理器函数的信号

若发送者的实际或者有效用户ID匹配于接受者的实际用户ID或者保存设置用户ID,那么非特权进程也可以向另一进程发送信号。(待解决)

SIGCONT 信号要特殊处理,非特权进程可以向同一会话中的任何其他进程发送这一信号。

如果进程无权发送信号给所请求的pid,那么kill()调用失败,将errno置为EPERM,只要有一个进程收到信号,kill调用就成功。

raise
#include <signal.h>
int raise(int sig);
相当于
kill(getpid(), sig);
raise()调用唯一可能错误是 EINVAL 无效。
killpg
include <signal.h>
int killpg(pid_t pgrep, int sig);
向进程组所有成员发送一个信号
pgrep为0,则会向调用者所属进程组的所有进程发送此信号。
相当于
kill(-pgrp,sig);

2、信号的传递

信号集:每个进程都有两个信号集,未决信号集和阻塞信号集,对于这两个信号集,我们无法直接进行修改只能通过传入自定义信号集进行修改。

int sigemptyset(sigset_t *set); 将set集合置空 int sigfillset(sigset_t *set); 将所有信号加入set集合 int sigaddset(sigset_t *set,int signo);将signo信号加入到set集合 int sigdelset(sigset_t *set,int signo);从set集合中移除signo信号 int sigismember(const sigset_t *set,int signo); 判断信号是否存在

除sigismember外,其余操作函数中的set均为传出参数。sigset_t类型的本质是位图。但不应该直接使用位操作,而应该使用上述函数,保证跨系统操作有效。

sigprocmask函数用来屏蔽信号、解除屏蔽也使用该函数。其本质,读取或修改进程控制块中的信号屏蔽字(阻塞信号集)。

函数原型:int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
函数返回值:成功:0;失败:-1,设置errno

how参数取值:假设当前的信号屏蔽字为mask
SIG_BLOCK: 当how设置为此值,set表示需要屏蔽的信号。相当于 mask = mask|set
SIG_UNBLOCK: 当how设置为此,set表示需要解除屏蔽的信号。相当于 mask = mask & ~set。
SIG_SETMASK: 当how设置为此,set表示用于替代原始屏蔽及的新屏蔽集。相当于mask = set若,调用sigprocmask解除了对当前若干个信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。
set:传入参数,是一个自定义信号集合。由参数how来指示如何修改当前信号屏蔽字。
oldset:传出参数,保存旧的信号屏蔽字。

int sigpending(sigset_t *set);  // set传出参数。

函数说明:读取当前进程的未决信号集
函数返回值:成功:0;失败:-1,设置errno。

3、信号的安装(捕获)

安装信号主要是用来确定信号值与该进程针对该信号执行的处理动作之间的映射关系。即:对于某一进程,若接收到某一信号要执行何种操作。

signal函数
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

函数返回值:成功:返回函数指针;失败:返回SIG_ERR,设置errno

sigaction
int sigaction(  int signum, 
                const struct sigaction *act, 
                struct sigaction *oldact
); 

函数参数:
sinnum:捕捉的信号
act:传入参数,新的处理方式。
oldact:传出参数,旧的处理方式。
struct sigaction结构体
struct sigaction {
void (*sa_handler)(int); // 信号处理函数
void (sa_sigaction)(int, siginfo_t , void *); // 信号处理函数
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};

sa_restorer:该元素是过时的,不应该使用,POSIX.1标准将不指定该元素。(弃用)

sa_sigaction:当sa_flags被指定为SA_SIGINFO标志时,使用该信号处理程序。(很少使用)

sa_handler:指定信号捕捉后的处理函数名(即注册函数)。也可赋值为SIG_IGN表忽略 或 SIG_DFL表执行默认动作

sa_mask: 用来指定在信号处理函数执行期间需要被屏蔽的信号,特别是当某个信号被处理时,它自身会被自动放入进程的信号掩码,因此在信号处理函数执行期间这个信号不会再度发生。注意:仅在处理函数被调用期间屏蔽生效,是临时性设置。

sa_flags:通常设置为0,表使用默认属性。

四、总结

内核产生信号后,会将信号发送到指定进程。每个进程都有未决信号集和阻塞信号集,信号会进入未决信号集等待处理。阻塞信号集由程序员设置,暂时屏蔽对应信号。当进程由内核态转到用户态的时候会首先查看信号集中是否有未阻塞且未处理的信号,将这些信号处理后再切换到用户态。

补充一句:Linux下内存分用户区和内核区。但是内核态和用户态跟内核区、用户区都没关系,内核区可以访问整个内存空间,用户区只能访问用户区。每次有系统调用产生就会产生内核态与用户态的切换,每次从用户态切换到内核态都要检查进程pcb,检查信号集,然后处理信号集。

转载于:https://my.oschina.net/dingjingMaster/blog/823909

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值