1. 相关名词解释

1.1      什么是timeval结构体

    timeval结构体在SylixOS系统中的定义如程序清单1.1所示:

程序清单1.1 timeval结构体定义

struct timeval {time_t         tv_sec;                /*  seconds                     */LONG           tv_usec;               /*  microseconds                */};

 

    tv_sec储存从UTC 1970年1月1日00时00分00秒到创建结构体时的秒数,tv_usec为微秒数。在SylixOS下,time_t的实际类型为long long型,大小为8个字节,长度64位。

2. 使用timeval结构体存在的问题

2.1      Linux下的timeval结构体

    Linux下的timeval结构体定义为程序清单2.1所示:

程序清单2.1 timeval结构体定义

struct timeval {__time_t           tv_sec;            /*  seconds                     */__suseconds_t      tv_usec;           /*  microseconds                */};

    其中tv_sec和tv_usec的储存内容与SylixOS下一致,但是在32位的Linux系统中,__time_t实际类型为long int,大小为4个字节,长度32位。

2.2      结构体不一致造成的问题

2.2.1   计时长度问题

    由于32位的Linux系统使用timeval结构体中的tv_sec长度为32位,则其最大值为2^32-1,意味着到UTC时间2038年1月19号03点14分07秒过后,32位Linux系统存储的时间变量将会溢出,造成系统将时间混乱。这很可能会引起软件故障,甚至是系统瘫痪。

    而使用SylixOS系统,无论是32位系统或是64位系统,tv_sec的长度均为64位,时间最多可以使用到UTC时间292,277,026,596年12月04日15时30分08秒则基本不会遇到这类溢出问题。

 

2.2.2   参数传入问题

    在完善glib移植的过程中,遇到这样一个问题:依赖glib库的lcm库,需要将GTimeVal结构体作为参数传入select函数。

    其中GTimeVal结构体的定义为程序清单2.2 所示:

程序清单2.2 GTimeVal结构体定义

typedef             long             glong;typedef  struct    _GTimeVal         GTimeVal;struct _GTimeVal

{

glong   tv_sec;

glong   tv_usec;

};

    传入方式为程序清单2.3 所示:

程序清单2.3 GTimeVal传入select函数

GTimeVal selectto;status=select (recvfd + 1,&readfds,0,0, (struct timeval*) &selectto);

    由于lcm库是基于32位Linux系统的,select函数接受的参数为timeval结构体类型的指针。虽然对于传入的指针做了强制类型转换,在编译时不会报错,但实际运行中,SylixOS系统寻找的地址为8字节+4字节,而实际只储存了4字节+4字节的内容,导致tv_usec变量获取值异常,出现不可预知的错误。

    所以需要手动添加timeval结构体,赋值后将timeval结构体传入select函数,如程序清单2.4 所示:

程序清单2.4 传参修改方法

#ifdef SYLIXOS        struct timeval selectt;

    selectt.tv_sec = selectto.tv_sec;

    selectt.tv_usec = selectto.tv_usec;

    status=select (recvfd + 1,&readfds,0,0, (struct timeval*) &selectt);#else    status=select (recvfd + 1,&readfds,0,0, (struct timeval*) &selectto);#endif

 

2.2.3   结构体大小问题

    使用sizeof运算符求SylixOS下timeval结构体的大小时,获得的值为12;32位Linux下获得值为8;在32位Windows平台仿造SylixOS下定义timeval结构体,获得其大小为16。

    这涉及了结构体字节对齐的问题,在用sizeof运算符求算某结构体所占空间时,并不是简单地将结构体中所有元素各自占的空间相加。从理论上讲,对于任何变量的访问都可以从任何地址开始访问,但是事实上不是如此,实际上访问特定类型的变量只能在特定的地址访问,需要各个变量在空间上按一定的规则排列,而不是简单地顺序排列,这就是内存对齐。

    内存对齐的原则为:

    1.结构体每个成员相对结构体首地址的偏移量是对齐参数的整数倍,如有需要会在成员之间填充字节。编译器在为结构体成员开辟空间时,首先检查预开辟空间的地址相对于结构体首地址的偏移量是否为对齐参数的整数倍,若是,则存放该成员;若不是,则填充若干字节,以达到整数倍的要求。

    2.结构体变量所占空间的大小是对齐参数大小的整数倍。如有需要会在最后一个成员末尾填充若干字节使得所占空间大小是对齐参数大小的整数倍。

    不同类型变量的长度与自身对齐参数如表2.5 所示:

表2.5 变量的长度与自身对齐参数



char

short

Int

float

double

指针

Windows(32位)

VisualStudio2010

长度

1

2

4

4

8

4

自身对齐参数

1

2

4

4

8

4


Linux(32位)

GCC

长度

1

2

4

4

8

4

自身对齐参数

1

2

4

4

4

4


SylixOS(32位)

GCC

长度

1

2

4

4

8

4

自身对齐参数

1

2

4

4

4

4


 

    从上面可以发现,在Windows(32)/ VisualStudio2010下各种类型的变量的自身对齐参数就是该类型变量所占字节数的大小,而在Linux(32)/GCC下double类型的变量自身对齐参数是4,是因为Linux(32)/GCC下如果该类型变量的长度没有超过CPU的字长,则以该类型变量的长度作为自身对齐参数,如果该类型变量的长度超过CPU字长,则自身对齐参数为CPU字长,而32位系统其CPU字长是4,所以Linux(32)/GCC下double类型的变量自身对齐参数是4,如果是在Linux(64)下,则double类型的自身对齐参数是8。SylixOS系统也与Linux系统类似。

    所以构造相同的结构体timeval在Windows下与32位SylixOS、32位Linux在内存中所占大小存在差异,需要在移植中注意。

3. 总结

    SylixOS与Linux、Windows存在差异,即使系统接口名称一样,参数类型看似一致,仍可能存在不兼容的情况。有些时候甚至编译运行都不会出错,但确实存在问题,移植时需要谨慎。

4. 参考资料

    《SylixOS应用程序开发手册》

    《RealEvo-IDE使用手册》