陌生的天花板

     也许是受了少许遗传因素或家庭传统的影响,从小就不安份于蛰居家中。时常抱着一枕少年的幻想与好奇,随着父母四处游历名山大川;稍大后更是少年不知愁滋味,任性的将时间和金钱挥洒于壮美山河之间,生活只是在旅行与筹划旅行之间切换。

    而在习惯了现代化的飞机、汽车,亦或传统一些的徒步、马帮旅行之后,回过头看看一千年前的中世纪,现代意义上旅行这个概念形成之初,会发现我们所谓的旅行实际上早已没有自以为的冒险或辛劳,更多的只是一种体验方式或生活态度。

 s2370556  

    《中世纪的旅行》并不能算是一本故事性非常强的书。某种程度上来说,他是将中世纪的客观世界和主观认知,在旅行这个层面做了一个切片,然后以旅行方式、行者的心态和回忆中的幻想,展示给我们这些所谓的现代旅行者。

    在旅行方式上,传统的交通工具和并不完善的路途保障,导致旅行的距离和时间之比,将人们的视野限制在一个狭隘的范围之内。源自于古罗马大道的路网体系,仅依赖于地方性领主和修道院来维护,必然导致旅行目的地可达的局部性。而时不时爆发的局部战争、四处游荡的匪徒、难以穿越的茂密森林和茫茫沙漠,致使长途旅行变成一项风险极大的行为。

    因而在行者的动机和心态上,贸易和宗教,是这样恶劣的环境下,驱使其继续前行的重要原因。朝圣者、僧侣和十字军,抱着单纯或并不单纯的目的,在几百年间不停的,向着远方神许之地进发。在那个传统和时间几乎停滞的时代,试图改善自我生存状态的人,只能选择这种冒险的长途迁徙,来获取心灵上的救赎或荣誉,以及更为现实的物质上的财富。

    当然,在这期间,也掺杂着与我们这个时代类似,将旅行作为一种游历的青年;为君王或雇主服务的信使和官员;以及将旅行作为生活本身的探险家和漂泊者。甚至一些朝圣者和大学生,也抱着类似的心态不停在城市间游荡。虽然千年已逝,但人性和生活本质上基本还是没有太大改变的。:)

    但因为信息量和交流上的限制,彼时的人们对世界的认识,是狭隘和充满幻想的。种种关于遥远世界的传言和揣测,在主观偏见和客观无知的影响下,形成一个混乱和无序的整体认知。但想想当今,纵然有精确到米的地图,和顷刻跨越千里的交通工具,大众对所谓世界的认知,又何尝不是如此。坐在家中,看着电视里精心剪辑的节目,或翻着层层筛选后的书籍,我们有着近乎于古人想象中神灵的感知能力。但在这呈现给我们的世界背后,同样夹杂着偏见和无知,只不过隐式的被应用于选材、剪辑和我们的遥控器动作中罢了。

    这也是为什么我一直倾向于,旅行时一定到当地去以当地人的方式与他们交流。走一样的路,吃一样的饭菜,干一样的活,平等的心态跟他们交流,才能感受到世界的真实存在。否则坐在大巴上看着旅行攻略,和中世纪酒馆里吟游诗人的传说,又有什么本质上的区别呢?

    但就像中世纪游历的青年游历骑士所感受的,在旅行中从未知到未知的无限可能性,对遥远世界的向往和体验,以及早上醒来时陌生的天花板,大概可以算是现代意义上旅行的魅力所在吧。

发表在 Uncategorized | 18条评论

中世纪的思维盛宴



@douban.com

@amazon.com


在这样一个懒散的冬日午后,一个充溢着阳光的房间里靠在桌前,耳边流淌着许巍悠远的歌声在诉说着什么,实在是阅读一本关于中世纪的历史书的最合适的情形。那一个个熟悉又陌生的名字,争先恐后的从书中跃起,在阳光中的尘埃里,大声宣读着他们的理念,自满、虔诚、愚昧、睿智、狡诈;却又随着书页的翻动,慢慢淡去只留下一个剪影,共同组成了那个波澜壮阔的时代。

