【Linux系统编程】26.信号、kill、alarm、setitimer

目录

信号

信号共性

信号特质

产生信号

信号相关概念

默认处理动作

信号4要素

[常规信号 ​编辑](#常规信号 编辑)

注意

kill

参数pid

测试代码1

测试结果

测试代码2

测试结果

alarm

参数seconds

返回值

取消闹钟

测试代码3

测试结果1

[测试结果2 ​编辑](#测试结果2 编辑)

setitimer

参数which

参数new_value

参数old_value

测试代码4

测试结果

信号集操作函数

信号

信号共性

  • 简单

  • 不能携带大量信息

  • 满足条件才能发送

信号特质

信号是软件层面上的"中断",一旦信号产生,无论程序执行到什么位置,都必须立即停止运行,处理信号,处理完成后,再继续执行后续程序。所有信号的产生及处理都是由内核完成。

产生信号

  1. 按键产生

  2. 系统调用产生

  3. 软件条件产生

  4. 硬件异常产生

  5. 命令产生

信号相关概念

未决:产生与递达之间的状态。

递达:产生并且送达到进程,直接被内核处理掉。

信号处理方式

  1. 执行默认处理动作。

  2. 忽略。

  3. 捕捉。

阻塞信号集(信号屏蔽字):本质是位图。用来记录信号的屏蔽状态。一旦被屏蔽的信号,在解除屏蔽之前,一直处于未决态。

未决信号集:本质是位图。用来记录信号的处理状态。该信号集中的信号表示已经产生,但未被处理。

默认处理动作

Term:终止进程。

lgn:忽略信号。

Core:终止进程,生成Core文件。可以查验进程死亡原因,用gdb进行调试。

Stop:停止进程或暂停进程。

Cont:继续运行进程。

信号4要素

信号使用之前,必须先确定其4要素,再进行使用。

  1. 信号编号

  2. 信号名称

  3. 信号对应时间

  4. 信号默认处理动作

bash 复制代码
man 7 signal

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

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

常规信号

  1. SIGHUP:当用户退出shell时,由该shell启动的所有进程将收到这个信号,默认动作为终止进程。

  2. SIGIN:当用户按下了<Ctrl+C>组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动作为终止进程。

  3. SIGQUIT:当用户按下<ctrl+\>组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出些信号。默认动作为终止进程。

  4. SIGILL:CPU检测到某进程执行了非法指令。默认动作为终止进程并产生core文件。

  5. SIGTRAP:该信号由断点指令或其他trap指令产生。默认动作为终止进程并产生core文件。

  6. SIGABRT:调用abort函数时产生该信号。默认动作为终止进程并产生core文件。

  7. SIGBUS:非法访问内存地址,包括内存对齐出错,默认动作为终止进程并产生core文件。

  8. SIGFPE:在发生致命的运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等所有的算法错误。 默认动作为终止进程并产生core文件。

  9. SIGKILL:无条件终止进程。本信号不能被忽略,处理和阻塞。默认动作为终止进程。它向系统管理员提供了可以杀死任何进程的方法。

  10. SIGUSE1:用户定义的信号。即程序员可以在程序中定义并使用该信号。默认动作为终止进程。

  11. SIGSEGV:指示进程进行了无效内存访问。默认动作为终止进程并产生core文件。

  12. SIGUSR2:另外一个用户自定义信号,程序员可以在程序中定义并使用该信号。默认动作为终止进程。

  13. SIGPIPE:Broken pipe向一个没有读端的管道写数据。默认动作为终止进程。

  14. SIGALRM:定时器超时,超时的时间由系统调用alarm设置。默认动作为终止进程。

  15. SIGTERM:程序结束信号,与SIGKILL不同的是,该信号可以被阻塞和终止。通常用来要示程序正常退出。执行 shell命令Kill时,缺省产生这个信号。默认动作为终止进程。

  16. SIGSTKFLT:Linux早期版本出现的信号,现仍保留向后兼容。默认动作为终止进程。

  17. SIGCHLD:子进程状态发生变化时,父进程会收到这个信号。默认动作为忽略这个信号。

  18. SIGCONT:如果进程已停止,则使其继续运行。默认动作为继续/忽略。

  19. SIGSTOP:停止进程的执行。信号不能被忽略,处理和阻塞。默认动作为暂停进程。

  20. SIGTSTP:停止终端交互进程的运行。按下<ctrl+z>组合键时发出这个信号。默认动作为暂停进程。

  21. SIGTTIN:后台进程读终端控制台。默认动作为暂停进程。

  22. SIGTTOU:该信号类似于SIGTTIN,在后台进程要向终端输出数据时发生。默认动作为暂停进程。

  23. SIGURG:套接字上有紧急数据时,向当前正在运行的进程发出些信号,报告有紧急数据到达。如网络带外数据到达,默认动作为忽略该信号。

  24. SIGXCPU:进程执行时间超过了分配给该进程的CPU时间 ,系统产生该信号并发送给该进程。默认动作为终止进程。

  25. SIGXFSZ:超过文件的最大长度设置。默认动作为终止进程。

  26. SIGVTALRM:虚拟时钟超时时产生该信号。类似于SIGALRM,但是该信号只计算该进程占用CPU的使用时间。默认动作为终止进程。

  27. SGIPROF:类似于SIGVTALRM,它不公包括该进程占用CPU时间还包括执行系统调用时间。默认动作为终止进程。

  28. SIGWINCH:窗口变化大小时发出。默认动作为忽略该信号。

  29. SIGIO:此信号向进程指示发出了一个异步IO事件。默认动作为忽略。

  30. SIGPWR:关机。默认动作为终止进程。

  31. SIGSYS:无效的系统调用。默认动作为终止进程并产生core文件。

34~64. LINUX 的实时信号,它们没有固定的含义,可以由用户自定义。所有的实时信号的默认动作都为终止进程。

注意

9) SIGKILL和19) SIGSTOP信号,不允许忽略和捕捉,只能执行默认动作。甚至不能将其设置为阻塞。

