信号、signal 函数、sigaction 函数

news2025/1/27 13:00:21

文章目录

  • 1.信号的基本概念
  • 2.利用 kill 命令发送信号
  • 3.信号处理的相关动作
  • 4.信号与 signal 函数
    • 4.1 signal 函数示例一
    • 4.2 signal 函数示例二
  • 5.利用 sigaction 函数进行信号处理
  • 6.利用信号处理技术消灭僵尸进程

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

输出结果:

在这里插入图片描述

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/396701.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

算法刷题-只出现一次的数字、输出每天是应该学习还是休息还是锻炼、将有序数组转换为二叉搜索树

只出现一次的数字&#xff08;位运算、数组&#xff09; 给定一个非空整数数组&#xff0c;除了某个元素只出现一次以外&#xff0c;其余每个元素均出现两次。找出那个只出现了一次的元素。 说明&#xff1a; 你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗&…

有java基础学习大数据该如何规划

大数据开发对于Java语言的依赖程度比较高&#xff0c;如果想尝试大数据开发&#xff0c;学习过Java语言就很容易上手 Java是目前使用广泛的编程语言之一&#xff0c;具有的众多特性&#xff0c;特别适合作为大数据应用的开发语言。 目前很多大数据开发团队都在使用Java语言&a…

MySQL——插入加锁/唯一索引插入死锁/批量插入效率

本篇主要介绍MySQL跟加锁相关的一些概念、MySQL执行插入Insert时的加锁过程、唯一索引下批量插入可能导致的死锁情况&#xff0c;以及分别从业务角度和MySQL配置角度介绍提升批量插入的效率的方法&#xff1b;MySQL跟加锁相关的一些概念在介绍MySQL执行插入的加锁过程之前&…

核酸检测信息管理系统

目录前言一、功能与需求分析二、详细设计与实现1、data包&#xff08;1&#xff09;DataDataBase&#xff08;2&#xff09;NaPaNamePassword2、operation包&#xff08;1&#xff09;操作接口&#xff08;2&#xff09;Resident用户功能&#xff08;3&#xff09;Simper用户功…

Java基础总结(jdk,jvm,异常,对象等)

文章目录前言一、Java基础part 1JDKJRE字节码位运算变量成员变量与局部变量的区别&#xff1f;基本数据类型装箱拆箱JVM1.Java内存区域Hotspot对象对象的创建&#xff1a;对象的内存布局part2面向对象面向对象三大特征构造方法接口&#xff1b;抽象类深拷贝和浅拷贝ObjectStrin…

阿里HPCC算法简介

摘要&#xff1a;HPCC&#xff08;高精度拥塞控制&#xff09;基于INT&#xff08;带内遥测&#xff09;技术&#xff0c;可以非常精确的获取网络的拥塞状态&#xff0c;能够实现快速的收敛以及利用带宽&#xff0c;并通过实现“零队列”来实现超低的延迟&#xff0c;下面将主要…

Presto本地开发,plugin的设置

1. 新的问题 之前搭建Presto的本地开发环境时&#xff0c;一直使用config.properties中的plugin.bundles配置项定义需要加载的plugin模块&#xff0c;详细可以参考博客《win10基于IDEA&#xff0c;搭建Presto开发环境》presto服务启动时&#xff0c;指定加载哪些组件&#xff…

kubernetes--监控容器运行时:Falco

目录 Falco介绍 Falco架构 Falco的安装 告警规则示列 威胁场景测试&#xff1a; 监控容器创建的不可信任进程&#xff08;自定义规则&#xff09; Falco支持五种输出告警方式falco.yaml&#xff1a; Falco告警集中化展示&#xff1a; Falco介绍 Falco是一个Linux安全工具…

使用chatgpt来提高你的编程能力,简直如虎添翼

下面大家跟着我的问题&#xff0c;可以跟着我一起向 chatgpt老师学习&#xff0c;相信我的问题可能你也会感兴趣。在Java中&#xff0c;boolean类型的数组默认初始化为false。也就是说&#xff0c;如果你创建了一个boolean类型的数组&#xff0c;但是没有初始化它&#xff0c;那…

