EDN首页   博客首页

最新日志

发表于:2007/1/29 8:53:55
标签:无标签

3

清理

很久没来,发现已经杂草丛生了,呵!

系统分类: 自由话题   |    用户分类:    |    来源: 原创

评论(0) | 阅读(1575)
发表于:2006/12/14 9:06:37
标签:无标签

6

Linux内核启动地址

最近在网上看到一位网友写得一篇文章,写得很好,加深了对Linux启动的认识,特意贴在这里:

 

内核编译链接过程是依靠vmlinux.lds文件,以arm为例vmlinux.lds文件位于kernel/arch/arm/vmlinux.lds,但是该文件是由vmlinux-armv.lds.in生成的,根据编译选项的不同源文件还可以是vmlinux-armo.lds.in,vmlinux-armv-xip.lds.in。
vmlinux-armv.lds的生成过程在kernel/arch/arm/Makefile中
LDSCRIPT     = arch/arm/vmlinux-armv.lds.in
arch/arm/vmlinux.lds: arch/arm/Makefile $(LDSCRIPT) \
 $(wildcard include/config/cpu/32.h) \
 $(wildcard include/config/cpu/26.h) \
 $(wildcard include/config/arch/*.h)
 @echo '  Generating $@'
 @sed 's/TEXTADDR/$(TEXTADDR)/;s/DATAADDR/$(DATAADDR)/' $(LDSCRIPT) >$@
vmlinux-armv.lds.in文件的内容:
OUTPUT_ARCH(arm)
ENTRY(stext)
SECTIONS
{
    . = TEXTADDR;
    .init : {           /* Init code and data       */
        _stext = .;
        __init_begin = .;
            *(.text.init)
        __proc_info_begin = .;
            *(.proc.info)
        __proc_info_end = .;
        __arch_info_begin = .;
            *(.arch.info)
        __arch_info_end = .;
        __tagtable_begin = .;
            *(.taglist)
        __tagtable_end = .;
            *(.data.init)
        . = ALIGN(16);
        __setup_start = .;
            *(.setup.init)
        __setup_end = .;
        __initcall_start = .;
            *(.initcall.init)
        __initcall_end = .;
        . = ALIGN(4096);
        __init_end = .;
    }
   
其中TEXTADDR就是内核启动的虚拟地址,定义在kernel/arch/arm/Makefile中:
ifeq ($(CONFIG_CPU_32),y)
PROCESSOR    = armv
TEXTADDR     = 0xC0008000
LDSCRIPT     = arch/arm/vmlinux-armv.lds.in
endif
需要注意的是这里是虚拟地址而不是物理地址。
一般情况下都在生成vmlinux后,再对内核进行压缩成为zImage,压缩的目录是kernel/arch/arm/boot。
下载到flash中的是压缩后的zImage文件,zImage是由压缩后的vmlinux和解压缩程序组成,如下图所示:
            |-----------------|\    |-----------------|
            |                 | \   |                 |
            |                 |  \  | decompress code |
            |     vmlinux     |   \ |-----------------|    zImage
            |                 |    \|                 |
            |                 |     |                 |
            |                 |     |                 |   
            |                 |     |                 |
            |                 |    /|-----------------|
            |                 |   /
            |                 |  /
            |                 | /
            |-----------------|/
           