断断续续用了大半年时间,才结束了这场中世纪的思维探险之旅。一方面是因为大量从前没有接触过的信息和思想,需要时间去深入了解和思考;另一方面也是因为一直不忍读得太快,希望能将思维的盛宴拖的尽可能长一些。

长久以来,在我等被传统教育摧残几至麻木的理科学生眼里,中世纪 (The Middle Age)欧洲的代名词几乎就是黑暗时代 (Dark Ages)、大瘟疫 (Great Plague),亦或百年战争 (Hundred Years’ War)、十字军远征 (Crusades) 这样的考点,以及罗马帝国
(Roman Empire)
的荣光、狮心王 (the Lion-Hearted) 的传奇或伊斯兰全盛时期 (Islamic Golden Age) 的神秘。这整整1000年(公元5-15世纪)仿佛被厚重的名为愚昧的迷雾所遮掩,直到名为文艺复兴 (Renaissance)
的闪电划过天际,欧洲才跃入了一个至今为人羡慕的黄金时代,并立刻与那无知的过去划清界限。

但偶尔在仰慕那些华丽的哥特式建筑 (Gothic Architecture)
的时候,也会悄悄给自己一个问题,那些理应在生存与死亡边缘挣扎的中世纪民众,何以在层层迷雾之中创建如此璀璨的艺术。而在阅读二战或当代关于欧洲民族问题的书籍时,也不时觉得他们那自认为理所当然的理由,似乎隐藏着什么不为我们所知的背书。英国和法国的互相讥讽,德国与法国的时代恩仇以及巴尔干的错综复杂,仿佛都有一个源头。反过来再翻看文艺复兴、工业革命以及现代大陆法系 (Civil
Law
)
和英美法系 (Common law) 等等至今对我们产生深远影响的事件和概念形成与发展,也无不与那被遗忘的时代有关。

 

正如作者在导言中说到:

 

“无论如何,中世纪都不是一个沉睡的、可怕的时代,而是一个充满变化的时代。公元600年的欧洲和公元1000年或1400年的欧洲有着截然不同的景象。

……

直到今天,中世纪时关于教育、政府、社会结构、社会公正的观念仍然影响着非欧洲国家。大学是中世纪的发明;而现在,大学这种教育机构遍布全球。美国、墨西哥、加拿大、以色列、日本,以及东欧的几个新的民主国家,其立法机构都可以说是源自于中世纪的国会和议会。当今中国、古巴、朝鲜等国的共产主义政体,也源西欧的思想;其中一部分可追溯至卡尔.马克思之前好几百年的中世纪晚期。

……

简而言之,西欧改变了整个世界,并将它带入我们身处其中的全球化文明;无论这种变化是好是坏,要了解他,我们必须回到中世纪来寻找一部分答案。在漫长的中世纪,欧洲从贫困而极不安定的农业社会发展成强盛而别具一格的文明社会,对当代世界的成形有着深远的影响。”

发表在 图书 | 2条评论

