信号(一)——简介、kill、signal、可/不可靠信号、实时/非实时信号——linux系统编程

linux的信号简介

信号
之前讲到父子进程没法共享全局变量,但是很多时候父子进程需要信息互通,所以需要:进程间通讯:linux为进程间交互提供了很多种方法
分别是:信号,管道,消息队列,socket网络通讯

本文讲信号.
信号的名称是在头文件 signal.h里定义的

信号:singnal来实现进程间简单的数据传递和通知

中断与信号的关系以及更多概念延伸:
linux系统编程之信号(一):中断与信号
BTW,这位大佬其他讲linux的博客也很不错

1、信号及信号来源

信号本质
信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。
信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。
信号来源
信号事件的发生有两个来源:硬件来源(比如我们按下了键盘或者其它硬件故障);软件来源,最常用发送信号的系统函数是kill, raise, alarm和setitimer以及sigqueue函数,软件来源还包括一些非法运算等操作。

2、信号的种类

可以从两个不同的分类角度对信号进行分类:(1)可靠性方面:可靠信号与不可靠信号;(2)与时间的关系上:实时信号与非实时信号。

类似于QT中,有发送信号函数emit和接收信号函数:槽函数
对应linux中 kill函数 signal函数

查看linux所支持的信号可用:kill –l
在这里插入图片描述
信号名称 描述
SIGABORT 进程停止运行
SIGALRM 警告钟
SIGFPE 浮点运算例外
SIGHUP 系统挂断
SIGILL 非法指令
SIGINT 终端中断
SIGKILL 停止进程(此信号不能被忽略或捕获)
SIGPIPE 向没有读者的管道
SIGSEGV 无效内存段访问
信号名称 描述
SIGQUIT 终端退出ctrl+
SIGTERM 正常终止
SIGUSR1 用户定义信号1
SIGUSR2 用户定义信号2
SIGCHLD 子进程已经停止或退出
SIGCONT 如果被停止则继续执行
SIGSTOP 停止执行
SIGTSTP 终端停止信号
SIGTOUT 后台进程请求进行写操作
SIGTTIN 后台进程请求进行读操作

3、进程对信号的响应

进程可以通过三种方式来响应一个信号:
(1)忽略信号,即对信号不做任何处理,其中,有两个信号不能忽略:SIGKILL及SIGSTOP;
(2)捕捉信号。定义信号处理函数,当信号发生时,执行相应的处理函数;
(3)执行缺省操作,Linux对每种信号都规定了默认操作。注意,进程对实时信号的缺省反应是进程终止。
Linux究竟采用上述三种方式的哪一个来响应信号,取决于传递给相应API函数的参数。

传统信号收发对:kill+signal

signal——信号接收的安装

man signal:

NAME
       signal - ANSI C signal handling

SYNOPSIS
       #include <signal.h>

       typedef void (*sighandler_t)(int);

       sighandler_t signal(int signum, sighandler_t handler);

