显示标签为“AVR”的博文。显示所有博文
显示标签为“AVR”的博文。显示所有博文

2015年3月3日星期二

C语言字节对齐

转自:http://blog.csdn.net/21aspnet/article/details/6729724

文章最后本人做了一幅图,一看就明白了,这个问题网上讲的不少,但是都没有把问题说透。
  一、概念
  
   对齐跟数据在内存中的位置有关。如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。比如在32位cpu下,假设一个整型变量的地址为0x00000004,那它就是自然对齐的。
  
  二、为什么要字节对齐
  
   需要字节对齐的根本原因在于CPU访问数据的效率问题。假设上面整型变量的地址不是自然对齐,比如为0x00000002,则CPU如果取它的值的话需要访问两次内存,第一次取从0x00000002-0x00000003的一个short,第二次取从0x00000004-0x00000005的一个short然后组合得到所要的数据,如果变量在0x00000003地址上的话则要访问三次内存,第一次为char,第二次为short,第三次为char,然后组合得到整型数据。而如果变量在自然对齐位置上,则只要一次就可以取出数据。一些系统对对齐要求非常严格,比如sparc系统,如果取未对齐的数据会发生错误,举个例:
  
  char ch[8];
  char *p = &ch[1];
  int i = *(int *)p;
  
  
  运行时会报segment error,而在x86上就不会出现错误,只是效率下降。
  
  三、正确处理字节对齐
  
   对于标准数据类型,它的地址只要是它的长度的整数倍就行了,而非标准数据类型按下面的原则对齐:
  
  数组 :按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。
  联合 :按其包含的长度最大的数据类型对齐。
  结构体: 结构体中每个数据类型都要对齐。
  比如有如下一个结构体:
  
  struct stu{
   char sex;
   int length;
   char name[10];
  };
  struct stu my_stu;
  
  
  由于在x86下,GCC默认按4字节对齐,它会在sex后面跟name后面分别填充三个和两个字节使length和整个结构体对齐。于是我们sizeof(my_stu)会得到长度为20,而不是15.
  
  四、__attribute__选项
  
  我们可以按照自己设定的对齐大小来编译程序,GNU使用__attribute__选项来设置,比如我们想让刚才的结构按一字节对齐,我们可以这样定义结构体
  
  struct stu{
   char sex;
   int length;
   char name[10];
  }__attribute__ ((aligned (1)));
  
  struct stu my_stu;
  
  
  则sizeof(my_stu)可以得到大小为15。
  
  上面的定义等同于
  
  struct stu{
   char sex;
   int length;
   char name[10];
  }__attribute__ ((packed));
  struct stu my_stu;
  
  
  __attribute__((packed))得变量或者结构体成员使用最小的对齐方式,即对变量是一字节对齐,对域(field)是位对齐.
  
  五、什么时候需要设置对齐
  
   在设计不同CPU下的通信协议时,或者编写硬件驱动程序时寄存器的结构这两个地方都需要按一字节对齐。即使看起来本来就自然对齐的也要使其对齐,以免不同的编译器生成的代码不一样.

一、快速理解
1. 什么是字节对齐?
在C语言中,结构是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构、联合等)的数据单元。在结构中,编译器为结构的每个成员按其自然边界(alignment)分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。
为了使CPU能够对变量进行快速的访问,变量的起始地址应该具有某些特性,即所谓的”对齐”. 比如4字节的int型,其起始地址应该位于4字节的边界上,即起始地址能够被4整除.
2. 字节对齐有什么作用?
字节对齐的作用不仅是便于cpu快速访问,同时合理的利用字节对齐可以有效地节省存储空间。
对于32位机来说,4字节对齐能够使cpu访问速度提高,比如说一个long类型的变量,如果跨越了4字节边界存储,那么cpu要读取两次,这样效率就低了。但是在32位机中使用1字节或者2字节对齐,反而会使变量访问速度降低。所以这要考虑处理器类型,另外还得考虑编译器的类型。在vc中默认是4字节对齐的,GNU gcc 也是默认4字节对齐。
3. 更改C编译器的缺省字节对齐方式
在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。一般地,可以通过下面的方法来改变缺省的对界条件:
· 使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。
· 使用伪指令#pragma pack (),取消自定义字节对齐方式。
另外,还有如下的一种方式:
· __attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
· __attribute__ ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。
4. 举例说明
例1
struct test
{
char x1;
short x2;
float x3;
char x4;
};
由于编译器默认情况下会对这个struct作自然边界(有人说“自然对界”我觉得边界更顺口)对齐,结构的第一个成员x1,其偏移地址为0,占据了第1个字节。第二个成员x2为short类型,其起始地址必须2字节对界,因此,编译器在x2和x1之间填充了一个空字节。结构的第三个成员x3和第四个成员x4恰好落在其自然边界地址上,在它们前面不需要额外的填充字节。在test结构中,成员x3要求4字节对界,是该结构所有成员中要求的最大边界单元,因而test结构的自然对界条件为4字节,编译器在成员x4后面填充了3个空字节。整个结构所占据空间为12字节。
例2
#pragma pack(1) //让编译器对这个结构作1字节对齐
struct test
{
char x1;
short x2;
float x3;
char x4;
};
#pragma pack() //取消1字节对齐,恢复为默认4字节对齐
这时候sizeof(struct test)的值为8。
例3
#define GNUC_PACKED __attribute__((packed))
struct PACKED test
{
char x1;
short x2;
float x3;
char x4;
}GNUC_PACKED;
这时候sizeof(struct test)的值仍为8。
二、深入理解
什么是字节对齐,为什么要对齐?
TragicJun 发表于 2006-9-18 9:41:00 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
      对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。显然在读取效率上下降很多。
二.字节对齐对程序的影响:
        先让我们看几个例子吧(32bit,x86环境,gcc编译器):
