信号、signal 函数、sigaction 函数

2023-11-12

1.信号的基本概念

发送信号是进程之间常用的通信手段。信号用来通知某个进程发生了某一个事情,事情、信号都是突发事件,信号是异步发生的,信号也被称为“软件中断”。

信号如何产生:

  • 某个进程发送给另外一个进程或者发送给自己
  • 由内核发送给某个进程
    • 通过在键盘上输入命令,如 Ctrl + Ckill
    • 内存访问异常、除数为 0 0 0 等等,硬件都会检测到并且通知内核

UNIX 以及类 UNIX 操作系统支持的信号数量各不相同。信号有名字,都是以 SIG 开头,如终端断开信号 SIGHUP。其实,信号就是一些宏定义的正整数常量(从数字 1 1 1 开始)。

查找 signal.hSIGHUP 的命令如下:

sudo find / -name "signal.h" | xargs grep -in "SIGHUP"

在这里插入图片描述

2.利用 kill 命令发送信号

创建一个 test.c 文件,其内容如下:

#include <stdio.h>
#include <unistd.h>

int main(int argc, char* const* argv)
{
    printf("你好,世界!\n");
    for (;;)
    {
        sleep(1);
        printf("休息1秒\n");
    }
    printf("程序退出,再见!\n");
    return 0;
}

编译 test.c 的命令如下:

gcc test.c -o test

运行 test 的命令如下:

./test

查看 bash 进程和 test 进程的命令如下:

ps -eo pid,ppid,pgid,sid,tty,comm | grep -E 'bash|PID|test'

在这里插入图片描述

跟踪 test 进程的命令如下:

sudo strace -e trace=signal -p 1268

kill 1268 命令就是往 test 进程发送 SIGTERM 终止信号:

在这里插入图片描述

kill -2 1365 命令就是往 test 进程发送 SIGINT 中断信号:

在这里插入图片描述

关于 kill 命令及 Linux 系统支持的部分信号:https://wker.com/linux-command/kill.html

3.信号处理的相关动作

上面提到的 kill 命令只是发个信号,而不是单纯的杀死的意思。

当某个信号出现时,我们可以按三种方式之一进行处理:

  • 执行系统默认动作,绝大多数信号的默认动作是杀死这个进程;
  • 忽略该信号;
  • 捕捉该信号,即自己写个处理函数,当信号来的时候,就调用处理函数来处理。

注意:SIGKILLSIGSTOP 信号既不能被忽略,也不能被捕捉。

4.信号与 signal 函数

进程:“嘿,操作系统!如果我之前创建的子进程终止,就帮我调用 zombie_handler 函数。”

操作系统:“好的!如果你的子进程终止,我会帮你调用 zombie_handler 函数,你先把该函数要执行的语句编好!”

上述对话中进程所讲的相当于“注册信号”过程,即进程发现自己的子进程结束时,请求操作系统调用特定函数。该请求通过 signal 函数调用完成,因此称 signal 函数为信号注册函数。

#include <signal.h>

void (*signal(int signo, void (*func)(int)))(int);

// 为了在产生信号时调用,返回之前注册的函数指针
// 函数名:signal
// 参数:int signo, void(*func)(int)
// 返回类型:参数为int型,返回void型函数指针

调用上述函数时,第一个参数为特殊情况信息,第二个参数为特殊情况下将要调用的函数的地址值(指针)。发生第一个参数代表的情况时,调用第二个参数所指的函数。

// 子进程终止则调用mychild函数
signal(SIGCHLD, mychild);
// 常数SIGCHLD定义了子进程终止的情况,应成为signal函数的第一个参数
// 此时mychild函数的参数应为int,返回值类型应为void,只有这样才能成为signal函数的第二个参数
// 已到通过alarm函数注册的时间,请调用timeout函数
signal(SIGALRM, timeout);
// 输入CTRL+C时调用keycontrol函数
signal(SIGINT, keycontrol);

以上就是信号注册过程。注册好信号后,发生注册信号时(注册的情况发生时),操作系统将调用该信号对应的函数。

4.1 signal 函数示例一

下面首先介绍 alarm 函数。

