1 信号
1.1 概述
信号是Linux进程间通信的最古老的方式之一,是事件发生时对进程的通知机制,有时也称之为软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式。信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。
发往进程的诸多信号,通常都是源于内核。引发内核为进程产生信号的各类事件如下:
-
对于前台进程,用户可以通过输入特殊的终端字符来给它发送信号。比如输入Ctrl+C通常会给进程发送一个中断信号。
-
硬件发生异常,即硬件检测到一个错误条件并通知内核,随即再由内核发送相应信号给相关进程。比如执行一条异常的机器语言指令,诸如被0除,或者引用了无法访问的内存区域。
-
系统状态变化,比如alarm定时器到期将引起SIGALRM信号,进程执行的CPU时间超限,或者该进程的某个子进程退出。
-
运行kill命令或调用kill函数。
使用信号的两个主要目的是:
- 让进程知道已经发生了一个特定的事情。
- 强迫进程执行它自己代码中的信号处理程序。
信号的特点:
- 简单
- 不能携带大量信息
- 满足某个特定条件才发送
- 优先级比较高
查看系统定义的信号列表: kill -l
前31个信号为常规信号,其余为实时信号。
1.2 Linux信号一览表
信号的5种默认处理动作
查看信号的详细信息:man 7 signal
信号的5中默认处理动作
- Term终止进程
- lgn当前进程忽略掉这个信号
- Core终止进程,并生成一个Core文件
- Stop暂停当前进程
- Cont继续执行当前被暂停的进程
信号的几种状态:产生、未决、递达
SIGKILL和SIGSTOP信号不能被捕捉、阻塞或者忽略,只能执行默认动作。
查看信号的详细信息:man 7 signal
1.3 alarm
/*
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
-功能:设置定时器(闹钟)。函数调用,开始倒计时,当倒计时为0的时候,
函数会给当前的进程发送一个信号:SIGALARM
-参数:
seconds:倒计时的时长,单位:秒。如果参数为0,定时器无效(不进行倒计时,不发信号)。
取消一个定时器,通过alarm(0)。
-返回值:
-之前没有定时器,返回0
-之前有定时器,返回之前的定时器剩余的时间
- SIGALARM:默认终止当前的进程,每一个进程都有且只有唯一的一个定时器。
alarm( 10); -> 返回0 //第一次调用alarm( 10),会返回0;
过了1秒
alarm(5); -〉 返回9 //过了一秒调用,alarm(5),会返回第一个定时器,剩余的值9
alarm(100) -> 该函数是不阻塞的
*/
#include<stdio.h>
#include <unistd.h>
int main() {
int seconds = alarm(5);
printf( "seconds = %d\n", seconds);//0,第一次调用alarm(5),会返回0;
sleep(2);
seconds = alarm(2);//不阻塞,,,睡眠2秒,在调用alarm(2),会返回第一个定时器,剩余的值,5-2(睡眠了2秒)
printf( "seconds = %d\n", seconds);// 3
while(1) {
}
return 0;
}
1.4 电脑一秒钟,能数多少个数?
/*
实际的时间 = 内核时间 + 用户时间(代码时间) + 消耗的时间(向屏幕输出打印时间);
进行文件IO操作的时候比较浪费时间;
定时器,与进程的状态无关(自然定时法)。无论进程处于什么状态,alarm都会计时。
*/
#include<stdio.h>
#include<unistd.h>
//需求:电脑一秒钟,能数多少个数?
int main(){
//进入程序,设一个一秒定时器;同时计数输出,当1秒时间到,函数会给当前的进程发送一个信号:SIGALARM,结束进程
alarm(1);
int i = 0;
while(1){ //当一秒结束,进程结束
printf("%i\n",i++);
}
return 0;
}
gcc alarm1.c
./a.out
printf输出 57万多个数,,,事实上,1秒钟计数元不止这么多,因为向屏幕输出打印,占用了大量时间
./a.out >> a.txt
重定向符号,将数字都重定向到某个文件里;
数据输入到文件也要花费时间,只不过是最后一次输出入
在vscode里打开,可以看到614万多个数字
1.5 信号相关的函数
int ki11(pid_t pid,int sig);
int raise(int sig);
void abort(void);
unsigned int alarm(unsigned int seconds);
int setitimer(int which,const struct itimerval *new_val,struct
itimerva1 *old_value);
/*
#include <sys/time.h>
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
-功能:设置定时器(闹钟)。可以替代alarm函数。精度微妙us,可以实现周期性定时
-参数:
- which :定时器以什么时间计时
ITIMER_REAL:真实时间,时间到达,发送SIGALRM常用
ITIMER_VIRTUAL:用户时间,时间到达,发送SIGVTALRM
ITIMER_PROF:以该进程在用户态和内核态下所消耗的时间来计算,时间到达,发送SIGPROF
- new_value:设置定时器的属性
struct itimerval { //定时器的结构体
struct timeval it_interval; //每个阶段的时间,间隔时间
struct timeval it_value; //延迟多长时间执行定时器
};
struct timeval { //时间的结构体
time_t tv_sec; //秒数
suseconds_t tv_usec; //微秒
};
过10秒后,每个2秒定时一次
- old_value :记录上一次的定时的时间参数,一般不使用,指定NULL
-返回值:
成功 0
失败 -1 并设置错误号
*/
#include<sys/time.h>
#include<stdio.h>
#include<stdlib.h>
//过3秒以后,每隔2秒钟定时一次
int main(){
struct itimerval new_value;
//设置间隔时间
new_value.it_interval.tv_sec = 2;
new_value.it_interval.tv_usec = 0;
//设置延迟时间,3秒之后开始第一次定时
new_value.it_interval.tv_sec = 3;
new_value.it_interval.tv_usec = 0;
int ret = setitimer(ITIMER_REAL, &new_value, NULL);
printf("定时器开始了。。。\n");
if(ret == -1){
perror("setitimer");
exit(0);
}
getchar();
return 0;
}
1.6 信号捕捉函数
sighandler_t signal(int signum,sighandler_t handler);
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact);
信号集
许多信号相关的系统调用都需要能表示一组不同的信号,多个信号可使用一个称之为信号集的数据结构来表示,其系统数据类型为sigset_t。
在PCB中有两个非常重要的信号集。一个称之为"阻塞信号集”,另一个称之为“未决信号集”。这两个信号集都是内核使用位图机制来实现的。但操作系统不允许我们直接对这两个信号集进行位操作。而需自定义另外一个集合,借助信号集操作函数来对PCB中的这两个信号集进行修改。
信号的“未决”是一种状态,指的是从信号的产生到信号被处理前的这一段时间。
信号的“阻塞”是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。
信号的阻塞就是让系统暂时保留信号留待以后发送。由于另外有办法让系统忽略信号,所以一般情况下信号的阻塞只是暂时的,只是为了防止信号打断敏感的操作。
信号集相关操作函数:
int sigemptyset(sigset_t *set);
int sigfi11set(sigset_t *set);
int sigaddset(sigset_t *set,int signum);
int sigdelset(sigset_t *set,int signum);
int sigismember(const sigset_t *set,int signum);
int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);int sigpending(sigset_t *set);
内核实现信号捕捉的过程
SIGCHILD信号
SIGCHLD信号产生的条件
- 子进程终止时
- 子进程接收到SIGSTOP信号停止时
- 子进程处在停止态,接受到SIGCONT后唤醒时
以上三种条件都会给父进程发送SIGCHLD信号,父进程默认会忽略该信号