XMOS软件开发入门(6) - xc语言(4)之事件处理以及任务间使用接口通讯

本篇目的

继续XMOS的程序开发-xc语言的任务间的通讯机制:使用接口的方式,以及xC的事件处理。

开发环境

  • 硬件平台使用官方评估板"xCORE VOCAL FUSION XP-VF3100-BASE"
  • IDE开发环境win10 下的 xTIMEcomposer

简介

C语言可以处理这样的事件:按键按下,定时器溢出,串口接收到数据。C语言一般通过中断/异常处理这样的事件。xC则是通过select的事件处理模式来处理这样的事件。

接口则是用于一个任务向另一个任务传递事务的方式,使用接口通讯的这两个任务,一个叫接口客户端,一个叫做接口服务端。比如接口客户端可以发起一个事务:键值为1的按键按下了。服务端通过接口可以获取到这个事务,并通过select事件处理方式对这个事务执行处理。

通过接口传递的事务是有类型定义的,这需要从接口说起。

接口定义

接口使用关键字interface定义,定义如下:

interface T {
    transaction1(...);
    transaction2(...);
    ...
};

interface是关键字,T是接口类型名称,interface体内的transaction1和transaction2等表示接口传递的事务,事务按照C函数那样定义,即函数名称和函数参数。比如:

interface my_interface {
    float add(float a, float b);  // 加法事务
    void inc(int& a);       // 自加1事务
};

定义了接口my_interface,事务可以传c语言支持的参数,可以传引用参数,可以带返回值,因此同一个接口可以用于任务之间的双向传递。

类似C语言定义了结构一样,还需要定义结构的实例才能使用;xC的接口也一样,使用时要定义接口实例,比如:

interface my_interface mi;  // 定义接口实例mi

select事件处理

语法如下:

select {
    case event1:  // 有event1事件
    	// 处理event1事件
    	break;
    case event2:  // 有event2事件
    	// 处理event2事件
    	break;
    ...
}

有点类似于C语言的没有default分支的switch case语句,但是只是写法有点像而已,实际上完全不一样。select处理事件,是阻塞等待事件发生的,如果没有任何一个case的事件发生的话,是阻塞不往下执行的。

event的写法和写接口实例的函数原型是一样的,比如接口interface my_interface定义了一个实例mi,那么

select {
    case mi.add(float a, float b) -> float ret:
    	// 处理add事件,也需要给返回值ret赋值
    	ret = 2;  // 给返回值赋值
    	break;
    case mi.inc(int& a):
    	// 处理inc事件
    	break;
}

注意语法case mi.inc(int& a):mi.inc(int& a)实例.函数(参数列表)这样的格式,是没有返回值的事件处理的写法。

case mi.add(float a, float b) -> float ret:这个是带有返回值的写法,即实例.函数(参数列表) -> 返回值类型 返回值变量。处理语句中给返回值变量赋值。

接口用于任务间通讯

上面简单的说明了接口和select事件处理之后,可以开始说明接口在任务间通讯的用法了。

前面简介中说:“接口则是用于一个任务向另一个任务传递事务的方式,使用接口通讯的这两个任务,一个叫接口客户端,一个叫做接口服务端。”

就是说接口用于任务间通讯包含3部分:

  • 定义的接口
  • 接口客户端
  • 接口服务端

下面以一个例子说明,还是用上面定义的my_interface接口,说明接口如何在两个任务间双向通讯,如何通过接口传递事务,select事件处理机制如何处理事务。

#include <stdio.h>
#include <platform.h>

/* 定义接口 */
interface my_interface {
    float add(float a, float b);  // 加法事务
    void inc(int& a);       // 自加1事务
};

/* 接口客户端 */
void task1(client interface my_interface i) {
    printf("task1: transaction add\n");
    float sum = i.add(1.2, 3.4);
    printf("task1: sum=%f\n", sum);
    int x = 5;
    printf("task1: transaction inc\n");
    i.inc(x);
    printf("task1: x=%d\n", x);
}

/* 接口服务端 */
void task2(server interface my_interface i) {
    while(1) {
        select {
            case i.add(float a, float b) -> float ret:
                printf("task2: handle event: add(%f, %f)\n", a, b);
                ret = a + b;
                break;
            case i.inc(int& a):
                printf("task2: handle event: inc(%d)\n", a);
                a++;
                break;
        }
    }
}

int main () {
    interface my_interface i;  // 定义一个接口实例,传给客户端和服务端
    par {
        task1(i);
        task2(i);
    }
    return 0;
}