嵌入式 Linux进程间通信之信号量

目录 一、信号量 1、信号量概述 2、什么是信号量 3、信号量的分类 4、进程获取共享资源要执行的操作 5、System V IPC 机制&#xff1a;信号量 5.1 semget函数 5.2 semop函数 5.3 semctl函数 一、信号量 1、信号量概述 信号量集&#xff1a;由若干个信号组成的集合&a…

JUC并发编程之Semaphore-应用与深度源码剖析

目录 JUC并发编程之Semaphore-应用与深度源码剖析 1. Semaphore 是什么&#xff1f; 2.怎么使用Semaphore&#xff1f; 2.1构造方法 2.2 重要方法 2.3 基本使用 需求场景 基础版代码实现 tryAcquire()引入代码实现 acquireUninterruptibly(),acquire()对比代码实现 3.…

【C++】C++11——简介|列表初始|简化声明|nullptr与范围for|STL中的变化

文章目录一、C11简介二、列表初始化三、简化声明四、nullptr与范围for五、STL中一些变化一、C11简介 在2003年C标准委员会曾经提交了一份技术勘误表(简称TC1)&#xff0c;使得C03这个名字已经取代了C98称为C11之前的最新C标准名称。不过由于TC1主要是对C98标准中的漏洞进行修复…

Easyrecovery16免费的电脑硬盘恢复数据软件

在我们的日常生活和工作中&#xff0c;很容易发生一些意外情况&#xff0c;比如误删文件。这种情况下&#xff0c;您可能会感到非常困惑和担心&#xff0c;担心文件已经永久丢失&#xff0c;无法恢复。但是&#xff0c;在大多数情况下&#xff0c;即使您误删了文件&#xff0c;…

Nativefier把网页打包成exe

前要&#xff1a; 今天遇到一个需求&#xff0c;之前的应用都是用的h5挂载在企业微信的小应用&#xff0c;但是现在需要电脑运行的exe安装包&#xff01; 所以需要用到nativefier导报工具&#xff1a;nativefier是一个使用electron将网页转换为app的插件&#xff0c;写这篇博客…

二、SpringMVC注解式开发

1. RequestMapping注解 此注解就是来映射服务器访问的路径 可加在方法上,是为此方法注册一个可以访问的名称(路径) 可以加在类上,相当于是包名(虚拟路径),区分不同类中相同的action的名称 可区分get请求和post请求 package com.powernode.controller;import org.springframe…

liunx下安装node exporter

1 建立文件夹 cd /opt mkdir software 下载最新的包&#xff0c;并解压 https://prometheus.io/download/ 下载 curl -LO https://github.com/prometheus/node_exporter/releases/download/v0.18.1/node_exporter-0.18.1.linux-amd64.tar.gz 3.解压 tar -xvf node_exporter-0.…

Gorm根据关系模型中的属性查询原模型数据

type ExamResult struct {gorm.ModelExamManagementID uintExamManagement ExamManagement json:"examManagement" // 一场考试&#xff0c;其中有试卷&#xff0c;有试题&#xff0c;有试题答案//MarkExamPaperRecord MarkExamPaperRecord //每一场考试对应的结…

测试经理:“你做了三年测试,连服务端的接口测试都不会?”

服务端的接口测试我们一般从功能开始进行测试&#xff0c;比如请求参数和响应参数的校验&#xff0c;业务逻辑或业务规则的校验&#xff0c;数据库操作的校验。 功能正常后会根据需要进行安全相关的检查、性能测试以及系列扩展测试&#xff0c;比如与历史版本的兼容性测试、接…

【微信小程序】-- WXS 脚本(二十九)

&#x1f48c; 所属专栏&#xff1a;【微信小程序开发教程】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &…

【Java基础】Linux系统

CONTENT一、常用命令时间日期关机&重启登录&注销运行级别找回root用户密码&#xff08;CentOS 7&#xff09;二、文件管理显示当前工作路径显示文件和目录切换目录创建文件&目录删除文件&目录拷贝文件&目录移动文件&目录 / 重命名查找文件&目录查看…