[置顶] Z-STACK之OSAL_Nv非易失性存储解读上

      本章解读Z-STACK中关于Nv操作的源码,以及z-stack中Nv的使用!

      在Z-STACK中Nv存储器主要用于保存网络的配置参数,如网络地址,使 系统在掉电重启仍然能读取一些参数,自动加入到原来的网络中,这样其网络地址没有变化!

     在z-stack中,每一个参数的配置对应的是一个Nv条目(item),每一个item都有自己的ID,z-stack中使用的条目ID范围如下:

    0x0000                              保留

    0x0001~0x0020              操作系统抽象层(OSAL)

    0x0021~0x0040              网络层(NWK)

    0x0041~0x0060              应用程序支持子层(APS)

    0x0061~0x0080              安全(Security)

    0x0081~0x00A0             Zigbee设备对象(ZDO)

    0x00A1~0x0200             保留

    0x0201~0x0FFF              应用程序

    0x1000~0xFFFF              保留

  如果是我们自己的应用程序中需要使用Nv,则定义其ID在0x0201~0x0FFF 范围内!

Z-STACK真正提供给用户使用的是五个函数:(在OSAL_Nv.h中声明)

1    void osal_nv_init( void *p );

2    uint8 osal_nv_item_init( uint16 id, uint16 len, void *buf );

3    uint8 osal_nv_read( uint16 id, uint16 offset, uint16 len, void *buf );

4    uint8 osal_nv_write( uint16 id, uint16 offset, uint16 len, void *buf );

5    uint16 osal_nv_item_len( uint16 id );

第1个函数在系统初始化的时候被调用,我们在应用程序中不用管!

第2个函数是我们在使用Nv时,初始化某个条目,如osal_nv_item_init(TEST_NV,1,NULL);

第3个函数是Nv读取某一个条目的数据,将其存储在buf中

第4个函数创建一个Nv条目(如果条目的ID不存在,如果存在,就将原来的item数据部分覆盖),并向其中写入数据

第5个函数是查询某一个item的数据长度。

 真正我们使用的是第2~4个函数。使用如下:

unsigned char value_read;

unsigned char value = 0x18;

osal_nv_item_init(TEST_NV,1,NULL);//NULL表示初始化的时候,item数据部分为空

osal_nv_item_write(TEST_NV,0,1,&value);

osal_nv_item_read(TEST_NV,0,1,&value_read);

value_read的值便是0x18,记住在write之前必须要初始化item,即调用osal_nv_item_init函数

 

下面我们打开OSAL_Nv.c源文件,通过分析源代码,就知道Z-STACK是如何抽象的封装出以上几个API,这对我们以后写程序还是很有帮助的!

 

在解读源码之前,必须要知道存储Nv条目的6个page如何存储Nv的,即其item在page中的结构和布局!

首先每一个page都有一个osalNvPgHdr_t结构体的头

typedef struct
{
  uint16 active;
  uint16 inUse;
  uint16 xfer;
  uint16 spare;
} osalNvPgHdr_t;     其中的几个成员稍后在做解释!

在这8个字节的page头部之后才是item的存储位置。而每一个item都有一个8字节的头部

typedef struct
{
  uint16 id;
  uint16 len;   // Enforce Flash-WORD size on len.
  uint16 chk;   // Byte-wise checksum of the 'len' data bytes of the item.
  uint16 stat;  // Item status.
} osalNvHdr_t;    从后面注释就知道了每一个成员变量的含义

 

然后我们还必须得知道几个全局变量和数组的含义:OSAL_NV_PAGES_USED值为6,即6个page

uint16 pgOff[OSAL_NV_PAGES_USED];

Offset into the page of the first available erased space.  每一个page的可用数据的偏移量

uint16 pgLost[OSAL_NV_PAGES_USED];

 Count of the bytes lost for the zeroed-out items.  为0数据的item的字节

uint8 pgRes;

Page reserved for item compacting transfer.     item 压缩传输的 保留page

uint8 findPg;

Saving ~100 code bytes to move a uint8* parameter/return value from findItem() to a global.

用一个全局变量能节省100字节的空间,指示某一个item对应的page

uint8 failF;  这个变量最用最后再解释!

 

 