天堂里没有痛苦——yuannian 师兄,一路走好

    晚上参加完公司的培训,回到宿舍刚坐定开始看文档,忽然收到QuickMouse从MSN上发来噩耗,原白云黄鹤BBS站长yuannian去世了。于是匆忙登录已经年余未上的白云黄鹤,在sysop看到了站务公告QuickMouse的哀悼文章。为进一步获取详细情况,我又google到其父在文学论坛介绍的详情:
    一页一页看下来,我已不知该如何形容此时的感觉:几分惋惜,几分悲哀,几分愤怒!
 
    30 岁正值黄金年华,无法成功立业也罢,竟至白发人送黑发人。虽未有妻儿之累,但难以想像其老父母,如何度过痛失骄子的残生,如何面对冰冷的遗像。
    5 年的青春都献给了华为,1 年至经理,3 年至技术负责人。在发现白血病时却已至中晚期,后四个月骤然而逝。作为师弟,作为站友,作为IT同业人,我感到悲哀的同时感到无比的愤怒!
   
    首先,根据 google 的结果,白血病分急性和慢性两种:前者是急性发作,起病的时候有很多症状、体征,比如出血,很难说有潜伏期;后者症状不明显,可能让你没有觉察到,很多人是到了有严重的症状才去看病。如果每年有体检的机会,可能比较早期就能发现。通过血象检查,白血病是比较容易确诊的。也就是说,作为一个有几万名高科技人才的华为,一个有着各种完善业务流程的国际化公司,竟然没有一个最基本的,确保其核心资产:人力资源长期可用性的体检制度。而与之相似的更多所谓高科技企业,在这方面也令人深表怀疑。有幸的是我工作的两家企业,无论规模大小在这方面还是非常正规和严谨的,也说明这跟企业规模无关而取决于重视程度。
    其次,白血病的早期症状中,除了中后期的各种出血外,“持续头晕、高烧不退,脸色苍白、容易疲倦”等现象,实际上跟目前大部分 IT 人士长期处于的“亚健康”状态类似。以我个人经历为例,在到目前公司之前的大概两三年时间中,也是长期处于高度紧张的工作状态。其时除了高烧不退的情况较少外,实际上跟白血病早期的症状难以分辨。而一旦常年处于这种状态,人对健康的敏感程度也会逐渐下降,等到出现出血等严重症状时可能已经完了。而自己年少轻狂之时,又往往视健康为无物,期望以燃烧生命来弥补知识和技能上的缺陷。
 
    低头反思,自己五一前西藏之行也是折戟于轻视二字。仗着较为丰富的户外经验、几次高原徒步的成功经历,在休息和准备不足的情况下,贸然组队租车向世界屋脊——阿里进发。途中又低估平均四千多米海拔缺氧的严重性,最终因睡觉着凉发展为肺水肿。如果不是路遇几位好心的当地人,被两天三夜连续驱车赶路,送回拉萨住院就诊,估计在白云黄鹤上站务公告里面就是我的名字了。躺在拉萨总参医院的病床上,同样也是连坐起或翻身都困难的我,最终侥幸得还;但师兄却没有这么幸运,没有逃脱病魔的魔爪。
    好在经过一段长考,我痛定思痛开始尝试转变生活方式,而这次的濒死体验也进一步完善了自己的认识。期望其他朋友也能多以长远的眼光,来思考和规划自己的未来之路。
 
btw: 刚刚跟身在华为的另一位同学聊,据称他一位华为的博后师兄,前不久也不幸去世。而他业已准备在今年合同到期后,离职暂时回家修养。
 
    希望这样的事情不要在无谓的重复,yuannian 师兄,一路走好!
 
 

发表在 Uncategorized | 4条评论

another day

    又是平淡、充实的一天,睡到自然醒、美食、电影、羽毛球、麻辣烫,然后在许威悠远的歌声中享受着龙井的清香。但在快步穿过繁华的新街口商业街时,在高高跃起将一个后场球斜线扣死时,亦或隔着车窗看着夜色中快速略过的灯红酒绿时,我却似乎无法感觉到一丝的真实感,仿佛像是一个路过的灵魂在尘世中穿行。
    当地铁列车从身边呼啸而过时,我仿佛又听到了小黑湖清晨时暴风雪的尖啸;当骑车的路人在车窗外缓缓穿行时,我仿佛又看到了烈日下慢慢爬升的马队;那路边欢喜的孩子的笑脸,也不知觉中与带着高原红的同样天真的孩子重合。
    一霎那间,我仿佛能理解到兄弟连里,温斯特从伦敦地铁出来,在河边漫步,回旅舍在浴缸中泡热水时的那种奇妙感觉。那是一种由两个不同世界堆叠在一起的错乱感,是一种对世俗社会缺乏信任感和归属感的困惑,是一次对 Who am I 和 What am I doing 的质询。看来上一次的长考只管了半年
发表在 Uncategorized | 5条评论

