POSIX信号处理
信号就是告知某个进程发生了某个事件的通知,有时也称为软件中断.信号通常是异步
发生的(指进程不知道信号发生的准确时刻)
信号可以:
- 由一个进程发给另一个进程
- 由内核发给某个进程
每个信号都有一个与之关联的处置,也称为行为。通过sigaction函数来设定一个信号的处置,并有三种选择:
- 提供一个函数(称之为信号处理函数),只要有特定信号发生就调用。这种行为称之为
捕获信号
。SIGKILL和SIGSTOP不能被捕获。信号处理函数原型
void handler(int signo); // signo: 信号对应的信号值
- 把某个信号的处置设定为
SIGIGN
来忽略它。同样,SIGKILL和SIGSTOP不能被忽略。 - 把某个信号的处置设定为
SIGDFL
来启用它的默认处置。
符合POSIX的系统的信号处理为以下几点:
- 安装信号处理函数后它会一直安装。
- 信号处理函数运行期间,信号是阻塞的。(阻塞是指???)。参考:(225条消息) Linux信号处理机制(二)——阻塞信号_victory_523的博客-CSDN博客_信号的阻塞机制
- Unix信号是默认不排队的,一个信号在被阻塞期间产生了一次或多次,那该信号被解阻塞后通常指递交一次
- 利用sigprocmask函数选择性地阻塞或解阻塞一组信号是可能的。????
SIGCHLD信号
SIGCHLD信号的介绍
SIGCHLD信号是由内核在任何一个进程终止时发给它的父进程的一个信号。
子进程终止后,会进入僵死状态(zombie),目的是维护子进程的信息(包括子进程的pid,终止状态,资源利用信息等等),直至父进程获取这些信息后,才清理子进程占有的资源。如果一直不读取,子进程就一直处于僵死状态,占有内核空间。
(当一个进程终止时,而且其子进程中有僵死的,那么所有僵死的子进程的父进程ID将会设置为1,init进程,清理这些僵死进程的工作交由init进程完成,即init进程wait这些僵死进程)
SIGCHLD信号的处理
通过signal函数: 将SIGCHLD信号的处置设为sig_chld(调用wait函数获取已终止的子进程的信息)
signal(SIGCHLD, sig_chld);
这样,每当有子进程终止,该信号处理函数都会被调用,从而获取子进程的信息,避免僵死进程
处理被中断的系统调用
-
慢调用:指那些可能永远阻塞的系统调用。(比如:read、write、accept等等)
-
当阻塞于某个慢调用时,一个进程捕获某个信号并且相应处理函数返回时,该系统调用可能返回
EINTR
错误。 -
关于于EINTR错误:ENITR错误时慢调用由于软中断(信号)所产生的错误。发生EINTR错误一般情况下是应该要重启该调用。(但是,这里举一个例外,connect函数,前面第4章提到,当connect函数失败时,不能对原来的sockfd重新调用connect,需要重新调用socket,对新的sockfd调用connect。)
如果进程在一个慢系统调用(slow system call)中阻塞时,当捕获到某个信号且相应信号处理函数返回时,这个系统调用不再阻塞而是被中断,就会调用返回错误(一般为-1)并且设置errno为EINTR(相应的错误描述为“Interrupted system call”)。
wait和waitpid
wait和waitpid均返回两个值:已终止的进程ID和statloc指向的状态信息。statloc通过以下几个宏来读取:
关于waitpid:当第一个参数pid设置为-1,其将会等待第一个终止的子进程。当第三个参数options为WNOHANG
,不会阻塞调用,而是立即返回。
下面是改进SIGCHLD信号处理函数:
如上图有5个客户接服务器,从而产生5个服务器子进程。当五个客户同时向服务器(子进程)发送FIN分节,导致服务器子进程终止,五个服务器子进程同时向服务器父
进程发送SIGCHLD信号,而Unix信号是不排队的,从而导致之前设置的SIGCHLD信号处理函数调用次数小于5,然后产生僵死进程。
下面对信号处理函数进行改进:循环调用waitpid(指定WNOHANG)直至返回-1,这样可以保证当这个信号处理函数被调用时,会清理掉当前所有的僵死进程。
SIGPIPE信号
SIGPIPE产生的原因:如果一个 socket 在接收到了 RST packet 之后,程序仍然向这个 socket 写入数据,那么就会产生SIGPIPE信号。
对于SIGPIPE信号的处理,一般就是忽略它。
在muduo中就是这样,假设:服务器和客户端真正进行友好交流,而客户端不辞而别,服务器后面在对这个套接字write时,会让服务端收到一个RST分节。如果
不理会,继续write,就会产生SIGPIPE信号。而SIGPIPE信号的默认行为是终止进程。这样就有点小题大作,只需忽略即可。
UNP书上的处理就不放这了。
数据格式
考虑一个场景,客户端往服务端发送一行文本(两个整数,之间用空格相隔,最后是换行符),服务端读取这行文本,计算两数之和,并发回客户端。
有两种传输方案可以选择:
- 传递字符串
- 传递二进制结构
struct arg
{
int a;
int b;
};
struct res
{
long long result;
};
第一种方案可以,第二种由于客户端和服务端运行在不同的机器,而且对齐方式或者数据类型的位数不一样等等问题,经常失效.
两种解决方案
- 将所有数据转换为字符串来传输
- 显示定义所支持数据类型的二进制格式(位数,大小端).RPC就是这样的.