前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Linux信号】一:信号的概念、信号的产生

【Linux信号】一:信号的概念、信号的产生

作者头像
mindtechnist
发布2024-08-08 17:17:19
680
发布2024-08-08 17:17:19
举报
文章被收录于专栏:机器和智能

一、什么是信号

1. 信号的概念

信号在生活中随处可见,比如体育比赛中使用的信号枪、我给你传递一个眼神(你懂的哈哈哈),等等。这些信号都有一些共同点:一是简单;而是不能携带大量信息;三是满足某个特设条件才发送。

信号是信息的载体,是Linux/UNIX 环境下,古老而经典的通信方式, 现在依然是主要的通信手段。Unix早期版本就提供了信号机制,但不可靠,信号可能丢失。Berkeley 和 AT&T都对信号模型做了更改,增加了可靠信号机制。但彼此不兼容。POSIX.1对可靠信号例程进行了标准化。

2. 信号的实现机制

进程A给B发送信号,B收到信号之前执行自己的代码,收到信号后,不管执行到程序的什么位置,都要暂停运行,去处理信号,处理完毕再继续执行。与硬件中断类似——异步模式。但信号是软件层面上实现的中断,早期常被称为“软中断”。信号具有这样的特质,由于信号是通过软件方法实现,其实现手段导致信号有很强的延时性,但是对于用户来说,这个延迟时间非常短,不易察觉。

每个进程收到的所有信号,本质上都是由内核负责发送的,由内核去处理,我们名义上说是进程A发送信号给进程B,实质上信号是由内核产生,由内核发送,并由内核处理的。进程收到信号要无条件处理信号,并且可以选择忽略(忽略也是对信号的一种处理)、捕捉、处理信号默认的动作等。

3. 信号相关的一些琐碎知识点

3.1 产生信号的方式

  • 按键产生,如:Ctrl+c、Ctrl+z、Ctrl+\
  • 系统调用产生,如:kill、raise、abort
  • 软件条件产生,如:定时器alarm,setitimer
  • 硬件异常产生,如:非法访问内存(段错误)、浮点型错误、除0(浮点数例外)、内存对齐出错(总线错误)、SIGPIPE
  • 命令产生,如:kill命令

3.2 信号的状态

  • 产生:
  • 递达:递送并且到达进程。
  • 未决:产生和递达之间的状态。主要由于阻塞(屏蔽)导致该状态。

3.3 信号的处理方式

  • 执行默认动作
  • 忽略(丢弃)
  • 捕捉(调用户处理函数),捕获可以说是学习信号最重要的原因之一,当我们的程序产生比如段错误、总线错误等等,程序会异常终止,我们通过捕获可以捕获到这种错误,并使程序不终止。

3.4 PCB中的信号相关信息

Linux内核的进程控制块PCB是一个结构体task_struct,除了包含进程id、状态、工作目录、用户id、组id、文件描述符表、还包含了信号相关的信息,主要指阻塞信号集和未决信号集。

  • 阻塞信号集:也叫信号屏蔽字,将某些信号加入集合,对他们设置屏蔽,当屏蔽某个信号后,再收到该信号,该信号的处理将推后(解除屏蔽后)。
  • 未决信号集:
    • 信号产生,未决信号集中描述该信号的位立刻翻转为1,表信号处于未决状态;当信号被处理对应位翻转回为0,这一时刻往往非常短暂。
    • 信号产生后由于某些原因主要是阻塞不能抵达,这类信号的集合称之为未决信号集。在屏蔽解除前,信号一直处于未决状态。

未决信号集就是没有被处理的信号,未决信号集实际上是一个32位数,每一位代表一个信号,当信号产生的时候,就把对应的位反转为1,如果该信号未被处理就反转回0,处理了就保持为1。

而阻塞信号集会影响到未决信号集,比如说我在阻塞信号集中将2号信号为置为1,也就是将2号信号屏蔽,那么未决信号集中2号信号对应的位就会变为1(未决状态),一直阻塞在这种状态。

3.5 信号的编号

可以使用 kill –l 命令查看当前系统有哪些可用信号

不存在编号为0的信号。其中1-31号信号称之为常规信号(也叫普通信号或标准信号),34-64称之为实时信号,驱动编程与硬件相关,这些信号名字类似。

3.6 信号四要素