设结构体如下定义:
struct A
{
        int a;
        char b;
        short c;
};
struct B
{
        char b;
        int a;
        short c;
};
现在已知32位机器上各种数据类型的长度如下:
char:1(有符号无符号同)   
short:2(有符号无符号同)   
int:4(有符号无符号同)   
long:4(有符号无符号同)   
float:4        double:8
那么上面两个结构大小如何呢?
结果是:
sizeof(strcut A)值为8
sizeof(struct B)的值却是12
结构体A中包含了4字节长度的int一个,1字节长度的char一个和2字节长度的short型数据一个,B也一样;按理说A,B大小应该都是7字节。
之所以出现上面的结果是因为编译器要对数据成员在空间上进行对齐。上面是按照编译器的默认设置进行对齐的结果,那么我们是不是可以改变编译器的这种默认对齐设置呢,当然可以.例如:
#pragma pack (2) /*指定按2字节对齐*/
struct C
{
        char b;
        int a;
        short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
sizeof(struct C)值是8。
修改对齐值为1:
#pragma pack (1) /*指定按1字节对齐*/
struct D
{
        char b;
        int a;
        short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
sizeof(struct D)值为7。
后面我们再讲解#pragma pack()的作用.
三.编译器是按照什么样的原则进行对齐的?
        先让我们看四个重要的基本概念:

1.数据类型自身的对齐值:
      对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。
2.结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
3.指定对齐值:#pragma pack (value)时的指定对齐值value。
4.数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。
有了这些值,我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。有效对齐值N是最终用来决定数据存放地址方式的值,最重要。有效对齐N,就是表示“对齐在N上”,也就是说该数据的"存放起始地址%N=0".而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是数据结构的起始地址。结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整数倍,结合下面例子理解)。这样就不能理解上面的几个例子的值了。
例子分析:
分析例子B;
struct B
{
        char b;
        int a;
        short c;
};
假设B从地址空间0x0000开始排放。该例子中没有定义指定对齐值,在笔者环境下,该值默认为4。第一个成员变量b的自身对齐值是1,比指定或者默认指定对齐值4小,所以其有效对齐值为1,所以其存放地址0x0000符合0x0000%1=0.第二个成员变量a,其自身对齐值为4,所以有效对齐值也为4,所以只能存放在起始地址为0x0004到0x0007这四个连续的字节空间中,复核0x0004%4=0,且紧靠第一个变量。第三个变量c,自身对齐值为2,所以有效对齐值也是2,可以存放在0x0008到0x0009这两个字节空间中,符合0x0008%2=0。所以从0x0000到0x0009存放的都是B内容。再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b)所以就是4,所以结构体的有效对齐值也是4。根据结构体圆整的要求,0x0009到0x0000=10字节,(10+2)%4=0。所以0x0000A到0x000B也为结构体B所占用。故B从0x0000到0x000B共有12个字节,sizeof(struct B)=12;其实如果就这一个就来说它已将满足字节对齐了,因为它的起始地址是0,因此肯定是对齐的,之所以在后面补充2个字节,是因为编译器为了实现结构数组的存取效率,试想如果我们定义了一个结构B的数组,那么第一个结构起始地址是0没有问题,但是第二个结构呢?按照数组的定义,数组中所有元素都是紧挨着的,如果我们不把结构的大小补充为4的整数倍,那么下一个结构的起始地址将是0x0000A,这显然不能满足结构的地址对齐了,因此我们要把结构补充成有效对齐大小的整数倍.其实诸如:对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,这些已有类型的自身对齐值也是基于数组考虑的,只是因为这些类型的长度已知了,所以他们的自身对齐值也就已知了.
同理,分析上面例子C:
#pragma pack (2) /*指定按2字节对齐*/
struct C
{
        char b;
        int a;
        short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
第一个变量b的自身对齐值为1,指定对齐值为2,所以,其有效对齐值为1,假设C从0x0000开始,那么b存放在0x0000,符合0x0000%1=0;第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x0002、0x0003、0x0004、0x0005四个连续字节中,符合0x0002%2=0。第三个变量c的自身对齐值为2,所以有效对齐值为2,顺序存放
在0x0006、0x0007中,符合0x0006%2=0。所以从0x0000到0x00007共八字节存放的是C的变量。又C的自身对齐值为4,所以C的有效对齐值为2。又8%2=0,C只占用0x0000到0x0007的八个字节。所以sizeof(struct C)=8.
四.如何修改编译器的默认对齐值?
1.在VC IDE中,可以这样修改:[Project]|[Settings],c/c++选项卡Category的Code Generation选项的Struct Member Alignment中修改,默认是8字节。
2.在编码时,可以这样动态修改:#pragma pack .注意:是pragma而不是progma.
五.针对字节对齐,我们在编程中如何考虑?
        如果在编程的时候要考虑节约空间的话,那么我们只需要假定结构的首地址是0,然后各个变量按照上面的原则进行排列即可,基本的原则就是把结构中的变量按照类型大小从小到大声明,尽量减少中间的填补空间.还有一种就是为了以空间换取时间的效率,我们显示的进行填补空间进行对齐,比如:有一种使用空间换时间做法是显式的插入reserved成员:
             struct A{
               char a;
               char reserved[3];//使用空间换时间
               int b;
}
reserved成员对我们的程序没有什么意义,它只是起到填补空间以达到字节对齐的目的,当然即使不加这个成员通常编译器也会给我们自动填补对齐,我们自己加上它只是起到显式的提醒作用.
六.字节对齐可能带来的隐患:
        代码中关于对齐的隐患,很多是隐式的。比如在强制类型转换的时候。例如:
unsigned int i = 0x12345678;
unsigned char *p=NULL;
unsigned short *p1=NULL;
p=&i;
*p=0x00;
p1=(unsigned short *)(p+1);
*p1=0x0000;
最后两句代码,从奇数边界去访问unsignedshort型变量,显然不符合对齐的规定。
在x86上,类似的操作只会影响效率,但是在MIPS或者sparc上,可能就是一个error,因为它们要求必须字节对齐.
七.如何查找与字节对齐方面的问题:
如果出现对齐或者赋值问题首先查看
1. 编译器的big little端设置
2. 看这种体系本身是否支持非对齐访问
3. 如果支持看设置了对齐与否,如果没有则看访问时需要加某些特殊的修饰来标志其特殊访问操作
举例:
  1. #include   
  2. main()  
  3. {  
  4. struct A {  
  5.     int a;  
  6.     char b;  
  7.     short c;  
  8. };  
  9.   
  10. struct B {  
  11.     char b;  
  12.     int a;  
  13.     short c;  
  14. };  
  15.   
  16. #pragma pack (2) /*指定按2字节对齐*/  
  17. struct C {  
  18.     char b;  
  19.     int a;  
  20.     short c;  
  21. };  
  22. #pragma pack () /*取消指定对齐,恢复缺省对齐*/  
  23.   
  24.   
  25.   
  26. #pragma pack (1) /*指定按1字节对齐*/  
  27. struct D {  
  28.     char b;  
  29.     int a;  
  30.     short c;  
  31. };  
  32. #pragma pack ()/*取消指定对齐,恢复缺省对齐*/  
  33.   
  34. int s1=sizeof(struct A);  
  35. int s2=sizeof(struct B);  
  36. int s3=sizeof(struct C);  
  37. int s4=sizeof(struct D);  
  38. printf("%d\n",s1);  
  39. printf("%d\n",s2);  
  40. printf("%d\n",s3);  
  41. printf("%d\n",s4);  
  42. }  

输出:
8
12
8
7

修改代码:
struct A {
   // int a;
    char b;
    short c;
};
struct B {
    char b;
   // int a;
    short c;
};
输出:
4
4
输出都是4,说明之前的int影响对齐!
看图就明白了

2014年10月3日星期五

extreme burn for Atmega1280

※还没确定是否正确!!!!

add


