0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

为什么MISRA要求你不要使用位域-本文告诉你真相

嵌入式USB开发 来源:嵌入式Lee 作者:嵌入式Lee 2023-06-21 17:36 次阅读

本文转自公众号,欢迎关注

为什么MISRA要求你不要使用位域-本文告诉你真相 (qq.com)

一.前言

做过嵌入式开发的一般会看到一条编程规范:”不要使用位域”,一般都是知其然不其所以然,了解的多一点的可能知道位域是实现相关不具备可移植性,那么继续追问哪些行为是实现相关哪些行为导致移植性问题? 或者还有人知道,存储布局,对齐等行为是实现相关会导致不可移植性。如果再追问位域产生的汇编代码是什么样的,怎么进行读-修改-写操作的?知道这些内容的就更加少之又少了。 读写肯定不能读指定位数,只能字节,或者16位,32位这种,那么编译器到底读写用什么宽度? 这时基本大部分人都不知道了。

知其然知其所以然,尤其是嵌入式开发和硬件结合比较紧密,所以一定要了解细节,我们这一篇从一个问题引出然后去分析查找原因,只有遇到问题然后去分析解决它才会有更深刻的映像。

二.问题分析过程

问题是驱动程序中一个寄存器的某个位域修改,导致其他位域的值被修改了。

关键代码如下,

1.typedefunionnfc_ena_union{

2. uint32_tw;

3. struct{

4. /*spienable,oncethespitransiscompleted,thisbitwillbeclearedbyHWautomaticlly*/

5. uint32_tnfc_ena:1; //[0]

6. uint32_treserved_0:3; //[1,3]

7. /*swrequesttousedp*/

8. uint32_tnfc_dp_req:1; //[4]

9. uint32_treserved_1:3; //[5,7]

10. /*duetodelayinreceivingdata,nfcdelayonebeattorx*/

11. uint32_tnfc_rx_delay_en:1; //[8]

12. uint32_treserved_2:7; //[9,15]

13. /*spitransdatalength,unitisbyte,oncethespitransiscompleted,thisbitwillbeclearedbyHWautomaticlly*/

14. uint32_tnfc_data_len:16; //[16,31]

15.}_b;

16.}nfc_ena_u;

1./**

2.*fnintnfc_set_datalen(uint8_tid,uint16_tlen)

3.*param[in]idportid

4.*param[in]lendatalen

5.*retval0ok

6.*retval<0 param err

7.*

8.*/

9.NFC_INLINEintnfc_set_datalen(uint8_tid,uint16_tlen)

10.{

11. if(id>=HW_NFC_PORT_MAX)

12.{

13. return-1;

14.}

15.nfc_base[id]->nfc_ena._b.nfc_data_len=len;

16. return0;

17.}

执行之前该寄存器值为0x00020100

34b4c4c2-0f04-11ee-9c1d-dac502259ad0.png

nfc_base[id]->nfc_ena._b.nfc_data_len= len

汇编代码被优化为了写高16位

34d22efe-0f04-11ee-9c1d-dac502259ad0.png

执行完后寄存器低16位变为了0

34f0fab4-0f04-11ee-9c1d-dac502259ad0.png

这是因为寄存器硬件上只支持32位的写操作,所以写高16位导致低16位清零了,这是硬件决定的。

二.验证

一般想到的就是优化相关,加volatile等,我们分别验证下。

3.1不使能编译器优化

编译器优化选项改为”-O0”

代码不变

依然会按照16位访问,导致低16位被清掉。

所以可以看到这个和编译器行为有关,编译器显然不是根据优化等级决定位域的操作宽度,这里而是根据位域的宽度刚好是16位对齐,所以优化为了16位操作指令。

3504cddc-0f04-11ee-9c1d-dac502259ad0.png351f3d70-0f04-11ee-9c1d-dac502259ad0.png

3.2使用volatile避免编译器优化

#ifndef__IOM

#define__IOM volatile

#endif

所有uint32_t替换为__IOM uint32_t

还是一样的

353ad710-0f04-11ee-9c1d-dac502259ad0.png

显然汇编代码的访问宽度也不受volatile影响。

3.3为什么指定了uint32_t和volatile还会优化。

