Unix信号之sigaction函数

sigaction函数是除signal函数之外的另一个可以设置信号处理的函数。sigaction用法比signal函数复杂,但是可以对信号处理进行更加精准更灵活的控制。sigaction是POSIX的信号接口,而signal函数是标准C的信号接口,所以sigaction函数的可移植性更强。

sigaction函数说明

函数功能

检查或修改与指定信号相关联的处理动作,可以同时检查和修改。

函数原型

1
2
3
4
5
6
7
8
struct sigaction{
void (*sa_handler)(int); //信号处理函数地址
sigset_t sa_mask; //信号掩码集,当调用信号处理函数时,程序将阻塞sa_mask中的信号
int sa_flag; //位掩码,指定用于控制信号处理过程中的各种选项。
void (*sa_sigaction)(int,siginfo_t*,void*); //暂不用
};
int sigaction(int signo,const struct sigaction*restrict act,struct sigaction*restrict oact);

函数参数

  • signo : 指定操作的信号

  • act : 新修改的sigaction

  • oldact : 保存该函数原有的sigaction。

上述struct sigaction中sa_flag取值说明(常用的有以下两个):

  • SA_NODEFER:当信号处理函数正在进行时,不堵塞对于信号处理函数自身信号功能。
  • SA_RESETHAND:当用户注册的信号处理函数被执行过一次后,该信号的处理函数被设为系统默认的处理函数

函数返回值

  • 0 : 返回0表示设置成功
  • -1 : 返回-1表示设置失败

函数示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
void sighandler(int sig) {
printf("this is in the sig handler\n");
for (int i = 0; i < 5; i++) {
printf("num:%d\n", i);
}
}
int main() {
struct sigaction act;
struct sigaction oldact;
act.sa_handler = sighandler;
act.sa_flags = SA_NODEFER ;//| SA_RESETHAND;
sigaction(SIGINT , &act,&oldact);
printf ("this is the main function\n");
pid_t pid = getpid();
printf("%d", pid);
kill(pid, SIGINT);
}

Comment and share

判断主机字节序大端规则或小端规则方法

大端规则与小端规则

在计算机存储中存储字节的顺序有两种分别为大端规则和小端规则。

  • 小端规则(littel endian):低序字节存储到内存较低的位置,即起始位置。
  • 大端规则(big endian):低序字节存储到内存较高的位置,即高序字节存储到起始位置。

有一个32位数字为:0x01020304

小端规则的机器上,其存储如下:

低地址 -> -> 高地址
0x04 0x03 0x02 0x01

大端规则机器上,其存储如下:

低地址 -> -> 高地址
0x01 0x02 0x03 0x04

判断当前机器字节序的方法

判断当前机器为大端规则还是小端规则,其本质是对于一个变量,判断其各字节的存储顺序

方法一:使用union判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;
int main() {
union {
short a;
char c[2];
} u;
u.a = 0x0102;
if (u.c[0] == 2 && u.c[1] == 1) {
std::cout << "little" << std::endl;
} else if (u.c[0] == 1 && u.c[1] == 2) {
std::cout << "big" << std::endl;
} else {
std::cout << "unkown" << std::endl;
}
return 0;
}

方法二:直接将字节取出,判断顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;
int main() {
short s = 0x0102;
char* a = (char*)(&s);
std::cout << (*a) << std::endl;
char b = 0x02, c = 0x01;
if (((*a) | b) == b) {
std::cout << "little" << std::endl;
} else if (((*a) | c) == b) {
std::cout << "big" << std::endl;
} else {
std::cout << "unknow" << std::endl;
}
return 0;
}

Comment and share

pack与aligned的区别

GCC支持用__attribute__为变量、类型、函数以及标签指定特殊属性。其中可以使用attribute的aligned属性控制变量或类型的内存对其规则,之前一篇文章已经提到pack可以改变结构体中各成员之间的内存对其规则。https://langzi989.github.io/2017/10/02/C语言内存对其相关/

其中#pragma pack()和__attribute__((aligned))区别很大。

aligned内存对其详解

使用场景:

  • 变量
  • 类型

功能说明:

  • 当aligned作用于变量时,其作用是告诉编译器为变量分配内存的时候,要分配在指定对其的内存上.作用于变量之上不会改变变量的大小。
    • 例如:int a __attribute__((aligned(16)));该变量a的内存起始地址为16的倍数。
  • 当aligned作用于类型时,其作用是告诉编译器该类型声明的所有变量都要分配在指定对齐的内存上。当该属性作用于结构体声明时可能会改变结构体的大小。