        ATmega1280
        131072
        4096
        0x0003971E
        256
        YES
        YES
        YES
        YES
        YES
        .\Images\Placements\ZIF_DIP_40.bmp
   


to chips.xml

2014年8月15日星期五

即時作業系統 (RTOS) 的基本概念

转自:http://cms.mcuapps.com/devscenes/ds0001/

筆者在 2012 年 2 月時收到一份 IAR 的電子刊物,裡面附帶有一篇不錯的 PDF 文章 – Basic Concepts for Real Time Operating Systems 
筆者閱讀之餘,覺得該文足夠精簡摘要地歸納出 RTOS 的要旨,因此嘗試作個翻譯,以饗懶得看英文的讀者。又筆者翻譯的方式有時會較為接近意譯,並不拘泥於原文的字句,也可能會配合文意,另外附加一些補充資料。
此外,MCUApps 將會持續整理一些各具特色的 RTOS 系統的資料,置於 RTOS 技術資料 提供給大家參考。
以下就是我們的譯文

本文將會闡釋 RTOS 的一些基本概念,但不會擴及個別的 RTOS 與其特點。為了簡化起見,只會討論 RTOS 最具代表性的一些重要特性。
在談過 RTOS 的架構,以及為何我們需要採用它之後,我 (IAR System 的 Mats Pettersson) 將會解釋典型 RTOS 中的每一個基本元件,並且展示他們是如何被整合到系統之中。
在本文中出現的少許程式碼範例,所採用的是 Express Logic 的 ThreadX 系統。

Thread 導向的設計

設計嵌入式應用幾乎總是相當具有挑戰性的。為了降低複雜度,我們通常會採用 threads 導向的設計,把一個專案切分成比較易於管理的小塊(也就是 threads),而後每一個 thread 負責該應用程式的一部分。這樣的系統有助於識別 threads 之間的重要順序。也就是說,某些 threads 具有即時性的需求,必須盡量快速並且正確地回應它們。如果你的系統採用了專業的 RTOS,一定會有劃分 threads 優先權 (prioritization) 的設計。除了優先權之外,也會提供一套乾淨而且被仔細測試過的 API,有助於簡化 threads 之間的通訊。
所以如果我們採用 RTOS 的話,就會獲得一些工具能夠:
  • 確保能夠在即時約束條件 (real-time constraints) 內執行時間關鍵 (time-critical) 部分的程式碼。或許同樣都很重要,但高優先權的 threads 所需要的即時行為,並不會受到低優先權 threads 的影響。
  • 確保易於開發和維護複雜的應用。開發和維護小的 threads,比起硬搞整套應用更為容易。此外,對於低優先權 threads 的更動也不會影響到高優先權 threads 的即時處理。
  • 能夠將整體應用程式的不同部分,分派給多個開發人員。每一個開發人員能夠擔負應用程式的一個或多個 threads,而且當他們在進行開發工作時,還會有一套乾淨的 API 能夠讓不同的 modules / threads 相互溝通。
當然也可以不必動用到 RTOS,就把應用大卸八塊成為不同的 threads。但是採用了 RTOS 的話,你不但能夠創建 threads,同時也具備一些讓它們能夠彼此溝通的工具,再加上能夠確保能夠在即時約束條件內執行完畢 threads 具時間關鍵的部分工作。由於採用了 RTOS,不同 threads 之間的介面將會變得非常乾淨,在進行開發的時候你就可以省時省力。

RTOS 是如何工作的?

RTOS 的核心被稱為 kernel,並提供有一個可以透過 kernel 去創建 threads 的 API。一個 thread 就像是一個擁有自己的堆疊、並帶有 Thread 控制區塊(TCB – Thread Control Block)的函式。除了 thread 本身私有的堆疊之外,每個 TCB 也保有一部分該 thread 的狀態訊息。
kernel 還包含有一個 scheuler,scheuler 會按照一套排程機制來執行 threads。各種 scheulers 之間主要的差異,就是如何分配執行他們所管理之各種 threads 的時間。基於優先權的 preemptive scheuler 是嵌入式 RTOS 之間最流行和普遍的 threads 調度演算法。通常情況下,相同優先權的 threads 會以 round-robin 循環的方式加以執行。
多數內核還會利用系統時脈 (system tick) 中斷,其典型的頻率為 10ms。如果在 RTOS 中缺乏系統時鐘,仍然能夠有某種基本形式的調度,但時間相關的服務則否。這種與時間有關的服務內容包括:軟體定時器、thread 睡眠 API 呼叫、thread 時間片段、以及逾時的 API 呼叫。
為了實現系統時脈中斷,可以透過嵌入式晶片的硬體計時器。大多數的 RTOS 有能力動態地擴增或重新設置計時器的中斷頻率,以便讓該系統進入睡眠,直到被下一個計時器期限或外部事件喚醒。例如,如果你有一個對耗能敏感的應用程式,您可能不希望每 10ms 就運行一次不必要的系統時脈處理程序。所以假設應用程式處於閒置狀態,想要把下一個定時器期限改為 1000ms。在這種情況下,計時器可以被重新規劃成 1000ms,應用程式則會進入低功耗模式。一旦在這種模式下,處理器將呈現休眠狀態,直到產生了外部事件、或是計時器的 1000ms 到期。在任一種情況之下,當處理器恢復執行時,RTOS 就會根據已經經過了多少時間來調整內部時間,並恢復 RTOS 和應用程式處理。如此一來,處理器只會在執行應用程式有事可做時進行運算。空閒期間處理器可以睡眠,並且節省電力。
在本文稍後,將有進一步關於調度演算法和系統時脈的討論。

思考 thread…

也許開始一個 RTOS 應用程式最好的方式,就是去思考如何將一個應用程式劃分為不同的 threads。例如,一個簡化的引擎輸入控制應用程式可以劃分為以下 threads:
  • 引擎溫度
  • 機油壓力
  • 每分鐘轉數 (RPM, rotation per minute)
  • 用戶輸入
這些模組可以被設置為 threads,也可以被劃分成子 threads。例如:
  • 引擎溫度
    • 讀取引擎溫度
    • 更新 LCD 的目前溫度
  • 機油壓力
    • 讀取目前的石油壓力
    • 展開應急發動機關機
  • 每分鐘轉速
    • 讀入 RPM
    • 更新 LCD 的目前 RPM
  • 用戶輸入
    • 油門踏板的角度
    • 獲取當前檔位
這種劃分成子 threads 的工作可以不斷繼續下去,直到可以被一個單獨的 thread 加以處理為止。

RTOS 的組件

讓我們來看看一個 RTOS 必須提供哪些功能,而這些功能又如何在不同的應用中派上用場。

Threads

