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

Linux中的信号

程序员文章站 2022-07-12 10:28:27
...

信号(signal)的引入

程序在执行的时候,几乎任何时刻都会反生事件。
信号通常用来向一个进程通知事件。
信号是不可提前预知的,所以信号是异步的。
信号随时都可能发生,接收信号的进程也可以没有控制权。
每个信号名都以SIG开头,信号名的定义在<signal.h>中。
信号名一般都是宏,内部通常是一个正整数。

信号

信号(signal)是一种软件中断,它提供了一种处理异步事件的方法,也是进程间惟一的异步通信方式。
Linux中的信号

kill(send a signal to a process)

给进程发信号

系统中有哪些信号?

#通过下面的命令查看linux系统中的信号
kill -l
Linux中的信号
可以查看到64个信号

信号的值定义在signal.h中,在Linux中没有32和33这两个信号。
其中编号34以上的是实时信号,34以下的信号是普通信号。
而这些信号各自在什么条件下产生,默认的处理动作是什么,
在signal(7)中都有详细说明,在命令行上输入m

可靠信号和不可靠信号

可靠信号又称为实时信号
非可靠信号又称为非实时信号

在Linux系统中,信号的可靠性是指信号是否会丢失,或者说该信号是否支持排除。
SIGHUP( 1 ) ~ SIGSYS( 31 )之间的信号都是继承自UNIX系统是不可靠信号。
Linux系统根据POSIX标准定义了SIGRTMIN(34) ~ SIGRTMAX(64)之间的信号,它们都是可靠信号,也称为实时信号。

什么情况下引发信号

1.键盘事件 ctrl +c ctrl +
2.非法内存 如果内存管理出错,系统就会发送一个信号进行处理
3.硬件故障 同样的,硬件出现故障系统也会产生一个信号
4.环境切换 比如说从用户态切换到其他态,状态的改变也会发送一个信号,这个信号会告知给系统

通常信号的来源:
1、程序的错误,如非法访问内存
2、外部信号,如按下了CTRL+C
3、通过kill或sigqueue向另一个进程发送信号(常用)

信号的处理方式

进程收到信号后,其可选的处理动作有以下三种:
1.忽略此信号。
2.执行该信号的默认处理动作(终止该信号)。
3.提供一个信号处理函数(自定义动作),要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉(Catch)一个信号。

进程可以屏蔽掉大多数的信号,除了SIGSTOP和SIGKILL
SIGSTOP:使正在运行的进程暂停
SIGKILL:使正在运行的进程退出

信号的优先级

信号实质上是软中断,中断有优先级,信号也有优先级。
如果一个进程有多个未决信号,则对于同一个未决的实时信号,内核将按照发送的顺序来递送信号。
如果存在多个未决信号,则值(或者说编号)越小的越先被递送。
如果即存在不可靠信号,又存在可靠信号(实时信号),
虽然POSIX对这一情况没有明确规定,但Linux系统和大多数遵循POSIX标准的操作系统一样,将优先递送不可靠信号。

通过案例观察信号

终端1中

out.sh
Linux中的信号

chmod u+x out.sh

./out.sh
Linux中的信号
信号未决?

执行信号的处理动作称为信号递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。
进程可以选择阻塞(Block)某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。注意,阻塞和忽略是不同,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

回调函数

通过函数指针调用的函数称为回调函数

信号处理函数与相关结构

signal函数用于截取系统的信号,对此信号挂接用户自己定义的处理函数
#include<signal.h>
typedef void (*sighandler)(int);//函数指针

sighandler signal(int signum,sighandler handler);

signum 信号编号

sighandler 信号的处理函数
SIG_IGN 忽略信号
SIG_DFL 使用默认的信号处理函数

kill 命令:

-l          查看所有的信号信息
    
-n pid      n是信号编号,向pid进程发送n信号 

int kill(pid_t pid, int sig);
    
	向pid进程发送sig信号
    
	pid         进程ID
    
	sig         信号编号

pid_t getpid(void);
    
	返回调用这个函数的进程的PID

sigset_t
unsigned long int __val[_SIGSET_NWORDS];

int sigemptyset(sigset_t *set);
清空信号集合

int sigfillset(sigset_t *set);
将所有信号都添加进入信号集合中

int sigprocmask(int how, const sigset_t *set, sigset_t *old‐set);
对信号集合当中的信号进行操作
how:
SIG_BLOCK 屏蔽信号集合当中的信号
SIG_UNBLOCK 解除信号集合当中的信号
SIG_SETMASK 设置信号集合当中的信号操作

信号会打断程序的所有阻塞

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_flags:
    0       默认信号处理方式-打断当前操作后,不会重新调用原先阻塞的操作
    SA_SIGINFO  在传递信号的时候有可能信号会带参数,因此,回掉函数需要指定为sa_sigaction类型

int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
为信号指定操作
signum 信号编号
act 对应的回调操作
oldact 将信号原先的操作进行存储

int sigqueue(pid_t pid, int sig, const union sigvalvalue);
向指定进程发送指定信号,并且可以携带一个参数
pid 进程ID
sig 信号编号
value : 为信号传递同时携带一个参数
typedef union sigval
{
int sival_int;
void *sival_ptr;
} sigval_t;

注意:
signal和kill配合使用

sigaction和sigqueue配合使用

SIGINT捕获

SIGINT: 程序中止信号,在用户按下Ctrl+C时发出

注意:可以使用Ctrl+\ 来退出(SIGQUIT)。
当我们按下:ctrl+\或kill –SIGQUIT pid发送SIGQUIT信号时,程序退出,那是因为进程对SIGQUIT信号的默认处理动作是退出程序。

signal函数
#include <signal.h>
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

第一个参数signum:指明了所要处理的信号类型,它可以取除了SIGKILL和SIGSTOP外的任何一种信号。  
第二个参数handler:描述了与信号关联的动作,它可以取以下三种值:
(1)SIG_IGN   
这个符号表示忽略该信号。
#include <stdio.h>
#include <signal.h>
int main(int argc, char *argv[])
{
signal(SIGINT, SIG_IGN);
while (1);
return 0;
}
按下Ctrl+C,由于信号忽略了,程序并不退出,必须通过kill -9 pid来干掉它
Linux中的信号
Linux中的信号
2)SIG_DFL   
这个符号表示恢复对信号的系统默认处理。不写此处理函数默认也是执行系统默认操作。
#include <stdio.h>
#include <signal.h>
int main(int argc, char *argv[])
{
signal(SIGINT, SIG_DFL);
while (1);
return 0;
}

这时就可以按下CTRL +C 来终止该进程。把signal(SIGINT, SIG_DFL);这句去掉,效果是一样的.
注意:signal函数只影响当前进程

案例

自定义函数处理信号
signal1.c
Linux中的信号
程序退出按ctrl+C,按Ctrl+\失效

Linux中的信号

案例

alarm触发SIGALRM信号

alarm(5);代表5秒后投递SIGALRM信号
alarm(0);代表信号作废,投递SIGALRM信号

alarm1.c
Linux中的信号
运行结果为:
Linux中的信号

案例

alarm3.c
Linux中的信号
父子进程间发信号

kill发送信号
signal设置信号关联
pause等待信号到来(阻塞)

sigquque
sigaction

sigaction函数和signal类似
sigaction函数的功能是用于改变进程接收到特定信号后的行为。

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

sigaction函数检查或修改与指定信号相关联的处理动作,该函数取代了signal函数。
因为signal函数在信号未决时接收信号可能出现问题,所以使用sigaction更安全

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_flags = 0代表
#define SIG_BLOCK 0 /* Block signals. */

案例

自定义信号

重点(procsignal)

#根据进程名获取进程ID
pidof 进程名

pidof mysqld

#查找进程对比ID号
ps -ef|grep “mysql”
Linux中的信号

popen:建立管道I/O

定义函数
FILE * popen( const char * command,const char * type);
函数说明
popen()会调用fork()产生子进程,然后从子进程中调用/bin/sh -c来执行参数command的指令。参数type可使用“r”代表读取,“w”代表写入。依照此type值,popen()会建立管道连到子进程的标准输出设备或标准输入设备,然后返回一个文件指针。随后进程便可利用此文件指针来读取子进程的输出设备或是写入到子进程的标准输入设备中。此外,所有使用文件指针(FILE*)操作的函数也都可以使用,除了fclose()以外。
返回值
若成功则返回文件指针,否则返回NULL,错误原因存于errno中。

pclose:(关闭管道I/O)

表头文件
#include<stdio.h>
定义函数
int pclose(FILE * stream);
函数说明
pclose()用来关闭由popen所建立的管道及文件指针。参数stream为先前由popen()所返回的文件指针。
返回值
返回子进程的结束状态。如果有错误则返回-1,错误原因存于errno中。

上一篇: 信号

下一篇: 信号