一、xC概念

xC作为一种多核语言,是在C语言的基础上进行的扩展。扩展的部分有基于任务的并行机制、通信机制、精确的时钟、I/O以及安全的存储空间管理功能。xC与C语言之间可以交叉编程,只不过,xC不支持goto、位域、函数指针等功能。编译平台为xTIME Studio ,由XMOS官网免费提供。

二、xC语法

这里只是介绍与C语言不同的部分,其他大部分特征与C语言相同。

  1. <:用于给端口赋值、发送数据;:>接收数据。
  2. 通过par{}语句建立平行任务。
    par ( size_t i = 0; i < 4; i ++)task (i);
    等价于
    par {task (0) ;task (1) ;task (2) ;task (3) ;}
  3. 任务可以对事件做出中断响应,其关键字为select{case xxx:   break; . . .},其语法类似于C语言中的switch语句。该结构同一时间只能执行一个event。
  4. xC只支持整数算术类型,所以要用到小数就直接化成分数的形式比如0.25就是1/4,需要同时传递两个整数。

三、调用关系

  1. xC调用C中的函数时候,需要在xC中申明其函数:

    extern "c" {extern int f(); // 这个函数是在某个C文件中定义的
    }void g() {par {f();f();}
    }
  2. 当从C调用到xC时,xC中的一些新类型在C中不可用,但是可以使用在编译链接时能够转换为xC类型的C类型。               例如,可以将一个xC中的chanend传递给一个C中接受int或unsigned int参数的函数。xTIMEcomposer套件中包含的xccompat.h头文件包含有用的宏和typedef,它们在其他语言中可自动扩展为正确的类型。

四、多核的运行机制

多任务在main函数中以平行的方式定义:

int main ( void ) {par {task1 (... 参数 ...) ;task2 (... 参数 ...) ;task3 (... 参数 ...) ;}
}

其中task1、task2等函数名可以任意命名,一般这些任务函数无返回值,其基本结构如下:

void task1 (... 参数 ...) {... 初始化代码...while (1) {... 主循环 ...}
}

1. 任务间的通信方式

a.显式通信:

(1)一般的通信方式:task1准备通信→task2进入查询task1状态→task2接受task1传递过来的数据→传递完成→task1和task2各自运行各自的程序。

(2)共享存储空间:task之间不能通过共享公共变量来通信(比如全局变量),所以那些互锁、互斥、信号量之类的就不能使用了。所以,当task1和task3之间需要同步信号或者通信,就需要一个“中介任务”——task2,task2中有一段data,可以同时让task1和task3访问,这种访问方式和共享内存一样快。

(3)异步通信:通过buffer可以实现异步通信,task1将要通信的数据放入buffer中,无阻塞的执行其它工作去了,task2需要的时候从buffer中提取数据即可。实现的具体方法有两种:内部通知机制,task1向task2发出notifications(“通知”)后,干其他事去了,一旦task2收到通知,会给task1一个回馈;“中介任务”方式,使用task3作为一个管理FIFO的任务,task1、task2都与task3通信。

b.基于事件的通信

2. 底层硬件结构

重要概念:只有在该tile上执行的代码才能直接访问该tile上的资源。

(1) Tiles

  1. tile与内存之间没有cache缓存,没有数据总线竞争、没有DMA,保证了执行时间的可预测性。一个tile包括多个核,每个核都可以执行代码。
  2. tile = 若干个核 + 1个参考时钟 + 一些存储空间 + 能够访问I/O子系统的端口。
  3. 所有的片上外围资源都是通过I/O子系统进行访问的,故不使用存储总线。
  4. 在xC中,目标的底层硬件平台提供了一组名称,可用来引用系统中的tile。这些在platform.h头文件中声明:提供一个名为tile的数组。因此,系统的tile可以称为tile[0]、tile[1]等。

(2) Cores

  1. tile中的所有core都说平行的,每个core的速度各异(看具体配置),但运行速度有个下限。
  2. 在xC中的引用方式tile[0].core[1] 或者 tile[1].core[7]。

(3) Timers

  1. 每一个tile都有一个指定参数的参考时钟,与其相关的是一个计数器。xC中定义一个timer类型来表示该计数器,用于读取计数值。