1
2
3
4
5
6
7
8
struct Test{
char a[3];
}__attribute__((aligned(8)));
int main() {
//8
std::cout << sizeof(Test);
}

如上所示,当align作用于结构体定义时会改变结构体的大小。结构体最终大小为aligned指定大小的整数倍。

aligned与pack的区别

从上面可以看出,aligned和pack的主要区别如下:

  • pack作用于结构体或类的定义,而aligned既可以作用于结构体或类的定义,也可以作用于变量的声明。
  • pack的作用是改变结构体或类中成员变量的布局规则,而aligned只是建议编译器对指定变量或指定类型的变量分配内存时的规则。
  • pack可以压缩变量所占内存的空间
  • align可以指定变量在内存的对其规则,而pack不可以。
  • 若某一个结构体的默认pack为n,pack指定的对齐规则m大于n,则该pack忽略。若aligned指定的对齐规则s大于n,则此时结构体的大小一定为s的整数倍。
  • aligned和pack指定规则时都必须为2的n次幂。

参考链接:http://blog.shengbin.me/posts/gcc-attribute-aligned-and-packed

Comment and share

C/C++语言内存对齐

内存对齐:在计算机中,内存空间按照字节划分,理论上可以从任何起始地址访问任何类型的变量。但实际上在访问特定类型的变量的时候需要从特定的地址开始,这就需要各种类型的数据按照一定的规则在空间上排列,而不是顺序的一个接一个的存放,这就是内存对齐,也叫字节对齐。

内存对齐的作用:

  • 可移植性:因为不同平台对数据的在内存中的访问规则不同,不是所有的硬件都可以访问任意地址上的数据,某些硬件平台只能在特定的地址开始访问数据。所以需要内存对齐。
  • 性能原因:一般使用内存对齐可以提高CPU访问内存的效率。如32位的intel处理器通过总线访问内存数据,每个总线周期从偶地址开始访问32位的内存数据,内存数据以字节为单位存放。如果32为的数据没有存放在4字节整除的内存地址处,那么处理器需要两个总线周期对数据进行访问,显然效率下降很多;另外合理的利用字节对齐可以有效的节省存储空间。

默认内存对齐影响因素:与平台架构(位数)和编译器的默认设置有关。

总线周期:CPU通过总线和存储器或者IO设备进行一次数据传输需要的时间,通常为四个或者多个时钟周期组成。

内存对齐规则

  1. 整体类型的对齐规则:若设置了内存对齐为m个字节,类中的最大成员的对齐字节为n,则该数据类型的对齐字节为p=min(m,n)。(一般32位机器的默认pack为4位;64位机器的默认pack为8位,程序中可以显式设置pack的大小)
  2. 类型中成员的对齐规则:类中的第一个成员放在offset为0的位置;对于其他成员,若设置了内存对齐为m个字节,假设该数据成员的对齐字节数(即当前成员所占的字节数)为k,则该数据成员的起始位置是min(m,k)的整数倍。
  3. 整体对齐规则:最后整个类型的大小为p=min(m,n)的整数倍。
  4. 当设置对齐字节数大于类中最大成员的对齐字节数的时候,这个设置实际不产生任何效果;当设置对齐字节数为1时,类的大小就是简单的把所有成员大小相加。

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <stddef.h>
using namespace std;
//前提条件:32位机器,当前编译器默认pack为4
struct T {
int a;
short b;
int c;
double d;
};
int main() {
cout << offsetof(T, a) << endl
<< offsetof(T, b) << endl
<< offsetof(T, c) << endl
<< offsetof(T, d) << endl;
return 0;
}

根据上面的分析:

  • a为第一个成员,offset为0。
  • b为short,对齐字节数为2,所以其对齐字节为min(2,4)=2,故offset为4
  • c为int,对齐字节数为4,所以其对齐字节数为min(4,4)=4,故offset为8
  • d为double,对齐字节数为8,故对齐字节数为min(4,8)=8,故offset为12
  • 总的大小为20,是,min(8,4)的倍数.

使用pragma pack修改系统默认pack

修改系统的默认pack可以使用系统函数pragma的pack参数,但是修改之后的pack一定是2的n次幂

1
2
3
#pragma pack(16) //修改pack修改为16
#pragma pack() //恢复系统的默认pack
#pragma pack(show) //返回系统当前的pack,由警告信息显示,注意gcc不支持。只有VS支持

此外pack还有push,pop其他参数可选,但是不同的编译器对这些参数的实现有不同的含义,如果需要了解可以参考对应的资料。

Comment and share

  • page 1 of 1

魏传柳(2824759538@qq.com)

author.bio


Tencent


ShenZhen,China