#include <unistd.h>

unsigned int alarm(unsigned int seconds);

// 返回0或以秒为单位的距SIGALRM信号发生所剩时间

如果调用该函数的同时向它传递一个正整型参数,相应时间后(以秒为单位)将产生 SIGALRM 信号。若向该函数传递 0 0 0,则之前对 SIGALRM 信号的预约将取消。如果通过该函数预约信号后未指定该信号对应的处理函数,则(通过调用 signal 函数)终止进程,不做任何处理。

接下来给出信号处理相关示例。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

// 定义信号处理函数,这种类型的函数称为信号处理器(Handler)
void timeout(int sig)
{
	if (sig == SIGALRM)
		puts("Time out!");
	
	// 为了每隔2秒重复产生SIGALRM信号,在信号处理器中调用alarm函数
	alarm(2);
}

// 定义信号处理函数,这种类型的函数称为信号处理器(Handler)
void keycontrol(int sig)
{
	if (sig == SIGINT)
		puts("CTRL+C pressed");
}

int main(int argc, char *argv[])
{
	int i;

	// 注册SIGALRM、SIGINT信号及相应处理器
	signal(SIGALRM, timeout);
	signal(SIGINT, keycontrol);
	
	// 预约2秒后发生SIGALRM信号
	alarm(2);

	// 为了查看信号产生和信号处理器的执行,提供每次100秒、共3次的等待时间,在循环中调用sleep函数。
	// 也就是说,再过300秒、约5分钟后终止程序,这是相当长的一段时间,但实际执行时只需不到10秒。
	for (i = 0; i < 3; i++)
	{
		puts("wait...");
		sleep(100);
	}

	return 0;
}

编译运行:

gcc signal.c -o signal
./signal

输出结果:

在这里插入图片描述

上述是没有任何输入时的运行结果。

下面在运行过程中输入 CTRL+C,可以看到输出“CTRL+C pressed”字符串。

在这里插入图片描述

有一点必须说明:“发生信号时将唤醒由于调用 sleep 函数而进入阻塞状态的进程。”

调用函数的主体的确是操作系统,但进程处于睡眠状态时无法调用函数。因此,产生信号时,为了调用信号处理器,将唤醒由于调用 sleep 函数而进入阻塞状态的进程。而且,进程一旦被唤醒,就不会再进入睡眠状态。即使还未到 sleep 函数中规定的时间也是如此。所以,上述示例运行不到 10 10 10 秒就会结束,连续输入 CTRL+C 则有可能 1 1 1 秒都不到。

4.2 signal 函数示例二

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>

// 信号处理函数
void sig_usr(int signo)
{
    if (signo == SIGUSR1)
    {
        printf("收到了SIGUSR1信号!\n");
    }
    else if (signo == SIGUSR2)
    {
        printf("收到了SIGUSR2信号!\n");
    }
    else
    {
        printf("收到了未捕捉的信号%d!\n", signo);
    }
}

