一,信号简介
1)信号是什么?
信号是进程在运⾏过程中,由⾃身产⽣或进程外部发过来,⽤来通知进程发⽣了异步事件的通信机制,是硬件中断的软件模拟。 信号可以导致⼀个正在运⾏的程序被异步打断,转⽽处理⼀个突发事件。
使⽤信号的⽬的是:
1)让进程知道已经发⽣了⼀个特定的事件;
1)让进程知道已经发⽣了⼀个特定的事件;
2)强迫进程执⾏它代码中的信号处理程序;
信号的特点:
1.简单
2.不能携带⼤量信息
3.满⾜某个特设条件才发送
⼀个完整的信号周期包括三个部分:信号的产⽣,信号在进程中的注册,信号在进程中的注销,执⾏信号处理函数。
2)常见信号
Linux系统共定义了64种信号,每个信号都有⼀个编号和⼀个宏名称,这些宏名称可以在signal.h中找到。以SIG开头,可以使⽤kill -l命令查看系统中定义的信号列表。
Linux系统共定义了64种信号,分为两⼤类:可靠信号与不可靠信号,前32种信号为不可靠信号,后32种为可靠信号。
不可靠信号: 也称为⾮实时信号,不⽀持排队,信号可能会丢失, ⽐如发送多次相同的信号, 进程只能收到⼀次. 信号值取值区间为1~31;
可靠信号: 也称为实时信号,⽀持排队, 信号不会丢失, 发多少次, 就可以收到多少次. 信号值取值区间为32~64
3)信号常见的生成方式
1.另⼀个进程(⽐如信号函数kill())发送过来的信号
2.⽤户在终端按下某些键时,⽐如CTRL+C(SIGINT),CTRL+Z(SIGTSTP)
3.⼦进程结束时向⽗进程发送SIGCHLD
4.程序中设置的定时器产⽣的SIGALRM。
5.程序执⾏错误,如除零,内存越界,总线错误,内核发送信号给程序
信号来源分为硬件类和软件类,硬件类⽐如硬件异常或按ctrl+c等产⽣信号,软件类主要通过调⽤系统函数产⽣信号。
二,信号函数简介
通过软件发送signal的信号函数有:kill(),raise(),sigqueue(),alarm(),和abort()等。
1)kill信号函数
进程可以通过调⽤kill函数向包括它本身在内的其他进程发送⼀个信号,这个函数
和shell中的命令kill命令完成相同的功能。
所需头⽂件:
#include <sys/types.h>
#include <signal.h>
函数格式:int kill(pid_t pid, int sig);
pid > 0 向pid指定的进程发送sig指定的信号.
pid == 0 向调⽤kill函数所在进程组中的所有进程发sig指定的信号.
pid == -1 向调⽤kill函数所在进程拥有权限的所有进程发宋sig信号,除了进程1(init).
pid < -1 向进程组id 为-pid 内的所有进程发送sig 信号.
返回值:成功返回0; 错误返回-1,并且errno会被设置相应的值
进程组id说明:
每个进程都会有进程组ID,表示该进程所属的进程组。默认情况下新创建的进程会继承⽗进程的进程组
ID和会话ID。
void getpgrp(); //获取当前进程的进程组id
进程组是⼀组相关进程的集合,会话是⼀组相关进程组的集合,但是可以通过特定的函数去修改。
2)raise()信号函数
所需头⽂件:
#include <signal.h>
函数原型:int raise(int sig)
功能:给进程⾃身发送信号,raise(sig)等价于kill(getpid(),sig)
函数传⼊值 : sig:信号
函数返回值: 成功:0 出错:-1
3)alarm()信号函数
alarm也称为闹钟函数,alarm()⽤来设置信号SIGALRM在经过参数seconds指定的
秒数后传送给⽬前的进程。
所需头⽂件:#include<unistd.h>
函数原型:unsigned int alarm(unsigned int seconds)
函数参数:
seconds:指定秒数
函数返回值
成功:如果调⽤此alarm()前,进程已经设置了闹钟时间,则返回上⼀个闹钟时间的剩余时间,否则返回0。出错:-1
注意:alarm信号可能会对sleep()函数产⽣影响。
除了alarm()外,使⽤功能ualarm()也可以产⽣SIGALRM信号,但该函数的参数不能超过1s
4)abort()信号函数
abort函数是⼀个⽐较严重的函数,当调⽤它时,会导致程序异常终⽌,⽽不会进⾏⼀些常规的清除⼯作,⽐如释放内存,将缓冲区数据刷到硬盘等。
所需头⽂件:#include<stdlib.h>
函数原型:void abort(void)
功能:使异常程序终⽌,同时发送SIGABRT信号给调⽤进程。
#include<stdio.h>
#include<stdlib.h>
int main()
{
FILE *fp;
if((fp=fopen("a.txt","r"))==NULL)
{
perror("not open file");
abort();
}
else
{
fclose(fp);
}
printf("end\n");
}
运行结果:

5)sigqueue()信号函数
主要针对实时信号,⽀持带有参数信号,常与函数sigaction()配合使⽤,如果需要给信号处理函数传参需要使⽤该信号函数。
所需头⽂件:
#include <sys/types.h>
#include <signal.h>
函数原型:int sigqueue(pid_t pid, int signo, const union sigval sigval_t)
参数说明
pid:接收信号的进程ID
signo:待发送信号
sigval_t:信号传递的参数(4字节)
第三个参数是⼀个联合数据结构union sigval
typedef union sigval
{
int sival_int; //传整数值
void *sival_ptr; //传⼀个集合⽐如字符串类型
}sigval_t;
调⽤sigqueue()时,sigval_t被拷⻉到信号处理函数
三,信号处理
常⻅的信号处理⽅式:
1. 忽略此信号,SIG_IGN,该常数表示信号函数的忽略,对信号不做任何处理,但是⼜两个信号是不能忽略的,即SIGKILL,SIGSTOP。
2. 执⾏该信号的默认处理动作。
3. 执⾏⾃定义信号处理函数(捕获)
信号的安装函数主要有两个:signal()与sigaction()。signal()常⽤于⾮实时信号,sigaction()常⽤于实时信号,它有更多的选项设置,最重要的是它可以为实时信号安装带参数的回调。主要包含在#include <signal.h>头⽂件中。
1)信号安装函数:signal()
函数原型如下:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:为指定的信号安装⼀个新的信号处理函数
参数:
signum:是信号名
handler:是⼀个⽆返回值、接收⼀个int形参的函数指针,指向对sig信号的处理函数, 信号发⽣时会调⽤该函数。
handler这个函数必须有⼀个int类型的参数(即接收到的信号代码),也可以是下⾯两个特殊值:
① SIG_IGN 屏蔽该信号
② SIG_DFL 恢复默认⾏为
例3 : singal函数基本使⽤举例
向⾃身进程发送信号举例
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
// 注册一个信号处理函数
// 该处理函数可以根据传入的信号编号响应不同的信号
void handler(int signum)
{
// 在信号处理函数中,我们使用 write 而不是 printf,因为 write 是异步信号安全的。
// 这样可以避免标准I/O库可能带来的重入问题。
switch(signum)
{
case SIGQUIT:
// PS: write 是一个系统调用,它直接向文件描述符写入,是安全的。
// 1 表示标准输出 (stdout)
write(1, "\nCaught SIGQUIT! (Ctrl+\\)\n", 28);
break;
case SIGINT:
write(1, "\nCaught SIGINT! (Ctrl+C)\n", 27);
break;
}
}
int main(void)
{
// 1. 注册信号处理函数
// 将 SIGQUIT (通常由 Ctrl+\ 触发) 和 SIGINT (通常由 Ctrl+C 触发)
// 都关联到我们自定义的 handler 函数上。
signal(SIGQUIT, handler);
signal(SIGINT, handler);
int i = 0;
printf("程序开始运行,PID: %d\n", getpid());
printf("你可以通过键盘 Ctrl+C (SIGINT) 或 Ctrl+\\ (SIGQUIT) 来发送信号。\n");
printf("程序也会在第5秒和第10秒自动触发信号。\n");
// 2. 进入主循环
while(1)
{
printf("main running... (%d/10)\n", i + 1);
sleep(1);
i++;
// 3. 在第5秒,程序自己给自己发送 SIGINT 信号
if (i == 5)
{
printf("--- 5秒已到,使用 raise() 发送 SIGINT ---\n");
raise(SIGINT);
}
// 4. 在第10秒,程序自己给自己发送 SIGQUIT 信号,并重置计数器
if (i == 10)
{
printf("--- 10秒已到,使用 raise() 发送 SIGQUIT ---\n");
raise(SIGQUIT);
i = 0; // 重置计数器,开始新的循环
}
}
return 0; // 这行代码实际上永远不会被执行到
}
运行结果