执行结果

task1: transaction add
task2: handle event: add(1.200000, 3.400000)
task1: sum=4.600000
task1: transaction inc
task2: handle event: inc(5)
task1: x=6

首先看3个部分,定义了接口my_interface,之后interface my_interface就是一个接口类型,main函数给这个接口类型定义了一个实例 i,并把接口实例传递给并发的两个任务task1和task2,task1传递参数类型写成client interface my_interface,client表明是当客户端,task2传递参数写成server interface my_interface,server表明是服务端。

客户端发起事务传输,方式就跟调用c语言de 结构函数一样,i.add(1.2, 3.4)i.inc(x)都是发起事务,客户端发起事务,服务端就能接收到事务。

服务端处理接收到的事务,通过select事件处理机制处理。当客户端发出add事务之后,服务端收到这个事务,会执行对应的case的代码,这个语法i.add(float a, float b) -> float ret就是事件处理结果返回值传回去的方法,表示传回给ret这个float类型,case语句给ret赋值就可以了。select case语法中没有返回值的事件,则不需要写->的部分

从执行结果看正确的处理了1.2+3.4的事务,以及5自加1的事务。

接口用于任务间通讯的程序语法要求

不遵循要求就会产生编译时错误。

要求:

  • 传递给并行任务的接口必须有且只有一个客户端和一个服务端

    客户端必须在参数列表中用client interface表明;服务端必须在参数列表中用server interface表明。

  • 普通的事务只能从客户端发起(notification机制可以由服务端发起,后续再说)

  • select中的case要包含所有的事务处理

下面举几个错误的例子,假设上面的例子编程错误

  • task1漏写了client修饰符
void task1(interface my_interface i)

编译错误

…/src/mytest.xc:86:23: error: interface parameter must have server' orclient’ modifier

没有client修饰符,会认为没有客户端。

  • task2没写server修饰符,即
void task2(interface my_interface i)

编译错误和没有client修饰符一样。没有server认为是没有服务端。

  • 客户端多于一个或者服务端多于一个

增加一个task3做客户端或者服务端,编译时错误:

error: interface used in two tasks as client
或者
error: interface used in two tasks as server

  • task2服务端发起事务

即在task2中调用float sum = i.add(1.2, 3.4);发起事务,比如