(4) Communication fabric(通信结构)

  1. 底层的xCONNECT结构用于任意tile中的任意core之间通信。

(5) I/O

  1. 每个tile都有它自己的I/O子系统,该子系统由端口(ports)时钟块组成,在xC中定义为port和clock类型。

3. 平行任务和通信

重要概念:多个任务可以运行在一个core上(合作式多任务处理),一个任务也可以运行在多个core上(可分配任务distributable
 task)。

# include <platform .h>...
int main () {par {on tile [0]: task1 ();on tile [1]. core [0]: task2 ();on tile [1]. core [0]: task3 ();}
}

(1) 内部任务通信的三种方式

  1. Channels(通道):任务间同步传输无类型数据。(硬件最底层抽象,同一个core上的任务之间不能使用)

    void task1 ( chanend c) {c <: 5;//从通道c发送数据5
    }void task2 ( chanend c) {
    //1.如果使用select来接收数据select {case c :> int i://从通道c接收数据到i中printintln (i);break ;}
    //2.如果使用其他方式来接收数据int x;c :> x;
    }int main ( void )
    {chan c;//建立通道类型变量par {task1 (c);task2 (c);}return 0;
    }

    注意:缺省状态下,Channel的I/O是同步的,也即是阻塞的。而Streaming Channel就可以解决这个问题,它建立了一个能够长期使用的路由方式,从而能够高效的异步传输数据。

  2. Streaming Channels(流通道):任务间异步传输无类型数据,任务间提供1~2Bytes的FIFO用于缓存。(硬件最底层抽象,同一个core上的任务之间不能使用)使用方法和Channel类似,关键字变成了streaming chan。
  3. Interfaces(接口):任务间传输指定类型的数据。可在同一个core上的任务之间使用。比如notifications异步信号传输。接口提供了结构化的、并且灵活的通信方式。接口可以有返回值和参数,整个结构由接口、客户端和服务器端构成:
    //客户端
    void task1 ( client interface my_interface i)// 'i' 是客户端的连接
    {i.fA (5, 10) ;
    }//接口
    interface my_interface {void fA( int x, int y);void fB( float x);
    };
    void task2 ( client interface my_interface j)
    {... ...}//服务器端
    void task3 ( server interface my_interface i,server interface my_interface j)// 等待 fA or fB 通过 'i'链接过来.
    {
    select {case i.fA( int x, int y):printf (" Received fA: %d, %d\n", x, y);break ;case i.fB( float x):printf (" Received fB: %f\n", x);break ;case j.Function(...)://......}
    }int main ( void )
    {interface my_interface i;//建立接口类型变量interface my_interface j;par {task1 (i);task2 (j);task3 (i, j);}return 0;
    }

    注意:①只有一个task可以使用连接的服务器端,也只有一个task可以使用客户机端。多个task(Client端)可以连接到同一个task(Server端);②notification提供了一种从Server端到Client端的独立的通信方式,而且是异步非阻塞的。以下程序提供了一种带有notification功能的接口:

    interface if1 {void f( int x);//常规函数[[ clears_notification ]]//定义函数的属性int get_data ();//被指定属性的函数:这里用于清除通知[[ notification ]] slave void data_ready ( void );//通知函数:必须是无返回值且无参数
    };

    程序中data_ready(void)前面的的slave表明这是由Server端发送到Client端的通知,对于这种通知,可以在Server中调用发送函数data_ready(),而在Client中列出select接口进行处理:case i. data_ready (): ... break;——这些和前面讲的Client端到Server端的传输处理刚好反过来。接口函数可以有返回值、函数参数可以是数组(详见P27),使用数组时,Server端就相当于直接对数据进行引用,可直接读写数组中的数据,直接操作数组(因为数组是在Client端建立的,所以可用作Client和Server之间的高速通信,比如在Server端使用memcpy()函数,效率非常高)。

(2) 内部任务的三种类型

  1. 常规任务:任务在core上运行,并独立于其他任务运行。这些任务具有可预测的运行时间,并且能够非常有效地响应外部事件。
  2. 组合式任务:同一个core上运行的多个任务,可以组合。core可以基于多任务交换索引来运行各个任务,进行任务切换。这个索引是编译器提供的。如果一个task内部死循环中出现select语句,则表明该task将会持续响应这些事件。符合了这个特征,再添加个组合属性,就成了组合任务。组合任务必须为无返回值,最后的语句必须为while(1)+select结构。
    [[ combinable ]]//组合属性
    void counter_task ( const char * taskId ) {int count = 0;timer tmr ;unsigned time ;tmr :> time ;
    while (1) {// 此任务执行特定次数的定时计数,然后退出select {case tmr when timerafter ( time ) :> int now :printf (" Counter tick at time %x on task %s\n", now , taskId );count ++;time += 1000;break ;}}
    }int main () {par {on tile [0]. core [0]: counter_task (" task1 ");on tile [0]. core [0]: counter_task (" task2 ");}return 0;
    //或者如下,由par来指明哪些是组合任务
    void f() {[[ combine ]]par {counter_task (" task1 ");counter_task (" task2 ");}}
    }

    一旦确定为联合任务,编译器首先执行每个任务的初始化部分,然后进入一个主循环,这个主循环内包括了每个联合任务的死循环部分。联合任务之间的通信必须使用接口interface类型。联合函数内部可以调用其他联合函数。

  3. 分布式任务:任务可以运行在多个core上,分布式任务不会运行在任何特定的core上,而是运行在调用它的任务的core上。分布式任务也必须在一个包含select的死循环中,其select结构下的case只能响应接口(Interface)传输。使用到的所有core如果都是位于同一个tile上,则效率会更高,更节省资源。

4. 定时器

外部参考时钟,经过tile上的定时器,确定了每个core的运行速度。每个core的定时器相互独立,基本的使用方法如下所示:

timer t;//定义一个定时器变量
uint32_t time;//定义局部变量t :> time;//读取当前core的定时器值

定时器可以触发事件(event),在select结构内实现,语法为case timer when timerafter ( value ) :> [ var | void ] :。定时功能与组合任务结合使用,效果更佳。

uint32_t x, y;
timer t;
const uint32_t period = 100000; // 100000 代表 1ms,100MHz主频下
...
select {case t when timerafter (x) :> void ://void表示触发时,忽略定时器的实际值// 处理定时器事件...time += period ;//定时间隔执行break ;case t when timerafter (x) :> int y://y表示触发时,定时器的精确值// 处理定时器事件...break ;
}

5. 事件(event)处理

事件的高级功能分为事件保护(Guards)、强制优先级属性([[ordered]])、default、重复case、选择函数。

(1)事件保护

表达式为 case expr => ... :,其中,当expr不为零的时候,case后面的事件才能被触发执行。接口加保护的时候需要在接口的内部函数名前面加上[[guarded]]属性。

(2)强制优先级属性

在select结构前加上[[ordered]]属性,可使得从上到下的case语句的有优先级特性,其优先级从上到下依次递减。不能用于组合、分部函数。

(3)缺省值(default)

为select结构增加缺省的default选项,使得如果在第一次执行select时没有准备好其他事件,则会触发该缺省值。不能用于组合、分部函数。

(4)重复case

重复case相当于case+for循环:case(size_t i = 0; i < 5; i ++)

(5)选择函数

选择函数是select结构和函数的复合体,在函数名前加上select关键字,函数内部可根据case的不同触发不同的事件。也可以直接在函数体内部使用select结构,进而通过case选择调用不同或者相同的函数,如果调用相同函数,从而可使得被调用函数以不同的参数执行。

6. 数据处理和内存安全

(1)数据处理

主要是引用这一块需要注意,其他都是浮云。

(2)内存安全

包括运行时检查、边界检查、并行使用检查和指针。这里重点说下指针,其他容后再议。

指针分为4种类型:restricted, aliasing, movableunsafe,它们也是指针的关键字。定义指针的时候如果没有定义指针类型,那么它将会有一个缺省值,缺省值不是固定的,依据情况而定:

全局变量 restricted
参数 restricted
局部变量 aliasing
函数返回值 必须显式声明,但不能为restricted

a. 别名指针(aliasing):要跟踪指向已分配内存的指针,或可能导致对内存的无效并行访问的指针,编译器必须能够跟踪指针aliasing。当两个程序元素引用同一内存区域时,会产生别名。①不能将别名指针传递到par中的不同任务;②不能间接访问别名指针(例如指向别名指针的指针);③全局指针不能是别名指针,因为无法跟踪别名。

int f() {int i = 5, j = 7;int *p = &i; //局部变量默认是别名指针int *q = &j; p = q; // 可以复制return i + *p + *q;
}

b. 受限指针(restricted):在C和xC中都有受限指针的概念。它是指,一个指针没有别名,即访问内存位置的唯一方法是通过该指针。在C语言中受限指针不会进行检查,默认其访问方式是无别名的(没有其他访问途径),但是在xC中,必须进行严格的检查,以确保受限指针访问的唯一性:①如果原始变量的地址已经赋值给了一个受限指针,则程序无法通过原始变量访问内存,操作原始变量的唯一途径就是通过受限指针了;②不能重新分配或者复制受限指针。

int i = 5;
int * restrict p = &i;//全局变量默认是受限指针,这里可以不说明
int * restrict q;
// 该函数中的指针p默认为受限指针
void f1( int *p) {i = 7; // ERR 应该写成 *p = 7;printf ("%d", i);// ERR 应该写成printf ("%d", *p);p = &j; // ERR 不能从新分配地址给受限指针q = p;  // ERR 不能复制受限指针
}void f2( int *px, int *py) { ... }//默认为受限指针,px和py不能指向同一块内存void g() {f1(&i);f2(&i, &i);// ERR 指向同一块内存(相当于复制指针)
}

注意:①一个受限制的指针可以传递给一个带有别名指针参数的函数;②如果受限指针没有发生任何的“别名操作”,且别名指针没有指向全局变量,则别名指针可以以参数的形式传递给受限指针。

c.转移指针(movable ):将所有权转移到一个全局变量,以便稍后使用;在并行运行的任务之间转移所有权。其限制:只能以一种方式被传输;它们保留受限指针(无别名)属性,并且永远不能引用已分配的内存,只能指向初始时指定的内存。

注意:当一个接口被声明为包含带有指针参数的函数时,它不能跨tile使用(因为tile有独立的内存空间)。受限指针和别名指针只能在事务区间内使用,不能扩大或者转移别名范围,否则就得使用转移指针。转移的基本意义就是一个task放弃对某一块内存的所有权,从而转移给另一个task。

d. 不安全指针(unsafe):与C语言指针兼容的指针,由程序员自己维护其安全性。不安全函数只能从不安全区域调用。可以通过将一个复合语句标记为不安全,从而使局部区域不安全。

unsafe void f( int * unsafe x) {
//可以在这里取消对x的引用,但是要小心——它可能指向垃圾printintln (*x);
}void g( int * unsafe p) {int i = 99;unsafe {//建立不安全区域p = &i;f(p);}f(p);// ERR 不能从这里取消引用p或调用f
}

注意:在不安全区域内,可以显式地将不安全指针转换为安全指针。从一个任务写入不安全指针并从另一个任务读取指针,这种操作是一种不确定的行为。

XMOS学习笔记之xC语言相关推荐

  1. 23 DesignPatterns学习笔记:C++语言实现 --- 2.2 Adapter

    23 DesignPatterns学习笔记:C++语言实现 --- 2.2 Adapter 2016-07-22 (www.cnblogs.com/icmzn) 模式理解 1. Adapter 定义 ...

  2. Java快速入门学习笔记9 | Java语言中的方法

    有人相爱,有人夜里开车看海,有人却连LeetCode第一题都解不出来!虽然之前系统地学习过java课程,但是到现在一年多没有碰过Java的代码,遇到LeetCode不知是喜是悲,思来想去,然后清空自己 ...

  3. Java快速入门学习笔记8 | Java语言中的数组

    有人相爱,有人夜里开车看海,有人却连LeetCode第一题都解不出来!虽然之前系统地学习过java课程,但是到现在一年多没有碰过Java的代码,遇到LeetCode不知是喜是悲,思来想去,然后清空自己 ...

  4. Java快速入门学习笔记7 | Java语言中的类与对象

    有人相爱,有人夜里开车看海,有人却连LeetCode第一题都解不出来!虽然之前系统地学习过java课程,但是到现在一年多没有碰过Java的代码,遇到LeetCode不知是喜是悲,思来想去,然后清空自己 ...

  5. Java快速入门学习笔记3 | Java语言中的表达式与操作符

    有人相爱,有人夜里开车看海,有人却连LeetCode第一题都解不出来!虽然之前系统地学习过java课程,但是到现在一年多没有碰过Java的代码,遇到LeetCode不知是喜是悲,思来想去,然后清空自己 ...

  6. Java快速入门学习笔记2 | Java语言中的基本类型

    有人相爱,有人夜里开车看海,有人却连LeetCode第一题都解不出来!虽然之前系统地学习过java课程,但是到现在一年多没有碰过Java的代码,遇到LeetCode不知是喜是悲,思来想去,然后清空自己 ...

  7. [编译原理学习笔记2-2] 程序语言的语法描述

    [编译原理学习笔记2-2] 程序语言的语法描述 文章目录 [编译原理学习笔记2-2] 程序语言的语法描述 [2.3.1] 上下文无关文法 [2.3.2] 语法分析树与二义性 [2.3.3] 形式语言鸟 ...

  8. 23 DesignPatterns学习笔记:C++语言实现 --- 1.2 AbstractFactory

    23 DesignPatterns学习笔记:C++语言实现 --- 1.2 AbstractFactory 2016-07-21 (www.cnblogs.com/icmzn) 模式理解   1. F ...

  9. 【学习笔记】C++语言程序设计(郑莉):多态性

    [学习笔记]C++语言程序设计(郑莉):多态性 1. 多态性 2. 运算符重载 2.1 运算符重载的规则 2.2 运算符重载为成员函数 2.3 运算符重载为非成员函数 3. 虚函数 3.1 一般虚函数 ...

最新文章

  1. python项目开发案例集锦 豆瓣-Python第三个项目:爬取豆瓣《哪吒之魔童降世》 短评...
  2. CSDN-Code平台使用过程中的5点经验教训
  3. Centos下机器学习算法Mahout库的安装和示例
  4. 如何将eclipse设置为炫丽的全黑背景!
  5. Ladda – 把加载提示效果集成到按钮中,提升用户体验
  6. Python 基础练习
  7. 单例模式(饿汉式和懒汉式)
  8. 3-5:类与对象中篇——默认成员函数之运算符重载
  9. 搭建深度学习推荐系统实战
  10. 3dmax材质通道插件_3dmax插件外挂神器【疯狂模渲大师蓝色经典版】第九章:渲染后期教程...
  11. 应用推荐:Fluent Reader
  12. 企业微信api,企业微信sdk接口调用实现消息收发
  13. 联想Thinkpad sl400 7HC入手感觉
  14. 电脑位数(32位或者64位)问题导致eclipse不能正常启动
  15. 电脑计算机为什么不是有效程序,电脑提示“不是有效的win32应用程序”是什么原因【解决方法】...
  16. VS找到w3wp.exe 附加到23wp.exe进行debug程序调试
  17. 12113个岗位争夺AI人才!中国成为AI岗位空缺最多的国家
  18. android 禁用触摸屏,animation时禁用所有触摸屏交互
  19. NAND FLASH加载ramdisk文件系统
  20. LoadRunner 的简单使用

热门文章

  1. JavaMail邮件回复
  2. Hadoop MapReduce 与关系型数据库的区别
  3. 冷原子量子计算机,量子计算新突破:在71个格点超冷原子量子模拟器中求解施温格方程...
  4. 10KV配电工程电力监控系统的设计及应用
  5. .net core生成PDF文件,iTextSharp使用
  6. T2080 U-BOOT与OS内核移植 准备篇(一)——开发调试环境简介
  7. 神犇营-26-最大数输出
  8. 一维前缀和与二维前缀和
  9. DIV+CSS基础教程笔记
  10. 从2300块月薪的女工,到年薪80万的程序员,我花了10年