问题来了为什么告诉了编译器是uint32_t和volatile,为什么其还要一意孤行,要优化为16位访问指令呢,答案就是因为是标准没有规定,这是编译器实现行为决定的,所以编译器设计者决定的(当然也会有一些现实考虑的),可能不同编译器行为不同,这里以GCC为例。

GCC编译器文档中可以找到答案

GCC的文档可以看到如下内容,也给出了最好是不使用位域的原因

354f86f6-0f04-11ee-9c1d-dac502259ad0.png

35645612-0f04-11ee-9c1d-dac502259ad0.png

另外也介绍了位域哪些行为也是编译器实现相关的,所以嵌入式可移植性考虑不要使用位域

3576ae5c-0f04-11ee-9c1d-dac502259ad0.png

那么有没有办法指定编译按照一定大小访问呢,GCC有编译选项可以控制见下一节。

3.4使用编译器选项-fstrict-volatile-bitfields

358c8eca-0f04-11ee-9c1d-dac502259ad0.png

可以看到改为了sw指令,按照32位进行了操作

359dc4f6-0f04-11ee-9c1d-dac502259ad0.png

四.一些厂家做法

如下可见

4.1CMSIS

core_cmxx.h中定义

CMSIS中进行了定义,寄存器个别使用位域

1./*IOdefinitions(accessrestrictionstoperipheralregisters)*/
2./**
3.defgroupCMSIS_glob_defsCMSISGlobalDefines
4.
5.<  strong  >IOTypeQualifiers<  /strong  >areused
6.litospecifytheaccesstoperipheralvariables.
7.liforautomaticgenerationofperipheralregisterdebuginformation.
8.*/
9.#ifdef__cplusplus
10.#define__Ivolatile/*!<   Defines 'read only' permissions */
11.#else
12.  #define   __I     volatile const       /*!<   Defines 'read only' permissions */
13.#endif
14.#define     __O     volatile             /*!<   Defines 'write only' permissions */
15.#define     __IO    volatile             /*!<   Defines 'read / write' permissions */
16.
17./* following defines should be used for structure members */
18.#define     __IM     volatile const      /*! Defines 'read only' structure member permissions */
19.#define     __OM     volatile            /*! Defines 'write only' structure member permissions */
20.#define     __IOM    volatile            /*! Defines 'read / write' structure member permissions */

1.
/**
2.  brief  Structure type to access the Instrumentation Trace Macrocell Register (ITM).
3. */
4.typedef struct
5.{
6.  __OM  union
7.  {
8.    __OM  uint8_t    u8;                 /*!<   Offset: 0x000 ( /W)  ITM Stimulus Port 8-bit */
9.    __OM  uint16_t   u16;                /*!<   Offset: 0x000 ( /W)  ITM Stimulus Port 16-bit */
10.    __OM  uint32_t   u32;                /*!<   Offset: 0x000 ( /W)  ITM Stimulus Port 32-bit */
11.  }  PORT [32U];                         /*!<   Offset: 0x000 ( /W)  ITM Stimulus Port Registers */
12.        uint32_t RESERVED0[864U];
13.  __IOM uint32_t TER;                    /*!<   Offset: 0xE00 (R/W)  ITM Trace Enable Register */
14.        uint32_t RESERVED1[15U];
15.  __IOM uint32_t TPR;                    /*!<   Offset: 0xE40 (R/W)  ITM Trace Privilege Register */
16.        uint32_t RESERVED2[15U];
17.  __IOM uint32_t TCR;                    /*!<   Offset: 0xE80 (R/W)  ITM Trace Control Register */
18.        uint32_t RESERVED3[29U];
19.  __OM  uint32_t IWR;                    /*!<   Offset: 0xEF8 ( /W)  ITM Integration Write Register */
20.  __IM  uint32_t IRR;                    /*!<   Offset: 0xEFC (R/ )  ITM Integration Read Register */
21.  __IOM uint32_t IMCR;                   /*!<   Offset: 0xF00 (R/W)  ITM Integration Mode Control Register */
22.        uint32_t RESERVED4[43U];
23.  __OM  uint32_t LAR;                    /*!<   Offset: 0xFB0 ( /W)  ITM Lock Access Register */
24.  __IM  uint32_t LSR;                    /*!<   Offset: 0xFB4 (R/ )  ITM Lock Status Register */
25.        uint32_t RESERVED5[6U];
26.  __IM  uint32_t PID4;                   /*!<   Offset: 0xFD0 (R/ )  ITM Peripheral Identification Register #4 */
27.  __IM  uint32_t PID5;                   /*!<   Offset: 0xFD4 (R/ )  ITM Peripheral Identification Register #5 */
28.  __IM  uint32_t PID6;                   /*!<   Offset: 0xFD8 (R/ )  ITM Peripheral Identification Register #6 */
29.  __IM  uint32_t PID7;                   /*!<   Offset: 0xFDC (R/ )  ITM Peripheral Identification Register #7 */
30.  __IM  uint32_t PID0;                   /*!<   Offset: 0xFE0 (R/ )  ITM Peripheral Identification Register #0 */
31.  __IM  uint32_t PID1;                   /*!<   Offset: 0xFE4 (R/ )  ITM Peripheral Identification Register #1 */
32.  __IM  uint32_t PID2;                   /*!<   Offset: 0xFE8 (R/ )  ITM Peripheral Identification Register #2 */
33.  __IM  uint32_t PID3;                   /*!<   Offset: 0xFEC (R/ )  ITM Peripheral Identification Register #3 */
34.  __IM  uint32_t CID0;                   /*!<   Offset: 0xFF0 (R/ )  ITM Component  Identification Register #0 */
35.  __IM  uint32_t CID1;                   /*!<   Offset: 0xFF4 (R/ )  ITM Component  Identification Register #1 */
36.  __IM  uint32_t CID2;                   /*!<   Offset: 0xFF8 (R/ )  ITM Component  Identification Register #2 */
37.  __IM  uint32_t CID3;                   /*!<   Offset: 0xFFC (R/ )  ITM Component  Identification Register #3 */
38.} ITM_Type;