void task2(server interface my_interface i) {
    float sum = i.add(1.2, 3.4);   // server端发起事务,错误

编译错误

error: trying to call interface function from a server interface

  • task2的select case中少处理一项事务,即
void task2(server interface my_interface i) {
    while(1) {
        select {
            case i.add(float a, float b) -> float ret:
                printf("task2: handle event: add(%f, %f)\n", a, b);
                ret = a + b;
                break;
        }
    }
}

编译错误

error: missing case for interface function inc' of interfacemy_interface’

只要写了select case处理了某个接口事件,就要把改接口要处理的所有事件case语句写全。要不就不要用select case处理该。比如删除task2中的select case块,编译是不会错误的。

void task2(server interface my_interface i) {
    while(1) {
    }
}
// 这样修改不会产生编译时错误
  • main函数只把接口实例传递给一个任务

比如以下的代码,main函数只把接口实例传递给task1或者task2,虽然不会产生编译时错误,只会警告,但是这样使用是没有任何意义的。

int main () {
    interface my_interface i;  // 定义一个接口实例,只传给一个任务
    par {
        task2(i);
    }
    return 0;
}

或者

int main () {
    interface my_interface i;  // 定义一个接口实例,只传给一个任务
    par {
        task1(i);
    }
    return 0;
}

编译结果都是警告

warning: `i’ not used in two parallel statements

但是这样的用法没什么意义。

任务间多个多个接口通讯

用于任务间通讯的接口,对于某单个的接口,遵循上面的原则就没问题;多个接口也是由多个遵循这些原则的接口组成的,任务可以是仅仅是某个接口的客户端,或者仅仅是某个接口的服务端,也可以是某个的接口的客户端,同时做另一个接口的服务端,只要遵循对于某个接口,有且只有一个客户端,有且只有一个服务端就可以了。

为了说明任务间使用多个接口通讯的工作方式,把例子简化点,使用两个接口,任务不对接口做任何操作,不发起事务,也不处理事务,便于理解多个接口做任务间通讯的工作方式。

  • 例子1

如下代码:

#include <stdio.h>
#include <platform.h>

/* 定义接口 */
interface my_interface {
    float add(float a, float b);  // 加法事务
    void inc(int& a);       // 自加1事务
};

/* 定义接口 */
interface you_interface {
    float mul(float a, float b);  // 乘法事务
    void dec(int& a);       // 自减1事务
};

/* 接口客户端 */
void task1(client interface my_interface i, client interface you_interface yi) {

}

/* 接口服务端 */
void task2(server interface my_interface i, server interface you_interface yi) {

}

int main () {
    interface my_interface i;  // 定义一个接口实例,传给客户端和服务端
    interface you_interface yi;  // 定义一个接口实例,传给客户端和服务端
    par {
        task1(i, yi);
        task2(i, yi);
    }
    return 0;
}

在前一个代码的基础上多定义一个you_interface接口,task1和task2任务都是使用两个接口实例,task1做两个接口的客户端,task2做两个实例的服务端。

接口i有且只有一个客户端task1,有且只有一个服务端task2;接口yi有且只有一个客户端task1,有且只有一个服务端task2。

  • 例子2

如下代码:

#include <stdio.h>
#include <platform.h>

/* 定义接口 */
interface my_interface {
    float add(float a, float b);  // 加法事务
    void inc(int& a);       // 自加1事务
};

/* 定义接口 */
interface you_interface {
    float mul(float a, float b);  // 乘法事务
    void dec(int& a);       // 自减1事务
};

/* 接口客户端 */
void task1(client interface my_interface i, server interface you_interface yi) {

}

/* 接口服务端 */
void task2(server interface my_interface i, client interface you_interface yi) {

}

int main () {
    interface my_interface i;  // 定义一个接口实例,传给客户端和服务端
    interface you_interface yi;  // 定义一个接口实例,传给客户端和服务端
    par {
        task1(i, yi);
        task2(i, yi);
    }
    return 0;
}

task1和task2任务都是使用两个接口实例,task1做i的客户端,yi的服务端,task2做i的服务端,yi的客户端。

接口i有且只有一个客户端task1,有且只有一个服务端task2;接口yi有且只有一个客户端task2,有且只有一个服务端task1。

  • 例子3

如下代码:

#include <stdio.h>
#include <platform.h>

/* 定义接口 */
interface my_interface {
    float add(float a, float b);  // 加法事务
    void inc(int& a);       // 自加1事务
};

/* 定义接口 */
interface you_interface {
    float mul(float a, float b);  // 乘法事务
    void dec(int& a);       // 自减1事务
};

/* 接口客户端 */
void task1(client interface my_interface i) {

}

void task3(client interface you_interface yi) {

}

/* 接口服务端 */
void task2(server interface my_interface i, server interface you_interface yi) {

}

int main () {
    interface my_interface i;  // 定义一个接口实例,传给客户端和服务端
    interface you_interface yi;  // 定义一个接口实例,传给客户端和服务端
    par {
        task1(i);
        task3(yi);
        task2(i, yi);
    }
    return 0;
}

task1做i的客户端,task3做yi的客户端,task2做i和yi的服务端。task1和task2通过接口i通讯,task3和task2通过yi通讯。

接口i有且只有一个客户端task1,有且只有一个服务端task2;接口yi有且只有一个客户端task3,有且只有一个服务端task2。

通过这几个例子应该能理解多个通道用于任务间通讯的用法了。

然后实际使用的时候,需要注意,任务发出事务后,会阻塞等到另一个任务处理事务之后才能往下执行,使用select case来处理事务的任务也会阻塞等待直到事务到来才往下执行,一不小心就会造成相互阻塞。

最后举一个2个接口用于任务间通讯的完整例子,task1做接口i的客户端,task3做yi的客户端,task2做2个接口的服务端,这样用,不会造成相互阻塞。

#include <stdio.h>
#include <platform.h>

/* 定义接口 */
interface my_interface {
    float add(float a, float b);  // 加法事务
    void inc(int& a);       // 自加1事务
};

/* 定义接口 */
interface you_interface {
    float mul(float a, float b);  // 乘法事务
    void dec(int& a);       // 自减1事务
};

/* 接口客户端 */
void task1(client interface my_interface i) {
    printf("task1: transaction add\n");
    float sum = i.add(1.2, 3.4);
    printf("task1: sum=%f\n", sum);
    int x = 5;
    printf("task1: transaction inc\n");
    i.inc(x);
    printf("task1: x=%d\n", x);
}

void task3(client interface you_interface yi) {
    printf("task3: transaction mul\n");
    float mul = yi.mul(1.2, 3.4);
    printf("task3: mul=%f\n", mul);
    int x = 5;
    printf("task3: transaction dec\n");
    yi.dec(x);
    printf("task3: x=%d\n", x);
}

/* 接口服务端 */
void task2(server interface my_interface i, server interface you_interface yi) {
    while(1) {
        select {
            case i.add(float a, float b) -> float ret:
                printf("task2: handle event: add(%f, %f)\n", a, b);
                ret = a + b;
                break;
            case i.inc(int& a):
                printf("task2: handle event: inc(%d)\n", a);
                a++;
                break;
            case yi.mul(float a, float b) -> float ret:
                printf("task2: handle event: mul(%f, %f)\n", a, b);
                ret = a * b;
                break;
            case yi.dec(int& a):
                printf("task2: handle event: dec(%d)\n", a);
                a--;
                break;
        }
    }
}

int main () {
    interface my_interface i;  // 定义一个接口实例,传给客户端和服务端
    interface you_interface yi;  // 定义一个接口实例,传给客户端和服务端
    par {
        task1(i);
        task3(yi);
        task2(i, yi);
    }
    return 0;
}

运行结果正确

task3: transaction mul
task1: transaction add
task2: handle event: mul(1.200000, 3.400000)
task3: mul=4.080000
task2: handle event: add(1.200000, 3.400000)
task3: transaction dec
task1: sum=4.600000
task2: handle event: dec(5)
task1: transaction inc
task3: x=4
task2: handle event: inc(5)
task1: x=6

服务端发起事务通知(Notifications)

以上的方式,都是客户端发起事务,按以上的描述,如果服务端发起事务,会引起编译时错误。再者,客户端发起事务后,如果没有服务端处理事务,客户端会引起阻塞。

这节就说由服务端发起事务的方法:Notifications方法。

接口定义中定义一种事务 - notification事务,notification事务的特点:

  • [[notification]] slave指定是notification事务
  • 不能带参数和返回值
  • 由服务端发起
  • 不阻塞
  • 接口定义[[clears_notification]]的函数用于客户端清除通知,同时发起事务
  • 客户端没清除通知前,重复发起无效,客户端清除通知之后,可再次发起通知事务

举个例子:客户端要从硬件输入读取两个数据输入,用于加法计算,硬件输入准备好之后,服务端发起通知,客户端清除这个通知同时发起读取事务,服务端处理读取事务,并把读取的结果传给客户端,如此就完成了一次读取。读取了2次数据后,客户端发起加法的事务,服务端处理传回结果。

#include <stdio.h>
#include <platform.h>

/* 定义接口 */
interface my_interface {
    int add(int a, int b);  // 加法事务

    [[clears_notification]] int read_input();

    [[notification]] slave void input_ready();   // 无参数,没返回值
};

/* 模拟从硬件读取到数据回来 */
int read_dat_from_hardware() {
    const int s_hwdat[] = {12, 34, 56, 78};
    static int hwdat_index = 0;
    int dat = s_hwdat[hwdat_index];
    hwdat_index = (hwdat_index + 1) % (sizeof(s_hwdat) / sizeof(s_hwdat[0]));
    return dat;
}

/* 接口客户端 */
void task1(client interface my_interface i) {
    int a[2];
    int recv_count = 0;
    while(recv_count < 2) {
        select {
            case i.input_ready():
                printf("task1: 清除通知同时发起读输入的事务\n");
                a[recv_count] = i.read_input();  // 清除 notification ,并引发服务端处理 read_input 事务
                printf("task1: 读到输入数据为%d\n", a[recv_count]);
                recv_count++;
                break;
        }
    }
    printf("task1: transaction add\n");
    int sum = i.add(a[0], a[1]);
    printf("task1: sum=%d\n", sum);
}

/* 接口服务端 */
void task2(server interface my_interface i) {
    while(1) {
        printf("task2: 通知客户端输入已准备\n");
        i.input_ready();
        select {
            case i.add(int a, int b) -> int ret:
                printf("task2: handle event: add(%d, %d)\n", a, b);
                ret = a + b;
                break;
            case i.read_input() -> int dat:
                printf("task2: 处理读输入事务,从硬件读输入并传回给客户端\n");
                dat = read_dat_from_hardware();
                break;
        }
    }
}

int main () {
    interface my_interface i;  // 定义一个接口实例,传给客户端和服务端
    par {
        task1(i);
        task2(i);
    }
    return 0;
}

执行结果为

task2: 通知客户端输入已准备
task1: 清除通知同时发起读输入的事务
task2: 处理读输入事务,从硬件读输入并传回给客户端
task1: 读到输入数据为12
task2: 通知客户端输入已准备
task1: 清除通知同时发起读输入的事务
task2: 处理读输入事务,从硬件读输入并传回给客户端
task1: 读到输入数据为34
task2: 通知客户端输入已准备
task1: transaction add
task2: handle event: add(12, 34)
task1: sum=46
task2: 通知客户端输入已准备

用时序图表示更清除

服务端 客户端 notification input_ready clears_notification read_input transaction read_input read_dat_from_hardware 读取硬件输入 dat 服务端 客户端 notification和clears_notification的时序图

接口数组

任务可以使用接口数组连接多个任务。

a[0]
a[1]
a[m]
task2
task1
task3
taskn

服务端使用interface_array[int index].接口事务原型这样的方式处理接口数组的事务,这样客户端发起事务的时候,下标会传给index,接口事务原型的参数传输和前面所述的事务传输一样。

举例

#include <stdio.h>
#include <platform.h>

/* 定义接口 */
interface my_interface {
    void f(int x);
};

/* 接口服务端 */
void task1(server interface my_interface a[n], unsigned int n) {
    while(1) {
        select {
            case a[int i].f(int x):  // 接口数组处理事务的语法
                printf("task1: 处理从接口%d发起的事务,值是%d\n", i, x);
                break;
        }
    }
}

/* 接口客户端 */
void task2(client interface my_interface i) {
    i.f(12);
}

/* 接口客户端 */
void task3(client interface my_interface i) {
    i.f(34);
}

int main () {
    interface my_interface a[2];  // 定义一个接口实例数组,传给客户端和服务端
    par {
        task1(a, 2);
        task2(a[0]);
        task3(a[1]);
    }
    return 0;
}

执行结果

task1: 处理从接口0发起的事务,值是12
task1: 处理从接口1发起的事务,值是34

扩展接口的客户端API

接口定义后,可以扩展定义客户端可以使用的API,即是说,可以扩展客户端可以发起的事务。比如原来定义了接口

interface 接口名 {
	// 事务函数
};

要扩展一个事务,使用如下语法:

extends client interface 接口名: {
	扩展的事务函数定义(client interface 接口名 实例, 参数列表) {
	        // 定义好函数功能 
	}
};

扩展客户端API有以下特点:

  • 第一个参数必须是客户端接口类型实例
  • 客户端使用事务函数时,函数参数只写参数列表就行了,那个接口实例不需要
  • 不能访问全局变量
  • 可以调用接口原有的事务函数
  • 扩展事务不是接口的成员,不可以在服务端的select case中处理

举例,比如原来定义了接口my_interface。

interface my_interface {
    int mul(int a, int b);
};

原来只有乘法事务,现在要扩展一个阶乘(factorial)功能,如下:

extends client interface my_interface: {
    int factorial(client interface my_interface self, int n) {
        int ret = 1;
        for(int i=n; i>=1; ++i) {
            ret *= i;
        }
        return ret;
    }
}

完整的程序例子

#include <stdio.h>
#include <platform.h>

/* 定义接口 */
interface my_interface {
    int mul(int a, int b);
};

extends client interface my_interface: {
    unsigned int factorial(client interface my_interface self, unsigned int n) {
        unsigned int ret = 1;
        for(int i=n; i>=1; --i) {
            ret *= i;
        }
        return ret;
    }
}

/* 接口客户端 */
void task1(client interface my_interface i) {
    int ret = i.mul(2, 3);
    printf("task1: 乘法结果是%d\n", ret);
    ret = i.factorial(4);
    printf("task1: 4的阶乘是: %d\n", ret);
}

/* 接口服务端 */
void task2(server interface my_interface i) {
    while(1) {
        select {
            case i.mul(int a, int b) -> int ret:
                ret = a * b;
                printf("task2: 处理mul(%d, %d)事务\n", a, b);
                break;
        }
    }
}

int main () {
    interface my_interface i;  // 定义一个接口实例,传给客户端和服务端
    par {
        task1(i);
        task2(i);
    }
    return 0;
}

执行结果为

task2: 处理mul(2, 3)事务
task1: 乘法结果是6
task1: 4的阶乘是: 24

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值