2)sigaction函数
功能:给指定的信号编号设置该信号对应的处理函数,如果需要信号携带数据传递
给信号处理函数,则需要使⽤sigaction函数。
#define _POSIX_C_SOURCE 199309L // sigqueue() 需要这个特性测试宏
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
/**
* @brief 这是一个扩展的信号处理函数。
*
* @param signum 接收到的信号编号。
* @param info 一个指向 siginfo_t 结构的指针,包含了信号的详细信息。
* @param context 指向 ucontext_t 结构的指针,包含了信号传递时的上下文(我们这里不用)。
*/
void handler_with_data(int signum, siginfo_t *info, void *context)
{
// --- 打印由内核自动填充的信息 ---
printf("\n--- Signal Handler Entered ---\n");
printf(" Signal Number: %d\n", info->si_signo);
printf(" Sending Process PID: %d\n", info->si_pid);
printf(" Sending Process UID: %d\n", info->si_uid);
// --- 打印我们最关心的、由 sigqueue() 发送过来的自定义数据 ---
// info->si_value 是一个联合体 (union sigval)
int received_value = info->si_value.sival_int;
printf(" Custom data received: %d\n", received_value);
printf("--- Signal Handler Exiting ---\n");
}
int main()
{
// 1. 准备 sigaction 结构体
struct sigaction sa;
// 清空结构体,避免脏数据
sigemptyset(&sa.sa_mask);
// 【核心步骤】
// a. 指定使用带有三个参数的扩展处理函数
sa.sa_sigaction = handler_with_data;
// b. 在标志位中必须包含 SA_SIGINFO,否则系统会调用 sa_handler 而不是 sa_sigaction
sa.sa_flags = SA_SIGINFO;
// 2. 注册信号处理器
// 我们选择 SIGUSR1,这是一个用户自定义信号,非常适合这类测试
if (sigaction(SIGUSR1, &sa, NULL) == -1) {
perror("sigaction registration failed");
return 1;
}
printf("Main: Signal handler for SIGUSR1 has been registered.\n");
// 3. 创建子进程
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
return 1;
}
if (pid == 0) {
// --- 子进程 (接收方) ---
printf("Child (PID: %d): I'm ready, waiting for a signal from my parent...\n", getpid());
// 使用 pause() 让子进程挂起,直到有信号到来
pause();
printf("Child: Signal received and handled. I am now exiting.\n");
exit(0);
} else {
// --- 父进程 (发送方) ---
// 等待1秒,确保子进程已经准备好并执行了 pause()
sleep(1);
printf("Parent (PID: %d): I will now send SIGUSR1 to child (PID: %d).\n", getpid(), pid);
// 准备要发送的数据
union sigval data_to_send;
data_to_send.sival_int = 2024; // 我们要发送的自定义整数
printf("Parent: The data I'm sending is: %d\n", data_to_send.sival_int);
// 【核心步骤】使用 sigqueue() 发送信号和数据
// 参数:目标PID, 信号编号, 包含数据的联合体
if (sigqueue(pid, SIGUSR1, data_to_send) == -1) {
perror("sigqueue failed");
}
// 等待子进程结束
wait(NULL);
printf("Parent: Child has terminated. Exiting.\n");
}
return 0;
}
运行结果: