信号、
相比于前面介绍的内容,Linux信号是一种更高层的软件形式的异常,它允许进程和内核中断其他进程。
一个信号就是一条小消息,它由内核发出,通知进程系统中发生了一个某种类型的事件。Linux系统支持30种不同类型的信号,信号类型用小整数ID来标识,每种信号类型都对应某种系统事件。
信号在内核中的表示如下图所示。pending位向量中维护着待处理信号的集合;blocked位向量中维护着被阻塞地信号集合。

发送信号、
发送信号:内核通过更新目的进程上下文中的某个状态,发送一个信号给目的进程。
发送信号可以有如下两个原因:
- 内核检测到一个系统事件如除零错误(SIGFPE)或子进程终止(SIGCHLD)。
- 一个进程调用了
kill
函数,显式的请求内核发送一个信号到目的进程。一个进程可以发送信号给它自己。
发送信号的机制都是基于进程组这个概念的。
每个进程都只属于一个进程组,进程组是由一个正整数进程组ID来标识的。getpgrp
()函数返回当前进程的进程组ID,setpgid
()函数可以改变自己或者其他进程的进程组。
pid_t getpgrp(void)
int setpgid(pid_t pid, pid_t pgid)
子进程同属于父进程一个进程组,但两个的pid不相同。

发送信号的方式:
- 用
/bin/kill
程序发送信号。/bin/kill -9 15213
表示发送信号9(SIGKILL)给进程24818 - 从键盘发送信号。Crtl+C会导致内核发送一个SIGINT信号到前台进程组中的每一个进程。
- 用
kill
函数发送信号。int kill(pid_t pid, int sig)
。
#include<iostream>
using namespace std;
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<signal.h>
int main()
{
int max = 5,pid = 0;
int pids[max];
cout << "parent pid = "<< getpid()<<" pgrp = "<<getpgrp()<<endl;
for(int i = 0;i < max;++i)
{
if((pid = fork()) == 0)
{
cout << "child : pid = "<<getpid()<<" pgrp = "<<getpgrp()<<endl;
while(1);
}
pids[i] = pid;
}
sleep(1);
for(int i = 0;i < max;++i)
{
//向每一个子进程发送信号ctrl+c
kill(pids[i],SIGINT);
}
int status = 0;
for(int i = 0;i < max;++i)
{
//父进程阻塞,等待子进程结束的信号
pid_t wpid = wait(&status);
//子进程正常结束,指return 0,exit()
if(WIFEXITED(status) != 0)
{
cout << "child " <<wpid <<" terminated normally with exit status " << WEXITSTATUS(status)<<endl;
}
//非正常结束
else if(WIFEXITED(status) == 0)
{ //如果子进程是通过信号非正常结束
if(WIFSIGNALED(status))
{
cout<< "child "<<wpid <<" terminated abnormally with signal "<<WTERMSIG(status)<<endl;
}
}
}
return 0;
}
接受信号、
接收信号:当目的进程被内核强迫以某种方式对信号的发送做出反应时,它就接收了信号。
接收信号的时机:内核把进程从内核模式切换到用户模式时,例如从系统调用返回或是完成了一次上下文切换。
接收信号的过程:
- 内核计算进程的为被阻塞的待处理信号的集合
pnb=pending & ~blocked
。 - 如果集合为空:将控制传递到逻辑控制流中的下一条指令。
- 否则
- 内核选择集合中最小的非零位,强制进程接收。
- 触发进程的某种行为。
- 对所有的非零重复上述操作。
- 将控制传递到逻辑控制流中的下一条指令。
接收信号后反应的方式:
- 默认行为,是下面的一种:
- 忽略这个信号。
- 终止进程。
- 通过用户层函数信号处理程序捕获这个信号。
- 指定行为:
- 调用执行预先设置好的信号处理程序。
linux下的信号
id | 信号名称 | 描述 | 默认行为 |
---|---|---|---|
SIGABORT | 进程停止运行 | ||
14 | SIGALRM | 警告钟,是一个可以自行安排的信号,可以自己发送给自己 | terminate |
SIGFPE | 浮点运算例外 | ||
SIGHUP | 系统挂断 | ||
SIGILL | 非法指令 | ||
2 | SIGINT | 终端中断,也就是ctrl+c | terminate |
9 | SIGKILL | 强制停止进程(此信号不能被忽略或捕获),kill -9,无法捕获,也无法编写自定义信号处理程序来处理 | terminate |
SIGPIPE | 向没有读者的管道 | ||
11 | SIGSEGV | 无效内存段访问,段错误,试图访问非法内存区域 | terminate |
SIGQUIT | 终端退出ctrl+\ | ||
15 | SIGTERM | 正常终止,kill -15 ,可以编写信号处理程序来决定是终止程序还是忽略,还是阻塞 | terminate or ignore or block |
SIGUSR1 | 用户定义信号1 | ||
SIGUSR2 | 用户定义信号2 | ||
17 | SIGCHLD | 子进程已经停止或退出,当子进程被终止或结束时,kernel就会通知它们的父进程 | ignore |
SIGCONT | 如果被停止则继续执行 | ||
SIGSTOP | 停止执行 | ||
SIGTSTP | 终端停止信号 | ||
SIGTOUT | 后台进程请求进行写操作 | ||
SIGTTIN | 后台进程请求进行读操作 |
子进程的结束状态返回后存于 int status,linux提供了一些宏可判别子进程的结束情况
- WIFEXITED(status) 如果子进程正常结束则为非0值,即返回子进程的pid,非正常结束返回0。正常结束是指return 0 或者exit()退出。
WEXITSTATUS(status) 取得子进程exit()返回的结束代码,一般会先用WIFEXITED 来判断是否正常结束才能使用此宏。 - WIFSIGNALED(status) 如果子进程是因为信号而结束则此宏值为真 。WTERMSIG(status)取得子进程因信号而中止的信号代码,一般会先用WIFSIGNALED 来判断后才使用此宏。
- WIFSTOPPED(status) 如果子进程处于暂停执行情况则此宏值为真。一般只有使用WUNTRACED 时才会有此情况。
WSTOPSIG(status) 取得引发子进程暂停的信号代码,一般会先用WIFSTOPPED 来判断后才使用此宏。
我们可以使用signal函数设置信号处理程序,从而修改和信号相关联的默认行为。
handler_t *signal(int signum, handler_t *handler)
handler的不同取值:
- SIG_IGN:忽略类型为signum的信号;
- SIG_DFL:恢复默认行为;
- 用户自定义handler,这个程序称为信号处理程序。
注意,信号处理程序是与主程序同时运行、独立的逻辑流(不是进程)。如下图所示。

注意,信号处理程序也可以被其他信号处理程序中断。

#include<iostream>
using namespace std;
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<signal.h>
#include<mutex>
//多进程也是并发的一种,也会对共享资源造成问题
std::mutex lo{};
//kill -15 的handler
void signal_SIGTERM_handler(int sig)
{
lo.lock();
cout<<getpid() << " was kill -15 "<<endl;
lo.unlock();
fflush(stdout);
exit(0);
}
//child process exit 的handler
void signal_SIGCHLD_handler(int sig)
{
lo.lock();
cout <<"one child process exit"<<endl;
lo.unlock();
fflush(stdout);
}
//ctrl + c 的handler
void signal_SIGINT_handler(int sig)
{
lo.lock();
cout <<getpid() << " Are u want to run ctrl+c ?" << endl;
cout<< "Well..."<<endl;
lo.unlock();
fflush(stdout);
exit(0);
}
int main()
{
//安装handler处理程序
if(signal(SIGINT,signal_SIGINT_handler) == SIG_ERR)
{
cout <<" install SIGINT handler error"<<endl;
exit(0);
}
if(signal(SIGTERM,signal_SIGTERM_handler) == SIG_ERR)
{
cout <<" install SIGTERM handler error"<<endl;
exit(0);
}
if(signal(SIGCHLD,signal_SIGCHLD_handler) == SIG_ERR)
{
cout <<" install SIGCHLD handler error"<<endl;
exit(0);
}
int max = 5,pid = 0;
int pids[max];
cout << "parent pid = "<< getpid()<<" pgrp = "<<getpgrp()<<endl;
for(int i = 0;i < max;++i)
{
if((pid = fork()) == 0)
{
cout << "child : pid = "<<getpid()<<" pgrp = "<<getpgrp()<<endl;
while(1);
}
pids[i] = pid;
}
sleep(1);
for(int i = 0;i < max;++i)
{
kill(pids[i],SIGTERM);
}
int status = 0;
for(int i = 0;i < max;++i)
{
pid_t wpid = wait(&status);
//子进程正常结束
if(WIFEXITED(status) != 0)
{
lo.lock();
cout << "child " <<wpid <<" terminated normally with exit status " << WEXITSTATUS(status)<<endl;
lo.unlock();
}
//非正常结束
else if(WIFEXITED(status) == 0)
{
if(WIFSIGNALED(status))
{
lo.lock();
cout<< "child "<<wpid <<" terminated abnormally with signal "<<WTERMSIG(status)<<endl;
lo.unlock();
}
}
}
sleep(1);
return 0;
}
自己发信号给自己,然后根据自己写的handler处理
#include<iostream>
using namespace std;
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<signal.h>
#include<mutex>
#include<atomic>
//打印输出的互斥锁
mutex print_mutex{};
//出现错误
void unix_error(const char* msg);
template<class... Args>
void print(const Args&...args)
{
lock_guard<mutex> m(print_mutex);
((cout << args), ...);
}
//kill -15
void signal_SIGTERM_handler(int sig)
{
print("child ",getpid()," was killed by signal 15 \n");
fflush(stdout);
_exit(0);
}
atomic<int> count = 0;
//child process exit
void signal_SIGCHLD_handler(int sig)
{
pid_t pid = 0;
while((pid = wait(NULL)) > 0)
{
print("child ",pid," process exit\n");
--count;
fflush(stdout);
return;
}
unix_error("wait error\n");
}
//ctrl + c
void signal_SIGINT_handler(int sig)
{
print("Are u want to run ctrl+c ? Well...\n");
fflush(stdout);
_exit(0);
}
//SIGALRM自己发信号给自己,handler处理程序
void signal_SIGALRM_handler(int sig)
{
printf("receive signal from self\n");
}
//出现错误
void unix_error(const char* msg)
{
print(msg);
_exit(0);
}
int main()
{
//安装handler
if(signal(SIGINT,signal_SIGINT_handler) == SIG_ERR)
{
unix_error(" install SIGINT handler error\n");
}
if(signal(SIGTERM,signal_SIGTERM_handler) == SIG_ERR)
{
unix_error(" install SIGTERM handler error\n");
}
if(signal(SIGCHLD,signal_SIGCHLD_handler) == SIG_ERR)
{
unix_error(" install SIGCHLD handler error\n");
}
if(signal(SIGALRM,signal_SIGALRM_handler) == SIG_ERR)
{
unix_error("install SIGALRM handler error\n");
}
kill(getpid(),SIGALRM);
print("hello,world\n");
return 0;
}
阻塞和解除阻塞信号、
待处理信号指发出而没有被接收的信号。在任何时刻,一种类型至多只会有一个待处理信号。一个待处理信号最多只能被接收一次。
一个进程可以有选择地阻塞接收某种信号。当一种信号被阻塞时,它仍可以被发送,但是产生的信号不会被接收,直到进程取消对这种信号的阻塞(信号被阻塞时不会消失)。
Linux提供信号的隐式和显式阻塞机制。
隐式阻塞机制:内核默认阻塞与当前正在处理信号类型相同的待处理信号。
显式阻塞机制:可以使用sigprocmask
函数和它的辅助函数明确地阻塞和解除阻塞选定的信号。
#include<iostream>
using namespace std;
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<signal.h>
#include<mutex>
std::mutex lo{};
//kill -15
void signal_SIGTERM_handler(int sig)
{
lo.lock();
cout<<getpid() << " was kill -15 "<<endl;
lo.unlock();
fflush(stdout);
exit(0);
}
//child process exit
void signal_SIGCHLD_handler(int sig)
{
lo.lock();
cout <<"one child process exit"<<endl;
lo.unlock();
fflush(stdout);
}
//ctrl + c
void signal_SIGINT_handler(int sig)
{
lo.lock();
cout <<getpid() << " Are u want to run ctrl+c ?" << endl;
cout<< "Well..."<<endl;
lo.unlock();
fflush(stdout);
exit(0);
}
int main()
{
if(signal(SIGINT,signal_SIGINT_handler) == SIG_ERR)
{
cout <<" install SIGINT handler error"<<endl;
exit(0);
}
if(signal(SIGTERM,signal_SIGTERM_handler) == SIG_ERR)
{
cout <<" install SIGTERM handler error"<<endl;
exit(0);
}
if(signal(SIGCHLD,signal_SIGCHLD_handler) == SIG_ERR)
{
cout <<" install SIGCHLD handler error"<<endl;
exit(0);
}
//显示阻塞信号SIGTERM
sigset_t mask,prev_mask;
sigemptyset(&mask);
sigaddset(&mask,SIGTERM);
sigprocmask(SIG_BLOCK,&mask,&prev_mask);
int max = 5,pid = 0;
int pids[max];
cout << "parent pid = "<< getpid()<<" pgrp = "<<getpgrp()<<endl;
for(int i = 0;i < max;++i)
{
if((pid = fork()) == 0)
{
cout << "child : pid = "<<getpid()<<" pgrp = "<<getpgrp()<<endl;
while(1);
}
pids[i] = pid;
}
sleep(1);
for(int i = 0;i < max;++i)
{
kill(pids[i],SIGTERM);
}
int status = 0;
for(int i = 0;i < max;++i)
{
pid_t wpid = wait(&status);
//子进程正常结束
if(WIFEXITED(status) != 0)
{
lo.lock();
cout << "child " <<wpid <<" terminated normally with exit status " << WEXITSTATUS(status)<<endl;
lo.unlock();
}
//非正常结束
else if(WIFEXITED(status) == 0)
{
if(WIFSIGNALED(status))
{
lo.lock();
cout<< "child "<<wpid <<" terminated abnormally with signal "<<WTERMSIG(status)<<endl;
lo.unlock();
}
}
}
//解开信号阻塞
sigprocmask(SIG_SETMASK,&prev_mask,nullptr);
sleep(1);
return 0;
}
编写信号处理程序、
信号处理程序很麻烦,因为它们和主程序并发地运行,共享相同的全局数据,共享的数据可能被破坏,有点类似于多线程程序。
安全的信号处理、
编写处理程序的原则:
- G0:处理程序尽可能简单:简单设置全局标志并立即返回。
- G1:在处理程序中只调用异步信号安全的函数:
printf
、sprintf
、malloc
、exit
都是不安全的,产生输出唯一安全的方法是使用write
函数。 - G2:保存和恢复errno:确保其他处理程序不会覆盖当前的errno。
- G3:阻塞所有信号,保护对共享全局数据结构的访问:避免可能的冲突。
- G4:用volatile声明全局变量:强迫编译器从内存中读取引用的值。
- G5:用sig_atomic_t声明标志。
异步信号安全的函数:要么是可重入的(如只访问局部变量,不引用任何共享数据),要么不能被信号处理程序中断。
#include<iostream>
using namespace std;
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<signal.h>
#include<mutex>
//打印错误
void unix_error(const char* msg)
{
print(msg);
exit(0);
}
/*进程安全的打印long函数
void sio_putl(long msg)
{
char* tmp = new char[256];
sprintf(tmp,"%d",msg);
write(STDOUT_FILENO,tmp,strlen(tmp));
delete[]tmp;
}
//进程安全的打印字符串函数
void Write(const char*msg)
{
write(STDOUT_FILENO,msg,strlen(msg));
}*/
//上面两个函数是模仿CSAPP原书中的进程安全输出函数写的,下面这个是我自己写的
//比上面的好用且简洁
//打印输出的互斥锁
mutex print_mutex{};
template<class... Args>
void print(const Args&...args)
{
lock_guard<mutex> m(print_mutex);
((cout << args), ...);
}
//kill -15
void signal_SIGTERM_handler(int sig)
{
print("child ",getpid()," was killed by signal 15 \n");
fflush(stdout);
_exit(0);
}
//child process exit
void signal_SIGCHLD_handler(int sig)
{
pid_t pid = 0;
while((pid = wait(NULL)) > 0)
{
print("child pid ",pid," process exit\n");
fflush(stdout);
return;
}
unix_error("wait error\n");
}
//ctrl + c
void signal_SIGINT_handler(int sig)
{
print("Are u want to run ctrl+c ? Well...\n");
fflush(stdout);
_exit(0);
}
//出现错误
void unix_error(const char* msg)
{
print(msg);
_exit(0);
}
int main()
{
//安装handler
if(signal(SIGINT,signal_SIGINT_handler) == SIG_ERR)
{
unix_error(" install SIGINT handler error\n");
}
if(signal(SIGTERM,signal_SIGTERM_handler) == SIG_ERR)
{
unix_error(" install SIGTERM handler error\n");
}
if(signal(SIGCHLD,signal_SIGCHLD_handler) == SIG_ERR)
{
unix_error(" install SIGCHLD handler error\n");
}
//实行对信号SIGTERM的阻塞,下面代码将无视SIGTERM
sigset_t mask,prev_mask;
sigemptyset(&mask);
sigaddset(&mask,SIGTERM);
sigprocmask(SIG_BLOCK,&mask,&prev_mask);
int max = 5,pid = 0,status = 0;
//发送的信号
int sig = SIGTERM;
//存放子进程进程号的数组
int pids[max];
//fork max个子进程
print("parent : pid = ",getpid()," pgrp = ",getpgrp(),"\n");
for(int i = 0;i < max;++i)
{
if((pids[i] = fork()) == 0)
{
print("child : pid = ",getpid()," pgrp = ",getpgrp(),"\n");
while(1);
}
}
sleep(1);
//向所有子进程发送信号sig
for(int i = 0;i < max;++i)
{
kill(pids[i],sig);
}
for(int i = 0;i < max;++i)
{
//父进程阻塞等待一个子进程结束
pid_t wpid = wait(&status);
//子进程正常结束
if(WIFEXITED(status) != 0)
{
print("child ",wpid," terminated normally with exit status ",WEXITSTATUS(status),"\n");
}
//非正常结束
else if(WIFEXITED(status) == 0)
{
//接收到信号结束
if(WIFSIGNALED(status))
{
print("child ",wpid," terminated abnormally with signal ",WTERMSIG(status),"\n");
}
}
}
//解除对信号SIGTERM的阻塞
sigprocmask(SIG_SETMASK,&prev_mask,nullptr);
return 0;
}
正确的信号处理、
待处理的信号是不排队的。对于每种信号类型,pending位向量只有1位与之对应,因此每种信号类型最多只能有1个未处理信号。
注意,如果存在一个未处理的信号就表明至少有一个信号到达了,所以不能用信号来对其它进程中发生的事件进行计数。
可移植的信号处理、
不同的系统有不同的信号处理语义:
signal
函数的语义各有不同。- 系统调用可以被中断。
要解决这些问题,定义了sigaction
函数,它允许用户在设置信号处理时,明确指定他们想要的信号处理语义。
int sigaction(int signum, struct sigaction *act, struct sigaction *oldact)
显式地等待信号、
有时主程序需要显式地等待某个信号处理程序运行。
使用sigsuspend
函数,暂时用mask
替换当前的阻塞集合,然后挂起该进程,直到收到一个信号。
sigsuspend
函数等价于下述代码地原子的(不可中断的)版本:
sigprocmask(SIG_SETMASK, &mask, &prev);
pause();
sigprocmask(SIG_SETMASK, &prev, NULL);
Comments NOTHING