信号
信号不同于信号量。
本质为宏,实际为数字。信号一定是由操作系统(管理者)发送给进程的。
信号产生和程序进行为异步进行,就是会中断处理。
在操作系统中,键盘输入可以包含普通字符(如字母等)和特殊控制字符(ctrl + c等)。对于前者是可以回显的,而后者不回显。
对于普通字符就直接从键盘文件拷贝到内核缓冲区;如果是特殊控制字符,操作系统会将解释成为某个信号发送给前台进程。
kill -l //终端输入查看系统可使用命令,64个一般
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
1~31 号信号为 普通信号(重点学习);剩下的 34~64 号信号为 实时信号(不考虑)。
- 普通信号:根据时间片实行公平调度,适用于个人电脑。(识别信号后可以不立即处理)
- 实时信号:高响应,适合任务较少、需要快速处理的平台,比如汽车车机、火箭发射控制台。(识别信号后必须立即处理)
前台进程:用户当前正在与之交互的进程称为前台进程。用户可以通过在终端按ctrl + c
或者使用kill
命令可以向进程发送信号9
(SIGKILL
)终止前台运行的进程。
后台进程:当进程以后台方式运行时,它不会占用终端并且不会阻塞用户的输入输出。使用命令:
./myprocess &
命令行解释器bash一般
就是前台进程。当一个前台进程运行起来后,bash
就变成了后台进程。一个终端仅一个前台和多个后台。Ctrl + c
不会终止bash
进程。因为bash
进程对其做了特殊处理。
产生信号Produce
按键产生
Ctrl + /向前台进程发送3
号信号SIGQUIT
来终止程序,同时会产生一个core
文件。
Ctrl + c前台进程发送2
号信号SIGINT
来中断程序。终端无提示。
Ctrl + z
会向前台进程发送19
号信号SIGSTOP,
不能使用signal
函数捕捉。类似地,9
号信号SIGKILL
用于立即终止一个进程,并且也不能被signal
函数捕捉。9号终端会有提示killed。
kill命令产生
kill -n <pid> # n是信号编号//<pid>为进程号,程序中可用gitpid()获取
kill -9 3024
向进程发送信号 0,可以检查该进程是否存在,正常无影响,无进程会报错。
系统调用接口
#include <sys/types.h>//系统调用使用的头文件
#include <signal.h>
int kill(pid_t pid, int sig);
pid
:要发送信号的进程pid
sig
:要发送的信号编号,可以是标准信号如SIGINT
、SIGKILL
,也可以是用户自定义的信号。- 返回值:成功时,返回
0
;失败时,返回-1
,并设置errno
来指示错误的原因。
#include <signal.h>
int raise(int sig)//向当前进程发送一个信号
sig
:要发送的信号编号,可以是标准信号(如SIGINT
、SIGTERM
等),也可以是用户自定义的信号。- 返回值:成功时,返回
0
;失败时,返回非0
值。
#include <stdlib.h>
void abort(void);//向自己发送6号信号SIGABRT,异常终止当前进程的执行
终端有提示aborted
硬件异常
注意:修改了系统默认的行为却没退出exit(0),只发生了一次 除0
异常,进程会非但没退出,还在死循环打印。类似单片机中断中调用中断。
异常信号被捕捉不是为了解决什么问题,而是让用户清除的知道程序是因为什么而挂掉的。
软件产生
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
//在指定的秒数后发送14号信号SIGALRM给调用进程。该信号的默认处理动作是终止当前进程。
//结束打印alarm clock
//也可以信号捕捉修改默认行为
signal(14,myhandler);
void myhandler(int signum)
{}
核心转储core dump
作用:调试(事后调试),并且直接从出错的地方开始调试。
man 7 signal//命令,可以查看信号的详细手册
//终止Term(terminate),暂停(Stop),继续(Cont),忽略(Ign),核心(Core)。
Signal Standard Action Comment
────────────────────────────────────────────────────────────────────────
SIGABRT P1990 Core Abort signal from abort(3)
SIGALRM P1990 Term Timer signal from alarm(2)
SIGBUS P2001 Core Bus error (bad memory access)
SIGCHLD P1990 Ign Child stopped or terminated
SIGCLD - Ign A synonym for SIGCHLD
SIGCONT P1990 Cont Continue if stopped
SIGEMT - Term Emulator trap
SIGFPE P1990 Core Floating-point exception
SIGHUP P1990 Term Hangup detected on controlling terminal
or death of controlling process
SIGILL P1990 Core Illegal Instruction
SIGINFO - A synonym for SIGPWR
SIGINT P1990 Term Interrupt from keyboard
SIGIO - Term I/O now possible (4.2BSD)
SIGIOT - Core IOT trap. A synonym for SIGABRT
SIGKILL P1990 Term Kill signal
SIGLOST - Term File lock lost (unused)
SIGPIPE P1990 Term Broken pipe: write to pipe with no
readers; see pipe(7)
SIGPOLL P2001 Term Pollable event (Sys V).
Synonym for SIGIO
SIGPROF P2001 Term Profiling timer expired
当一个进程异常退出时(参看上面Action
为Core
信号),该core dump
标志会被设置成1
,并且会生成一个core.pid
二进制文件,然后不再设置退出码(为0
,因为这种情况下程序没有正常的执行路径结束)。和进程控制有关。
核心转储功能也可以关闭,core dump就不会设为1,不生成core.pid二进制文件。core.pid文件有调试作用。
ulimit -a //命令
core file size (blocks, -c) 0//核心转储文件大小为0,即不生成核心转储文件。
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 15135
max locked memory (kbytes, -l) 65536
max memory size (kbytes, -m) unlimited
ulimit -c <大小自己定>//ulimit -c 1024
# 关闭大小设置为0即可
# 重启xhell自动关闭
一般关闭是因为许多进程都可能会产生,文件过大过多容易崩溃。
gdb调试
可以看信号,快速定位至出错处。
首先gcc/g++在编译时加-g生成可调试文件一般在makefile下,或者命令也可以
gdb <可执行文件> 进入调试模式 //gdb myprogrem
core-file <核心转储文件>。
保存信号Pending
产生到处理的中间状态。
普通信号(位图,收到就保存)还没处理,又来个信号,那么就只记得最近一次的信号。1-31,int位图即可全部表示。
实时信号。只要发送了,就要立即处理,有队列。
进程还允许阻塞(屏蔽)某些信号,阻塞信号处于保存状态。
block表:也称信号阻塞表(位图)。它主要用于记录信号有没有被阻塞。如果某信号(比特位的位置)被设置成1,表示信号被阻塞;如果某信号被设置成0,表示信号没有被阻塞。
pending表:也称未决信号表(位图)。它主要用于记录已经向进程发送但尚未被处理的信号(信号保存)。如果信号(比特位的位置)对应的比特位是1,则表示该信号是pending的,即信号产生但还未被处理。当进程的信号处理函数还没有准备好处理信号时,信号会保持在pending表中。一旦信号的处理函数准备好,内核会从pending表中选择一个信号交付给进程。注意:如果这个信号还没处理,又来个信号,那么就只记得最近一次的信号。(多次产生相同信号仅保存一次)
handler表:也称信号处理程序表(函数指针数组)。该表存储着信号[1,31]的系统默认处理动作的函数指针(函数地址);如果用户自定设定了方法(如singal函数自定义处理方式),则会将该方法的地址填入到信号处理程序表中。当进程接收到一个信号时,内核会查找该信号对应的处理函数,并执行该处理函数来响应信号事件。
block
表还是pending
表,它们都是位图结构。不允许直接修改,但是提供接口。对位图结构封装为__sigset_t类型,在用户层可以使用,sigset_t
称为信号集类型,这个类型可以表示每个信号的有
或无
状态。对与block为是否阻塞,对pending为是否处于未决。
信号集操作函数
#include <signal.h>
int sigemptyset(sigset_t *set);//初始化set所指向的信号集,将所有信号的对应比特位清零,表示该信号集不包含任何有效信号。
int sigfillset(sigset_t *set);//初始化set所指向的信号集,将所有信号的对应bit设置成1,表示该信号集的有效信号包括系统支持的所有信号。
int sigaddset (sigset_t *set, int signo);//向指定的信号集中添加特定的信号,设置1。
int sigdelset(sigset_t *set, int signo);//向指定的信号集中去掉特定的信号,设置0。
int sigismember(const sigset_t *set, int signo);//判断一个信号集的有效信号中是否包含某种信号。若包含则返回1,不包含则返回0。
以上四个函数都是成功返回0,出错返回-1。
使用前一定要调用sigemptyset
或sigfillset
做初始化。
系统调用接口
sigprocmask
#include <signal.h>
//sigprocmask函数用来对block表进行操作(阻塞信号)
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how参数:指定操作的类型,可以是以下值之一:
SIG_BLOCK:将 set中的信号添加到当前进程的block表中(mask|=set)。
SIG_UNBLOCK:从当前进程的block表中移除set中的信号(mask&=~set)。
SIG_SETMASK:设置当前进程的block表为 set 中的值(mask==set)。
set 参数:就是一个信号集,主要从此信号集中获取屏蔽信号信息
oldset 参数:指向 sigset_t 类型的指针,用于存储之前的block表。如果不需要获取旧的block表,可以将 oldset 设为 nullptr。
//sigpending函数用来获取当前进程中的未决信号集pengding表
int sigpending(sigset_t *set);
信号 9
号和19
号不可被屏蔽,也不可被捕捉。
处理信号Delivery
进程处于内核状态 ,进程调用系统调用时通过转换状态。
-
用户态:执行用户所写的代码时,就属于用户态。
-
内核态:执行操作系统的代码时,就属于内核态。
系统调用
sigaction signal
#include <signal.h>
//可以用户自定义动作
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
signum:要处理的信号的编号,比如 SIGINT等。
act:指向 struct sigaction 结构的指针,用来设置新的信号处理方式。
oldact:指向 struct sigaction 结构的指针,用来获取之前该信号的处理方式。或者可以设置为nullptr表示不获取之前该信号的处理方式。
返回值:成功时返回0,失败时返回-1,并设置errno表示具体错误原因。
struct sigaction
{
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_handler)(void);
};
void (*sa_handler)(int);:指定的信号处理函数,可以是一个函数指针,用于处理指定的信号。可以设置为 SIG_IGN(忽略信号)或 SIG_DFL(默认处理方式)。
sigset_t sa_mask;:当信号在执行 用户自定义动作时,可以将部分信号进行屏蔽,直到用户自定义动作执行完成。也就是说,我们可以提前设置一批 待阻塞 的 屏蔽信号集,当执行用户自定义动作时,这些 屏蔽信号集 中的 信号将会被屏蔽(避免干扰用户自定义动作的执行),直到用户自定义动作执行完成。
void (*sa_sigaction)(int, siginfo_t *, void *);:不关心。
int sa_flags;:不关心。
void (*sa_handler)(void);:不关心。
sa_mask要用 sigemptyset(&sa.sa_mask)或者memset(&sa, 0, sizeof(sa))清理。
sa_handler设置为使用的函数,也可以设置忽略。
对于sa_flags一般设置为0。
sigaction函数的第三项一般设为0就行。
使用
处理信号有三种方式:忽略SIG_IGN
、系统默认动作SIG_DEL
、用户自定义
#include <signal.h>
signal(2,SIG_IGN);//忽略
signal(2,SIG_DEL);//默认
用户自定义信号可以用SIGUSR1和SIGUSR2普通信号,SIGMIN等和SIGMAX等实时信号。你可以配合POSIX定时器使用实时信号。
#include <signal.h>
typedef void (*sighandler_t)(int);
void (*signal(int signum, sighandler_t handler);//函数原型
signum
:要处理的信号的编号。handler
:是一个函数指针(函数地址)。
#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;
void myhandler(int signum)
{
cout << "信号编号为:" << signum << endl;
exit(1);
}
int main()
{
signal(SIGINT, myhandler);
while (true)
{
pause();
}
return 0;
}
示例
下面代码功能在ctrl+C时进行程序清理并且关闭程序。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
// 全局变量,用于存储文件描述符
int file_descriptor = -1;
// 信号处理函数
void signal_handler(int signum) {
printf("Received signal %d\n", signum);
// 在这里进行清理工作
if (file_descriptor != -1) {
printf("Closing file descriptor...\n");
close(file_descriptor); // 关闭文件描述符
file_descriptor = -1;
}
printf("Cleanup done. Exiting program...\n");
exit(0); // 安全地退出程序
}
int main() {
// 打开一个文件(这里只是一个示例,实际使用时请替换为真实的文件操作)
file_descriptor = open("example.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
if (file_descriptor == -1) {
perror("Failed to open file");
return 1;
}
// 设置信号处理函数
signal(SIGRTMIN, signal_handler);
// 无限循环,等待信号
while (1) {
printf("Program is running... Press Ctrl+C to interrupt.\n");
sleep(1); // 暂停1秒,以减少CPU占用
}
return 0;
}