DESCRIPTION
       The behavior of signal() varies across UNIX versions, and has also varied historically across different versions of Linux.  Avoid its use: use sigaction(2) instead.
       See Portability below.

       signal() sets the disposition of the signal signum to handler, which is either SIG_IGN, SIG_DFL, or the address of a programmer-defined  function  (a  "signal  han‐
       dler").

       If the signal signum is delivered to the process, then one of the following happens:

       *  If the disposition is set to SIG_IGN, then the signal is ignored.

       *  If the disposition is set to SIG_DFL, then the default action associated with the signal (see signal(7)) occurs.

       *  If  the  disposition is set to a function, then first either the disposition is reset to SIG_DFL, or the signal is blocked (see Portability below), and then han‐
          dler is called with argument signum.  If invocation of the handler caused the signal to be blocked, then the signal is unblocked upon return from the handler.

       The signals SIGKILL and SIGSTOP cannot be caught or ignored.

RETURN VALUE
       signal() returns the previous value of the signal handler, or SIG_ERR on error.  In the event of an error, errno is set to indicate the cause.

ERRORS
       EINVAL signum is invalid.

函数的原型是
void (*signal(int sig, void (*func)(int))) (int);
也可以理解为
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signal是一个带sig和func两个参数的函数

  • sig:准备捕捉或屏蔽的信号
  • func:接收到指定信号时将要调用的函数。 func这个函数必须有一个int类型的参数(即接收到的信号代码,即kill -l中那些一一对应的数字),它本身的类型是void
  • func也可以是下面两个特殊值: SIG_IGN 屏蔽该信号 SIG_DFL 恢复默认行为

kill——软件发送信号

kill支持命令发送和系统函数调用
首先来看命令行man kill:

NAME
       kill - send a signal to a process
SYNOPSIS
       kill [options] <pid> [...]
OPTIONS
       <pid> [...]
              Send signal to every <pid> listed.
       -<signal>
       -s <signal>
       --signal <signal>
              Specify the signal to be sent.  The signal can be specified by using name or number.  The behavior of signals is explained in signal(7) manual page.

       -l, --list [signal]
              List signal names.  This option has optional argument, which will convert signal number to signal name, or other way round.

       -L, --table
              List signal names in a nice table.

       NOTES  Your shell (command line interpreter) may have a built-in kill command.  You may need to run the command described here as /bin/kill to solve the conflict.

常用的有

  • kill -l
  • kill -信号num 进程号
  • kill -9 要杀死进程号

再来看看函数kill
man 2 kill:

NAME
       kill - send signal to a process

SYNOPSIS
       #include <sys/types.h>
       #include <signal.h>

       int kill(pid_t pid, int sig);

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       kill(): _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE

DESCRIPTION
       The kill() system call can be used to send any signal to any process group or process.

       If pid is positive, then signal sig is sent to the process with the ID specified by pid.

       If pid equals 0, then sig is sent to every process in the process group of the calling process.

       If pid equals -1, then sig is sent to every process for which the calling process has permission to send signals, except for process 1 (init), but see below.

       If pid is less than -1, then sig is sent to every process in the process group whose ID is -pid.

       If sig is 0, then no signal is sent, but error checking is still performed; this can be used to check for the existence of a process ID or process group ID.

       For  a process to have permission to send a signal it must either be privileged (under Linux: have the CAP_KILL capability), or the real or effective user ID of the
       sending process must equal the real or saved set-user-ID of the target process.  In the case of SIGCONT it suffices when the sending and receiving processes  belong
       to the same session.  (Historically, the rules were different; see NOTES.)

RETURN VALUE
       On success (at least one signal was sent), zero is returned.  On error, -1 is returned, and errno is set appropriately.

  • kill函数的作用是把参数sig给定的信号发送给标识号为pid的进程。
  • 要想发送一个信号,发送者进程必须拥有相应的权限。这通常意味着两个进程必须拥有同样的用户ID
  • int kill(pid_t pid, int sig);中sig是具有宏定义的,直接写kill -l中的名字即可,方便阅读

该系统调用可以用来向任何进程或进程组发送任何信号。参数pid的值为信号的接收进程

pid>0 进程ID为pid的进程
pid=0 同一个进程组的进程
pid<0 pid!=-1 进程组ID为 -pid的所有进程
pid=-1 除发送给每一个调用进程有权限发送的进程除自身及1(init)进程外

Kill()最常用于pid>0时的信号发送。该调用执行成功时,返回值为0;错误时,返回-1,并设置相应的错误代码。下面是一些可能返回的错误代码:

EINVAL:指定的信号sig无效。

ESRCH:参数pid指定的进程或进程组不存在。注意,在进程表项中存在的进程,可能是一个还没有被wait收回,但已经终止执行的僵死进程。

EPERM:
进程没有权力将这个信号发送到指定接收信号的进程。因为,一个进程被允许将信号发送到进程pid时,必须拥有root权力,或者是发出调用的进程的UID
或EUID与指定接收的进程的UID或保存用户ID(savedset-user-ID)相同。如果参数pid小于-1,即该信号发送给一个组,则该错误表示组中有成员进程不能接收该信号。

代码示例

#include <iostream>
#include<unistd.h>//unix stand lib
#include<sys/types.h>
#include<sys/fcntl.h>
#include<sys/stat.h>
#include<stdio.h>
#include<fcntl.h>
#include<string.h>
#include<dirent.h>//file dir
#include <sys/wait.h>//wait func
#include <stdlib.h>//ststem
#include <signal.h>

using namespace std;

//信号处理函数
void signal_fun(int num)
{
	cout << "signal_fun pid=" << getpid() << endl;
	//打印出来的是子进程的pid,说明这个signal函数是在子进程里面执行的,这是因为kill指定了发送的pid
	//当用户发送信号内核会给他一个信号编号num,具体数字详见kill -l
	cout << "get signal number=" << num << endl;
}

int main(int argc, char *argv[])
{
	pid_t pid;
	//先安装信号再发送信号
	signal(SIGUSR1, signal_fun);//在fork之前安装了对应的信号安装函数,意味着fork后面子进程也同样安装了信号处理函数
	pid = fork();
	if (pid == 0)
	{
		while (1)
		{
			sleep(1);
			//为了降低CPU使用率
			//假设子进程在睡眠状态下,收到信号,会自然打断去执行信号处理函数
			//但并不是说程序进入睡眠状态才能接收信号
		}	
	}
	else if (pid > 0)
	{
		sleep(1);//保证让子进程先走
		kill(pid, SIGUSR1);//指定发送SIGUSR1信号到子进程pid
		cout << "child pid:" << pid << endl;
	}
	return 0;
}

打印结果如下:
在这里插入图片描述
使用kill发送信号给指定的子进程,并用signal将信号与信号处理函数signal_fun安装起来,就可以实现signal_fun在指定的pid里面运行
注意int num是信号处理函数必须要写的参数,并由内核自动赋值为信号宏定义对应int数字(kill -l中的)SIGUSR1——10故打印出来为10

可靠信号与不可靠信号

接着上面的实验代码,我们将发送部分使用一个for循环连续发送10次:

	else if (pid > 0)
	{
		sleep(1);//保证让子进程先走
		for (int i = 0; i < 10; i++)
		{
			kill(pid, SIGUSR1);//指定发送SIGUSR1信号到子进程pid
			cout << i << endl;
		}
		cout << "child pid:" << pid << endl;
	}

结果如下:
在这里插入图片描述
发送了10个信号却只响应了2个信号?
对的,这是因为linux信号机制基本上是从unix系统中继承过来的。早期unix系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题:

  • 进程每次处理信号后,就将对信号的响应设置为默认动作。在某些情况下,将导致对信号的错误处理;因此,用户如果不希望这样的操作,那么就要在信号处理函数结尾再一次调用signal(),重新安装该信号。
  • 早期unix下的不可靠信号主要指的是进程可能对信号做出错误的反应以及信号可能丢失。
  • linux支持不可靠信号,但是对不可靠信号机制做了改进:在调用完信号处理函数后,不必重新调用该信号的安装函数(信号安装函数是在可靠机制上的实现)。
  • 因此,linux下的不可靠信号问题主要指的是信号可能丢失。

随着时间的发展,实践证明了有必要对信号的原始机制加以改进和扩充。所以,后来出现的各种unix版本分别在这方面进行了研究,力图实现可靠信号。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失

不可靠信号:1-31之间的信号
可靠信号:34-64的信号(信号值位于SIGRTMIN及SIGRTMAX之间)

同时,信号的发送和安装也出现了新版本:信号发送函数sigqueue()及信号安装函数sigaction()。(这将在下一篇中带来~)

注:不要有这样的误解:由sigqueue()发送、sigaction安装的信号就是可靠的。事实上,可靠信号是指后来添加的新信号(信号值位于SIGRTMIN及SIGRTMAX之间);不可靠信号是信号值小于SIGRTMIN的信号。信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。目前linux中的signal()是通过sigation()函数实现的,因此,即使通过signal()安装的信号,在信号处理函数的结尾也不必再调用一次信号安装函数。同时,由signal()安装的实时信号支持排队,同样不会丢失。

对于目前linux的两个信号安装函数:signal()及sigaction()来说,它们都不能把SIGRTMIN以前的信号变成可靠信号(都不支持排队,仍有可能丢失,仍然是不可靠信号),而且对SIGRTMIN以后的信号都支持排队。这两个函数的最大区别在于,经过sigaction安装的信号都能传递信息给信号处理函数(对所有信号这一点都成立),而经过signal安装的信号却不能向信号处理函数传递信息。对于信号发送函数来说也是一样的。

实时与非实时信号

目前来说:非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。

早期Unix系统只定义了32种信号,Ret hat7.2支持64种信号,编号0-63(SIGRTMIN=31,SIGRTMAX=63),将来可能进一步增加,这需要得到内核的支持。前32种信号已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都有各自的缺省动作。如按键盘的CTRL ^C时,会产生SIGINT信号,对该信号的默认反应就是进程终止。后32个信号表示实时信号,等同于前面阐述的可靠信号。这保证了发送的多个实时信号都被接收。实时信号是POSIX标准的一部分,可用于应用进程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值