NXT 学习笔记 [3] 获取 NXT 信息 (C++)

    这个系列文章因为开始涉及到太多技术细节,跟这儿的定位不符合,改到另外一个 blog 上连载了,呵呵
 
  作为一个开放式扩展平台,LEGO 提供了 NXT 相关的非常详尽的资料。而作为二次开发不可或缺的 SDK,当然也包括在其中。在其网站的 NXT’reme 栏目中可以下载到针对 C++ 开发环境的 Driver SDK,支持 Windows 和 MAC 系统。
    Driver SDK (fantomSDK1.0.2f0.zip 2.21MB)
   
  因为 NXT 提供了 USB 和蓝牙两种连接方式,所以为了提供同一的编程接口,LEGO 通过一个 DLL 对其进行了封装。这个 DLL 有一个很 cool 的名字 —— 饭桶 (fantom.dll) -_-b
  对 USB 方式的连接,LEGO 提供的驱动 fantom.sys 与 NXT 直接进行通讯; 对蓝牙方式,则是在进行配对 (pair) 后,把 NXT 设备映射到一个 COM 端口,然后通过 Win32 文件方式访问端口进行通讯。Microsoft 在  Robotics Studio 中对 NXT 的支持就是通过后者的方式实现的。前者的好处是速度快且一次安装后使用时无需配置;后者则更为灵活,能够无线通讯,且不需要依赖特定驱动的安装。
  对于我们日常开发来说,直接使用 fantom.dll 可以很大程度上简化工作,下面我们看看如何通过 fantom 来获取 NXT 的各种信息。
  
  安装 Drvier SDK 后,在 documentfantom.chm 里是基本封装类型的使用帮助,以及一个简单的示例程序代码;includes 里是 fantom 的头文件,以及 labview 的 vi 支持库,这个后面有机会再详细介绍。targetswin32Ui386msvc71release 里是需要 link 的库文件。
  
  首先我们需要建立一个 iNXTIterator 对象,此对象能够对注册到系统上的 NXT 设备进行遍历。

int _tmain(int argc, _TCHAR* argv[])
{
  tStatus status;

  std::cout << "枚举 NXT 设备…";
  // 建立一个迭代子遍历所有的 NXT 实例
  iNXTIterator *nxtIteratorPtr = iNXT::createNXTIterator(
    false /* 不搜索通过蓝牙连接的 NXT (仅应用 USB 连接 ) */,
    0     /* 忽略蓝牙搜索的超时时间 */,
    status );
  if (status.isFatal())
  {
    std::cout << "失败,错误码:" << status.getCode() << std::endl;
    return status.getCode();
  }
  else
  {
    std::cout << "成功!" << std::endl << std::endl;
  }
  for (; status.isNotFatal(); nxtIteratorPtr->advance(status))
  {
    iNXT* nxtPtr = nxtIteratorPtr->getNXT(status);
    if (nxtPtr && status.isSuccess())
    {
     // 获取 NXT 设备的信息
   
      iNXT::destroyNXT( nxtPtr );
    }
  }
  iNXT::destroyNXTIterator(nxtIteratorPtr);
}

   这里的 tStatus 对象是对各种错误状态的封装,因为 LEGO 提供的是调试版本的 DLL,所以还可以通过此对象取得错误发生所在的文件和行号 :S 不知道 LEGO 是不是准备后期把这块的代码也开放了。
   值得注意的是,在操作 NXT 时创建的各种 iterator 或普通对象,使用完后都必须调用相应的 destroy 方法释放,最好是用 C++ 封装一个类来管理,这里为了简洁暂且不管。
  
   在获得了 NXT 对象后,可以调用其 getDeviceInfo 和 getFirmwareVersion 获得设备的详细信息  

const size_t MAX_DEVICENAME_LEN = 16;

void dump(iNXT& nxt)
{
   ViChar name[MAX_DEVICENAME_LEN];
   ViByte bluetoothAddress[6];
   ViUInt8 signalStrength[4];
   ViUInt32 availableFlash;
   tStatus status;
   nxt.getDeviceInfo(name, bluetoothAddress, signalStrength, availableFlash, status);
   if (status.isFatal())
   {
     std::cerr << "获取 NXT 设备信息失败,错误码:" << status.getCode() << std::endl;
     return;
   }
   ViUInt8 protocolVersionMajor, protocolVersionMinor;
   ViUInt8 firmwareVersionMajor, firmwareVersionMinor;
   nxt.getFirmwareVersion(protocolVersionMajor, protocolVersionMinor,
     firmwareVersionMajor, firmwareVersionMinor, status);
   if (status.isFatal())
   {
     std::cerr << "获取 NXT 设备固件版本失败,错误码:" << status.getCode() << std::endl;
     return;
   }
 
   std::cout << "设备 " << (char *)name
     << ", protocol=" << (int)protocolVersionMajor << "." << (int) protocolVersionMinor
     << ", firmware=" << (int)firmwareVersionMajor << "." << (int)firmwareVersionMinor
     << ", Flash=" << std::setprecision(4) << (double)availableFlash / 1024 << "KB"
     << std::endl;
}

  
  因为前面指定只枚举 USB 方式连接的设备,因此这儿的蓝牙地址等信息暂时无用。
  
  输出信息大概如下:

D:StudyRobotLegoDemoDebug>LegoDemo.exe
枚举 NXT 设备…成功!

设备 NXT, protocol=1.124, firmware=1.3, Flash=50.32KB
  对每个 NXT 设备来说,都安装了很多缺省的模块用于对各种外设提供支持,我们可以通过 iModuleIterator 获取获得支持的模块列表。

const size_t MAX_MODULENAME_LEN = 20;

void dump(iModule& mod)
{
  ViChar name[MAX_MODULENAME_LEN];
  mod.getName(name);
  std::cout << "    模块 " << std::setw(sizeof(name)) << (char *)name
    << "tid=0x" << std::hex << mod.getModuleID() << std::dec
    << "tsize=" << mod.getModuleSize()
    << "tio=" << mod.getModuleIOMapSize() << std::endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
 // 枚举 NXT 设备
 std::cout << "  枚举设备支持模块…";
 
 iModuleIterator *modIteratorPtr = nxtPtr->createModuleIterator("*.mod", status);
 
 if (!modIteratorPtr || status.isFatal())
 {
   std::cout << "失败,错误码:" << status.getCode() << std::endl;
 }
 else
 {
   std::cout << "成功!" << std::endl;
 
   for (; status.isNotFatal(); modIteratorPtr->advance(status))
   {
     iModule *modPtr = modIteratorPtr->getModule(status);
 
     if (modPtr && status.isSuccess())
     {
       dump(*modPtr);        
 
       nxtPtr->destroyModule(modPtr);
     }
   }
 
   nxtPtr->destroyModuleIterator(modIteratorPtr);
 } 
}

  在我的 8527 上的模块列表大致如下,此列表在以后刷 firmware 的时候有可能会变化。
  id 是模块的唯一编号;size 是模块的大小,不过似乎没用上;io 是此模块映射的 I/O 地址空间大小。

设备 NXT, protocol=1.124, firmware=1.3, Flash=50.32KB
  枚举设备支持模块…成功!
    模块             Comm.mod   id=0×50001      size=0  io=1896
    模块            Input.mod   id=0×30001      size=0  io=80
    模块           Button.mod   id=0×40001      size=0  io=36
    模块          Display.mod   id=0xa0001      size=0  io=1720
    模块           Loader.mod   id=0×90001      size=0  io=8
    模块        Low Speed.mod   id=0xb0001      size=0  io=167
    模块           Output.mod   id=0×20001      size=0  io=100
    模块            Sound.mod   id=0×80001      size=0  io=30
    模块           IOCtrl.mod   id=0×60001      size=0  io=2
    模块          Command.mod   id=0×10001      size=0  io=32820
    模块               Ui.mod   id=0xc0001      size=0  io=40

  此外另一类重要信息是 NXT 上的文件列表,遍历的方式非常类似
