Quadlator II--T.I.Tech wire的驱版本RT-Linux的兼容性问题 写于: 星期六 29 四月 @ 13:23:23 |
Quadlator II第四章 四, T.I.Tech wire的驱版本RT-Linux的兼容性问题 作者:baredog 决定写这个内容的时候,是2003年,然而今天动笔的时候,却已经是2006年了。3年改变了太多的东西,以至于已经没有意义再写太多关于T.I.Tech wire和RT-Linux的一些细节了。在google上搜索一下RT-Linux的中文资源,目前在国内的嵌入式应用领域的uCLinux和RT-Linux取得了流行的地位。如果写,也是这2年一直在坚持做这方面的人更有资格写。 因此,大概以下这些内容也许还能有些帮助:
关于Linux驱动的著述也有不少,比较出名的有O'Relly的《Linux Device Driver》。驱动的概念在现代操作系统,例如Linux和Windows 2K/XP后是个很重要的概念。尤其是涉及控制硬件的一些开发。在早期Dos和Windows 9X平台下,没有太多对用户的限制,因为本身他们并不强调真正的多用户,任何人都可以直接操控硬件资源。相信那时的开发人员中,不少人有利用Turbo C操纵BIOS中断和IO端口的经历。在Visual C++盛行之后,仍然可以利用_inp和_outp系列函数,直接访问端口。而有相当的设备,如ISA板卡,直接对寄存器IO就可以完成大部分的控制。 然而这样简单的生活,不能永远继续下去。Windows 95后VxD的概念逐渐提了出来,一些复杂的设备,如PCI已经没有办法再用简单的IO加中断完成控制。好在设计机器人控制的一些设备-DA,AD和IO仍然不是很复杂。随后的Windows XP彻底让这些“老”开发人员退出了历史舞台,砸掉了他们的饭碗。XP下,禁止用户直接IO,必须通过WDM驱动来访问硬件,并且复杂的Plug & Play及电源管理,IRP队列等等,把WDM的学习曲线一下子提高到让人望而生畏的地步。在大学和研究者圈内(设备开发商们当然只好硬攻WDM了)大家开始逐渐把目光放开,寻找其他能够让学生在短期内掌握的方法,缩短学习周期,把主要注意力集中到课题本身上来。 这样的方法有3个:
为什么说Linux的驱动比WDM易学呢?最重要的一点就是内核模式。在kernel space中,程序几乎可以作任何事情,访问硬件和物理内存等等。而把一个程序放到内核里也非常容易,提供init module和cleanup module两个接口就可以了。用户空间的数据和内核空间的数据交换也非常容易,都是1个函数就搞定的事情。编译也同其他Linux程序没有什么区别。 内核空间和用户空间大概可以表示如上图,几乎所有的应用程序运行于用户空间,而系统级的一些进程,运行于内核空间,包括调度器,系统调用等等,硬件驱动程序正好也运行于内核空间。用户空间的进程,和内核空间的进程被彼此分隔开来。他们之间通过一些系统调用进行数据交换,例如用户空间的程序调用open, close, write, read可以访问内核空间的驱动程序;反之,内核空间的驱动程序通过copy_from_user和copy_to_user和用户空间交换数据。 这里,有读者也许问,既然驱动硬件的程序在内核空间,那么要用户空间程序还有什么用呢?难道不能像单片机程序那样,内核空间的程序(驱动)一启动,就进入一个无限循环,完成所有功能,直到系统下电退出?是的,理论上完全可以这样做,但是这样做的话,就根本不需要提供一个嵌入式的操作系统。嵌入式的操作系统给我们带来的好处是,把高层算法和底层驱动分开,把用户界面和后台任务分开的选择。 为什么要分开呢?这里我们举一个例子:比如一个走迷宫的机器老鼠,迷宫由白色的线画出,老鼠是一个3轮电动小车;老鼠眼睛是一个巡线光传感器。我们可以把控制机器老鼠的程序分成两个部分,一个是根据白线绘制地图,利用深度优先搜索算法,探索迷宫的出路。注意记录一些位置,防止老鼠在环路上绕圈,而永远走不出去。这类算法的时间要求不高,可以从容计算。另一部分是对电动小车轮子的控制,包括直行,后退,转弯,刹车等,这类控制算法对时间要求很高,比如一旦没有及时转弯,老鼠就可能脱离白线(相当于火车脱轨)冲出去等等。如果我们把对时间要求不高的算法也放在内核空间,不但白白耗费大量的时间计算,还可能影响轮子控制程序,造成老鼠反应迟钝。 事实上,我们人的大脑和神经系统,也是这样控制我们的身体的。比如我们清晨锻炼时候跑步,我们的一边从容的欣赏清晨的街景,可以呼吸早上新鲜的空气,可以让思维临时开小差想别的东西。可是我们的小脑和运动神经却在高速运转控制我们的跑步的姿态,迈步、落地、蹬腿,这些动作稍微失控,我们都会摔倒。我们的大脑和神经系统,就是这样分工合作,从而控制我们的身体的。 那么RT-Linux和Linux之间是什么样的关系呢? 可以这样理解RT-Linux,它是一个独立的内核,独立于Linux内核而单独存在,事实上RT-Linux把Linux内核变成了一个子任务,可以和其他实时任务共同被调度。最后RT-Linux以一个内核模块的形式出现,可以被动态的装载和卸除。 为了方便开发人员,RT-Linux还提供了一组扩展的API,可以方便的控制时间、内核内部线程;并且在内核空间和用户空间之间交换数据。 在兼容性上,我遇到过一些实际的问题,都是在2003年时候的事情了。首先RT-Linux包内有cpp目录,看上去以为支持C++,并且还有相关的例子。但是实际上cpp的程序有些问题,并不是说都不能用,也许在老的Linux内核上可以,但是当时的2.4上不成。所以titech wire光盘中的驱动(C++写的),没有办法用,我花了1周的时间,把他们全改写成了C的。现在回想起来,那时候我真是以一当十啊。 我当初在东京工业大学松野研究室第一次学习RT-Linux是通过一个例子,之后我就帮助一个大四的毕业生写了摄像头跟踪驱动程序。例子是学习最好的途径之一,在这里我也用一个例子来作为本章的结尾: 这个例子是rtlinux包中的一个例子,名叫frank,我对其稍作改动,程序的功能如下,它在内核空间启动2个线程,一个500ms执行一次,一个200ms执行一次,每个线程打印不同的内容到系统message里面。用户空间的程序可以理解为一个UI,它运行后,会通知内核的这个模块启动。 首先,由于内核空间的模块和用户程序需要交换数据,所以他们需要定义一个共同的数据结构,这个数据结构定义在一个单独的头文件control.h中 #define COMMAND_FIFO 3 //用户程序向内核模块发送命令所用的FIFO号 #define START_TASK 1 //用户程序通知内核模块开始运行的命令 struct my_msg_struct { 这里需要解释一下FIFO的概念,学习过数据结构课程的话,应该对FIFO不陌生。FIFO就是(First In First Out)的缩写,这种类型的典型实现是一个队列。大家排队买票,早到早买早离开。RT-Linux中大量使用FIFO作为通讯的途径。原因还是出于实时的考虑,一组数据到来后,如果还没有来得及处理,就先放入FIFO的末尾,处理线程总是从FIFO的头部取出数据进行处理。FIFO不仅用户用户空间的程序和内核模块通讯,也用户内核模块中的线程间彼此的通讯。 之后就可以写一个内核模块了。内核模块没有任何复杂要求,除了要包含必要的头文件外,只要提供入口和出口就可以了。他们的定义如下: int init_module(void){ void cleanup_module(void) 在入口初始化代码中,思路是首先建立5个FIFO,其中3个FIFO用于和用户通信,这3个FIFO中,一个负责传达用户的命令到内核空间,另外两个负责交换内核的2个线程的数据到用户程序。5个FIFO中剩下的2个FIFO用于内核内部2个线程间数据的传递。这个工作完成后,下一步的初始化工作是启动2个独立的线程,然后这2个线程进入休眠状态,等待用户的指令唤醒。有关这部分的一些概念可以参考前面的第二章。最后一步是启动一个handler来监测用户发来命令的那个FIFO。一图胜千言,这些思路可以参考下面的示意图: 其中,粉红色的是命令FIFO通道,负责传达用户命令给内核模块,两个绿色的是内部FIFO通道,负责将用户命令handler发出的数据传送给内部的两个独立线程,两个独立线程的运行结果通过两个蓝色的FIFO通道传给用户程序显示。 把这个思路用代码写出来就是这个样子: pthread_t tasks[2]; int init_module(void) //建立5个FIFO //建立第一个独立线程 //建立第二个独立线程 //建立响应用户命令的handler,监听3号FIFO 相对应的释放代码就简单多了: void cleanup_module(void) pthread_cancel (tasks[0]); 独立线程共用一段代码: static char *data[] = {"Frank ", "Zappa "}; #define TASK_CONTROL_FIFO_OFFSET 4 void *thread_code(void *t) while (1) { 这段代码是一个很典型的线程代码,函数一起动后,就进入线程循环,监听内部FIFO通道上的数据,如果通过rtf_get收到启动命令,就根据用户的要求,设置这段实时线程的周期(单位可以精确到ns)。如果受到结束命令,就挂起线程停止运行。其他情况下,程序出于实时循环状态,各自不断的利用rtf_put将frank和zippa两个字符串通过FIFO通道发送给用户程序。其中还会把一些额外的信息通过rtl_printf打印到系统的log里(在/var/log/messages) 那么这两个线程由谁控制呢?如上面的框图所示,handler用于控制这两个实时线程,handler的代码如下: int my_handler(unsigned int fifo) while ((err = rtf_get(COMMAND_FIFO, &msg,sizeof(msg))) == sizeof(msg)) { if (err != 0) { return 0; 这个handler同样是一旦运行起来,就不断监视FIFO命令通道,一旦得到用户的命令,就将命令直接传递给响应的实时线程,并且唤起正在休眠的线程。 内核模块还必须和用户程序配合才能工作frank的用户程序非常简单直观,由于内核实时线程发上来的数据都是字符串(frank和zippa),所以用户程序用一个buffer来接受数据: #define BUFSIZE 70 然后主程序的任务,就是打开3个FIFO,一个用户发送命令,2个用户接受数据: int main() int fd0, fd1, ctl; //3个FIFO的描述符,fd是file descriptor的缩写 int n; //打开第一个FIFO用于读数据 //打开第二个FIFO用于读数据 //打开第三个FIFO用于发送命令 /* 发送命令到命令FIFO,来通知内核模块启动2个内核实时线程 */ if (write(ctl, &msg, sizeof(msg)) < 0) { msg.task = 1; //监听2个数据FIFO,显示内核模块发送上来的数据,总共显示100条后停止。 tv.tv_sec = 1; //超时周期为1秒,如果1秒内没有从内核受到任何数据,则下面的select函数返回。 retval = select(FD_SETSIZE, &rfds, NULL, NULL, &tv); if (FD_ISSET(fd1, &rfds)) { //第二个FIFO通道内有数据送了上来 fprintf(stderr, "frank_app: now sending commands to stop RT-tasks "); /* 通过命令FIFO发送停止命令给内核模块 */ //停止第二个实时线程 return 0; 至此,RT-Linux的一个最简单程序就完成了,我想读者应该思考的问题是,如果不是这样简单的例子,而是机器人的控制程序,那么实时的PID循环应该放在哪里? 机器人的姿态命令如何通过FIFO传给内核模块?各个电机的位置,速度,转矩等物理量在测量后(通过DIO或者AD),如何通过FIFO传回给用户程序?怎样通过内核模块,用户程序,和各硬件组成大的闭环。 ===== |
共1条
1/1 1 跳转至页
T.I.Tech wire的驱版本RT-Linux的兼容性问题
只看楼主 1楼
关键词: T.I.Tech 版本 RT-Linux 兼容性
共1条
1/1 1 跳转至页
回复
有奖活动 | |
---|---|
【有奖活动】分享技术经验,兑换京东卡 | |
话不多说,快进群! | |
请大声喊出:我要开发板! | |
【有奖活动】EEPW网站征稿正在进行时,欢迎踊跃投稿啦 | |
奖!发布技术笔记,技术评测贴换取您心仪的礼品 | |
打赏了!打赏了!打赏了! |