在系统初始的时候调用osal_nv_init函数,它有调用initNV()函数,这个函数的作用就是初始化NV flash page,那在初始化中都做了什么呢?

 for ( pg = OSAL_NV_PAGE_BEG; pg <= OSAL_NV_PAGE_END; pg++ )
  {
    HalFlashRead(pg, OSAL_NV_PAGE_HDR_OFFSET, (uint8 *)(&pgHdr), OSAL_NV_HDR_SIZE);

    if ( pgHdr.active == OSAL_NV_ERASED_ID )
    {
      if ( pgRes == OSAL_NV_PAGE_NULL )
      {
        pgRes = pg;
      }
      else
      {
        setPageUse( pg, TRUE );
      }
    }
    else  // Page is active.
    {
      // If the page is not yet in use, it is the tgt of items from an xfer.
      if ( pgHdr.inUse == OSAL_NV_ERASED_ID )
      {
        newPg = pg;
      }
      // An Xfer from this page was in progress.
      else if ( pgHdr.xfer != OSAL_NV_ERASED_ID )
      {
        oldPg = pg;
      }
    }

    // Calculate page offset and lost bytes - any "old" item triggers an N^2 re-scan from start.
    if ( initPage( pg, OSAL_NV_ITEM_NULL, findDups ) != OSAL_NV_ITEM_NULL )
    {
      findDups = TRUE;
      pg = OSAL_NV_PAGE_BEG-1;
      continue;
    }

}

先看看这个for循环,循环每一个page,然后读取其page头部存储在pgHdr中,如果其active成员为
OSAL_NV_ERASED_ID(0xFFFF),表示此page还没有被激活(想想我们的flash中没写的数据每一位为1,一字节就为0xFF,active占2个字节)。如果此页没有激活,且此时pgRes为OSAL_NV_PAGE_NULL(0),则我们不激活此page,而是将此页作为后面压缩的保留页,如果pgRes不为0,即已经有了保留页,则将此page激活,且使此页投入以后使用中,调用setPageUse( pg, TRUE );我们看看这个函数

osalNvPgHdr_t pgHdr;

  pgHdr.active = OSAL_NV_ZEROED_ID;

  if ( inUse )
  {
    pgHdr.inUse = OSAL_NV_ZEROED_ID;
  }
  else
  {
    pgHdr.inUse = OSAL_NV_ERASED_ID;
  }

  writeWord( pg, OSAL_NV_PAGE_HDR_OFFSET, (uint8*)(&pgHdr) );

调用此函数激活page,即使active为OSAL_NV_ZEROED_ID为0x0000,如果inUse为TRUE,则置其inUse为OSAL_NV_ZEROED_ID(0x0000),表示此页投入使用中!否则置为OSAL_NV_ERASED_ID(0xFFFF),表示弃用该页!最后调用writeWord,将pgHdr头写进page的头部位置!

 

