今天在阅读项目代码的时候看到这样一段代码:
//信号处理函数
void Utils::sig_handler(int sig)
{
//为保证函数的可重入性,保留原来的errno
int save_errno = errno;
int msg = sig;
send(u_pipefd[1], (char *)&msg, 1, 0);
errno = save_errno;
}
这是一个普通的信号处理函数,用于指定信号sig触发时的行为。即向管道u_pipefd[1]发送该信号本身。
当管道的另一端u_pipefd[0]接收到信号时,处理的逻辑如下
bool WebServer::dealwithsignal(bool &timeout, bool &stop_server)
{
int ret = 0;
int sig;
char signals[1024];
ret = recv(m_pipefd[0], signals, sizeof(signals), 0);
if (ret == -1){
return false;
}
else if (ret == 0){
return false;
}
else{
for (int i = 0; i < ret; ++i){
switch (signals[i]){
case SIGALRM:{
timeout = true;
break;
}
case SIGTERM:{
stop_server = true;
break;
}
}
}
}
return true;
}
先忽略代码其他不重要的逻辑,重点看ret = recv(m_pipefd[0], signals, sizeof(signals), 0);。该函数首先将信号从管道中读取出来,然根据取出的值进行相应的逻辑处理
switch (signals[i]) {
case SIGALRM: {
timeout = true; break;
}
case SIGTERM: {
stop_server = true; break;
}
}
具体来说,项目中注册了SIGALRM和SIGTERM,他们对应的值和含义如下:
回到标题,看到这段代码时,我产生了第一个疑问:
为什么只发送一个字节?
- 因为信号是通过一个整数编号来标识的。这个编号通常很小,一个字节(8位)足够表示,所以能发一个字节就别发4个字节了。
为什么要将&msg强制转换为char*,不转换行不行?
- 先说答案:可以不转,但是和原本的代码一样存在风险(大小端问题)
- 强制类型转换改变了什么?
- 从数据的表示来说,什么也没有改变
- 它仅仅改变了数据解释的方式
- 从下面的实例代码一目了然
# include <stdio.h>
void show_bytes(char * start, size_t len) {
size_t i;
for (i = 0; i < len; i++)
printf(" %.2x", start[i]);
printf("\n");
}
int main() {
int sig = 15;
show_bytes((char *)&sig, sizeof(int));
return 0;
}
//输出
0f 00 00 00
由于我的机器为小端法(数据的低字节存储在存储器地址的低位,数据的高字节存储在存储器地址的高位),所以将源程序改为下面的形式也是一模一样的。
//信号处理函数
void Utils::sig_handler(int sig)
{
//为保证函数的可重入性,保留原来的errno
int save_errno = errno;
send(u_pipefd[1], &sig, 1, 0);
errno = save_errno;
}
不管转不转换,发送到管道中的都是0x0f这8个比特的数据
问题出在哪里?
- 如前所述,如果机器采用小端法表示数据,就没有任何问题,但是如果机器采用大端法表示,就会出现严重的问题。
- 倘若为大端法表示(数据的低字节存储在存储器地址的高位,数据的高字节存储在存储器地址的地位),
SIGTERM(15)在机器中的存储顺序为:00 00 00 0f - 不管转不转换,发送到管道中的都是
0x00这8个比特的数据
如何解决?
将代码改为下面,即可解决
//信号处理函数
void Utils::sig_handler(int sig)
{
//为保证函数的可重入性,保留原来的errno
int save_errno = errno;
char msg = static_cast<char>(sig);
send(u_pipefd[1], &msg, 1, 0);
errno = save_errno;
}
最后一点
强制类型转换不会改变数据在机器上的表示,只会改变对数据的解释。
例如SIGTERM(15)在机器上的表示为0x0f,如果用int 来解释就是15,如果用char来解释就是ASCII码表中的一个字符
所以信号处理逻辑部分和下面是等价的:
switch (signals[i]) {
case 0x0e: {
timeout = true; break;
}
case 0x0f: {
stop_server = true; break;
}
}
当然写成宏定义的形式更直观,这里只是为了便于理解。