const size_t MAX_FILENAME_LEN = 20;
void dump(iFile& file)
{
  tStatus status;
  ViChar name[MAX_FILENAME_LEN];
  file.getName(name);
  std::cout << "    文件 " << std::setw(sizeof(name)) << std::right << (char *)name << std::left
    << "tsize=" << std::setw(10) << std::left << file.getSize(status)
    << "tavailable=" << file.getAvailableSize(status) << std::endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
 // 枚举 NXT 设备
  status.clear();
  std::cout << "  枚举设备包含文件…";
  iFileIterator *fileIteratorPtr = nxtPtr->createFileIterator("*.*", status);
  if (!fileIteratorPtr || status.isFatal())
  {
    std::cout << "失败,错误码:" << status.getCode() << std::endl;
  }
  else
  {       
    std::cout << "成功!" << std::endl;
    for (; status.isNotFatal(); fileIteratorPtr->advance(status))
    {
      iFile *filePtr = fileIteratorPtr->getFile(status);
      if (filePtr && status.isSuccess())
      {
        dump(*filePtr);
        nxtPtr->destroyFile(filePtr);
      }
    }
    nxtPtr->destroyFileIterator(fileIteratorPtr);
  }
}

  注意在两次枚举之间,需要用 status.clear 显式重置状态,否则调用会失败。
  
  一个典型的文件列表如下,此列表包括在 My Files 菜单下的所有类型文件
  size 是文件的实际大小;available 是文件的可用大小,不过似乎也没有支持。

  枚举设备包含文件…成功!
    文件           config.txt   size=77         available=0
    文件        Timeglass.ric   size=76         available=0
    文件           hands2.ric   size=834        available=0
    文件             main.rxe   size=16930      available=0
    文件          Program.tmp   size=13         available=0
    文件         NVConfig.sys   size=1          available=0
    文件        RPGReader.sys   size=14346      available=0
    文件             Demo.rxe   size=9436       available=0
    文件        Try-Touch.rtm   size=3788       available=0
    文件        Try-Light.rtm   size=4456       available=0
    文件        Try-Sound.rtm   size=6864       available=0
    文件   Try-Ultrasonic.rtm   size=3756       available=0
    文件        Try-Motor.rtm   size=2630       available=0
    文件            Woops.rso   size=4699       available=0
    文件         faceopen.ric   size=316        available=0
    文件       faceclosed.ric   size=316        available=0
    文件        ! Startup.rso   size=8161       available=0
    文件          ! Click.rso   size=451        available=0
    文件      ! Attention.rso   size=1755       available=0

 
  实际上这种种的 API 调用,最终实现上都是通过相同的一套通讯协议完成的,也就是前面 NXT 信息中 protocol 版本所指定的协议。LEGO 在 Bluetooth Developer Kit (BDK) 中有一份 Appendix 1-LEGO MINDSTORMS NXT Communication protocol 文档详细介绍了这个协议。
  因此我们也可以直接使用此协议,向 NXT 发送命令,这也是 Microsoft Robotics Studio 之所以能通过蓝牙方式,跳过必须安装驱动的限制的原因。

void dump(iNXT& nxt)
{
 // 打印 NXT 基本信息
 
   ViUInt8 cmdPlayTone[] = { 0×03, 0×00, 0×18, 0×10, 0×00 };
   
   nxt.sendDirectCommand(
     false /* 此命令不需要等待响应 */,
     reinterpret_cast< ViByte* >( cmdPlayTone ), sizeof( cmdPlayTone ),
     NULL, 0 /* 没有响应缓冲区 */,
     status );
}

  上述代码会向 NXT 设备发送一个 Play Tone 的直接命令,发出一个短促的响声。回头有机会再详细分析吧 :)

发表在 机器人 | 10条评论