 Threads 類似於函式,但每個 thread 都會有它自己的堆疊和 thread 控制塊(TCB)。然而與大多數函式不同的是,一個 thread 幾乎總是一個無限循環。也就是說,一旦它被創建,它(經常是)永遠不會退出。
1
2
3
4
5
6
void ThreadSendData( void )
{
  while (1) {
    // Send the data...
  }
}
一個 thread 總是處於幾種 states 其中之一。一個 thread 可以準備好被執行,也就是說,在 READY 狀態。或者該 thread 可能會被暫停(pending),也就是該 thread 在進入 READY 狀態之前,正在等待某事發生。這就是所謂的 WAITING state。
以下是我們對於 ThreadX 中 states 的一段簡短描述。
State說明
Executing這是當前正在運行的 thread。
Ready這個 thread 已經就緒
Suspended這個 thread 正在等待某件東西。這可能是一個事件或一個消息,也可能是等 RTOS 時鐘到達某個特定的值(延遲)。
Completed一個處於完成狀態的 thread 已經完成其運算處理、 並且自它的入口函數返回。(處於完成狀態的 thread 不會再次被執行。)
Terminated一個 thread 之所以會處於終止狀態,是因為另一個 thread 或是該 thread 本身呼叫了 tx_thread_terminate 服務。(處於終止狀態的 thread 不會再次被執行。)
  • :不同的 RTOS 可能會對這些 states 賦予不同的名稱*
Scheduler
你可以從兩種主要類型的 schedulers 中加以挑選:
  1. 事件驅動 (Event-driven) – 具優先權控制的調度演算法
    通常,不同的 threads 會有不同的響應要求。例如,在一個控制馬達、鍵盤和顯示器的應用程式中,馬達通常比鍵盤和顯示器需要更快的反應時間。這必須得靠一個事件驅動的 scheduler。
    在事件驅動的系統中,每個 thread 都會被分配到一個優先權,而優先權最高的 thread 就會被執行。執行的順序都仰賴於這個優先權。規則非常簡單:schedule 從所有就續的 threads 中挑出具備最高優先權的 thread 予以執行。
  2. 分時共享 (Time-sharing)
    最常見的分時演算法叫做 round-robin。也就是 scheduler 列出系統中所有 threads 的清單,然後一一查驗下一個 thread 是否就緒可以被執行。如果 thread 為 READY 時,該 thread 就會執行。每個 thread 又分派到一份 時間切片 (time-slice)。時間切片是每一回合中,單一 thread 被容許之最長的執行時間。
典型的基於優先權的 preemptive scheduler 會同時支援 preemptive 與 non-preemptive 的調度。在 preemptive 的情況下,較高優先權的 thread 會立即打斷(搶佔)執行中的低優先權 thread。而具備相同優先權的 threads 則會以 non-preemptive 的方式進行調度,而正在被執行的 thread 會繼續完成它的執行,再輪到另一個具備相同或較低優先權的 thread。
指派優先權 (Assigning priorities)
將正確的優先權指派給不同的 threads 是相當重要的。許多論文都在探討在基於 RTOS 的應用程式中,如何把這件工作做到完美。我們並不會深入這個題目,但是在此要談一些有用的規則:
  1. 盡量採用最少的優先權層級
    僅在搶佔是絕對必要的狀況下指派不同的優先權。這樣可以降低系統中 context switches 的數量,越少的 context switches,就表示有越多的時間是花費在執行應用程式代碼。
  2. 確認滿足了你應用程式中所有的時間關鍵約束條件
    端視你的應用程式類型而定,這可能會很難搞。有個解法是採用 RMA (Rate Monotonic Algorithm)。ThreadX RTOS 也提供了一個獨門絕活叫做 preemption-threshold。這能夠被用於降低 context switches,也能確保應用程式 threads 的執行。詳見 http://www.nxtbook.com/nxtbooks/cmp/esd0311/#/26
Internet 上有許多關於指派優先權的相關資訊。想要深入的朋友可以看下列兩則文章:
Thread 通訊 (Thread communications)
在 RTOS 應用程式中,我們也必須能夠在 threads 之間相互通訊。通訊可以採用 event、semaphore(旗號)的形式,或者是以訊息的方式傳送給另外一個 thread。
最基本的通訊是透過 event。一個中斷服務函式 (ISR) 也能夠傳送一個 event 給某個 thread。有些 RTOS 還能將單一 event 傳送給多個 threads。
Semaphores 通常被用於保護共享的資源,譬如說不只一個 thread 想要對同一塊記憶體(變數)進行讀寫時。作法是讓一個變數不會隨著另外一個作用中的 thread 而被改變。原則就是在你讀寫這塊記憶體之前,你必須先獲取一個以一個變數保護著的 semaphore。一旦你獲得這個 semaphore 之後,其他人都不能對這塊記憶體進行讀寫,直到你釋放 semaphore 為止。這樣一來你就可以確保同時只有一個 thread 會對該記憶體位置或變數進行讀寫。
訊息 (messages) 則能夠讓你將資料傳送給一個或多個 threads。這些訊息幾乎可以是任意的大小,通常以 mailbox 或 queue 的方式來實作。而 mailboxes 與 message queues 的行為會隨不同的 RTOS 而有所差異。
通盤整合 (Putting it all tegether)…
讓我們透過下面這張圖重新歸納我們至今所談過的東西。
那麼現在,讓我們看看我們能夠利用手上的組件做些什麼。試著回想一下,我們需要創建一個引擎控制的應用,而我們有一個微控制器和 LCD 顯示屏。我們還想在 LCD 顯示屏上顯示當前時間。我們將會重用早先將此應用程式分為不同的模組和 threads 的例子。(我們會忽略 “用戶輸入” 便於解說。)
針對這個練習,我們將創建下面的 threads:
  1. // Read the temperature and oil pressure of the engine
    void T_OilRead(void) and void T_TempRead(void)
  2. // Print the temperature and oil pressure on the LCD
    void T_OilLCD(void) and void T_TempLCD(void)
  3. // Controls the engine
    void T_EngineCTRL(void)
系統方塊圖如下所式:
其中我們以不同的顏色和外觀,分別表示了 threads、mailboxes、以及 semaphores 等元素。
關於這個系統,我們將需要一個 semaphore 來控制對 LCD 的寫入。因為我們有兩個不同的 threads,所以我們需要這個機制,如果其中一個 thread 被另一個打斷,輸出很有可能會被搞爛掉。
我們需要以下的信號傳導機制。
  • mailboxes M_TempLCD 與 M_OilLCD
    這些 mailboxes 包含要列印在 LCD 上的訊息。
  • mailboxes M_TempCTRL 和 M_OilCTRL
    這些 mailboxes 包含要送給引擎控制 thread 的訊息。
  • semaphore S_LCD
    這個 semaphore 將會確保在同一時刻當下,只有唯一的一個 thread 能夠列印到 LCD 上。
系統時脈 System ticks
我們現在有了一個系統,它可以更新 LCD上的馬達資訊、並顯示當前時間。然而,有一個非常重要的事情還沒搞定,就是 RTOS 的時脈功能。正如早些時候所言,kernel 需要隨時能夠掌控系統,然後才能夠執行例如根據 RTOS 的 scheduler 切換 threads 的工作。這通常是透過 MCU 內部計時器所驅動的時脈處理 API 呼叫達成。如果沒有系統時脈,整個系統就無法動彈。
連接到 MCU 的硬體
拼圖的最後一塊就是將這一切連接到硬體。在我們的例子中,我們將假設我們可以利用一套韌體函式庫。我們假設我們有以下的韌體 API 函式:
  • // Set text X,Y coordinate in characters
    void GLCD_TextSetPos(int X, int Y);
  • // Set draw window XY coordinate in pixels
    void GLCD_SetWindow(int X_Left, int Y_Up, int X_Right, int Y_Down);
  • // Function that reads the engine temperature
    char Engine_ReadTemp(void);
  • // Function that reads the engine oil pressure
    char Engine_ReadOilPressure(void);
  • // Function that controls the engine
    char Engine_Control(int CtrlValue);
現在,我們只要把這些片段兜在一起,看看我們的系統是否如同預期一般工作。最簡單的方法就是使用 RTOS 廠商的 BSP,如果有辦法取得的話。對於大多數 RTOS 而言,都會有為各種設備所設計的許多不同的 BSP。
如果是要在沒有 BSP 的情況下來建立一個應用,則需要:
  • 將編譯器/組譯器所需 include paths 加入到建構工具。
    我們需要確保編譯器和組譯器可以找到我們系統所需要的頭文件。
  • 添加共通的 RTOS 函式庫或源碼文件。
    我們需要添加 kernel。kernel 可能是源碼,也可能會是函式庫。
  • 添加 target-specific 的 RTOS 文件。
    很多 RTOS 對於不同的電路板/設備都會有一個電路板支援包(BSP, Board Support Package)。在這樣的一個情況下,你只需要確保你已經包含了這些 target-specific 的文件。在這樣的一個 BSP 環境下,你應該就會擁有已經配置好時鐘的程式碼,能夠正確配置 RTOS 的系統時脈。
  • 您的應用程式必須初始化 RTOS,並且在啟動 RTOS 之前就創建至少一個 thread。這通常是在 main() 函式中,但也可以在你應用程式的其他地方。
假設我們已經根據 RTOS 的規格添加了所有的文件,並且設置好建構選項,我們就可以開始創建我們應用程式中的 threads。但是在此之前,我們需要定義它們的 Thread Control Blocks(TCB)。
下面就是 Express Logic 之 ThreadX 的 TCB 定義範例。
1
2
3
4
5
6
// Express Logic ThreadX example
TX_THREAD TCBEngineCTRL;
TX_THREAD TCBOilLCD;
TX_THREAD TCBTempLCD;
TX_THREAD TCBOilRead;
TX_THREAD TCBTempRead;
現在,剩下的就是在我們創建的 threads 中建立我們的主要功能,以便完成我們所需要的應用程式。
在 ThreadX 中,我們會在 tx_application_define 這個 API 函式中創建我們最初的 threads。因此,在 main中我們唯一必須做的事情就是去呼叫 API 函式tx_kernel_enter。隨後 ThreadX 就會呼叫 tx_application_define(),我們在其中創建了我們的 threads、semaphores、message queues 之類一開始就會需要用到的東西。
大多數的 RTOS 都允許你在運行應用程式時才動態地創建資源,例如 threads 之類的。但是,我們至少需要有一個 thread 作為 schedular,用以接管我們的應用程式。
當做到這一步時,我們就可以啟動我們的 RTOS,並且讓 schedular 接手,以確保在正確的時間執行正確的 thread。

總結

這篇文章解釋了 kernel、threads、訊息(如 events 和 mailboxes)、scheduler、以及 RTOS 應用程式中主要元件的概念。一個真正的應用程式還會需要更多的細節,例如,如何創建 mailboxes 和 events。
如果你剛接觸 RTOS,上手最好的辦法就是從各家 RTOS 供應商的許多範例中,挑一個架起來並且用用看。在上述文章中,我已經用上了 Express Logic 的程式碼片段作為示範。Express Logic 提供了許多的應用範例,並有許多不同電路板和設備的 BSPs (Board Support Packages)。

RTOS系统简介

目录·RTOS系统的定义
·什么是RTOS
·RTOS系统的特点
·RTOS系统的分类
·RTOS系统的调度
·RTOS产生并得到迅速发展的原因

·RTOS系统的定义



