int main ()
{
while ( true ){
std :: cout << "I am a process, I am waiting signal!" << std :: endl ;
sleep( 1 );
}
} g++ sig.cc -o sig
./sig
I am a process, I am waiting signal!
I am a process, I am waiting signal!
^C
⽤⼾输⼊命令,在Shell下启动⼀个前台进程
⽤⼾按下 Ctrl+C ,这个键盘输⼊产⽣⼀个硬件中断,被OS获取,解释成信号,发送给⽬标前台进
程 , 前台进程因为收到信号,进⽽引起进程退出
1.2.2⼀个系统函数
NAME
signal - ANSI C signal handling
SYNOPSIS
include <signal.h>
typedef void (* sighandler_t )( int );
sighandler_t signal ( int signum, sighandler_t handler)
参数说明:
signum :信号编号 [ 后⾯解释,只需要知道是数字即可 ]
handler :函数指针,表⽰更改信号的处理动作,当收到对应的信号,就回调执⾏ handler ⽅法
⽽其实, Ctrl+C 的本质是向前台进程发送 SIGINT 即 2 号信号,我们证明⼀下,这⾥需要引⼊⼀
个系统调⽤函数
开始测试
include <iostream>
include <unistd.h>
include <signal.h>
void handler ( int signumber)
{
std::cout << " 我是 : " << getpid () << ", 我获得了⼀个信号 : " << signumber <<
std::endl;
}
int main ()
{
std::cout << " 我是进程 : " << getpid () << std::endl;
signal (SIGINT /*2*/ , handler);
while ( true ){
std::cout << "I am a process, I am waiting signal!" << std::endl;
sleep ( 1 );
}
} g++ sig.cc -o sig
./sig
我是进程 : 212569
I am a process, I am waiting signal!
I am a process, I am waiting signal!
^C 我是 : 212569 , 我获得了⼀个信号 : 2
I am a process, I am waiting signal!
I am a process, I am waiting signal!
^C 我是 : 212569 , 我获得了⼀个信号 : 2
I am a process, I am waiting signal!
I am a process, I am waiting signal!
📌 思考:
这⾥进程为什么不退出?
这个例⼦能说明哪些问题?信号处理,是⾃⼰处理
请将⽣活例⼦和 Ctrl-C 信号处理过程相结合,解释⼀下信号处理过程?进程就是你,
操作系统就是快递员,信号就是快递,发信号的过程就类似给你打电话
void handler ( int signumber)
{
std::cout << " 我是 : " << getpid () << ", 我获得了⼀个信号 : " << signumber
<< std::endl;
}
int main ()
{
std::cout << " 我是进程 : " << getpid () << std::endl;
signal (SIGINT /*2*/ , SIG_IGN); //设置忽略信号的宏
while ( true ){
std::cout << "I am a process, I am waiting signal!" << std::endl;
sleep ( 1 );
}
} g++ sig.cc -o sig
./sig
我是进程 : 212681
I am a process, I am waiting signal!
I am a process, I am waiting signal!
^C^C^C^C^C^CI am a process, I am waiting signal! //输⼊ctrl+c毫⽆反应
I am a process, I am waiting signal!
执⾏该信号的默认处理动作
include <iostream>
include <unistd.h>
include <signal.h>
void handler ( int signumber)
{
std::cout << " 我是 : " << getpid () << ", 我获得了⼀个信号 : " << signumber
<< std::endl;
}
int main ()
{
std::cout << " 我是进程 : " << getpid () << std::endl;
signal (SIGINT /*2*/ , SIG_DFL);
while ( true ){
std::cout << "I am a process, I am waiting signal!" << std::endl;
sleep ( 1 );
}
} g++ sig.cc -o sig
./sig
我是进程 : 212791
I am a process, I am waiting signal!
I am a process, I am waiting signal!
^C //输⼊ctrl+c,进程退出,就是默认动作
提供⼀个信号处理函数,要求内核在处理该信号时切换到⽤⼾态执⾏这个处理函数,这种⽅式称为⾃
定义捕捉(Catch)⼀个信号。
上⾯的所有内容,我们都没有做⾮常多的解释,主要是先⽤起来,然后渗透部分概念和共识,下⾯我们从理论和实操两个层⾯,来进⾏对信号的详细学习、论证和理解。为了保证条理,我们采⽤如下思路来进⾏阐述:
int sigemptyset ( sigset_t * set );
int sigfillset ( sigset_t * set );
int sigaddset ( sigset_t * set , int signo);
int sigdelset ( sigset_t * set , int signo);
int sigismember ( const sigset_t * set , int signo);
函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表⽰该信号集不包含任何有效信号。
函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表⽰ 该信号集的有效信号包括系统⽀持的所有信号。
注意,在使⽤sigset_ t类型的变量之前,⼀定要调 ⽤sigemptyset或sigfillset做初始化,使信号集处于确定的 状态。初始化sigset_t变量之后就可以在调⽤sigaddset和sigdelset在该信号集中添加或删除某种有效信号。
这四个函数都是成功返回0,出错返回-1。sigismember是⼀个布尔函数,⽤于判断⼀个信号集的有效信号中是否包含 某种 信号,若包含则返回1,不包含则返回0,出错返回-1。
调⽤函数 sigprocmask 可以读取或更改进程的信号屏蔽字(阻塞信号集)。
include <signal.h>
int sigprocmask ( int how, const sigset_t * set , sigset_t *oset);
如果oset是⾮空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是⾮空指针,则 更改
进程的信 号屏蔽字,参数how指⽰如何更改。如果oset和set都是⾮空指针,则先将原来的信号 屏蔽字备份到oset⾥,然后 根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。
如果调⽤sigprocmask解除了对当前若⼲个未决信号的阻塞,则在sigprocmask返回前,⾄少将其中⼀
个信号递达。
include <signal.h>
int sigpending ( sigset_t * set );
读取当前进程的未决信号集 , 通过 set 参数传出。
调⽤成功则返回 0 , 出错则返回 -1
4.捕捉信号
4.1 信号捕捉的流程
如果信号的处理动作是⽤⼾⾃定义函数,在信号递达时就调⽤这个函数,这称为捕捉信号。
由于信号处理函数的代码是在⽤⼾空间的,处理过程⽐较复杂,举例如下:
⽤⼾程序注册了 SIGQUIT 信号的处理函数 sighandler 。
当前正在执⾏ main 函数,这时发⽣中断或异常切换到内核态。
在中断处理完毕后要返回⽤⼾态的 main 函数之前检查到有信号 SIGQUIT 递达。
内核决定返回⽤⼾态后不是恢复 main 函数的上下⽂继续执⾏,⽽是执⾏ sighandler 函
数, sighandler 和 main 函数使⽤不同的堆栈空间,它们之间不存在调⽤和被调⽤的关系,是两个
独⽴的控制流程。
sighandler 函数返回后⾃动执⾏特殊的系统调⽤ sigreturn 再次进⼊内核态。
如果没有新的信号要递达,这次再返回⽤⼾态就是恢复 main 函数的上下⽂继续执⾏了。