1./**
2.  brief  Union type to access the Application Program Status Register (APSR).
3. */
4.typedef union
5.{
6.  struct
7.  {
8.    uint32_t _reserved0:27;              /*!<   bit:  0..26  Reserved */
9.    uint32_t Q:1;                        /*!<   bit:     27  Saturation condition flag */
10.    uint32_t V:1;                        /*!<   bit:     28  Overflow condition code flag */
11.    uint32_t C:1;                        /*!<   bit:     29  Carry condition code flag */
12.    uint32_t Z:1;                        /*!<   bit:     30  Zero condition code flag */
13.    uint32_t N:1;                        /*!<   bit:     31  Negative condition code flag */
14.  } b;                                   /*!<   Structure used for bit  access */
15.  uint32_t w;                            /*!<   Type      used for word access */
16.} APSR_Type;

4.2ST

1./**
2.*@briefUniversalSerialBusFullSpeedDevice
3.*/
4.
5.typedefstruct
6.{
7.__IOuint16_tEP0R;/*!<   USB Endpoint 0 register,                   Address offset: 0x00 */ 
8.  __IO uint16_t RESERVED0;            /*!<   Reserved */     
9.  __IO uint16_t EP1R;                 /*!<   USB Endpoint 1 register,                   Address offset: 0x04 */
10.  __IO uint16_t RESERVED1;            /*!<   Reserved */       
11.  __IO uint16_t EP2R;                 /*!<   USB Endpoint 2 register,                   Address offset: 0x08 */
12.  __IO uint16_t RESERVED2;            /*!<   Reserved */       
13.  __IO uint16_t EP3R;                 /*!<   USB Endpoint 3 register,                   Address offset: 0x0C */ 
14.  __IO uint16_t RESERVED3;            /*!<   Reserved */       
15.  __IO uint16_t EP4R;                 /*!<   USB Endpoint 4 register,                   Address offset: 0x10 */
16.  __IO uint16_t RESERVED4;            /*!<   Reserved */       
17.  __IO uint16_t EP5R;                 /*!<   USB Endpoint 5 register,                   Address offset: 0x14 */
18.  __IO uint16_t RESERVED5;            /*!<   Reserved */       
19.  __IO uint16_t EP6R;                 /*!<   USB Endpoint 6 register,                   Address offset: 0x18 */
20.  __IO uint16_t RESERVED6;            /*!<   Reserved */       
21.  __IO uint16_t EP7R;                 /*!<   USB Endpoint 7 register,                   Address offset: 0x1C */
22.  __IO uint16_t RESERVED7[17];        /*!<   Reserved */     
23.  __IO uint16_t CNTR;                 /*!<   Control register,                          Address offset: 0x40 */
24.  __IO uint16_t RESERVED8;            /*!<   Reserved */       
25.  __IO uint16_t ISTR;                 /*!<   Interrupt status register,                 Address offset: 0x44 */
26.  __IO uint16_t RESERVED9;            /*!<   Reserved */       
27.  __IO uint16_t FNR;                  /*!<   Frame number register,                     Address offset: 0x48 */
28.  __IO uint16_t RESERVEDA;            /*!<   Reserved */       
29.  __IO uint16_t DADDR;                /*!<   Device address register,                   Address offset: 0x4C */
30.  __IO uint16_t RESERVEDB;            /*!<   Reserved */       
31.  __IO uint16_t BTABLE;               /*!<   Buffer Table address register,             Address offset: 0x50 */
32.  __IO uint16_t RESERVEDC;            /*!<   Reserved */       
33.} USB_TypeDef;


