逻辑地址与物理地址转换 和 字节对齐
2012-05-15 22:20
316 查看
例如,一个逻辑地址已经被转换成了线性地址,0x08147258,换成二制进,也就是:
0000100000 0101000111 001001011000
内核对这个地址进行划分,
PGD = 0000100000
PUD = 0
PMD = 0
PT = 0101000111
offset = 001001011000
现在来理解Linux针对硬件的花招,因为硬件根本看不到所谓PUD,PMD,所以,本质上要求PGD索引,直接就对应了PT的地址。而不是再 到PUD和PMD中去查数组(虽然它们两个在线性地址中,长度为0,2^0 =1,也就是说,它们都是有一个数组元素的数组),那么,内核如何合理安排地址呢?
从软件的角度上来讲,因为它的项只有一个,32位,刚好可以存放与PGD中长度一样的地址指针。那么所谓先到PUD,到到PMD中做映射转换, 就变成了保持原值不变,一一转手就可以了。这样,就实现了“逻辑上指向一个PUD,再指向一个PDM,但在物理上是直接指向相应的PT的这个抽像,因为硬 件根本不知道有PUD、PMD这个东西”。然后交给硬件,硬件对这个地址进行划分,看到的是:
页目录 = 0000100000
PT = 0101000111
offset = 001001011000
嗯,先根据0000100000(32),在页目录数组中索引,找到其元素中的地址,取其高20位,找到页表的地址,页表的地址是由内核动态分配的,接着,再加一个offset,就是最终的物理地址了。
union u { double a; int b; }; union u2 { char a[13]; int b; }; union u3 { char a[13]; char b; }; cout<<sizeof(u)<<endl; // 8 cout<<sizeof(u2)<<endl; // 16 cout<<sizeof(u3)<<endl; // 13 |
结论:复合数据类型,如union,struct,class的对齐方式为成员中对齐方式最大的成员的对齐方式。
顺便提一下CPU对界问题,32的C++采用8位对界来提高运行速度,所以编译器会尽量把数据放在它的对界上以提高内存命中率。对界是可以更改的,使用#pragma pack(x)宏可以改变编译器的对界方式,默认是8。C++固有类型的对界取编译器对界方式与自身大小中较小的一个。例如,指定编译器按2对界,int类型的大小是4,则int的对界为2和4中较小的2。在默认的对界方式下,因为几乎所有的数据类型都不大于默认的对界方式8(除了long double),所以所有的固有类型的对界方式可以认为就是类型自身的大小。更改一下上面的程序:
#pragma pack(2) union u2 { char a[13]; int b; }; union u3 { char a[13]; char b; }; #pragma pack(8) cout<<sizeof(u2)<<endl; // 14 cout<<sizeof(u3)<<endl; // 13 |
结论:C++固有类型的对界取编译器对界方式与自身大小中较小的一个。
9、struct的sizeof问题
因为对齐问题使结构体的sizeof变得比较复杂,看下面的例子:(默认对齐方式下)
struct s1 { char a; double b; int c; char d; }; struct s2 { char a; char b; int c; double d; }; cout<<sizeof(s1)<<endl; // 24 cout<<sizeof(s2)<<endl; // 16 |
对于s1,首先把a放到8的对界,假定是0,此时下一个空闲的地址是1,但是下一个元素d是double类型,要放到8的对界上,离1最接近的地址是8了,所以d被放在了8,此时下一个空闲地址变成了16,下一个元素c的对界是4,16可以满足,所以c放在了16,此时下一个空闲地址变成了20,下一个元素d需要对界1,也正好落在对界上,所以d放在了20,结构体在地址21处结束。由于s1的大小需要是8的倍数,所以21-23的空间被保留,s1的大小变成了24。
对于s2,首先把a放到8的对界,假定是0,此时下一个空闲地址是1,下一个元素的对界也是1,所以b摆放在1,下一个空闲地址变成了2;下一个元素c的对界是4,所以取离2最近的地址4摆放c,下一个空闲地址变成了8,下一个元素d的对界是8,所以d摆放在8,所有元素摆放完毕,结构体在15处结束,占用总空间为16,正好是8的倍数。
这里有个陷阱,对于结构体中的结构体成员,不要认为它的对齐方式就是他的大小,看下面的例子:
struct s1 { char a[8]; }; struct s2 { double d; }; struct s3 { s1 s; char a; }; struct s4 { s2 s; char a; }; cout<<sizeof(s1)<<endl; // 8 cout<<sizeof(s2)<<endl; // 8 cout<<sizeof(s3)<<endl; // 9 cout<<sizeof(s4)<<endl; // 16; |
所以,在自己定义结构体的时候,如果空间紧张的话,最好考虑对齐因素来排列结构体里的元素。
结论:struct 里面的元素是顺序存储的,每个元素占用的字节数根据对齐字节数N(struct 里占用字节最多的元素与CPU对齐字节数中较小的一个)进行调整.如果从左至右M个元素加起来的字节数大于N,则按从右至左舍去K个元素直至M-K个元素加起来的字节数小于等于N,如果等于N则不用字节填充,小于N则把M-K-1的元素填充直至=N.
#pragma pack也可以这样:
他完全的写法是:
#pragma pack( [ show ] | [ push | pop ] [, identifier ] , n )
【】里面的是可有可无的但是n一定要指定,如果没有编译器默认n=8
使用方式:
#pragma pack(push) //保存对齐状态
#pragma pack(1)//设定为1字节对齐
#pragma pack(show); //编译的时候会在警告信息里面体现具体的字长的设置信息
struct test
{
char m1;
double m4;
int m3;
};
#pragma pack(pop)
test将不再以double的大小为准则,开始用1个字节的对齐方式。
另外一种设置对齐方式的是:
__declspec( align( # ) )
里面#必须的是2的n次方,从1,2,4,8,16到8192,这样的话,如果定义一个结构体,他将以结构体的大小为准,补足到最临近的#的的整数倍。
比如:
__declspec(align(8)) struct Str1{
int a, b, c, d, e;
char f;
char *g;
}; //本身是25个字节补足到最近的8的倍数也就是32
__declspec(align(4)) struct Str1{
int a, b, c, d, e;
char f;
char *g;
}; //本身是25个字节补足到最近的4的倍数也就是28
另外有一点要注意的就是GCC编译的程序,最大就是以4字节对齐,,不管你怎么弄,或者说怎么设置,它是不可能以8比如double来对齐的,比如:
struct test
{
char m1;
double m4;
};
gcc编译的话,那么他的大小是多少呢?如果不设置对齐的话是12,4个字节对齐的结果.
但是如果设置了:
#pragma pack(8)
是什么效果?
依旧是4个字节对齐,而且 #pragma pack()是属于c语言的,跟系统无关,不管是windows还是linux!
相关文章推荐
- 图示逻辑地址转换到物理地址
- 图示逻辑地址转换到物理地址
- 【Linux内存管理】虚拟地址、逻辑地址、线性地址、物理地址之间的转换
- 页表实现从逻辑地址到物理地址的转换
- 逻辑地址与物理地址的转换
- 内存管理笔记(分页,分段,逻辑地址,物理地址与地址转换方式)
- 逻辑地址转换为物理地址
- 逻辑地址到物理地址的转换
- 内存管理笔记(分页,分段,逻辑地址,物理地址与地址转换方式)
- 主存储器物理地址,逻辑地址,转换
- linux内存管理---物理地址、线性地址、虚拟地址、逻辑地址之间的转换
- 基本分页存储管理方式中关于逻辑地址和物理地址的转换
- 逻辑地址到线性地址的转换,线性地址到物理地址
- 逻辑地址到物理地址的转换
- Linux中的逻辑地址,线性地址和物理地址转换关系
- Linux逻辑地址到物理地址转换
- 内存管理笔记(分页,分段,逻辑地址,物理地址与地址转换方式)
- 操作系统_逻辑地址转换为物理地址