  实时系统(Real-time operating system,RTOS)的正确性不仅依耐系统计算的逻辑结果,还依赖于产生这个结果的时间。实时系统能够在指定或者确定的时间内完成系统功能和外部或内部、同步或异步时间做出响应的系统。因此实时系统应该在事先先定义的时间范围内识别和处理离散事件的能力;系统能够处理和储存控制系统所需要的大量数据。

·什么是RTOS


  1.RTOS是一个内核
  典型的单片机程序在程序指针复位后,首先进行堆栈、中断、中断向量、定时器、串行口等接口设置、初始化数据存储区和显示内容,然后就来到了一个监测、等待或空循环,在这个循环中,CPU可以监视外设、响应中断或用户输入。
  这段主程序可以看作是一个内核,内核负责系统的初始化和开放、调度其它任务,相当于C语言中的主函数。
  RTOS就是这样的一个标准内核,包括了各种片上外设初始化和数据结构的格式化,不必、也不推荐用户再对硬件设备和资源进行直接操作,所有的硬件设置和资源访问都要通过RTOS核心。硬件这样屏蔽起来以后,用户不必清楚硬件系统的每一个细节就可以进行开发,这样就减少了开发前的学习量。
  一般来说,对硬件的直接访问越少,系统的可靠性越高。RTOS是一个经过测试的内核,与一般用户自行编写的主程序内核相比,更规范,效率和可靠性更高。对于一个精通单片机硬件系统和编程的“老手”而言,通过RTOS对系统进行管理可能不如直接访问更直观、自由度大,但是通过RTOS管理能够排除人为疏忽因素,提高软件可靠性。
  另外,高效率地进行多任务支持是RTOS设计从始至终的一条主线,采用RTOS管理系统可以统一协调各个任务,优化CPU时间和系统资源的分配,使之不空闲、不拥塞。针对某种具体应用,精细推敲的应用程序不采用RTOS可能比采用RTOS能达到更高的效率;但是对于大多数一般用户和新手而言,采用RTOS是可以提高资源利用率的,尤其是在片上资源不断增长、产品可靠性和进入市场时间更重要的今天。
  2.RTOS是一个平台
  RTOS建立在单片机硬件系统之上,用户的一切开发工作都进行于其上,因此它可以称作是一个平台。采用RTOS的用户不必花大量时间学习硬件,和直接开发相比起点更高。
  RTOS还是一个标准化的平台,它定义了每个应用任务和内核的接口,也促进了应用程序的标准化。应用程序标准化后便于软件的存档、交流、修改和扩展,为嵌入式软件开发的工程化创造了条件、减少开发管理工作量。嵌入式软件标准化推广到社会后,可以促进软件开发的分工,减少重复劳动,近来出现的建立于RTOS上的文件和通信协议库函数产品等就是实例。
  RTOS对于开发单位和开发者个人来说也是一种提高。引入RTOS的开发单位,相当于引入了一套行业中广泛采用的嵌入式系统应用程序开发标准,使开发管理更简易、有效。基于RTOS和C语言的开发,具有良好的可继承性,在应用程序、处理器升级以及更换处理器类型时,现存的软件大部分可以不经修改地移植过来。
  对于开发人员来说,则相当于在程序设计中采用一种标准化的思维方式,提高知识创造的效率;同时因为具有类似的思路,可以更快地理解同行其它人员的创造成果

·RTOS系统的特点



