QNX操作系统是一个类Unix实时操作系统,遵从POSIX规范,驱动程序具有良好的可移植性。
编写任何驱动程序都会遇到同样的一个问题:应用程序与驱动程序之间是如何进行交互的。其实这个问题很简单,QNX有大量资料说明这一点。
当客户端调用fd = open(“dev/mydevice”,O_RDWR)打开设备mydevice,并期望从设定的地址上读写数据时,这个问题就产生了。实际上QNX提供了一套灵活的消息交互机制,大致上可以分为以下三个步骤:
第一,加载驱动程序,创建服务线程,把底层IO函数与POSIX函数进行连接,在命名空间注册设备名,通过Event loop或Thread pool等待消息的接收,同时使父进程在后台运行以加载其他应用程序。
第二,当应用程序调用open()函数时,process manger受到请求在命名空间中找到名为dev/mydevice的resource manger,QNX内核库打开它,应用程序通过返回的句柄与之建立连接。
第三,随后当调用read (fd, buf, 512)函数时,内核库发送了一个_IO_READ的消息,此时之前建立的Event loop或Thread pool就可以接收到这个消息,通过判断消息的类型调用到相应的IO函数,比如:
int io_read (resmgr_context_t *ctp, io_read_t *msg RESMGR_OCB_T*ocb);
其实可以看出来了,fd, buf, 512这几个参数主要就是通过io_read_t *msg这个参数传过来的。其实client主要指定了一个设备,希望向这个设备的某个地址读取长度为512字节的数据,然后放到buf当中。
接下来发生的事情就比较简单了,就是在自己实现的io_read函数中解析这个消息传递来的参数,并给出回复。在client-server消息交互模型中,此时client就处在了reply blocked的状态等待server的回复。在io_read中做了哪些事情呢?首先要验证下传来的消息是否是正确的io_read消息,同时检查到底是否是nonblock方式打开。然后解析msg->i.nbytes来确定需要传递多少个数据,然后调用底层函数读取硬件数据,通过_IO_SET_READ_NBYTES (ctp, msg->i.nbytes);来告诉client可以返回的数据量。
对于如何回复数据来说,QNX确实提供了不少简单的方法。可以使用return(ENOMEM)返回一个错误;或者使用return(EOK)返回操作成功。如果想返回一定量数据的话,可以设置IOV数组返回,
比如通过设置IOV来返回一个或多个数组,比如:
SETIOV (ctp->iov, buffer, nbytes);
return (_RESMGR_NPARTS(1));
或者直接调用宏返回一个完整的buffer:
return (_RESMGR_PTR(ctp, buffer, nbytes));
写数据的操作与读数据的操作类似,不再赘述。这样就完成了上层数据请求,下层数据读取并返回的过程,server重新回到receive blocked的状态。细心的朋友可能已经看出来了,既然是读写数据,那么地址是如何设置的呢?其实是通过devctl来设置的,其格式为
int devctl( int fd, int dcmd, void *data, size_t nbytes, int * return_info);
其中最值得一提的就是int dcmd这个参数,这是一个自定义的命令,可以通过这个命令传递一个结构体指针,比如:
Typedef struct{
Uint32_t addr_t;
Uint32_t status_r;
} my_cfg_t;
#define MYCMD_SET_ADDR __DIOT(_DCMD_MISC, 0x01, my_cfg_t)
在client应用程序中定义my_cfg_t addr;通过命令
Devctl(fd, MYCMD_SET_ADDR,& addr,sizeof(my_cfg_t),NULL);
完成设置。对于底层来说就比较简单了,主要分为获取数据指针,解析传来的命令获得数据就可以了。