每个信号必须要有的四个要素:

  • 编号
  • 名称
  • 事件
  • 默认处理动作,信号的默认动作主要有:
    • Term:终止进程
    • Ign: 忽略信号 ,默认即时对该种信号忽略操作
    • Core:终止进程,生成Core文件,主要是查验进程终止原因, 用于gdb调试
    • Stop:停止(暂停)进程
    • Cont:继续运行进程

3.7 信号帮助文档

可通过 man 7 signal 查看帮助文档获取标准信号信息表

在标准信号中,有一些信号是有三个 "Value",第一个值通常对alpha和sparc架构有效,中间值针对x86、arm和其他架构,最后一个应用于mips架构。一个 '-' 表示在对应架构上尚未定义该信号。我们主要关注中间的那个值。

不同的操作系统定义了不同的系统信号。因此有些信号出现在Unix系统内,也出现在Linux中,而有的信号出现在FreeBSD或 Mac OS 中却没有出现在Linux下。这里我们只研究Linux系统中的信号。

注意从 man 7 signal 帮助文档中可看到 : The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored. 这里特别强调了SIGKILL 和SIGSTOP信号,不允许忽略和捕捉,只能执行默认动作。甚至不能将其设置为阻塞。另外需清楚,只有每个信号所对应的事件发生了,该信号才会被递送(但不一定递达)。

二、信号的产生

1. 终端按键产生信号

  • Ctrl+c :2号信号SIGINT,表示终止/中断。(SIG INT → signal interrupt)
  • Ctrl+z:20号信号SIGTSTP,表示暂停/停止。(SIG T STP → signal terminal stop)
  • Ctrl+\ :3号信号SIGQUIT,表示退出。(SIG QUIT → signal quit)

2. 硬件异常信号

当程序出现硬件异常会产生信号:

  • 除0操作,浮点型错误,8号信号SIGFPE。
  • 非法访问内存,11号信号SIGSEGV,段错误。
  • 总线错误,7号信号SIGNUS。

3. 函数产生信号

3.1 kill函数

kill命令和kill函数都可以产生信号来杀死进程。kill命令产生信号:kill -SIGKILL pid;kill函数:给指定进程发送指定信号(不一定杀死)。

  • 头文件及函数原型
代码语言:javascript
复制
#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);
  • 函数功能 The kill() system call can be used to send any signal to any process group or process. 给指定进程发送指定信号。
  • 函数参数
    • pid:进程ID
      • If pid is positive, then signal sig is sent to the process with the ID specified by pid. 如果pid > 0,发送信号给指定的进程。
      • If pid equals 0, then sig is sent to every process in the process group of the calling process. 如果pid = 0,发送信号给与调用kill函数的进程属于同一进程组的所有进程。
      • 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. 如果pid = -1,发送给进程有权限发送的系统中所有进程。
      • If pid is less than -1, then sig is sent to every process in the process group whose ID is -pid. 如果pid < -1,对pid取模发给对应进程组。
    • sig:信号名,不推荐直接使用数字,应使用宏名,因为不同操作系统信号编号可能不同,但名称一致。 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.
  • 函数返回值
    • On success (at least one signal was sent), zero is returned. 成功返回0。
    • On error, -1 is returned, and errno is set appropriately. 失败返回-1 (ID非法,信号非法,普通用户杀init进程等权级问题),设置errno。

3.2 raise函数

  • 包含头文件与函数原型
代码语言:javascript
复制
#include <signal.h>

int raise(int sig);
  • 函数功能 he raise() function sends a signal to the calling process or thread. 给当前进程发送指定信号(自己给自己发) 。
    • In a single-threaded program it is equivalent to kill(getpid(), sig);
    • In a multithreaded program it is equivalent to pthread_kill(pthread_self(), sig);
    • If the signal causes a handler to be called, raise() will only return after the signal handler has returned.
  • 函数参数
    • sig
  • 函数返回值 raise() returns 0 on success, and non-zero for failure. 成功返回0,失败返回非0值。
代码语言:javascript
复制
/************************************************************
  >File Name  : test.c
  >Author     : Mindtechnist
  >Company    : Mindtechnist
  >Create Time: 2022年05月23日 星期一 14时20分42秒
************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>

int main(int argc, char* argv[])
{
    printf("pid: %d\n", getpid());
    sleep(1);
    raise(SIGKILL); /* 相当于
    kill(getpid(), SIGKILL); */
    return 0;
}

3.3 abort函数

  • 包含头文件及函数原型
代码语言:javascript
复制
#include <stdlib.h>