  一、时间约束性
  实时系统的任务具有一定的时间约束(截止时间)。根据截止时间,实时系统的实时性分为“硬实时”和“软实时”。硬实时是指应用的时间需求能够得到完全满足,否则就造成重大安全事故,甚至造成重大的生命财产损失和生态破坏,如在航空航天、军事、核工业等一些关键领域中的应用。软实时是指某些应用虽然提出时间需求,但实时任务偶尔违反这种需求对系统运行及环境不会造成严重影响,如监控系统等和信息采集系统等。
  二、可预测性
  可预测性是指系统能够对实时任务的执行时间进行判断,确定是否能够满足任务的时限要求。由于实时系统对时间约束要求的严格性,使可预测性称为实时系统的一项重要性能要求。除了要求硬件延迟的可预测性以外,还要求软件系统的可预测性,包括应用程序的响应时间是可预测的,即在有限的时间内完成必须的工作;以及操作系统的可预测性,即实时原语、调度函数等运行开销应是有界的,以保证应用程序执行时间的有界性。
  三、可靠性
  大多数实时系统要求有较高的可靠性。在一些重要的实时应用中,任何不可靠因素和计算机的一个微小故障,或某些特定强实时任务(又叫关键任务)超过时限,都可能引起难以预测的严重后果。为此,系统需要采用静态分析和保留资源的方法及冗余配置,使系统在最坏情况下都能正常工作或避免损失。可靠性已成为衡量实时系统性能不可缺少的重要指标。
  四、与外部环境的交互作用性
  实时系统通常运行在一定的环境下,外部环境是实时系统不可缺少的一个组成部分。计算机子系统一般是控制系统,它必须在规定的时间内对外部请求做出反应。外部物理环境往往是被控子系统,两者互相作用构成完整的实时系统。大多数控制子系统必须连续运转以保证子系统的正常工作或准备对任何异常行为采取行动。
  早期的实时系统功能简单,包括单板机、单片机,以及简单的嵌入式实时系统等,其调度过程相对简单。随着实时系统应用范围的不断扩大,系统复杂性不断提高,实时系统具有以下新特点。
  1、多任务类型
  在实时系统中,不但包括周期任务、偶发任务、非周期任务,还包括非实时任务。实时任务要求要满足时限,而非实时任务要求要使其响应时间尽可能的短。多种类型任务的混合,使系统的可调度性分析更加困难。
  2、约束的复杂性
  任务的约束包括时间约束、资源约束、执行顺序约束和性能约束。时间约束是任何实时系统都固有的约束。资源约束是指多个实时任务共享有限的资源时,必须按照一定的资源访问控制协议进行同步,以避免死锁和高优先级任务被低优先级任务堵塞的时间(即优先级倒置时间)不可预测。执行顺序约束是指各任务的启动和执行必须满足一定的时间和顺序约束。例如,在分布式端到端(end-to-end)实时系统很重,同一任务的各子任务之间存在前驱/后驱约束关系,需要执行同步协议来管理子任务的启动和控制子任务的执行,使它们满足时间约束和系统可调度要求。性能约束是指必须满足如可靠性、可用性、可预测性、服务质量(Quality of Service,QoS)等性能指标。
  3、具有短暂超载的特点
  在实时系统中,即使一个功能设计合理、资源充足的系统也可能由于一下原因超载:
  1)系统元件出现老化,外围设备错误或系统发生故障。随着系统运行时间的增长,系统元件出现老化,系统部件可能发生故障,导致系统可用资源降低,不能满足实时任务的时间约束要求。
  2)环境的动态变化。由于不能对未来的环境、系统状态进行正确有效地预测,因此不能从整体角度上对任务进行调度,可能导致系统超载。
  3)应用规模的扩大。原先满足实时任务时限要求的系统,随着应用规模的增大,可能出现不能满足任务时限要求的情况,而重新设计、重建系统在时间和经济上又不允许。

·RTOS系统的分类



  实时系统主要分为以下两类。
  强实时系统(Hard Real-Time):在航空航天、军事、核工业等一些关键领域中,应用时间需求应能够得到完全满足,否则就造成如飞机失事等重大地安全事故,造成重大地生命财产损失和生态破坏。因此,在这类系统的设计和实现过程中,应采用各种分析、模拟及形式化验证方法对系统进行严格的检验,以保证在各种情况下应用的时间需求和功能需求都能够得到满足。
  弱实时系统(Soft Real-Time):某些应用虽然提出了时间需求,但实时任务偶尔违反这种需求对系统的运行以及环境不会造成严重影响,如视频点播(Video-On-Demand,VOD)系统、信息采集与检索系统就是典型的弱实时系统。在VOD系统中,系统只需保证绝大多数情况下视频数据能够及时传输给用户即可,偶尔的数据传输延迟对用户不会造成很大影响,也不会造成像飞机失事一样严重的后果。