zImage链接脚本也叫做vmlinux.lds,位于kernel/arch/arm/boot/compressed。
是由同一目录下的vmlinux.lds.in文件生成的,内容如下:
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
 {
   . = LOAD_ADDR;
   _load_addr = .;
 
   . = TEXT_START;
   _text = .;
 
   .text : {
     _start = .;
    
其中LOAD_ADDR就是zImage中解压缩代码的ram偏移地址,TEXT_START是内核ram启动的偏移地址,这个地址是物理地址。
在kernel/arch/arm/boot/Makefile文件中定义了:
ZTEXTADDR   =0
ZRELADDR     = 0xa0008000
ZTEXTADDR就是解压缩代码的ram偏移地址,ZRELADDR是内核ram启动的偏移地址,这里看到指定ZTEXTADDR的地址为0,
明显是不正确的,因为我的平台上的ram起始地址是0xa0000000,在Makefile文件中看到了对该地址设置的几行注释:
# We now have a PIC decompressor implementation.  Decompressors running
# from RAM should not define ZTEXTADDR.  Decompressors running directly
# from ROM or Flash must define ZTEXTADDR (preferably via the config)
他的意识是如果是在ram中进行解压缩时,不用指定它在ram中的运行地址,如果是在flash中就必须指定他的地址。所以这里将ZTEXTADDR指定为0,也就是没有真正指定地址。
在kernel/arch/arm/boot/compressed/Makefile文件有一行脚本:
SEDFLAGS    = s/TEXT_START/$(ZTEXTADDR)/;s/LOAD_ADDR/$(ZRELADDR)/;s/BSS_START/$(ZBSSADDR)/
使得TEXT_START = ZTEXTADDR,LOAD_ADDR = ZRELADDR。
这样vmlinux.lds的生成过程如下:
vmlinux.lds:    vmlinux.lds.in Makefile $(TOPDIR)/arch/$(ARCH)/boot/Makefile $(TOPDIR)/.config
 @sed "$(SEDFLAGS)" < vmlinux.lds.in > $@
 
以上就是我对内核启动地址的分析,总结一下内核启动地址的设置:
1、设置kernel/arch/arm/Makefile文件中的
   TEXTADDR     = 0xC0008000
   内核启动的虚拟地址
2、设置kernel/arch/arm/boot/Makefile文件中的
   ZRELADDR     = 0xa0008000
   内核启动的物理地址
   如果需要从flash中启动还需要设置
   ZTEXTADDR地址。

系统分类: 嵌入式   |    用户分类:    |    来源: 转贴

评论(3) | 阅读(3857)
发表于:2006/11/26 16:14:35
标签:无标签

3

一声感叹!Linuxreiserfs文件系统即将陨落?

 Linux著名的高性能文件系统reiserfs向来是Linux fans眼中的挚爱,但是reiserfs即将陨落。

前段时间已经风传Linux Kernel拒绝将reiserfs4加入kernel source,随后SuSE,这个reiserfs最大的赞助商也准备在下一个版本的SuSE Linux中不再将reiserfs作为默认文件系统。

如果说由于政治原因导致reiserfs文件系统被排斥在Linux主流之外的话,Hans Reiser则亲手埋葬了自己和自己的作品:

最近Hans Reiser因为谋杀妻子的罪名被警方逮捕,而且警方拒绝辩护律师,估计证据已经确凿。Hans同学就算不是死刑,估计也得在监狱里面度过余生了。Reiserfs文件系统,即将陨落。

相关链接:http://blog.80s.net.cn/article.asp?id=357

                    http://www.idiom.com/~beverly/hans_resume.html
                    http://www.linuxeden.com/doc/24610.html

 

系统分类: 嵌入式   |    用户分类:    |    来源: 无分类

评论(0) | 阅读(2066)
发表于:2006/11/16 8:43:13
标签:无标签

2

轻松一下

发信人: Kinglish (King of English), 信区: Joke
标  题: 够淫荡吗?(x
发信站: 水木社区 (Thu Nov 16 00:49:23 2006), 站内

上午我到一家外商公司联络业务完毕,乘电梯下楼。

  在某一层电梯停住了,门打开,看见一个衣着性感的女郎,一手挽着名牌手袋,
一手扶着电梯门,身体斜靠着,
  用挑逗的语气问我:够淫荡吗?
  
  我控制住汹涌的思潮冷静分析,人家公司就是不同,
  人家外商企业的女职员就是开放,怪不得有人说,
  我们比他们落后起码三十年,这句话是有道理的。
  
  我平静地说:淫荡是淫荡了点,但我喜欢!!
  
  我知道我说这句话的样子也一定很酷,
  作一个有骨气的受传统文化熏陶男子汉,
  要在新时代新潮流面前努力转变思想,
  不能甘于落后。
  
  突然间那女郎用手袋猛地向我砸来,一边还说:你这变态!.....
  
  直到晚上我才醒悟,原来她说的是:
  
  Going Down 吗?(下楼吗?)

系统分类: 自由话题   |    用户分类:    |    来源: 无分类

评论(1) | 阅读(1666)
发表于:2006/11/11 13:22:12
标签:无标签

20

教你如何构建嵌入式linux系统

pdf构建嵌入式linux系统非常好的一篇文章,非常详细的过程!

系统分类: 嵌入式   |    用户分类:    |    来源: 无分类

评论(6) | 阅读(4999)
发表于:2006/11/11 13:04:33
标签:无标签

16

IBM原版linux教材

IBM原版linux教材:

rar1rar2rar3rar4

系统分类: 嵌入式   |    用户分类:    |    来源: 无分类

评论(2) | 阅读(1431)
发表于:2006/11/10 13:00:38
标签:无标签

17

PCBLayout中的走线策略--(转)

布线(Layout)是PCB设计工程师最基本的工作技能之一。走线的好坏将直接影响到整个系统的性能,大多数高速的设计理论也要最终经过Layout得以实现并验证,由此可见,布线在高速PCB设计中是至关重要的。下面将针对实际布线中可能遇到的一些情况,分析其合理性,并给出一些比较优化的走线策略。主要从直角走线,差分走线,蛇形线等三个方面来阐述。
1
直角走线
直角走线一般是PCB布线中要求尽量避免的情况,也几乎成为衡量布线好坏的标准之一,那么直角走线究竟会对信号传输产生多大的影响呢?从原理上说,直角走线会使传输线的线宽发生变化,造成阻抗的不连续。其实不光是直角走线,顿角,锐角走线都可能会造成阻抗变化的情况。

点击看大图

直角走线的对信号的影响就是主要体现在三个方面:一是拐角可以等效为传输线上的容性负载,减缓上升时间;二是阻抗不连续会造成信号的反射;三是直角尖端产生的EMI

传输线的直角带来的寄生电容可以由下面这个经验公式来计算:
C="61W"(Er)[size="1"]1/2[/size]/Z0
在上式中,C就是指拐角的等效电容(单位:pF),W指走线的宽度(单位:inch),εr指介质的介电常数,Z0就是传输线的特征阻抗。举个例子,对于一个4Mils50欧姆传输线(εr4.3)来说,一个直角带来的电容量大概为0.0101pF,进而可以估算由此引起的上升时间变化量:
T10-90%=2.2*C*Z0/2 = 2.2*0.0101*50/2 = 0.556ps
通过计算可以看出,直角走线带来的电容效应是极其微小的。

由于直角走线的线宽增加,该处的阻抗将减小,于是会产生一定的信号反射现象,我们可以根据传输线章节中提到的阻抗计算公式来算出线宽增加后的等效阻抗,然后根据经验公式计算反射系数:ρ=(Zs-Z0)/(Zs+Z0),一般直角走线导致的阻抗变化在7%-20%之间,因而反射系数最大为0.1左右。而且,从下图可以看到,在W/2线长的时间内传输线阻抗变化到最小,再经过W/2时间又恢复到正常的阻抗,整个发生阻抗变化的时间极短,往往在10ps之内,这样快而且微小的变化对一般的信号传输来说几乎是可以忽略的。

点击看大图

很多人对直角走线都有这样的理解,认为尖端容易发射或接收电磁波,产生EMI,这也成为许多人认为不能直角走线的理由之一。然而很多实际测试的结果显示,直角走线并不会比直线产生很明显的EMI。也许目前的仪器性能,测试水平制约了测试的精确性,但至少说明了一个问题,直角走线的辐射已经小于仪器本身的测量误差。

总的说来,直角走线并不是想象中的那么可怕。至少在GHz以下的应用中,其产生的任何诸如电容,反射,EMI等效应在TDR测试中几乎体现不出来,高速PCB设计工程师的重点还是应该放在布局,电源/地设计,走线设计,过孔等其他方面。当然,尽管直角走线带来的影响不是很严重,但并不是说我们以后都可以走直角线,注意细节是每个优秀工程师必备的基本素质,而且,随着数字电路的飞速发展,PCB工程师处理的信号频率也会不断提高,到10GHz以上的RF设计领域,这些小小的直角都可能成为高速问题的重点对象。


2
差分走线
差分信号(Differential Signal)在高速电路设计中的应用越来越广泛,电路中最关键的信号往往都要采用差分结构设计,什么另它这么倍受青睐呢?在PCB设计中又如何能保证其良好的性能呢?带着这两个问题,我们进行下一部分的讨论。

何为差分信号?通俗地说,就是驱动端发送两个等值、反相的信号,接收端通过比较这两个电压的差值来判断逻辑状态“0”还是“1”。而承载差分信号的那一对走线就称为差分走线。

点击看大图

差分信号和普通的单端信号走线相比,最明显的优势体现在以下三个方面:
a.
抗干扰能力强,因为两根差分走线之间的耦合很好,当外界存在噪声干扰时,几乎是同时被耦合到两条线上,而接收端关心的只是两信号的差值,所以外界的共模噪声可以被完全抵消。
b.
能有效抑制EMI,同样的道理,由于两根信号的极性相反,他们对外辐射的电磁场可以相互抵消,耦合的越紧密,泄放到外界的电磁能量越少。
c.
时序定位精确,由于差分信号的开关变化是位于两个信号的交点,而不像普通单端信号依靠高低两个阈值电压判断,因而受工艺,温度的影响小,能降低时序上的误差,同时也更适合于低幅度信号的电路。目前流行的LVDSlow voltage differential signaling)就是指这种小振幅差分信号技术。

对于PCB工程师来说,最关注的还是如何确保在实际走线中能完全发挥差分走线的这些优势。也许只要是接触过Layout的人都会了解差分走线的一般要求,那就是等长、等距。等长是为了保证两个差分信号时刻保持相反极性,减少共模分量;等距则主要是为了保证两者差分阻抗一致,减少反射。尽量靠近原则有时候也是差分走线的要求之一。但所有这些规则都不是用来生搬硬套的,不少工程师似乎还不了解高速差分信号传输的本质。下面重点讨论一下PCB差分信号设计中几个常见的误区。

误区一:认为差分信号不需要地平面作为回流路径,或者认为差分走线彼此为对方提供回流途径。造成这种误区的原因是被表面现象迷惑,或者对高速信号传输的机理认识还不够深入。从图1-8-15的接收端的结构可以看到,晶体管Q3,Q4的发射极电流是等值,反向的,他们在接地处的电流正好相互抵消(I1=0),因而差分电路对于类似地弹以及其它可能存在于电源和地平面上的噪音信号是不敏感的。地平面的部分回流抵消并不代表差分电路就不以参考平面作为信号返回路径,其实在信号回流分析上,差分走线和普通的单端走线的机理是一致的,即高频信号总是沿着电感最小的回路进行回流,最大的区别在于差分线除了有对地的耦合之外,还存在相互之间的耦合,哪一种耦合强,那一种就成为主要的回流通路,图1-8-16是单端信号和差分信号的地磁场分布示意图。

点击看大图

PCB电路设计中,一般差分走线之间的耦合较小,往往只占10~20%的耦合度,更多的还是对地的耦合,所以差分走线的主要回流路径还是存在于地平面。当地平面发生不连续的时候,无参考平面的区域,差分走线之间的耦合才会提供主要的回流通路,见图1-8-17所示。尽管参考平面的不连续对差分走线的影响没有对普通的单端走线来的严重,但还是会降低差分信号的质量,增加EMI,要尽量避免。也有些设计人员认为,可以去掉差分走线下方的参考平面,以抑制差分传输中的部分共模信号,但从理论上看这种做法是不可取的,阻抗如何控制?不给共模信号提供地阻抗回路,势必会造成EMI辐射,这种做法弊大于利。

点击看大图

误区二:认为保持等间距比匹配线长更重要。在实际的PCB布线中,往往不能同时满足差分设计的要求。由于管脚分布,过孔,以及走线空间等因素存在,必须通过适当的绕线才能达到线长匹配的目的,但带来的结果必然是差分对的部分区域无法平行,这时候我们该如何取舍呢?在下结论之前我们先看看下面一个仿真结果。

点击看大图

从上面的仿真结果看来,方案1和方案2波形几乎是重合的,也就是说,间距不等造成的影响是微乎其微的,相比较而言,线长不匹配对时序的影响要大得多(方案3)。再从理论分析来看,间距不一致虽然会导致差分阻抗发生变化,但因为差分对之间的耦合本身就不显著,所以阻抗变化范围也是很小的,通常在10%以内,只相当于一个过孔造成的反射,这对信号传输不会造成明显的影响。而线长一旦不匹配,除了时序上会发生偏移,还给差分信号中引入了共模的成分,降低信号的质量,增加了EMI

可以这么说,PCB差分走线的设计中最重要的规则就是匹配线长,其它的规则都可以根据设计要求和实际应用进行灵活处理。

误区三:认为差分走线一定要靠的很近。让差分走线靠近无非是为了增强他们的耦合,既可以提高对噪声的免疫力,还能充分利用磁场的相反极性来抵消对外界的电磁干扰。虽说这种做法在大多数情况下是非常有利的,但不是绝对的,如果能保证让它们得到充分的屏蔽,不受外界干扰,那么我们也就不需要再让通过彼此的强耦合达到抗干扰和抑制EMI的目的了。如何才能保证差分走线具有良好的隔离和屏蔽呢?增大与其它信号走线的间距是最基本的途径之一,电磁场能量是随着距离呈平方关系递减的,一般线间距超过4倍线宽时,它们之间的干扰就极其微弱了,基本可以忽略。此外,通过地平面的隔离也可以起到很好的屏蔽作用,这种结构在高频的(10G以上)IC封装PCB设计中经常会用采用,被称为CPW结构,可以保证严格的差分阻抗控制(2Z0),如图1-8-19

点击看大图

差分走线也可以走在不同的信号层中,但一般不建议这种走法,因为不同的层产生的诸如阻抗、过孔的差别会破坏差模传输的效果,引入共模噪声。此外,如果相邻两层耦合不够紧密的话,会降低差分走线抵抗噪声的能力,但如果能保持和周围走线适当的间距,串扰就不是个问题。在一般频率(GHz以下),EMI也不会是很严重的问题,实验表明,相距500Mils的差分走线,在3之外的辐射能量衰减已经达到60dB,足以满足FCC的电磁辐射标准,所以设计者根本不用过分担心差分线耦合不够而造成电磁不兼容问题。

3
蛇形线
蛇形线是Layout中经常使用的一类走线方式。其主要目的就是为了调节延时,满足系统时序设计要求。设计者首先要有这样的认识:蛇形线会破坏信号质量,改变传输延时,布线时要尽量避免使用。但实际设计中,为了保证信号有足够的保持时间,或者减小同组信号之间的时间偏移,往往不得不故意进行绕线。

点击看大图

点击看大图

那么,蛇形线对信号传输有什么影响呢?走线时要注意些什么呢?其中最关键的两个参数就是平行耦合长度(Lp)和耦合距离(S),如图1-8-21所示。很明显,信号在蛇形走线上传输时,相互平行的线段之间会发生耦合,呈差模形式,S越小,Lp越大,则耦合程度也越大。可能会导致传输延时减小,以及由于串扰而大大降低信号的质量,其机理可以参考第三章对共模和差模串扰的分析。

下面是给Layout工程师处理蛇形线时的几点建议:
1
尽量增加平行线段的距离(S),至少大于3HH指信号走线到参考平面的距离。通俗的说就是绕大弯走线,只要S足够大,就几乎能完全避免相互的耦合效应。
2
减小耦合长度Lp,当两倍的Lp延时接近或超过信号上升时间时,产生的串扰将达到饱和。
3
带状线(Strip-Line)或者埋式微带线(Embedded Micro-strip)的蛇形线引起的信号传输延时小于微带走线(Micro-strip)。理论上,带状线不会因为差模串扰影响传输速率。
4
高速以及对时序要求较为严格的信号线,尽量不要走蛇形线,尤其不能在小范围内蜿蜒走线。
5
可以经常采用任意角度的蛇形走线,如图1-8-20中的C结构,能有效的减少相互间的耦合。
6
高速PCB设计中,蛇形线没有所谓滤波或抗干扰的能力,只可能降低信号质量,所以只作时序匹配之用而无其它目的。
7
有时可以考虑螺旋走线的方式进行绕线,仿真表明,其效果要优于正常的蛇形走线。

点击看大图

系统分类: PCB   |    用户分类:    |    来源: 无分类

评论(3) | 阅读(1929)
发表于:2006/11/9 13:34:18
标签:isp  

15

ISP工具

曾经用过的一个ISP软件,支持多系列的芯片,配有使用说明:

rar

系统分类: 单片机   |    用户分类:    |    来源: 无分类

评论(0) | 阅读(1344)
发表于:2006/11/9 13:12:38
标签:linux  Driver  

16

Linux设备驱动程序的简单示例

一、Linux device driver 的概念 

  系统调用是操作系统内核和应用程序之间的接口,设备驱动程序  器硬件之间的接口.设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件, 应用程序可以象操作普通文件一样对硬件设备进行操作.设备驱动程序是内核的一部分,它完成以下的功能

  1.对设备初始化和释放

  2.把数据从内核传送到硬件和从硬件读取数据

  3.读取应用程序传送给设备文件的数据和回送应用程序请求的数据

  4.检测和处理设备出现的错误

  在Linux操作系统下有两类主要的设备文件类型,一种是字符设备,另一种是块设备.字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般就紧接着发生了,块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请求能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际的I/O操作.块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间来等待

  已经提到,用户进程是通过设备文件来与实际的硬件打交道.每个设备文件都都有其文件属性(c/b),表示是字符设备还蔤强樯璞?另外每个文件都有两个设备号,第一个是主设备号,标识驱动程序,第二个是从设备号,标识使用同一个设备驱动程序的不同的硬件设备,比如有两个软盘,就可以用从设备号来区分他们.设备文件的的主设备号必须与设备驱动程序在登记时申请的主设备号一致,否则用户进程将无法访问到驱动程序

  最后必须提到的是,在用户进程调用驱动程序时,系统进入核心态,这时不再是抢先式调度.也就是说,系统必须在你的驱动程序的子函数返回后才能进行其他的工作.如果你的驱动程序陷入死循环,不幸的是你只有重新启动机器了,然后就是漫长的fsck.//hehe 

  读/写时,它首先察看缓冲区的内容,如果缓冲区的数据 

  如何编写Linux操作系统下的设备驱动程序 
  二、实例剖析 

  我们来写一个最简单的字符设备驱动程序。虽然它什么也不做,但是通过它可以了解Linux的设备驱动程序的工作原理.把下面的C代码输入机器,你就会获得一个真正的设备驱动程序.不过我的kernel2.0.34,在低版本的kernel上可能会出现问题,我还没测试过.//xixi 

  #define __NO_VERSION__ 
  
#include <linux/modules.h> 
  #include <linux/version.h> 

  char kernel_version [] = UTS_RELEASE; 

  由于用户进程是通过设备文件同硬件打交道,对设备文件的操作方式不外乎就是一些系统调用,如 openreadwriteclose.... 注意,不是fopen fread,但是如何把系统调用和驱动程序关联起来呢?这需要了解一个非常关键的数据结构

struct file_operations 

 int (*seek)    (struct inode * struct file * off_t int); 
 int (*read)    (struct inode * 
struct file * char 
int); 
 int (*write)   (struct inode * 
struct file * off_t 
int); 
 int (*readdir) (struct inode * 
struct file * struct dirent * 
int); 
 int (*select)  (struct inode * 
struct file * int 
select_table *); 
 int (*ioctl)   (struct inode * 
struct file * unsined int 
unsigned long); 
 int (*mmap)    (struct inode * 
struct file *
 struct vm_area_struct *); 
 int (*open)    (struct inode * 
struct file *); 
 int (*release) (struct inode * 
struct file *); 
 int (*fsync)   (struct inode * 
struct file *); 
 int (*fasync)  (struct inode * 
struct file *
int); 
 int (*check_media_change) (struct inode * 
struct file *); 
 int (*revalidate) (dev_t dev);

 

 这个结构的每一个成员的名字都对应着一个系统调用.用户进程利用系统调用在对设备文件进行诸如read/write操作时,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数.这是linux的设备驱动程序工作的基本原理.既然是这样,则编写设备驱动程序的主要工作就是编写子函数,并填充file_operations的各个域

  相当简单,不是吗

  下面就开始写子程序

#include <linux/types.h> 
#include <linux/fs.h> 
#include <linux/mm.h> 
#include <linux/errno.h> 
#include <asm/segment.h> 
unsigned int test_major = 0; 

static int read_test(struct inode *nodestruct file *file 
char *buf
int count) 

int left; 

if (verify_area(VERIFY_WRITEbufcount) == -EFAULT ) 
return -EFAULT; 

for(left = count ; left > 0 ; left--) 

__put_user(1
buf1); 
buf++; 

return count; 

  这个函数是为read调用准备的.当调用read时,read_test()被调用,它把用户的缓冲区全部写1.buf read调用的一个参数.它是用户进程空间的一个地址.但是在read_test被调用时,系统进入核心态.所以不能使用buf这个地址,必须用__put_user(),这是kernel提供的一个函数,用于向用户传送数据.另外还有很多类似功能的函数.请参考.在向用户空间拷贝数据之前,必须验证buf是否可用。

  这就用到函数verify_area. 

static int write_tibet(struct inode *inodestruct file *file 
const char *buf
int count) 

 return count; 

static int open_tibet(struct inode *inodestruct file *file ) 

 MOD_INC_USE_COUNT; 
 return 0; 

static void release_tibet(struct inode *inodestruct file *file ) 

 MOD_DEC_USE_COUNT; 


  这几个函数都是空操作.实际调用发生时什么也不做,他们仅仅为下面的结构提供函数指针。 

struct file_operations test_fops = { 
NULL
 
read_test
 
write_test
 
NULL
 /* test_readdir */ 
NULL
 
NULL
 /* test_ioctl */ 
NULL
 /* test_mmap */ 
open_test
 
release_test
 NULL
 /* test_fsync */ 
NULL
 /* test_fasync */ 
/* nothing more
 fill with NULLs */ 
};  

  设备驱动程序的主体可以说是写好了。现在要把驱动程序嵌入内核。驱动程序可以按照两种方式编译。一种是编译进kernel,另一种是编译成模块(modules),如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能动态的卸载,不利于调试,所以推荐使用模块方式。 

int init_module(void) 

int result; 

result = register_chrdev(0 "test" &test_fops); 

if (result < 0) { 
printk(KERN_INFO "test: can't get major number\n" ; 
return result; 

if (test_major == 0) test_major = result; /* dynamic */ 
return 0; 

  在用insmod命令将编译好的模块调入内存时,init_module 函数被调用。在这里,init_module只做了一件事,就是向系统的字符设备表登记了一个字符设备。register_chrdev需要三个参数,参数一是希望获得的设备号,如果是零的话,系统将选择一个没有被占用的设备号返回。参数二是设备文件名,参数三用来登记驱动程序实际执行操作的函数的指针。 

  如果登记成功,返回设备的主设备号,不成功,返回一个负值。 

void cleanup_module(void) 

unregister_chrdev(test_major
 "test" ; 
}  

  在用rmmod卸载模块时,cleanup_module函数被调用,它释放字符设备test在系统字符设备表中占有的表项。 

  一个极其简单的字符设备可以说写好了,文件名就叫test.c吧。 

  下面编译 

  $ gcc -O2 -DMODULE -D__KERNEL__ -c test.c 

  得到文件test.o就是一个设备驱动程序。 

  如果设备驱动程序有多个文件,把每个文件按上面的命令行编译,然后 

  ld -r file1.o file2.o -o modulename. 

  驱动程序已经编译好了,现在把它安装到系统中去。 

  $ insmod -f test.o 

  如果安装成功,在/proc/devices文件中就可以看到设备test,并可以看到它的主设备号。
  要卸载的话,运行 

  $ rmmod test 

  下一步要创建设备文件。 

  mknod /dev/test c major minor 

  是指字符设备,major是主设备号,就是在/proc/devices里看到的。 

  用shell命令 

  $ cat /proc/devices | awk "\\$2==\"test\" {print \\$1}" 

  就可以获得主设备号,可以把上面的命令行加入你的shell script中去。 

  minor是从设备号,设置成0就可以了。 

  我们现在可以通过设备文件来访问我们的驱动程序。写一个小小的测试程序。 

#include <stdio.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 

main() 

int testdev; 
int i; 
char buf[10]; 

testdev = open("/dev/test"O_RDWR); 

if ( testdev == -1 ) 

printf("Cann't open file \n" ; 
exit(0); 

read(testdevbuf10); 

for (i = 0; i < 10;i++) 
printf("%d\n"
buf); 

close(testdev); 

  编译运行,看看是不是打印出全 

  以上只是一个简单的演示。真正实用的驱动程序要复杂的多,要处理如中断,DMAI/O port等问题。这些才是真正的难点。请看下节,实际情况的处理。 

  如何编写Linux操作系统下的设备驱动程序 

  三、设备驱动程序中的一些具体问题

  1. I/O Port. 

  和硬件打交道离不开I/O Port,老的ISA设备经常是占用实际的I/O端口,在linux下,操作系统没有对I/O口屏蔽,也就是说,任何驱动程序都可对任意的I/O口操作,这样就很容易引起混乱。每个驱动程序应该自己避免误用端口。 

  有两个重要的kernel函数可以保证驱动程序做到这一点。 

  1check_region(int io_port int off_set) 

  这个函数察看系统的I/O表,看是否有别的驱动程序占用某一段I/O口。 

  参数1io端口的基地址, 

  参数2io端口占用的范围。 

  返回值:没有占用, 0,已经被占用。 

  2request_region(int io_port int off_setchar *devname) 

  如果这段I/O端口没有被占用,在我们的驱动程序中就可以使用它。在使用之前,必须向系统登记,以防止被其他程序占用。登记后,在/proc/ioports文件中可以看到你登记的io口。 

  参数1io端口的基地址。 

  参数2io端口占用的范围。 

  参数3:使用这段io地址的设备名。 

  在对I/O口登记后,就可以放心地用inb() outb()之类的函来访问了。 

  在一些pci设备中,I/O端口被映射到一段内存中去,要访问这些端口就相当于访问一段内存。经常性的,我们要获得一块内存的物理地址。在dos环境下,(之所以不说是dos操作系统是因为我认为DOS根本就不是一个操作系统,它实在是太简单,太不安全了)只要用段:偏移就可以了。在window95中,95ddk提供了一个vmm 调用 _MapLinearToPhys,用以把线性地址转化为物理地址。但在Linux中是怎样做的呢? 

  2.内存操作 

  在设备驱动程序中动态开辟内存,不是用malloc,而是kmalloc,或者用get_free_pages直接申请页。释放内存用的是kfree,或free_pages. 请注意,kmalloc等函数返回的是物理地址!而malloc等返回的是线性地址!关于kmalloc返回的是物理地址这一点本人有点不太明白:既然从线性地址到物理地址的转换是由386cpu硬件完成的,那样汇编指令的操作数应该是线性地址,驱动程序同样也不能直接使用物理地址而是线性地址。但是事实上kmalloc返回的确实是物理地址,而且也可以直接通过它访问实际的RAM,我想这样可以由两种解释,一种是在核心态禁止分页,但是这好像不太现实;另一种是linux的页目录和页表项设计得正好使得物理地址等同于线性地址。我的想法不知对不对,还请高手指教。 

  言归正传,要注意kmalloc最大只能开辟128k-1616个字节是被页描述符结构占用了。kmalloc用法参见khg. 

  内存映射的I/O口,寄存器或者是硬件设备的RAM(如显存)一般占用F0000000以上的地址空间。在驱动程序中不能直接访问,要通过kernel函数vremap获得重新映射以后的地址。 

  另外,很多硬件需要一块比较大的连续内存用作DMA传送。这块内存需要一直驻留在内存,不能被交换到文件中去。但是kmalloc最多只能开辟128k的内存。 

  这可以通过牺牲一些系统内存的方法来解决。 

  具体做法是:比如说你的机器由32M的内存,在lilo.conf的启动参数中加上mem=30M,这样linux就认为你的机器只有30M的内存,剩下的2M内存在vremap之后就可以为DMA所用了。 

  请记住,用vremap映射后的内存,不用时应用unremap释放,否则会浪费页表。 

  3.中断处理 

  同处理I/O端口一样,要使用一个中断,必须先向系统登记。 

int request_irq(unsigned int irq  

void(*handle)(intvoid *struct pt_regs *) 

unsigned int long flags 

const char *device); 

irq: 是要申请的中断。 

handle:中断处理函数指针。 

flagsSA_INTERRUPT 请求一个快速中断,正常中断。 

device:设备名。


如果登记成功,返回0,这时在/proc/interrupts文件中可以看你请求的中断。 

  4.一些常见的问题。 

  对硬件操作,有时时序很重要。但是如果用C语言写一些低级的硬件操作的话,gcc往往会对你的程序进行优化,这样时序就错掉了。如果用汇编写呢,gcc同样会对汇编代码进行优化,除非你用volatile关键字修饰。最保险的办法是禁止优化。这当然只能对一部分你自己编写的代码。如果对所有的代码都不优化,你会发现驱动程序根本无法装载。这是因为在编译驱动程序时要用到gcc的一些扩展特性,而这些扩展特性必须在加了优化选项之后才能体现出来。

系统分类: 嵌入式   |    用户分类:    |    来源: 无分类

评论(2) | 阅读(1723)
发表于:2006/11/8 15:14:15
标签:无标签

14

RFID标准体系

对RFID感兴趣的朋友可以看一下这篇文章:http://blog.csdn.net/WAST/archive/2005/11/08/525160.aspx

对RFID体系标准作了全面的介绍,适合RFID入门的朋友。

系统分类: 通信网络   |    用户分类:    |    来源: 无分类

评论(0) | 阅读(1552)
23456Next >Total , Page /