逻辑地址与物理地址转换 和 字节对齐

来源:互联网 发布:网络舆论发展的因素 编辑:程序博客网 时间:2024/05/29 11:00

例如,一个逻辑地址已经被转换成了线性地址,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的大小取决于它所有的成员中,占用空间最大的一个成员的大小。所以对于u来说,大小就是最大的double类型成员a了,所以sizeof(u)=sizeof(double)=8。但是对于u2和u3,最大的空间都是char[13]类型的数组,为什么u3的大小是13,而u2是16呢?关键在于u2中的成员int b。由于int类型成员的存在,使u2的对齐方式变成4,也就是说,u2的大小必须在4的对界上,所以占用的空间变成了16(最接近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


  由于手动更改对界方式为2,所以int的对界也变成了2,u2的对界取成员中最大的对界,也是2了,所以此时sizeof(u2)=14。 

  结论: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


  同样是两个char类型(1),一个int类型(4),一个double类型(8),但是因为对界问题,导致他们的大小不同。计算结构体大小可以采用元素摆放法,我举例子说明一下:首先,CPU判断结构体的对界,根据上一节的结论,s1和s2的对界都取最大的元素类型,也就是double类型的对界8。然后开始摆放每个元素。

  对于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;


  s1和s2大小虽然都是8,但是s1的对齐方式是1,s2是8(double),所以在s3和s4中才有这样的差异。 

  所以,在自己定义结构体的时候,如果空间紧张的话,最好考虑对齐因素来排列结构体里的元素。

 

结论: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

使用方式:

  1. #pragma pack(push) //保存对齐状态 
  2. #pragma pack(1)//设定为1字节对齐 
  3. #pragma pack(show); //编译的时候会在警告信息里面体现具体的字长的设置信息 
  4. struct test 
  5.     char m1; 
  6.     double m4; 
  7.     int   m3; 
  8. }; 
  9. #pragma pack(pop)

test将不再以double的大小为准则,开始用1个字节的对齐方式。

 

另外一种设置对齐方式的是:

__declspec( align( ) )

里面#必须的是2的n次方,从1,2,4,8,16到8192,这样的话,如果定义一个结构体,他将以结构体的大小为准,补足到最临近的#的的整数倍。

比如:

  1. __declspec(align(8)) struct Str1{
  2.     int a, b, c, d, e;
  3.     char f;
  4.     char *g;
  5. }; //本身是25个字节补足到最近的8的倍数也就是32
  6. __declspec(align(4)) struct Str1{
  7.     int a, b, c, d, e;
  8.     char f;
  9.     char *g;
  10. }; //本身是25个字节补足到最近的4的倍数也就是28

另外有一点要注意的就是GCC编译的程序,最大就是以4字节对齐,,不管你怎么弄,或者说怎么设置,它是不可能以8比如double来对齐的,比如:
  1. struct test 
  2.     
  3.     char m1; 
  4.     double m4; 
  5. }; 

gcc编译的话,那么他的大小是多少呢?如果不设置对齐的话是12,4个字节对齐的结果.
但是如果设置了:
#pragma pack(8)
是什么效果?
依旧是4个字节对齐,而且 #pragma pack()是属于c语言的,跟系统无关,不管是windows还是linux!


原创粉丝点击