1./** @defgroup USBD_SCSI_Exported_TypesDefinitions
2.  * @{
3.  */
4.
5.typedef struct _SENSE_ITEM
6.{
7.  char Skey;
8.  union
9.  {
10.    struct _ASCs
11.    {
12.      char ASC;
13.      char ASCQ;
14.    } b;
15.    uint8_t ASC;
16.    char *pData;
17.  } w;
18.} USBD_SCSI_SenseTypeDef;

4.3瑞萨

__I,__O__ROM也是core_cmxx.h中定义,大量使用位域

1.#ifndef__IM/*!<   Fallback for older CMSIS versions                                         */
2.  #define __IM     __I
3. #endif
4. #ifndef __OM                              /*!<   Fallback for older CMSIS versions                                         */
5.  #define __OM     __O
6. #endif
7. #ifndef __IOM                             /*!<   Fallback for older CMSIS versions                                         */
8.  #define __IOM    __IO
9. #endif

1./**
2. * @brief R_BUS_CSa [CSa] (CS Registers)
3. */
4.typedef struct
5.{
6.    __IM uint16_t RESERVED;
7.
8.    union
9.    {
10.        __IOM uint16_t MOD;            /*!<   (@ 0x00000002) Mode Register                                              */
11.
12.        struct
13.        {
14.            __IOM uint16_t WRMOD : 1;  /*!<   [0..0] Write Access Mode Select                                           */
15.            uint16_t             : 2;
16.            __IOM uint16_t EWENB : 1;  /*!<   [3..3] External Wait Enable                                               */
17.            uint16_t             : 4;
18.            __IOM uint16_t PRENB : 1;  /*!<   [8..8] Page Read Access Enable                                            */
19.            __IOM uint16_t PWENB : 1;  /*!<   [9..9] Page Write Access Enable                                           */
20.            uint16_t             : 5;
21.            __IOM uint16_t PRMOD : 1;  /*!<   [15..15] Page Read Access Mode Select                                     */
22.        } MOD_b;
23.    };
24.
25.    union
26.    {
27.        __IOM uint32_t WCR1;             /*!<   (@ 0x00000004) Wait Control Register 1                                    */
28.
29.        struct
30.        {
31.            __IOM uint32_t CSPWWAIT : 3; /*!<   [2..0] Page Write Cycle Wait SelectNOTE: The CSPWWAIT value
32.                                          *   is valid only when the PWENB bit in CSnMOD is set to 1.                   */
33.            uint32_t                : 5;
34.            __IOM uint32_t CSPRWAIT : 3; /*!<   [10..8] Page Read Cycle Wait SelectNOTE: The CSPRWAIT value
35.                                          *   is valid only when the PRENB bit in CSnMOD is set to 1.                   */
36.            uint32_t               : 5;
37.            __IOM uint32_t CSWWAIT : 5;  /*!<   [20..16] Normal Write Cycle Wait Select                                   */
38.            uint32_t               : 3;
39.            __IOM uint32_t CSRWAIT : 5;  /*!<   [28..24] Normal Read Cycle Wait Select                                    */
40.            uint32_t               : 3;
41.        } WCR1_b;
42.    };
43.
44.    union
45.    {
46.        __IOM uint32_t WCR2;           /*!<   (@ 0x00000008) Wait Control Register 2                                    */
47.
48.        struct
49.        {
50.            __IOM uint32_t CSROFF : 3; /*!<   [2..0] Read-Access CS Extension Cycle Select                              */
51.            uint32_t              : 1;
52.            __IOM uint32_t CSWOFF : 3; /*!<   [6..4] Write-Access CS Extension Cycle Select                             */
53.            uint32_t              : 1;
54.            __IOM uint32_t WDOFF  : 3; /*!<   [10..8] Write Data Output Extension Cycle Select                          */
55.            uint32_t              : 1;
56.            __IOM uint32_t AWAIT  : 2; /*!<   [13..12] CS Assert Wait Select                                            */
57.            uint32_t              : 2;
58.            __IOM uint32_t RDON   : 3; /*!<   [18..16] RD Assert Wait Select                                            */
59.            uint32_t              : 1;
60.            __IOM uint32_t WRON   : 3; /*!<   [22..20] WR Assert Wait Select                                            */
61.            uint32_t              : 1;
62.            __IOM uint32_t WDON   : 3; /*!<   [26..24] Write Data Output Wait Select                                    */
63.            uint32_t              : 1;
64.            __IOM uint32_t CSON   : 3; /*!<   [30..28] CS Assert Wait Select                                            */
65.            uint32_t              : 1;
66.        } WCR2_b;
67.    };
68.    __IM uint32_t RESERVED1;
69.} R_BUS_CSa_Type;                      /*!<   Size = 16 (0x10)                                                          */

