转载popsonic朋友的
http://www.eetop.cn/blog/html/74/81174-1413.html
操作外设<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
不知道读者有没有写过51,或者arm 上的程序。作为成长的第一步,我们从arm说开去。我们选择一款arm来做我们讨论的主角。鉴于学习Arm 的人多数从arm7开始,在前面的章节中我们将选用大学里比较常用的 Philips LPC 21XX系列的ARM作为我们研究的主要对象,其代码实现也将主要以此款芯片为主。而在后面的章节中,我们将使用SANGSUMG的 S3C2410为目标芯片来讲述Linux 在上面的实现。
我们首先从一个最简单的操作例子说起:点亮和熄灭一个LED。
<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" />
Arm7 -2138 |
VCC |
P0.10 |
图 1.1 |
如图,我们连接了一个LED灯 H1。我们要做的就是将p0.10这个端口的输出电压用程序来控制。当输出为高时那么H1灯就不会点亮,相反为低时H1灯就会被点亮。P0.10是通用的io端口(GPIO)。
例程C01-01:
#include "config.h" //定义了与主板相关的寄存器的地址
#include "dcomm.h"
#include "dconst.h"
#include <stdio.h>
#define IO_LED 0X00040000 //这个作用是标志GPIO的第10位
//_____________________________//
//这个程序只是为了得到一个延迟//
//_____________________________//
void Delay(int count)
{
int i,j;
for (i=0;i<count;i++)
{
for (j=0;j<1000;j++)
{
//这个只是延迟
}
}
}
main()
{
uinsigned int i;
PINSEL0 = 0x00005500; // 设置SPI0管脚连接
PINSEL1 = 0x00000000;
IO0DIR = LINE_IOCON; //设置SPIO的管脚的输出输入方向
for (;;)
{
if(i++%2)
{
IO0SET= IO_LED; //管脚置一寄存器
}
else
{
IO0CLR= IO_LED; //管脚清零寄存器
}
Delay(20); //延迟一段时间否则闪的过快会看不清楚.
}
}
以上的代码是最简单的操作外设的代码。在该代码中对外设的控制由主程序直接操作完成。IO0SET、IO0CLR、PINSEL0、PINSEL1、IO0DIR是LPC2138的寄存器,这些是在option.h中定义的。这段代码里没有任何驱动程序的概念,亦无任何的操作系统的概念。操作底层外设的代码和主逻辑代码混淆在一起,我们不妨称这样的状态为馄饨。硬件代码穿插在逻辑的每一个部分,这样做会发生什么?如果我们现在不用LPC2138而使用同级别的44B0X的芯片!天哪,我们要修改程序的每一个部分。大多数在写裸机程序的程序员在移植前辈的代码的时候多少都会去抱怨,在不同厂商甚至在同一厂商的不同产品系列中寄存器名称和地址都会有很多差异,对外设的操作与管理也会因为架构的不同而天差地别!譬如刚刚的option.h中的IO0SET、IO0CLR、PINSEL0、PINSEL1、IO0DIR全部需要改动才能适应新的芯片。如果将代码中的操作部分和逻辑混淆在一起,对移植来将结果将是灾难性的!
将操作与逻辑分开
通常操作系统负责底层操作与高层逻辑之间的衔接。
这个是架构级的问题,如果你感兴趣可以看看tcp/ip的协议栈。每一个层都是为上一层服务的,提供更为底层的服务。而层与层之间通过协议约束和联系。操作系统也是如此,Linux 是属于几个为数不多的主流操作系统。是的,如果你仔细看它的内核代码的话,太棒了简直是个奇迹!相对与windows 的那个庞大的身躯,Linux 确实体现了它魅力的一面。
让我们来看一下Linux 的操作系统的模型(图1.2)。最底下的那层是hardware, hardware 是最基础的,当然可能并没有下面那张表中那么完备,但是2个是最基本的,那就是cpu 和主存。对于冯氏结构的计算机来讲cpu和主存可以构成最小的系统!
与cpu 打交道的是arch dependant code,有的系统里面把这个移植过程叫porting 如uc/osII。对于不同的cpu由于其内部构造不同,这段代码也不同,但主要做的就是初始化,并且给出堆栈,task切换时的上下文保存及现场恢复时所需要的操作。一般情况下这里使用汇编实现。再后面就是内存管理的代码了,内存在系统启动的时候以参数的形式传递给内核的内存管理进程。通常这一层中只实现读取,写入等操作,而类似与置换算法之类的则在上层的Memory management 中实现。文件系统是一个块设备,关于设备的分类及详细解释请参考《Linux 设备驱动程序》一书。值得提一提的是,这里的驱动程序象usb驱动这样的是基于桥模式的(参看《设计模式》一书)。比较特殊的是Linux 里对驱动的分类为3类:字符设备,块设备,网络设备。各个驱动都可以划归为这3类中的一种!但是字符设备和块设备之间的界限又不是严格区分的。关键可能要看dev 的结构更加适合哪个了。
Software support 这层中都是与底层打交道的代码,如果因为硬件的改变而需要变动的话,我们之需要修改这里的代码。这个是驱动程序的重要功能之一,我们不需要和高层的逻辑去打交道了!修改量也随之变少。
Kernel subsystems是kernel 中实现各个功能的子系统。无非就是进程管理(负责进程调度),内存管理,文件系统,设备控制,网络。在经典的操作系统书籍中基本上不把网络作为一个独立的部分,不过在Linux 中这个奇怪的分类方法是有其道理的。网络部分的特殊使其在整个系统中不得不特殊处理!
Kernel 向高层的应用提供服务接口。内核运行起来之后,可能GUI也会被运行起来,应用程序也会被运行,然后这些进程调用系统内核提供的接口进行工作。
当然不是所有的系统都会实现上面的一个完整功能,比如uc/osII网络部分,文件系统并不成为其内核中必须的部分。这样的高裁剪性,使得uc/osII在某些场合更加适合于使用!uc/OSII的驱动模型是比较模糊的那种,并不通过严格的结构来定义和约束。而是通过类似于接口技术来开放若干需要实现的函数,让porting 来实现。当然这样的做法更加灵活一些。
操作系统和裸机程序之间有天差地别吗?我认为好的裸机程序其实可以实现很多的功能甚至实现了操作系统中的大多特性。这个时候他们之间已经没有太多的差异了,如果愿意你可以写自己的操作系统代码!所以,在我们讨论操作系统的驱动的时候,请不要太过专注于系统相关的东西。这是一种思想,一种解决差异的分层思想!
黑盒子
我有一个盒子,漂亮的魔法黑盒子。每次我需要食物的时候,之要向它叫:“盒子盒子,请给我食物!”。盒子就会为我变出一顿丰盛的筵席。是的,它是怎么办到的呢?这个我不知道,但是我知道如何使用它,呵呵我有魔咒——“盒子盒子,请给我食物!”是的,盒子除此之外无法知道我需要食物!而且,无法知道更多的事情。而我只知道通过魔咒得到食物,其它的也是一无所知。魔咒成为了我们联系唯一的纽带。
这个黑盒子就是驱动程序的角色,我们不需要它知道我们的逻辑(原书称为策略)。而它本身内部也不需要逻辑,逻辑让高级的部分来做。
责任
是的,从这一刻起,你成为了内核模块的开发者。请尊重这个工作,因为你写的驱动程序将有可能被分发给每个人使用。类似于缓冲区溢出,访问隔离失败等低级错误而产生的系统崩溃是很容易产生的。我们有责任写好它,并保证其正常的工作。
用户1113894 2008-8-6 10:20
用户461316 2008-8-6 00:19