·RTOS系统的调度


  为了精确管理“时间”资源,已达到实时性和与预测性要求,并能够满足是实时系统的新要求,需用实时调度理论对任务进行调度和可调度性分析。任务调度技术包括调度策略和可调度性分析方法,两者是紧密结合的。任务调度技术研究的范围包括任务使用系统资源(包括处理机、内存、I/O、网络等资源)的策略和机制,以及提供判断系统性能是否可预测的方法和手段。例如,什么时候调度任务运行、在哪运行(当系统为多处理机系统或分布式系统时)、运行多长时间等等;以及判断分析用一定参数描述的实时任务能否被系统正确调度。
  给定一组实时任务和系统资源,确定每个任务何时何地执行的整个过程就是调度。在非实时系统中,调度的主要目的是缩短系统平均响应时间,提高系统资源利用率,或优化某一项指标;而实时系统中调度的目的则是要尽可能地保证每个任务满足他们的时间约束,及时对外部请求做出响应。实时调度技术通常有多种划分方法,常用以下两种。
  抢占式调度和非抢占式调度
  1)抢占式调度通常是优先级驱动的调度。每个任务都有优先级,任何时候具有最高优先级且已启动的任务先执行。一个正在执行的任务放弃处理器的条件为:自愿放弃处理器(等待资源或执行完毕);有高优先级任务启动,该高优先级任务将抢占其执行。除了共享资源的临界段之外,高优先级任务一旦准备就绪,可在任何时候抢占低优先级任务的执行。抢占式调度的优点是实时性好、反应快,调度算法相对简单,可优先保证高优先级任务的时间约束,其缺点是上下文切换多。而非抢占式调度是指不允许任务在执行期间被中断,任务一旦占用处理器就必须执行完毕或自愿放弃。其优点是上下文切换少;缺点是在一般情况下,处理器有效资源利用率低,可调度性不好。
  静态表驱动策略和优先级驱动策略
  2)静态表驱动策略(Static Table-Driven Scheduling)是一中离线调度策略,指在系统运行前根据各任务的时间约束及关联关系,采用某种 搜索策略生成一张运行时刻表。这张运行时刻表与列车运行时刻表类似,指明了各任务的起始运行时刻及运行时间。运行时刻表一旦生成就不再发生变化了。在系统运行时,调度器只需根据这张时刻表启动相应的任务即可。由于所有调度策略在离线情况下指定,因此调度器的功能被弱化,只具有分派器(Dispatcher)的功能。
  优先级驱动策略指按照任务优先级的高低确定任务的高低确定任务的执行顺序。优先级驱动策略又分为静态优先级调度策略。静态优先级调度是指任务的优先级分配好之后,在任务的运行过程中,优先级不会发生改变。静态优先级调度又称为固态优先级调度。动态优先级调度是指任务的优先级可以随着时间或系统状态的变化而发生变化。

·RTOS产生并得到迅速发展的原因



  单片机处理器能力的提高和应用程序功能的复杂化、精确化,迫使应用程序划分为多个重要性不同的任务,在各任务间优化地分配CPU时间和系统资源,同时还要保证实时性。靠用户自己编写一个实现上述功能的内核一般是不现实的,而这种需求又是普遍的。在这种形势之下,由专业人员编写的、满足大多数用户需要的高性能RTOS内核就是一种必然结果了。
  对程序实时性和可靠性要求的提高也是RTOS发展的一个原因。此外,单片机系统软件开发日趋工程化,产品进入市场时间不断缩短,也迫使管理人员寻找一种有利于程序继承性、标准化、多人并行开发的管理方式。从长远的意义上来讲,RTOS的推广能够带来嵌入式软件工业更有效、更专业化的分工,减少社会重复劳动、提高劳动生产率。

2014年8月11日星期一

跳出来看FreeRTOS

题目自命题

转自:http://www.programmer-club.com.tw/showSameTitleN/embedded/73.html


雖然嵌入式系統與RTOS有密切的關係,但是在選用前,最好還是釐清一些問題。
1. 你真的需要RTOS嗎?(如果你只需要簡單的應用,RTOS反而會拖慢你的系統)
2. 你的硬體負擔的了RTOS嗎?(RTOS雖然都標榜很小的CODE SIZE、但他所耗用的RAM(Task Stack, Task Structures...),或CPU資源(Context switch損耗,Scheduling 損耗...)等部分,卻著墨很少)
3. RTOS增加的成本有計算過嗎?(即使是免費的,仍有潛在的成本)
4. 你絕對需要他的原因?
如果要我用一句話來回答第四個問題,我會答:程式比較容易寫(也表示開發上會比較快)。我的回答其實是希望淡化:"RTOS是一定要的啦!"的立場!
沒有硬體TIMER的中斷支援,RTOS根本無法做到Preemption,另外,RTOS提供的不同優先權(Priority)的功能,現階段的CPU多有支援,另外,一些SOC開發的特殊功能(例如省電模式、動態調頻等),通常能提供更有效率的使用,只是,在RTOS的加持下,這些功能可能會被隱形、或放棄掉!
如果你的系統很複雜,你真的不用考慮太多,用(RTOS)就對了!如果經驗不是很豐富,又不用考慮成本,用就對了!如果你的人手不足,許多模組要用買的,用就對了!如果沒有RTOS你不會寫程式了,用就對了!
RTOS會耗掉多少資源?(就像我問你,Win-XP能不能在486的電腦上跑一樣?486太弱了嗎...請再想想)一個作業系統的介入能提昇效能?非也非也!RTOS跟我有仇?非也非也!我只是站在反方提出一些看法罷了!

2014年8月8日星期五

堆(heap)和栈(stack)有什么区别?

简单的可以理解为:
heap:是由malloc之类函数分配的空间所在地。地址是由低向高增长的。
stack:是自动分配变量,以及函数调用的时候所使用的一些空间。地址是由高向低减少的。


预备知识—程序的内存分配

一个由c/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放
4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区—存放函数体的二进制代码。

二、例子程序
这是一个前辈写的,非常详细
//main.cpp
int a = 0; 全局初始化区
char *p1; 全局未初始化区
main()
{
int b; 栈
char s[] = "abc"; 栈
char *p2; 栈
char *p3 = "123456"; 123456在常量区,p3在栈上。
static int c =0; 全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
分配得来得10和20字节的区域就在堆区。
strcpy(p1, "123456"); 123456放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
}


二、堆和栈的理论知识
2.1申请方式
stack:
由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间
heap:
需要程序员自己申请,并指明大小,在c中malloc函数
如p1 = (char *)malloc(10);
在C++中用new运算符
如p2 = (char *)malloc(10);
但是注意p1、p2本身是在栈中的。
2.2
申请后系统的响应
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,
会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
2.3申请大小的限制
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
2.4申请效率的比较:
栈由系统自动分配,速度较快。但程序员是无法控制的。
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度, 也最灵活
2.5堆和栈中的存储内容
栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。
当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。
2.6存取效率的比较

char s1[] = "aaaaaaaaaaaaaaa";
char *s2 = "bbbbbbbbbbbbbbbbb";
aaaaaaaaaaa是在运行时刻赋值的;
而bbbbbbbbbbb是在编译时就确定的;
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。
比如:
#include
void main()
{
char a = 1;
char c[] = "1234567890";
char *p ="1234567890";
a = c[1];
a = p[1];
return;
}
对应的汇编代码
10: a = c[1];
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4],cl
11: a = p[1];
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
00401070 8A 42 01 mov al,byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4],al
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指edx中,在根据edx读取字符,显然慢了。
?

2.7小结:
堆和栈的区别可以用如下的比喻来看出:
使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。
使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。

堆和栈的区别主要分:
操作系统方面的堆和栈,如上面说的那些,不多说了。
还有就是数据结构方面的堆和栈,这些都是不同的概念。这里的堆实际上指的就是(满足堆性质的)优先队列的一种数据结构,第1个元素有最高的优先权;栈实际上就是满足先进后出的性质的数学或数据结构。
虽然堆栈,堆栈的说法是连起来叫,但是他们还是有很大区别的,连着叫只是由于历史的原因针值读.