五.总结

结论就是正如很多嵌入式编程规范所描述的(比如MISRA),一般不建议使用位域,因为涉及到位域的访问,存储等行为都是实现定义的,不具备可移植性。

嵌入式领域寄存器的定义也最好不要使用位域,到寄存器级别以寄存器操作为单位即可,每个寄存器都要使用__IM,__OM,__IOM描述。

如果一定要使用位域可以使用-fstrict-volatile-bitfields选项,使用GCC测试可以保证按照固定指定大小访问,但是不保证其他编译器也支持该选项,最好能不使用就不使用位域。


审核编辑黄宇

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 嵌入式
    +关注

    关注

    5087

    文章

    19148

    浏览量

    306180
  • 寄存器
    +关注

    关注

    31

    文章

    5357

    浏览量

    120689
  • 编译器
    +关注

    关注

    1

    文章

    1636

    浏览量

    49173
  • MISRA
    +关注

    关注

    0

    文章

    21

    浏览量

    6983
  • 嵌入式编程
    +关注

    关注

    0

    文章

    27

    浏览量

    10335
收藏 人收藏

    评论

    相关推荐

    IC设计:ram的应用-异步时钟宽转换

    在进行模块设计时,我们经常需要进行数据宽的转换,常见的两种转换场景有同步时钟宽转换和异步时钟宽转换。
    的头像 发表于 11-23 16:41 891次阅读
    IC设计:ram的应用-异步时钟<b class='flag-5'>域</b><b class='flag-5'>位</b>宽转换

    使用问题

    在支持操作的单片机中,如C51,使用定义变量或者寄存器,操作方便并且节约空间。 问题1:但是很多单片机不支持操作,仍然使用
    发表于 09-16 22:25

    MISRA C编程规范标准有什么规则要求

    如何衡量代码是否满足某些标准?MISRA C编程规范标准有什么规则要求
    发表于 04-19 07:20

    所不知道的关于iPhone7的几个真相,看完让所有人震惊!

    Phone7发布这么久了,用的还好吗?希望你的iPhone不要出现掉漆等不好的现象。你们在使用的过程中有没有发现iPhone7什么不为人知的秘密呢?如果没发现的话,下面我就来告诉大家关于iPhone7的一些
    发表于 02-23 09:40 3450次阅读

    什么是分层架构的依据与原则?本文告诉答案!

    分层架构是运用最为广泛的架构模式,几乎每个软件系统都需要通过层(Layer)来隔离不同的关注点(Concern Point),以此应对不同需求的变化,使得这种变化可以独立进行;此外,分层架构模式还是隔离业务复杂度与技术复杂度的利器,《领域驱动设计模式、原理与实践》写道:
    发表于 07-27 14:16 7486次阅读
    什么是分层架构的依据与原则?<b class='flag-5'>本文告诉</b><b class='flag-5'>你</b>答案!

    如何区分FPGA与CPLD?本文告诉答案!

    如何区分CPLD或FPGA和哪一个更适合自己?这是一个老生常谈的问题,尤其是学生和初学者。如果您也在这个问题上很迷茫,那么就请听小编为您区分FPGA与CPLD。
    发表于 09-04 14:16 2241次阅读
    如何区分FPGA与CPLD?<b class='flag-5'>本文告诉</b><b class='flag-5'>你</b>答案!

    智能音箱究竟有哪些用处?本文告诉答案!

    在许多人心中一直以来都有个困惑,智能音箱到底有什么作用?无可否认,智能音箱能够让我们解放双手,只通过语音就能够进行操作。智能音箱也能够打通许多智能家居通道,消费者能够舒服的躺在沙发上控制家中灯光、电视、空调等电器是多么惬意的一件事。但这里有一个问题,能用智能音箱办到的事情,使用智能手机也一样可以做到,两者似乎在这一点上有很多功能重合。
    发表于 09-20 16:46 2394次阅读

    怎么分辨机器人种类 本文告诉答案

    自20世纪中期出现的第一台现代机器人后,随着相关技术的快速演进,时至今日,机器人已广泛地被应用在许多不同的领域,来协助或取代人类完成各式各样的工作。依照不同的需求,将机器人做出不同的分类,以达到有效区分或辨识他们的目的。
    发表于 01-14 15:41 5012次阅读

    2018的量子计算是怎么跟AI一起两开花的 本文告诉答案

    回首刚刚过去的2018,如果让我回答一个“科技产业怎么看”的问题。那答案应该是这样的:上看AI,下看IoT,近看5G,远看量子计算,千万不要看区块链,因为太乱,看了容易上头。
    发表于 01-15 10:16 863次阅读

    怎么放置洗衣机最好 本文告诉答案

    洗衣机可以说是家里很重要的一种电器了,没有洗衣机的时候,人们只能辛辛苦苦的用手来洗衣服,不仅伤手而且效率还很低,但是自从有了洗衣机,我们的生活的确是方便了很多。不过,通常人们喜欢把洗衣机放在阳台上,但是小编不建议大家这样做,最好还是放在卫生间。
    发表于 02-15 11:40 785次阅读

    智慧城市该如何建设 本文告诉答案

    智慧城市建设兴起于欧美地区,世界各国都将发展智慧城市定为未来几年的目标。我国虽起步较晚,但在政府的支持和企业的参与下,智慧城市建设也取得阶段性进展,在我国目前已有超过500个城市开展了相关建设。随着人工智能、云计算、大数据等技术成熟,我国智慧城市发展将逐步向数据共享、万物互联、生态共赢迈进。
    发表于 05-29 08:53 1312次阅读
    智慧城市该如何建设 <b class='flag-5'>本文告诉</b><b class='flag-5'>你</b>答案

    文告诉单相电机运转无力该如何检测

    本文小编告诉大家单相电机运转无力的3个检测方法。
    的头像 发表于 12-14 21:28 1990次阅读

    文告诉为什么电机过载保护元件常用热继电器?

    小编告诉大家电机过载保护元件常用的是热继电器,因为它能满足一些要求
    的头像 发表于 12-14 22:09 2741次阅读

    文告诉什么是电机短时运行?

    本文小编告诉大家什么是电机短时运行。
    的头像 发表于 12-14 22:12 3452次阅读

    服务器ups运行时间,图文告诉关于UPS电源的一些基础知识

    原标题:图文告诉关于UPS电源的一些基础知识UPS - Uninterrupted Power System利用电池化学能作为后备能量,在市电断电等电网故障时,不间断地为用户设备提供(交流)电能
    发表于 11-08 19:21 8次下载
    服务器ups运行时间,图<b class='flag-5'>文告诉</b><b class='flag-5'>你</b>关于UPS电源的一些基础知识