①(与上面的①对应,表示if和else

如果该page 的active为OSAL_NV_ZEROED_ID(0x0000),此page 为激活状态,此时检查此page是否投入使用中,如果其inUse为OSAL_NV_ERASED_ID(0xFFFF),即没有投入到使用中,那么If the page is not yet in use, it is the tgt of items from an xfer.//将其作为后面压缩传输的目标,即使newPg = pg;

 

如果此页的xfer不为OSAL_NV_ERASED_ID(0xFFFF),表明其处于Xfer的过程中,(有时候机器意外断电,而此时刚好有page在Xfer过程,那么page的xfer位就为非0xFFFF,即0x0000)。这个时候 我们使 oldPg = pg;

 

然后调用了initPage( pg, OSAL_NV_ITEM_NULL, findDups ),这个函数有什么用呢?我们先看其代码:

static uint16 initPage( uint8 pg, uint16 id, uint8 findDups )
{
  uint16 offset = OSAL_NV_PAGE_HDR_SIZE;
  uint16 sz, lost = 0;
  osalNvHdr_t hdr;

  do
  {
    HalFlashRead(pg, offset, (uint8 *)(&hdr), OSAL_NV_HDR_SIZE);

    if ( hdr.id == OSAL_NV_ERASED_ID )
    {
      break;
    }
    offset += OSAL_NV_HDR_SIZE;
    sz = OSAL_NV_DATA_SIZE( hdr.len );

      if ( (offset + sz) > OSAL_NV_PAGE_FREE )
    {
      lost += (OSAL_NV_PAGE_FREE - offset + OSAL_NV_HDR_SIZE);
      offset = OSAL_NV_PAGE_FREE;
      break;
    }

    if ( hdr.id != OSAL_NV_ZEROED_ID )
    {
      if ( id != OSAL_NV_ITEM_NULL )
      {
         if ( (id & 0x7fff) == hdr.id )
        {
          if ( (((id & OSAL_NV_SOURCE_ID) == 0) && (hdr.stat == OSAL_NV_ERASED_ID)) ||
               (((id & OSAL_NV_SOURCE_ID) != 0) && (hdr.stat != OSAL_NV_ERASED_ID)) )
          {
            return offset;
          }
        }
      }
      else
      {
        if ( hdr.chk == calcChkF( pg, offset, hdr.len ) )
        {
          if ( findDups )
          {
            if ( hdr.stat == OSAL_NV_ERASED_ID )
            {
               uint16 off = findItem( (hdr.id | OSAL_NV_SOURCE_ID) );

              if ( off != OSAL_NV_ITEM_NULL )
              {
                setItem( findPg, off, eNvZero );  // Mark old duplicate as invalid.
              }
            }
          }
          else if ( hdr.stat != OSAL_NV_ERASED_ID )
          {
            return OSAL_NV_ERASED_ID;
          }
        }
        else
        {
          setItem( pg, offset, eNvZero );  // Mark bad checksum as invalid.
          lost += (OSAL_NV_HDR_SIZE + sz);
        }
      }
    }
    else
    {
      lost += (OSAL_NV_HDR_SIZE + sz);
    }
    offset += sz;

  } while ( TRUE );

  pgOff[pg - OSAL_NV_PAGE_BEG] = offset;
  pgLost[pg - OSAL_NV_PAGE_BEG] = lost;

  return OSAL_NV_ITEM_NULL;
}

代码有点长!其实这个函数的最用通过注释就知道了,Walk the page items; calculate checksums, lost bytes & page offset. 对于某个page,逐个item地计算其checksums,lost bytes,然后计算page offset!再看下其返回值

If 'id' is non-NULL and good checksums are found, return the offset   of the data corresponding to item Id; else OSAL_NV_ITEM_NULL.  如果id值不为0,且校验和正确就返回和此item的数据的偏移量,否则返回OSAL_NV_ITEM_NULL(0)

那么在initNV的for循环中

 if ( initPage( pg, OSAL_NV_ITEM_NULL, findDups ) != OSAL_NV_ITEM_NULL )
    {
      findDups = TRUE;
      pg = OSAL_NV_PAGE_BEG-1;
      continue;
    }

这个if语句干什么的呢?知道了initPage的返回值,不难理解其用途!如果if为真,即initPage返回的值为OSAL_NV_ERASED_ID(0xFFFF)

initPage执行到下面一句

else if ( hdr.stat != OSAL_NV_ERASED_ID )
{
            return OSAL_NV_ERASED_ID;
}

此时Any "old" item immediately exits and triggers the N^2 exhaustive initialization.为什么呢?因为如果是id为0,那么该处的hdr.stat值应该为0xFFFF,如果某种意外情况导致其不为0xFFFF,则说明出了问题,得重新去初始化所有的item(即检查他们的头部)

 

回归到上面,如果initPage返回值为OSAL_NV_ERASED_ID(0xFFFF),则

      findDups = TRUE;
      pg = OSAL_NV_PAGE_BEG-1;
      continue;

置findDups为TRUE,那么在下次调用initPage的时候就会去初始化所有item,然后pg =OSAL_NV_PAGE_BEG-1

for循环从开头执行! 这就是for循环中的代码,重要的是记住newPg 和oldPg ;

 

接下来

if ( newPg != OSAL_NV_PAGE_NULL )
  {
     if ( pgRes != OSAL_NV_PAGE_NULL )
    {
      setPageUse( newPg, TRUE );
    }
    else if ( oldPg != OSAL_NV_PAGE_NULL )
    {
      pgRes = newPg;
    }

     if ( oldPg != OSAL_NV_PAGE_NULL )
    {
      compactPage( oldPg );
    }

}

newPage保存的是inUse为OSAL_NV_ERASED_ID(0xFFFF)即还没有投入使用中的页,如果有这样的page,我们再进行下一步判断pgRes,如果其值不为OSAL_NV_PAGE_NULL,即保留了某一个page为compact xfer page。

这个时候调用setPageUse( newPg, TRUE );即使其inUse为OSAL_NV_ZEROED_ID(0x0000),此页将投入使用中。如果pgReg为OSAL_NV_PAGE_NULL(此时所有的page均激活了),且某一页其xfer为OSAL_NV_ZEROED_ID,其保存在oldPg中,此时们将newPg 赋值给pgRes,即将newPg作为compact的保留page(此时newPg没有投入使用中),接下来如果oldPg中保存了xfer被打断了的page,则调用compactPage( oldPg ),将其进行压缩!

有这段注释:

/* If a page compaction was interrupted and the page being compacted is not
     * yet erased, then there may be items remaining to xfer before erasing.
     */

 

看下这个函数代码:

static void compactPage( uint8 srcPg )
{
  uint16 dstOff = pgOff[pgRes-OSAL_NV_PAGE_BEG];
  uint16 srcOff = OSAL_NV_ZEROED_ID;
  osalNvHdr_t hdr;
  writeWordH( srcPg, OSAL_NV_PG_XFER, (uint8*)(&srcOff) );

  srcOff = OSAL_NV_PAGE_HDR_SIZE;

  do
  {
    uint16 sz;
    HalFlashRead(srcPg, srcOff, (uint8 *)(&hdr), OSAL_NV_HDR_SIZE);

    if ( hdr.id == OSAL_NV_ERASED_ID )
    {
      break;
    }

    srcOff += OSAL_NV_HDR_SIZE;

    if ( (srcOff + hdr.len) > OSAL_NV_PAGE_FREE )
    {
      break;
    }

    sz = OSAL_NV_DATA_SIZE( hdr.len );

    if ( hdr.id != OSAL_NV_ZEROED_ID )
    {
      if ( hdr.chk == calcChkF( srcPg, srcOff, hdr.len ) )
      {
        setItem( srcPg, srcOff, eNvXfer );
        writeBuf( pgRes, dstOff, OSAL_NV_HDR_SIZE, (byte *)(&hdr) );
        dstOff += OSAL_NV_HDR_SIZE;
        xferBuf( srcPg, srcOff, pgRes, dstOff, sz );
        dstOff += sz;
      }

      setItem( srcPg, srcOff, eNvZero );  // Mark old location as invalid.
    }

    srcOff += sz;

  } while ( TRUE );

  pgOff[pgRes-OSAL_NV_PAGE_BEG] = dstOff;
  erasePage( srcPg );

  setPageUse( pgRes, TRUE );
  pgRes = srcPg;
}

首先 Mark page as being in process of compaction. 标志该页正在压缩处理中!

然后依次读取srcPg中的每一个item,然后对每一个item进行处理,处理过程如下:

1,如果item的id不为OSAL_NV_ZEROED_ID(0x0000),如果id为0x0000,则直接跳到步骤4

对其进行和校验,如果正确的话转下一步,如果不正确转到步骤3

2,调用setItem( srcPg, srcOff, eNvXfer );设置item 的状态位为激活状态,即使其stat位为OSAL_NV_ACTIVE(0x00),然后调用writeBuf( pgRes, dstOff, OSAL_NV_HDR_SIZE, (byte *)(&hdr) );将该item头部八字节写进pgRes页的dstOff处,此页为保留页,记住此时我们已经从前面的步骤中划分出了一个page为pgRes。最后调用xferBuf( srcPg, srcOff, pgRes, dstOff, sz );将该item的数据部分从srcPg中转移到pgRes中,其中sz为item的数据长度。转下一步

3,调用setItem( srcPg, srcOff, eNvZero );标记srcPg中这些被转移的item为invalid,即将他们的id全部置0,函数中最后调整了pgLost数组中该page的lost bytes,即为该item的数据长度!

4,调整srcOff, srcOff += sz;即指向下一个srcPg的item。

经过上述步骤,就处理完了srcPg中的所有item,将他们都转移到pgRes中,其实就是压缩的是其中那些id为0x0000的item。

 

 pgOff[pgRes-OSAL_NV_PAGE_BEG] = dstOff;调整pgRes的pgOff;

 

erasePage( srcPg );擦出被compact的page,

 

setPageUse( pgRes, TRUE );   // Mark the reserve page as being in use. 

 

 pgRes = srcPg;  // Set the reserve page to be the newly erased page.

 

这样compactPage就完成了,还记得它前后完成的工作吧!

 

 

继续回到initNV函数最后一个if语句:

if ( pgRes == OSAL_NV_PAGE_NULL )
  {
    for ( pg = OSAL_NV_PAGE_BEG; pg <= OSAL_NV_PAGE_END; pg++ )
    {
      erasePage( pg );
    }
    initNV();
  }
 

/* If no page met the criteria to be the reserve page:
   *  - A compactPage() failed or board reset before doing so.
   *  - Perhaps the user changed which Flash pages are dedicated to NV and downloaded the code
   *    without erasing Flash?
   */

如果没有一个page满足“标准”称为the reserve page 那么将所有Nv page擦出掉,然后重新初始化NV。

至此initNV()函数完成!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

   

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/547997.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ElementUI+Java实现搜索提示列表

效果 实现流程 首先我们需要在后端获取数据&#xff0c;我们可以根据name属性去模糊查询&#xff0c;返回Map类型的列表 然后将它返回给前端。 controller ApiOperation("根据关键字查询讲师名列表")GetMapping("list/name/{key}")public ResultVo sele…

CentOS7 搭建Pulsar 消息队列环境,CentOS(Linux)部署Pulsar,亲测成功,以及Python操作Pulsar实例驱动

在 最佳开源数据库与数据分析平台奖 中&#xff0c;之前曾连续两年入选的 Kafka 意外滑铁卢落选&#xff0c;取而代之的是新兴项目 Pulsar&#xff0c;Bossie Awards中对 Pulsar 点评如下&#xff1a;“Pulsar 旨在取代 Apache Kafka 多年的主宰地位。Pulsar在很多情况下提供了…

为什么要学python语言、学完有什么好处_学编程为什么首选Python?学完Python的优势有哪些?...

原标题&#xff1a;学编程为什么首选Python&#xff1f;学完Python的优势有哪些&#xff1f;Python 是一门更注重可读性和效率的语言&#xff0c;尤其是相较于 Java&#xff0c;PHP 以及 C 这样的语言&#xff0c;它的这两个优势让其在开发者中大受欢迎。正是由于 Python 易于上…

SpringSecurity +Jwt 实现权限管理

目录标题原理架构图demo的项目结构JwtTokenUtilRestAuthenticationEntryPoint 和 RestfulAccessDeniedHandlerMyUserDetailsServiceJwtAuthenticationTokenFilterSecurityConfigControllerPostman 测试为了方便&#xff0c;未采用是从数据库读取的方式。工具类都是别人那偷的&a…

FreeMarker_模板引擎_代码自动生成器_源码下载

首先我们先来认识一下Freemarker 1.what is the FreeMarker? 你可以到freemarker的官网上去&#xff0c;那里有很详细的介绍&#xff1a;http://freemarker.org/ 这里大概说一下&#xff1a;FreeMarker是一个用Java语言编写的模板引擎&#xff0c;它基于模板来生成文本输出。 …

CentOS7 搭建Kafka消息队列环境,以及Python3操作Kafka Demo

Kafka适合什么样的场景? 它可以用于两大类别的应用: 构造实时流数据管道&#xff0c;它可以在系统或应用之间可靠地获取数据。 (相当于message queue)构建实时流式应用程序&#xff0c;对这些流数据进行转换或者影响。 (就是流处理&#xff0c;通过kafka stream topic和topi…

hive序列生成_常见的序列化框架及Protobuf原理

享学课堂作者&#xff1a;逐梦々少年转载请声明出处&#xff01;上次我们详细的学习了Java中的序列化机制&#xff0c;但是我们日常开发过程中&#xff0c;因为java的序列化机制的压缩效率问题&#xff0c;以及序列化大小带来的传输的效率问题&#xff0c;一般很少会使用原生的…

Linux(CentOS 7)安装docker

此文转载&#xff1a;https://www.cnblogs.com/qgc1995/p/9553572.html&#xff0c;感谢原作者写出了这么棒的Docker部署文章。 我是虚拟机装的Centos7&#xff0c;linux 3.10 内核&#xff0c;docker官方说至少3.8以上&#xff0c;建议3.10以上&#xff08;ubuntu下要linux内…

decode语句不能再嵌套_自学C++基础教程【流程控制语句】(for、while 、do while 语句 )...

for语句for语句是C语言所提供的一种功能广泛的循环语句。下图为for语句的标准形式&#xff1a;表达式1&#xff1a;通常用于给循环变量赋初值&#xff0c;一般是赋值表达式。表达式2&#xff1a;通常用于设立循环条件&#xff0c;一般为关系表达式或逻辑表达式。表达式3&#x…

CentOS 7 利用Docker搭建禅道系统

1&#xff0c;系统环境 a&#xff0c;操作系统 CentOS Linux release 7.6.1810 (Core) 64位 b&#xff0c;确保Docker环境已经安装&#xff0c;具体教程请看 CentOS 安装docker 禅道系统一键安装说明文档&#xff1a;http://www.zentao.net/book/zentaopmshelp/90.html …

centos7 docker删除端口映射_centos7安装docker,结合docker安装mysql,学习简单使用

需要快速安装centos7的可以结合上一遍文章vagrant结合virtualbox让你直接在cmd窗口操作linux系统centos7地址&#xff1a;https://www.toutiao.com/i6858180977164812811/?group_id6858180977164812811Docker先说一下个人理解&#xff1a;docker其实就是一个工具&#xff0c;镜…

MongoDB中关于64位整型存储解决方案

为什么80%的码农都做不了架构师&#xff1f;>>> 社区内一哥们smcboy 提出关于php中操作MongoDB存储整数问题&#xff0c;找到点资料花点时间翻译过来&#xff0c;是个很好的学习方式。红薯 那篇讨论我的修改回复&#xff0c;仍然没有更新可恶啊~&#xff01;&#…

滑动窗口--单调队列

给定一个大小为 n≤106 的数组。 有一个大小为 k 的滑动窗口&#xff0c;它从数组的最左边移动到最右边。 你只能在窗口中看到 k 个数字。 每次滑动窗口向右移动一个位置。 以下是一个例子&#xff1a; 该数组为 [1 3 -1 -3 5 3 6 7]&#xff0c;k 为 3。 窗口位置 最小值…

CentOS7 源码编译安装MySQL8.0.15 shell脚本

使用MySQL8无需像MySQL5那样需要Boost依赖&#xff0c;和经过35-55分钟的等待编译完成&#xff0c;直接解压即可使用&#xff0c;方便快捷&#xff01; 1&#xff0c;环境&#xff1a; 操作系统 CentOS Linux release 7.6.1810 (Core) 64位 服务器环境 “腾讯云”服务器…

切割图形_泉州泡沫景观字切割机厂家

泉州泡沫景观字切割机厂家 jz4rw0qv泉州泡沫景观字切割机厂家 巨源线条切割机同步带型结构合理、性能、精密度高、、操作简便、价格合理&#xff0c;比同行业同款机床更高&#xff0c;是原有同步带型泡沫切割机的替代产品。自动编程使用计算机利用配合切割机应用&#xff0c;只…

你的搜索其实很糟糕?

为什么80%的码农都做不了架构师&#xff1f;>>> 日期&#xff1a;2013-3-27 来源&#xff1a;GBin1.com 尽管你非常擅长搜索&#xff0c;但是很多时候搜索内容和你想要的并不吻合。事实上&#xff0c;用户体验专家Jakob Nielsen认为大多数人都非常的不擅长搜索。…

Element Tree型控件

效果 前端 <template><div class"app-container"><el-inputplaceholder"输入关键字进行过滤"、<! -- 双向绑定-- >v-model"filterText"></el-input><el-tree ref"tree":data"subjectList"…

快速根据注释生成接口文档网页工具——Apidoc的使用教程

环境&#xff1a; 操作系统 CentOS Linux release 7.6.1810 (Core) 64位 服务器环境 “腾讯云”服务器 1&#xff0c;安装Node.js的npm工具环境&#xff1a; 如有不懂&#xff0c;请看我的博客&#xff1a;CentOS7 源码编译安装NodeJS 最新版本 2&#xff0c;npm环境搭…

频段表_5G频段范围之:频段3.3GHz-4.2GHz (n77,n78)

本文版权归“5G通信(tongxin5g)”和5G哥所有&#xff0c;未经授权&#xff0c;请勿转载比起以前的移动通信网络&#xff0c;5G探索的新频谱范围包括&#xff1a;3.3GHz-4.2GHz&#xff0c;4.4GHz-5.0GHz&#xff0c;24.25-29.5 GHz今天主要看频段3.3GHz-4.2GHz在3GPP中&#xf…

Signals Slots(Qt5)

>Signal-Slot的作用是对象间的通信; Signals-Slots机制是Qt的核心特性, 也可能是Qt和其他大多数框架提供的特性不同的部分; 介绍 >GUI编程中, 当我们改变了一个widget,经常希望另一个widget能被通知到; 通常我们希望各种对象间能互相通信. Example: 用户点击了CLOSE按钮,…