void abort(void);
  • 函数功能 The abort() first unblocks the SIGABRT signal, and then raises that signal for the calling process. 给自己发送异常终止信号 6) SIGABRT 信号,终止并产生core文件。
  • 函数参数
    • void
  • 函数返回值 The abort() function never returns.

4. 时钟信号

4.1 alarm函数

  • 包含头文件及函数原型
代码语言:javascript
复制
#include <unistd.h>

unsigned int alarm(unsigned int seconds);

函数功能

设置定时器(闹钟),定时给调用进程(也就是自己)发送SIGALRM,来约定进程几秒钟后结束。在指定seconds后,内核会给当前进程发送14)SIGALRM信号,进程收到该信号,默认动作终止。 每个进程都有且只有唯一一个定时器。定时与进程状态无关(自然定时法),就绪、运行、挂起(阻塞、暂停)、终止、僵尸等等无论进程处于何种状态,alarm都会计时。

alarm() arranges for a SIGALRM signal to be delivered to the calling process in seconds seconds.

Signal

Value

Action

Comment

SIGALRM

14

Term

Timer signal from alarm(2)

代码语言:javascript
复制
Term   Default action is to terminate the process. /*终止进程*/
  • If seconds is zero, no new alarm() is scheduled.
  • In any event any previously set alarm() is canceled.

函数参数

  • seconds:时间,单位秒。alarm(0)相当于取消闹钟。

函数返回值

alarm() returns the number of seconds remaining until any previously scheduled alarm was due to be delivered, or zero if there was no previously scheduled alarm. 返回上次定时器剩余的秒数。我们实现约定好多少秒时候发送一个信号,alarm()函数返回距离发送信号还剩余的秒数,如果没有剩余时间或没有约定发送信号返回0。可以这么理解,如果是第一次开启定时器,返回0;如果上一次设定了alarm(5),两秒之后又设置了alarm(3),那么这个alarm()返回上一次定时器剩余的时间,也就是5-2=3秒。

用法示例:

代码语言:javascript
复制
/************************************************************
  >File Name  : test.c
  >Author     : Mindtechnist
  >Company    : Mindtechnist
  >Create Time: 2022年05月23日 星期一 14时20分42秒
************************************************************/
#include <stdio.h>
#include <unistd.h>

int main(int argc, char* argv[])
{
    int ret = alarm(3);
    printf("first alarm(3) return: %d\n", ret);
    sleep(2);
    ret = alarm(5);
    printf("second alarm(5) return: %d\n", ret);
    while(1)
    {
        printf("pid: %d\n", getpid());
        sleep(1);
    }
    return 0;
}

编译运行得到结果

示例2:time命令计时与IO优化

代码语言:javascript
复制
#include <stdio.h>
#include <unistd.h>

int main(int argc, char* argv[])
{
    int count = 0;
    alarm(1);
    while(1)
    {
        printf("%d\n", count++);
    }
    return 0;
}

编译运行,使用time命令可以查看程序执行的时间(实际执行时间 = 系统时间 + 用户时间 + 等待时间),time ./a.out

在上面的时间中:

real:总共的时间(自然时间);

user:用户使用时间;

sys:系统时间;

可以看到最大计数到了7572,并且user几乎没有分配到时间,这是因为IO操作(打印屏幕)造成的,我们可以重定向一下输出

可以看到user的时间增加了,并且最大计数达到了306087。实际上程序运行的瓶颈大部分在于IO,优化程序,首先优化IO。

4.2 setitimer函数

  • 包含头文件及函数原型
代码语言:javascript
复制
#include <sys/time.h>

int getitimer(int which, struct itimerval *curr_value);
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
  • 函数功能 The system provides each process with three interval timers, each decrementing in a distinct time domain. 设置定时器(闹钟),可代替alarm函数,精度微秒us,可以实现周期定时。
  • 函数参数
    • which:指定定时方式
      • ITIMER_REAL:decrements in real time, and delivers SIGALRM upon expiration. 自然定时法,ITIMER_REAL对应信号14)SIGLARM,计算自然时间。(实际执行时间 = 系统时间 + 用户时间 + 等待时间)。
      • ITIMER_VIRTUAL:decrements only when the process is executing, and delivers SIGVTALRM upon expiration. 虚拟空间计时(用户空间),ITIMER_VIRTUAL对应信号26)SIGVTALRM,只计算进程占用cpu的时间。(计算进程执行时间)
      • ITIMER_PROF:decrements both when the process executes and when the system is executing on behalf of the process. Coupled with ITIMER_VIRTUAL, this timer is usually used to profile the time spent by the application in user and kernel space. SIGPROF is delivered upon expiration. 运行时计时(用户+内核),ITIMER_PROF对应信号27)SIGPROF,计算占用cpu及执行系统调用的时间。(进程执行时间+调度时间)
    • new_value:要设置的定时器时间 struct itimerval { struct timeval it_interval; /* next value 周期性的时间*/ struct timeval it_value; /* current value 下一次闹钟到来的时间 */ }; struct timeval { long tv_sec; /* seconds 秒*/ long tv_usec; /* microseconds 微秒*/ }; /*秒+微妙才是真正的时间,微妙是为了更精确*/
      • it_interval:用来设定两次定时任务之间间隔的时间。
      • it_value:定时的时长 。
      • 两个参数都设置为0,即清0操作。
    • old_value:原来的定时器时间
  • 函数返回值
    • On success, zero is returned.
    • On error, -1 is returned, and errno is set appropriately.