NXT 学习笔记 [2] 运行时数据

    前面曾经提到 NXT 的可执行文件由运行时数据 (Run-time Data)、调度 (Scheduling) 和字节码 (ByteCodes) 三部分组成。其中运行时数据可进一步分为数据空间目录 (Dataspace table of contents – DSTOC) 和数据空间缺省值 (Dataspace Defaults)。VM 在加载一个可执行文件时,会在对应的 32K 内存中划出一块专门用于存储数据空间 (Dataspace),这块内存是由一系列不同类型的数据项组成,直接对应与可执行文件中的 DSTOC,如果有缺省值的话则自动进行初始化。因而后期字节码访问数据空间时,可以直接使用进程唯一的 DSTOC 索引号定位实际数据。与字节码指令类似,可执行文件中的 DSTOC 和缺省值不被加载到内存中,直接在 Flash 卡上进行地址映射。
    而在数据空间中存储的数据项,其类型也是由 VM 预定义好的,直接在指令一级提供支持。
  
    最常见的简单数据类型就是整数了,NXT 支持 8/16/32 位有符号或无符号整数,以及一系列针对整数的算术操作符。布尔类型在 NXT 中直接以 unsigned byte 表示,0 表示 FALSE,其它值均为 TRUE。而浮点数、分数甚至复数之类都不在直接支持的范畴内,不过可以通过整数运算进行模拟,只是语法一级的问题罢了。
    较为复杂的数据类型是集合类型,NXT 提供内建数组 (array) 和簇 (cluster) 的支持。
    数组 (array) 是一组具有相同数据类型子元素的集合,并支持在运行时调整大小。例如可以定义一个长度为 0 的数组表示一组点,在运行时根据传感器采样进行扩展和填充。ASCII 字符串是一种特殊的 unsigned byte 数组,为兼容 C 的习惯其末尾会有一个 NULL(0) 作为结束符。
    簇 (cluster) 这是类似于其它语言中结构 (struct) 的概念,由一组不同类型子元素组合而成,一般被用于将相关数据封装到一起。
    NXT 中聚合类型是可以相互嵌套的,可以在一个簇里面定义几个不同类型的数组,也可以在将数组的元素类型定义为数组来模拟多维数组。
    此外 NXT 为多任务支持还提供了一种互斥量 (mutex) 类型。这种类型虽然实现上由一个 32bit 整数表示,但不能被用作聚集类型的子元素,因此只能独立存在并通过内建 OP_ACQUIRE/OP_RELEASE 指令进行操作。
  
    既然 DSTOC 将程序可用到的数据及其类型都已定义清楚,从 VM 角度就可以将之进一步分为运行时内存布局不会发生变化的静态数据,以及运行时需要进行调整的动态数据。前者包括 DSTOC 中绝大多数类型的数据,它们在编译期就有编译器根据情况确定了布局;后者则包括所有的数组类型,因为 NXT 提供动态调整数组大小的能力,所以它们在 DSTOC 中只是一个入口,由 VM 在运行器进行管理。
    落实到实现上,VM 将静态数据和动态数据放到两个不同的内存池中,前者在内存低地址区域固定,后者在内存高地址区域向下增长。如果动态内存区域超出内存池限额,系统会显示一个 File Error 的错误(不知道原因的话谁猜得出来)。
    而对程序的字节码来说,这些都是透明的,指令只需要使用全局唯一的 DSTOC 索引号即可访问到相应的数据项
  
    数据项地址 = 数据空间开始地址 + DSTOC[数据空间索引].offset
  
    对 VM 来说,静态数据基本上在编译期就确定了,它所需要关心的只是如何对动态数据进行管理。NXT 中引入了一个 dope vector (DV) 的概念,来针对每个动态数据项的变化进行跟踪。DV 结构的伪代码如下:
  
  typedef struct DopeVector {
   short Offset,    // 数组数据在内存中的偏移
   short ElementSize, // 数组子元素的大小
   short ElementCount, // 数组子元素的个数
   short BackPointer, // 未用,用于建立双向链表?
   short LinkIndex   // 下一个 DV 的索引
  } DV, *PDV;
  
    VM 维护了一个全局唯一的 dope vector array (DVA) 来跟踪在动态数据区域中分配的所有内存,所有 DV 又通过 DV.LinkIndex 以链表形式组织在一起,便于系统进行遍历和维护。当然这个 DVA 的存在对字节码来说是完全透明的,只是最大限度利用现有机制来实现自我管理。
    当字节码需要通过 DSTOC 索引访问数组时,通过 DSTOC 获得的内存偏移地址实际上指向的是一个 DV 索引,然后再根据 DV 索引从 DVA 中获取到实际的数组偏移地址。计算的方式如下:
  
  DV 索引地址 = 数据空间开始地址 + DSTOC[数据空间索引].offset
  数据项地址 = 数据空间开始地址 + DVA[DV 索引].offset
  
    通过这样一个两级索引定位,VM 可以在不需要对 DSTOC 和字节码做任何调整的情况下,根据运行时指令的需求修改数组的大小,因此所有的 DV 索引会跟 DSTOC 中的数组数据项绑定。
  
    此外对于嵌套实现的多维数组,除了第一维数组是在 DSTOC 中固定死的,其它每个子数组实际上在上一维中只是一个 DV 而已,字节码可以通过 OP_INDEX 等指令间接任意访问。
发表在 机器人 | 1条评论