int main(int argc, char* const* argv)
{
    // 系统函数,第一个参数是个信号,第二个参数是个函数指针,代表一个针对该信号的捕捉处理函数
    if (signal(SIGUSR1, sig_usr) == SIG_ERR)
    {
        printf("无法捕捉SIGUSR1信号!\n");
    }

    if (signal(SIGUSR2, sig_usr) == SIG_ERR)
    {
        printf("无法捕捉SIGUSR2信号!\n");
    }

    for (;;)
    {
        sleep(1);
        printf("休息1秒\n");
    }

    printf("再见!\n");

    return 0;
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

5.利用 sigaction 函数进行信号处理

sigaction 函数,它类似于 signal 函数,而且完全可以代替 signal 函数,也更稳定。之所以稳定,是因为:“signal 函数在 UNIX 系列的不同操作系统中可能存在区别,但 sigaction 函数完全相同。”

实际上现在很少使用 signal 函数编写程序,它只是为了保持对旧程序的兼容。下面介绍 sigaction 函数,但只讲解可替换 signal 函数的功能,因为全面介绍会给各位带来不必要的负担。

#include <signal.h>

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

// 成功时返回0,失败时返回-1。
// signo:与signal函数相同,传递信号信息。
// act:对应于第一个参数的信号处理函数(信号处理器)信息。
// oldact:通过此参数获取之前注册的信号处理函数指针,若不需要则传递0。

声明并初始化 sigaction 结构体变量以调用上述函数,该结构体定义如下:

struct sigaction
{
    void (*sa_handler)(int);
    sigset_t sa_mask;
    int sa_flags;
};

此结构体的 sa_handler 成员保存信号处理函数的指针值(地址值)。sa_mask 和 sa_flags 的所有位均初始化为 0 0 0 即可。这 2 2 2 个成员用于指定信号相关的选项和特性,而我们的目的主要是防止产生僵尸进程,故省略。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

// #define _XOPEN_SOURCE 700

void timeout(int sig)
{
	if (sig == SIGALRM)
		puts("Time out!");

	alarm(2);
}

int main(int argc, char *argv[])
{
	int i;

	// 为了注册信号处理函数,声明sigaction结构体变量并在sa_handler成员中保存函数指针值
	struct sigaction act;
	act.sa_handler = timeout;

	// 调用sigemptyset函数将sa_mask成员的所有位初始化为0
	sigemptyset(&act.sa_mask);

	// sa_flags成员同样初始化为0
	act.sa_flags = 0;

	// 注册SIGALRM信号的处理器。调用alarm函数预约2秒后发生SIGALRM信号
	sigaction(SIGALRM, &act, 0);

	alarm(2);

	for (i = 0; i < 3; i++)
	{
		puts("wait...");
		sleep(100);
	}
	
	return 0;
}

编译运行:

gcc sigaction.c -o sigaction
./sigaction

输出结果:

在这里插入图片描述

6.利用信号处理技术消灭僵尸进程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

// #define _XOPEN_SOURCE 700

void read_childproc(int sig)
{
	int status;
	pid_t id = waitpid(-1, &status, WNOHANG);
	if (WIFEXITED(status))
	{
		printf("Removed proc id: %d\n", id);
		printf("Child send: %d\n", WEXITSTATUS(status));
	}
}

int main(int argc, char *argv[])
{
	pid_t pid;

	// 注册SIGCHLD信号对应的处理器。若子进程终止,则调用第7行中定义的函数。
	// 处理函数中调用了waitpid函数,所以子进程将正常终止,不会成为僵尸进程。
	struct sigaction act;
	act.sa_handler = read_childproc;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	sigaction(SIGCHLD, &act, 0);

	// 创建子进程
	pid = fork();

	if (pid == 0)    // 子进程执行区域
	{
		puts("Hi! I'm child process");
		sleep(10);
		return 12;
	}
	else    // 父进程执行区域
	{
		printf("Child proc id: %d\n", pid);

		// 创建子进程
		pid = fork();

		if (pid == 0)    // 另一个子进程执行区域
		{
			puts("Hi! I'm child process");
			sleep(15);
			exit(24);
		}
		else
		{
			int i;
			printf("Child proc id: %d\n", pid);

			// 为了等待发生SIGCHLD信号,使父进程共暂停5次,每次间隔5秒。
			// 发生信号时,父进程将被唤醒,因此实际暂停时间不到25秒。
			for (i = 0; i < 5; i++)
			{
				puts("wait...");
				sleep(5);
			}
		}
	}

	return 0;
}

编译运行:

gcc remove_zombie.c -o zombie
./zombie

输出结果:

在这里插入图片描述

可以看出,子进程并未变成僵尸进程,而是正常终止了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

信号、signal 函数、sigaction 函数 的相关文章

  • 在CXXLD libwebkitgtk-1.0.la时候发生 ld terminated with signal 9 [Killed]错误

    当时内存几乎用完了 发生这个错误是因为内存不够 编译不过来 系统是ubuntu 11 04 2G的物理内存不够 swap分区是1G CXXLD libwebkitgtk 1 0 la collect2 ld terminated with
  • gcc编译器、Makefile

    一 编译程序的基础知识 1 gcc对程序的编译过程 预编译 编译和优化 汇编 链接 预编译 将程序中引用的头文件包含进源代码中 并对宏进行替换 gcc E hello c o hello i 编译 将用户可识别的语言翻译成处理器可识别的汇编
  • Linux网络编程socket错误分析

    转自 http aigo iteye com blog 1911134 socket错误码 EINTR 4 阻塞的操作被取消阻塞的调用打断 如设置了发送接收超时 就会遇到这种错误 只能针对阻塞模式的socket 读 写阻塞的socket时
  • QListWidget和QListWidgetItem的简单使用

    QListWidget可以显示一个清单 清单中的每个项目是QListWidgetItem的一个实例 每个项目可以通过QListWidgetItem来操作 可以通过QListWidgetItem来设置每个项目的图像与文字 下面说明3个例子 一
  • Qt子线程的“信号队列”(转载)

    对Qt的多线程编程没有深究 只了解了基本的用法 够我用就行了 之所以写这篇文章是因为前几天遇到一个疑问 如果其他几个线程同时向一个线程发signal 而这个线程没有自己的事件循环 那是不是会丢失signal呢 下面是我总结的两种子线程的工作
  • moudo网络库剖析

    muduo简介 muduo是陈硕大神在Linux平台下基于C C 开发的高性能网络库 在此基础上可以很方便的扩展 进行二次开发编写如http服务器 muduo网络库的核心框架 one thread per thread Reactor模式
  • QT编程----事件(一)

    review ui 生成 h cpp文件 uic form1 ui o form1 h uic form1 ui i form1 h o form1 cpp C 三个特点 继承 重载 封装 QT程序设计进阶 事件 Qt事件 Qt程序是事件驱
  • Linux下的时间(ZZ)

    1 Linux下的时间 1 1 Linux下的时间系统 1 2 Linux下与时间有关的数据结构 2 获得当前时间 3 延时 4 定时器 4 1 alarm 4 2 setitimer 1 Linux下的时间1 1 Linux下的时间系统
  • 关于AF_INET和PF_INET

    AF 表示ADDRESS FAMILY 地址族 PF 表示PROTOCL FAMILY 协议族 Winsock2 h中 define AF INET 0 define PF INET AF INET 所以在windows中AF INET与P
  • Ubuntu 设置时区

    我们要设置成 CST 时区 以保证正确地显示日期 时间 我们常看到的时区有如下几个 PST 美国太平洋标准时间 PST GMT 8 GMT 格林尼治平均时间 等同于英国伦敦本地时间 UTC 通用协调时间 UTC GMT CST 北京时间 北
  • 分配给套接字的IP地址与端口号

    文章目录 1 网络地址 Internet Address 2 网络地址分类与主机地址边界 3 用于区分套接字的端口号 IP 是 Internet Protocol 网络协议 的简写 是为收发网络数据而分配给计算机的值 端口号并非赋予计算机的
  • 信号、signal 函数、sigaction 函数

    文章目录 1 信号的基本概念 2 利用 kill 命令发送信号 3 信号处理的相关动作 4 信号与 signal 函数 4 1 signal 函数示例一 4 2 signal 函数示例二 5 利用 sigaction 函数进行信号处理 6
  • 使用SARIMA做季节时间序列预测全流程(附MATLAB代码)

    在之前的专栏中我们用ARIMA的方法做了时间序列的趋势性预测 不过我们经常还会遇到一种情况 即某些时间序列中存在明显的周期性变化 这种周期是由于季节性变化 季度 月度等 引起的 如下图所示 为1949年到1960年每月国际航空公司的乘客人数
  • DBus研究笔记(一)

    一 建立连接 要使用DBus进行通信必须首先与系统建立连接 并申请一个 域名 使得其他应用可以找到你 常用DBusConnection dbus bus get DBusBusType DBusError 系列函数来与bus daemon建
  • 类EMD的“信号分解方法”及MATLAB实现(第四篇)——VMD

    重头戏来了 在以往的应用经验里 VMD方法在众多模态分解方法中可以说是非常好的 从催更力度上看 这个方法也是格外受关注 笔者决定加快进度快一些写完这个方法 十月份了有些同学要开始做毕设 希望这篇文能帮上忙 1 VMD 变分模态分解 的概念
  • QT 异步函数转为同步函数的方法

    QT 异步函数转为同步函数的方法 2010 11 18 17 29 13 转载 标签 eventloop 同步函数 异步函数 qt it 分类 C和Cpp 在QT中 一般推荐使用异步函数 除了异步函数的非阻塞特性外 QT的Signal Sl
  • QT中实现应用程序的单例化

    一介绍 通过编写一个QSingleApplication类 来实现Qt程序的单例化 原文的作者是在Windows Vista Qt4 4 下实现的 不过应用在其他平台上是没问题的 本文是我在http www qtcentre org wik
  • TCP发送数据流程详解

    B S通信简述 整个计算机网络的实现体现为协议的实现 TCP IP协议是Internet的核心协议 HTTP协议是比TCP更高层次的应用层协议 HTTP HyperText Transfer Protocol 超文本传输协议 是互联网上应用
  • 《软件调试的艺术》学习笔记——GDB使用技巧摘要(3)——程序崩溃处理

    程序为什么会崩溃 内存中的程序布局 当某个错误导致程序突然和异常地停止执行时 程序崩溃 迄今为止最为常见的导致程序崩溃的原因是试图在未经允许的情况下访问一个内存位置 硬件会感知这件事 并执行对操作系统的跳转 Unix系列的平台上 操作系统一
  • QT信号和槽以结构体为参数传递复杂数据

    QT 的信号和槽机制能十分方便的用来传输数据 但是如果数据种类比较多 分类比较多的时候 就需要更好地更高效的来传递数据的方法 以结构体作为参数是个很不错的选择 这几天写的程序正好需要以结构体来作为参数 但是网上搜的资料很少 讲的也不详细 我

随机推荐

  • Hadoop HDFS shell 命令行常用操作

    前言 Hadoop HDFS shell的命令和Linux的shell命令有类似的地方 基本上会使用Linux命令的话 把HDFS的理论理解一下就可以了 一 创建文件夹 hadoop fs mkdir p
  • 【915程序设计】21西交大软件专硕915程序设计真题讲解

    28 马鞍点 include
  • 【华为OD机试c++】Excel单元格数值统计【 2023 Q1

    华为OD机试 题目列表 2023Q1 点这里 2023华为OD机试 刷题指南 点这里 题目描述 Excel 工作表中对选定区域的数值进行统计的功能非常实用 仿照Excel的这个功能 请对给定表格中选中区域中的单元格进行求和统计 并输出统计结
  • Halide学习笔记---Halide语言设计的初衷

    Halide语言设计初衷 我们正处于一个数据密集的时代 4D广场相机 图形渲染 3D打印 图像传感器 高质量医学图像等 每天生产大量的图像数据 面对这样一个图像时代 急需要针对图像处理算法设计的高性能图像处理编程语言 在这样的需求下 Hal
  • 第五届蓝桥杯——java c组 1/a 的分数称为单位分数

    形如 1 a 的分数称为单位分数 可以把1分解为若干个互不相同的单位分数之和 例如 1 1 2 1 3 1 9 1 18 1 1 2 1 3 1 10 1 15 1 1 3 1 5 1 7 1 9 1 11 1 15 1 35 1 45 1
  • 一文读懂 React16.0-16.6 新特性(实践 思考)

    首先 把我知道从16 0开始到现在为止新特性全部列举出来 v16 0 render 支持返回数组和字符串 createPortal 支持自定义 DOM 属性 Error Boundary Fiber SSR优化 减少文件体积v16 1 re
  • 对区块链钱包的简单认识

    钱包是存储和使用数字货币的工具 在区块链领域有举足轻重的地位 在对钱包分类之前 需要先理解几个概念 钱包地址 它类似于银行卡号 一个人可以拥有多张银行卡 所以他也可以拥有多个钱包地址 一个钱包地址只能对应一个私钥 在一个钱包中 可以拥有多个
  • VMware导入别人的虚拟机

    根据老师发的文件 将分卷压缩文件下载到一个文件夹中 选择解压第一个文件 即可得到所有资源 可以看出得到如下文件 这里需要导入Vmware中 我查询了几种导入的方法 1 导入OVF 首先选择导入ovf文件 直接点击打开虚拟机 选择导入Ubun
  • Go 每日一库之 cobra

    简介 cobra是一个命令行程序库 可以用来编写命令行程序 同时 它也提供了一个脚手架 用于生成基于 cobra 的应用程序框架 非常多知名的开源项目使用了 cobra 库构建命令行 如Kubernetes Hugo etcd等等等等 本文
  • $('#dg').datagrid('getSelections');", 得到的rows.length在多选情况下总为1, 不选择时为0.

    dg datagrid getSelections 得到的rows length在多选情况下总为1 不选择时为0 方案 title ID field dataId width 80 hidden true 的field一定要与idField
  • 订单系统设计 —— 订单管理

    文章目录 一 方案背景 1 1 考虑因素 1 2 数据特点 二 方案演进 三 MySQL架构 3 1 单库单表 3 2 读写分离 3 3 垂直拆分 3 4 数据归档 冷热分离 3 5 分库分表 方案1 路由表 方案2 哈希 四 MySQL
  • windows linux环境搭建

    1 windows中linux环境wsl 2 powershell scoop https zhuanlan zhihu com p 463284082 powershell通过scoop可以安装各种软件 安装git就是 scoop ins
  • 提升UE5写实效果的项目设置

    随着虚幻引擎5 Unreal Engine 5 简称UE5 的发布 游戏开发者和数字艺术家们迎来了一个全新的机会 可以在其强大的渲染引擎下创建更加逼真和令人惊叹的游戏和虚拟场景 然而 要实现出色的写实效果 需要合理设置项目并运用一些技巧和策
  • 使用IDEA2023创建Servlet模板,使其右键显示Servlet选项

    使用IDEA2023创建Servlet模板 使其右键显示Servlet选项 之前在IDEA2022及以前可以通过一些方法创建Servlet模板 但我不知道为什么2023版本没有作用 下面提供另一种方式实现Servlet的创建 直接参考IDE
  • 系统化程序分析

    左志强 南京大学计算机系副研究员 研究领域包括程序分析 编译技术 系统软件等 本文以技术文章的方式回顾左老师在 SIG 程序分析 技术沙龙上的分享 回顾视频也已经上传 B 站 欢迎小伙伴们点开观看 SIG 程序分析技术沙龙回顾 面向千万行代
  • 单电源运放和双电源运放及其供电方式选择与转换的注意事项

    文章目录 前言 一 运放之双电源供电和单电源供电 1 如何区分单电源运放和双电源运放 2 单电源供电运放特性 3 运放的两种供电模式转换 4 单端偏置的缺陷 二 仿真验证 1 两阶高通滤波放大电路 两端偏置 2 两阶高通滤波放大电路 单端偏
  • 20-10-026-安装-KyLin-2.6.0-单机版安装(MAC官网下载)-spark引擎

    文章目录 1 视界 1 官网 2 安装要求 2 1 软件要求 2 2 硬件要求 2 3 Hadoop 环境 3 本次环境 4 HBASE 1 2 0安装 5 kylin 安装 6 检查zk jar 7 启动Hbase 8 添加依赖 9 添加
  • VS附加到进程调试

    操作 要附加到进程中调试外部可执行文件 您需要使用Visual Studio的 调试附加 功能 以下是附加到进程中调试外部可执行文件的步骤 打开您要调试的源代码文件或可执行文件 打开Visual Studio 选择 调试 菜单 然后选择 附
  • ffmpeg读取rtsp并保存到mp4文件

    本文章只讲述mp4文件的录像 至于音频录入 会在下个文章中介绍 总体思路为 初始化 连接相机获取码流 读取码流中的视频 创建输出mp4上下文 写mp4头 循环读取码流 写入mp4 写文件尾 关闭文件 第一步 初始化网络环境 环境注册 av
  • 信号、signal 函数、sigaction 函数

    文章目录 1 信号的基本概念 2 利用 kill 命令发送信号 3 信号处理的相关动作 4 信号与 signal 函数 4 1 signal 函数示例一 4 2 signal 函数示例二 5 利用 sigaction 函数进行信号处理 6