示例1:使用setitimer实现alarm函数定时功能

代码语言:javascript
复制
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>

int main(int argc, char* argv[])
{
    struct itimerval temp = {{0, 0}, {3, 0}};
    setitimer(ITIMER_REAL, &temp, NULL); /*ITIMER_REAL,3秒后发送SIGALRM信号*/
    while(1)
    {
        printf("pid: %d\n", getpid());
        sleep(1);
    }
    return 0;
}

编译运行,3秒后闹钟

示例2:周期性定时器

代码语言:javascript
复制
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>

/*信号处理回调函数*/
void m_catch(int sig)
{
    /*捕捉到信号执行此函数,不杀死进程*/
    printf("catch signal: %d\n", sig);
}

int main(int argc, char* argv[])
{
    signal(SIGALRM, m_catch/*函数指针做函数参数*/);
    /*signal信号捕捉函数,当产生SIGALRM信号的时候,去执行m_catch函数*/
    struct itimerval temp = {{2, 0}, {4, 0}}; /*第一次等待4秒,以后每隔2秒发送一个信号*/
    setitimer(ITIMER_REAL, &temp, NULL);
    while(1)
    {
        printf("pid: %d\n", getpid());
        sleep(1);
    }
    return 0;
}

编译执行,可以看到第一次隔了4秒捕获到信号,后面周期性的每隔2秒捕获一次信号,不会杀死进程,可以通过ctrl+c杀掉进程。

示例3:setitimer实现alarm函数

代码语言:javascript
复制
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>

unsigned int malarm(unsigned int seconds)
{
    struct itimerval temp = {{0, 0}, {0, 0}};
    struct itimerval ret;
    temp.it_value.tv_sec = seconds;
    setitimer(ITIMER_REAL, &temp, &ret);
    printf("tv_sec: %ld, tv_mirsec: %ld\n", ret.it_value.tv_sec, ret.it_value.tv_usec);
    return ret.it_value.tv_sec;
}

int main(int argc, char* argv[])
{
    int ret = malarm(5);
    printf("malarm() return: %d\n", ret);
    sleep(2);
    ret = malarm(6);
    printf("malarm() return: %d\n", ret);
    while(1)
    {
        printf("pid: %d\n", getpid());
        return 0;
    }
    return 0;
}

编译运行,时间可能会不太准确,这是正常的

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-04-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 机器和智能 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、什么是信号
    • 1. 信号的概念
      • 2. 信号的实现机制
        • 3. 信号相关的一些琐碎知识点
          • 3.1 产生信号的方式
          • 3.2 信号的状态
          • 3.3 信号的处理方式
          • 3.4 PCB中的信号相关信息
          • 3.5 信号的编号
          • 3.6 信号四要素
          • 3.7 信号帮助文档
      • 二、信号的产生
        • 1. 终端按键产生信号
          • 2. 硬件异常信号
            • 3. 函数产生信号
              • 3.1 kill函数
              • 3.2 raise函数
              • 3.3 abort函数
            • 4. 时钟信号
              • 4.1 alarm函数
              • 4.2 setitimer函数
          相关产品与服务
          轻量应用服务器
          轻量应用服务器(TencentCloud Lighthouse)是新一代开箱即用、面向轻量应用场景的云服务器产品,助力中小企业和开发者便捷高效的在云端构建网站、Web应用、小程序/小游戏、游戏服、电商应用、云盘/图床和开发测试环境,相比普通云服务器更加简单易用且更贴近应用,以套餐形式整体售卖云资源并提供高带宽流量包,将热门软件打包实现一键构建应用,提供极简上云体验。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档