只有每个信号所对应的事件发生了,该信号才会被递送,但不一定递达,不应乱发信号!

kill

bash 复制代码
man 2 kill

参数pid

大于0:发送信号给指定进程。

等于0:发送信号给跟调用kill函数的那个进程处于同一进程组的进程。

小于-1:取绝对值,发送信号给该绝对值所对应的进程组的所有进程。

等于-1:发送信号给有权限发送的所有进程,普通用户基本规则是,发送者实际或有效用户ID==接收者实际或有效用户ID。

测试代码1

用子进程杀了父进程。

cpp 复制代码
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>

int main(int argc, char *argv[])
{
    pid_t JinCheng_ID;
    JinCheng_ID = fork();
    if (JinCheng_ID > 0) //父进程
    {
        printf("这是父进程,该进程ID是%d。开始进入死循环。\n", getpid());
        while (1)
        {
            printf("这是父进程,已在死循环。\n");
            sleep(1);
        }
    }
    else if (JinCheng_ID == 0) //子进程
    {
        printf("这是子进程,该进程ID是%d。先睡一会再杀父进程。\n", getpid());
        sleep(3);
        printf("这是子进程,该进程ID是%d。睡醒了,我杀。\n", getpid());
        kill(getppid(),SIGKILL);
        printf("这是子进程,该进程ID是%d。杀完了,拜拜。\n", getpid());
    }
    return 0;
}

测试结果

测试代码2

杀进程组

cpp 复制代码
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>

int main(int argc, char *argv[])
{
    pid_t JinCheng_ID;
    JinCheng_ID = fork();
    if (JinCheng_ID > 0) //父进程
    {
        printf("这是父进程,该进程ID是%d。开始进入死循环。\n", getpid());
        while (1)
        {
            printf("这是父进程,已在死循环。\n");
            sleep(1);
        }
    }
    else if (JinCheng_ID == 0) //子进程
    {
        printf("这是子进程,该进程ID是%d。先睡一会再杀进程组。\n", getpid());
        sleep(3);
        printf("这是子进程,该进程ID是%d。睡醒了,我杀。\n", getpid());
        kill(0, SIGKILL);
        printf("这是子进程,该进程ID是%d。杀完了,拜拜。\n", getpid());
    }
    return 0;
}

测试结果

进程组被杀后,子进程最后的输出不再进行输出。