2014年8月7日星期四

关于typedef的用法总结



转自:http://www.cnblogs.com/csyisong/archive/2009/01/09/1372363.html




不管实在C还是C++代码中,typedef这个词都不少见,当然出现频率较高的还是在C代码中。typedef与#define有些相似,但更多的是不同,特别是在一些复杂的用法上,就完全不同了,看了网上一些C/C++的学习者的博客,其中有一篇关于typedef的总结还是很不错,由于总结的很好,我就不加修改的引用过来了,以下是引用的内容(红色部分是我自己写的内容)。


用途一:

定义一种类型的别名,而不只是简单的宏替换。可以用作同时声明指针型的多个对象。比如:

char* pa, pb; // 这多数不符合我们的意图,它只声明了一个指向字符变量的指针,

// 和一个字符变量;

以下则可行:

typedef char* PCHAR;

PCHAR pa, pb;
这种用法很有用,特别是char* pa, pb的定义,初学者往往认为是定义了两个字符型指针,其实不是,而用typedef char* PCHAR就不会出现这样的问题,减少了错误的发生。


用途二:
用在旧的C代码中,帮助struct。以前的代码中,声明struct新对象时,必须要带上struct,即形式为: struct 结构名对象名,如:

struct tagPOINT1

{
int x;

int y;
};

struct tagPOINT1 p1;

而在C++中,则可以直接写:结构名对象名,即:tagPOINT1 p1;

typedef struct tagPOINT
{
int x;

int y;
}POINT;

POINT p1; // 这样就比原来的方式少写了一个struct,比较省事,尤其在大量使用的时

候,或许,在C++中,typedef的这种用途二不是很大,但是理解了它,对掌握以前的旧代

码还是有帮助的,毕竟我们在项目中有可能会遇到较早些年代遗留下来的代码。

用途三:

用typedef来定义与平台无关的类型。

比如定义一个叫 REAL 的浮点类型,在目标平台一上,让它表示最高精度的类型为:

typedef long double REAL;

在不支持 long double 的平台二上,改为:

typedef double REAL;

在连 double 都不支持的平台三上,改为:

typedef float REAL;

也就是说,当跨平台时,只要改下 typedef 本身就行,不用对其他源码做任何修改。

标准库就广泛使用了这个技巧,比如size_t。另外,因为typedef是定义了一种类型的新别名,不是简单的字符串替换,所以它比宏来得稳健。
     这个优点在我们写代码的过程中可以减少不少代码量哦!


用途四:

为复杂的声明定义一个新的简单的别名。方法是:在原来的声明里逐步用别名替换一部

分复杂声明,如此循环,把带变量名的部分留到最后替换,得到的就是原声明的最简化
版。举例: 
 原声明:void (*b[10]) (void (*)());
变量名为b,先替换右边部分括号里的,pFunParam为别名一:
typedef void (*pFunParam)();
再替换左边的变量b,pFunx为别名二:
typedef void (*pFunx)(pFunParam);
原声明的最简化版:
pFunx b[10];
 
原声明:doube(*)() (*e)[9];
变量名为e,先替换左边部分,pFuny为别名一:
typedef double(*pFuny)();
再替换右边的变量e,pFunParamy为别名二
typedef pFuny (*pFunParamy)[9];
原声明的最简化版:
pFunParamy e;
理解复杂声明可用的“右左法则”:从变量名看起,先往右,再往左,碰到一个圆括号
就调转阅读的方向;括号内分析完就跳出括号,还是按先右后左的顺序,如此循环,直
到整个声明分析完。举例:
int (*func)(int *p);
首先找到变量名func,外面有一对圆括号,而且左边是一个*号,这说明func是一个指针
;然后跳出这个圆括号,先看右边,又遇到圆括号,这说明(*func)是一个函数,所以
func是一个指向这类函数的指针,即函数指针,这类函数具有int*类型的形参,返回值
类型是int。
int (*func[5])(int *);
func右边是一个[]运算符,说明func是具有5个元素的数组;func的左边有一个*,说明
func的元素是指针(注意这里的*不是修饰func,而是修饰func[5]的,原因是[]运算符
优先级比*高,func先跟[]结合)。跳出这个括号,看右边,又遇到圆括号,说明func数
组的元素是函数类型的指针,它指向的函数具有int*类型的形参,返回值类型为int。
这种用法是比较复杂的,出现的频率也不少,往往在看到这样的用法却不能理解,相信以上的解释能有所帮助。
*****以上为参考部分,以下为本人领悟部分*****
使用示例:
1.比较一:
#include
using namespace std;
typedef int (*A) (char, char);
int ss(char a, char b)
{
    cout<<"功能1"<
    cout<
    cout<
    return 0;
}
 
int bb(char a, char b)
{
    cout<<"功能2"<
    cout<
    cout<
    return 0;
}
void main()
{
    A a;
    a = ss;
    a('a','b');
    a = bb;
    a('a', 'b');
}
2.比较二:
typedef int (A) (char, char);
void main()
{
    A *a;
    a = ss;
    a('a','b');
    a = bb;
    a('a','b');
}
 
两个程序的结果都一样:
功能1
a
b
功能2
b
a

*****以下是参考部分*****
参考自:http://blog.hc360.com/portal/personShowArticle.do?articleId=57527
typedef 与 #define的区别:
案例一:
通常讲,typedef要比#define要好,特别是在有指针的场合。请看例子:
typedef char *pStr1;
#define pStr2 char *;
pStr1 s1, s2;
pStr2 s3, s4;
在上述的变量定义中,s1、s2、s3都被定义为char *,而s4则定义成了char,不是我们
所预期的指针变量,根本原因就在于#define只是简单的字符串替换而typedef则是为一
个类型起新名字。
案例二:
下面的代码中编译器会报一个错误,你知道是哪个语句错了吗?
typedef char * pStr;
char string[4] = "abc";
const char *p1 = string;
const pStr p2 = string;
p1++;
p2++;
  是p2++出错了。这个问题再一次提醒我们:typedef和#define不同,它不是简单的
文本替换。上述代码中const pStr p2并不等于const char * p2。const pStr p2和
const long x本质上没有区别,都是对变量进行只读限制,只不过此处变量p2的数据类
型是我们自己定义的而不是系统固有类型而已。因此,const pStr p2的含义是:限定数
据类型为char *的变量p2为只读,因此p2++错误。虽然作者在这里已经解释得很清楚了,可我在这个地方仍然还是糊涂的,真的希望哪位高手能帮忙指点一下,特别是这一句“只不过此处变量p2的数据类型是我们自己定义的而不是系统固有类型而已”,难道自己定义的类型前面用const修饰后,就不能执行更改运算,而系统定义的类型却可以?