NXT 学习笔记 [1] 系统架构

    熟悉了基本功能,接着可以看看一些技术层面的内容了,呵呵,毕竟买回来不是看手册拼模型的 :P
    Lego 这方面非常厚道,从系统结构到指令集,设置关键部件的线路图都有提供,看来是拼足了劲要把这摊子事搞大啊,呵呵
    这个手册虽然号称是可执行文件标准,但实际上对整个系统架构都做了比较详细的介绍。下面的大部分内容都是我通过阅读此手册总结或者翻译过来的,呵呵
 
    NXT 的运算核心是一个 32bit 的 ARM7TDMI 嵌入式 RISC 处理器,主频一般在 115MHz(0.18 μm)-133MHz(0.13 μm) 之间,理论上回头可以自己把它换成 236MHz(90 nm) 的 ARM7,甚至接口兼容的其它型号,ARM 9/9E/10 都提供对 ARM7 程序的二进制兼容性。存储则包括 256K Flash 和 64K RAM,容量上跟现在动辄几 G 的系统没有可比性,不过对于嵌入式系统来说勉强也算够用。此外还集成了一个 8bit 的 AVR 嵌入式 RISC 处理器,以及对应的 4K Flash 和 512B RAM (-_-b),估计是用于 I/O 或步进电机控制。
    NXT 的软件系统,主要是一个扮演 OS 角色的 ANSI C 编写的 Frimware,提供对各种传感器/步进电机的控制,并为二次开发提供虚拟机(VM)支持。用户编写的程序会被编译成 NXT 的字节码(Byte Code)指令,以后缀为 .RXE 的特定可执行文件格式存储在 Flash 上,在执行时由 VM 加载并解析,并提供 32K 的 RAM 供执行。
    NXT 的可执行文件主要分为运行时数据 (Run-time Data)、调度 (Scheduling) 和字节码 (ByteCodes) 三部分。而在执行时 VM 会根据需求,为前两部分提供对应的 RAM 空间,存储可变的运行时状态信息。
    NXT 的字节码允许用户执行常规的数学/逻辑运算、指令流控制以及 I/O 操作等等,基本上是一个精简版本的 RISC 指令集。这些指令将根据功能以名为 clump 的方式组织在一起,类似子过程。NXT 中较为特殊的指令调度信息,将提供各个 clump 分别包括哪些字节码、clump 的运行时状态以及当前 clump 执行后应加载的其它 clump。VM 会根据这些记录,在 clump 所依赖数据就绪后再调用之。clump 之间也可以相互调用,但这个调用过程必须是同步的,调用者需挂起直到调用返回。此外 clump 本身是不可重入的,如果两个不同 clump 需要调用一个共享 clump,则调用者必须通过互斥量 (Mutex) 确保不会重入,因此 clump 调用自身的递归操作也是不被支持的。
    NXT 之所以采用这样一个机制,估计是放弃了基于堆栈的思路,通过 clump 为单位来简化 VM 的设计。与之相对应的是其可视化编程环境中,常用操作都是被一个个小方块(Lego 积木?)封装起来,然后通过拖拉和逻辑组合来实现算法;这样一来实现上就较为简单,常用操作映射到 clump,组合逻辑映射到调度 (Scheduling)。不过基于其字节码支持访问内存的考虑,理论上不排除能对其架构进行改进,通过内存操作模仿堆栈来实现 clump 重入和递归的可能性。
    此外一个好消息是 VM 能够提供多个 clump 并行执行的支持,完成多个任务的 clump 在数据就绪的情况下能够逻辑上并行执行;同时一个坏消息则是虽然 VM 能在指令一级帮助解决资源冲突,但还是需要自己来判断并处理竞争条件。
  
    NXT 的程序运行可以分为四个阶段:
  
    1.验证 (Validation): 加载文件并验证版本和其它文件头信息
    2.激活 (Activation): 在 RAM 中分配并初始化运行时数据
    3.执行 (Execution): 解析文件中的字节码指令,根据调度信息运行相应 clump
    4.停止 (Deactivation): 重新初始化所有 I/O 系统和 RAM 中的数据结构
  
    值得注意的是在执行过程中,所有的字节码都不会被载入到内存,而是通过直接映射 Flash 卡到地址空间完成。这样的好处是内存只被用来保存可变得程序数据,坏处则是代码在执行的过程中无法被改变。
 
to be continue…
 
发表在 机器人 | 3条评论