alarm

定时发送SIGALRM给当前进程。

实际时间=用户时间+内核时间+等待时间,代码优化,优先从IO下手。

bash 复制代码
man 2 alarm

参数seconds

定时秒数。

返回值

上次剩余的时间。

取消闹钟

cpp 复制代码
alarm(0);

测试代码3

查看程序1秒钟的计数次数。

cpp 复制代码
#include <stdio.h>
#include <unistd.h>

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

测试结果1

测试结果2

setitimer

设置定时器(闹钟)。 可代替alarm函数。精度微秒us,可以实现周期定时。

bash 复制代码
man 2 setitimer

参数which

指定定时方式。

  • ITIMER_REAL:自然定时,计算自然时间,信号是14)SIGLARM。

  • ITIMER_VIRTUAL:虚拟空间计时(用户空间),只计算进程占用cpu的时间,信号是26)SIGVTALRM。

  • ITIMER_PROF:运行时计时(用户+内核),计算占用cpu及执行系统调用的时间,信号是27)SIGPROF。

参数new_value

定时秒数。

cpp 复制代码
struct itimerval {
    struct timeval it_interval; //后续每个中断的间隔执行时间
    struct timeval it_value;    //第一次执行中断的时间
};

struct timeval {
    time_t      tv_sec;         //秒
    suseconds_t tv_usec;        //微秒
};
cpp 复制代码
struct itimerval {
    struct timeval {	//后续每个中断的间隔执行时间
        time_t      tv_sec;         //秒
        suseconds_t tv_usec;        //微秒
    }it_interval;
    struct timeval {	//第一次执行中断的时间
        time_t      tv_sec;         //秒
        suseconds_t tv_usec;        //微秒
    }it_value;
};

参数old_value

传出参数,上次定时剩余的时间。

测试代码4

周期性输出内容。

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>

void ChuLi_HanShu()
{
    printf("你好,世界!\n");
}

int main(int argc, char *argv[])
{
    int i = 0;
    struct itimerval new_value, old_value;
    signal(SIGALRM, ChuLi_HanShu); //注册SIGALRM信号的捕捉处理函数

    //2秒后开始第1次中断
    new_value.it_value.tv_sec=2;
    new_value.it_value.tv_usec=0;

    //每5秒来一次中断
    new_value.it_interval.tv_sec=5;
    new_value.it_interval.tv_usec=0;

    if(setitimer(ITIMER_REAL,&new_value,&old_value)==-1){
        perror("设置中断错误");
        exit(1);
    }

    while (1)
    {
        printf("%d秒\n", i++);
        sleep(1);
    }
    return 0;
}static

测试结果

信号集操作函数

cpp 复制代码
sigset_t set;	//自定义信号集
sigemptyset(sigset_t *set);		//清空信号集
sigfillset(sigset_t *set);		//全部置1
sigaddset(sigset_t *set,int signum);	//每一个信号添加到集合中
sigdelset(sigset_t *set,int signum);	//将一个信号从集合中移除
sigismember(const sigset_t *set,int signum);	//判断一个信号是否在集合中。返回值:1:在;0:不在。
相关推荐
whoarethenext2 分钟前
使用 C++/Faiss 加速海量 MFCC 特征的相似性搜索
开发语言·c++·faiss
only-lucky9 分钟前
C语言socket编程-补充
服务器·c语言·php
try2find24 分钟前
移动conda虚拟环境的安装目录
linux·运维·conda
笑衬人心。26 分钟前
Ubuntu 22.04 修改默认 Python 版本为 Python3 笔记
笔记·python·ubuntu
码农101号1 小时前
Linux中容器文件操作和数据卷使用以及目录挂载
linux·运维·服务器
PanZonghui1 小时前
Centos项目部署之Nginx 的安装与卸载
linux·nginx
PanZonghui1 小时前
Centos项目部署之安装数据库MySQL8
linux·后端·mysql
PanZonghui1 小时前
Centos项目部署之运行SpringBoot打包后的jar文件
linux·spring boot
PanZonghui1 小时前
Centos项目部署之Java安装与配置
java·linux
向阳@向远方1 小时前
第二章 简单程序设计
开发语言·c++·算法