linux设备驱动

linux设备驱动
linux设备驱动

Linux设备驱动

操作系统的目的之一就是将系统硬件设备细节从用户视线中隐藏起来。例如虚拟文件系统对各种类型已安装的文件系统提供了统一的视图而屏蔽了具体底层细节。本章将描叙Linux核心对系统中物理设备的管理。

CPU并不是系统中唯一的智能设备,每个物理设备都拥有自己的控制器。键盘、鼠标和串行口由一个高级I/O芯片统一管理,IDE控制器控制IDE硬盘而SCSI控制器控制SCSI硬盘等等。每个硬件控制器都有各自的控制和状态寄存器(CSR)并且各不相同。例如Adaptec 2940 SCSI控制器的CSR与NCR 810 SCSI控制器完全不一样。这些CSR被用来启动和停止,初始化设备及对设备进行诊断。在Linux中管理硬件设备控制器的代码并没有放置在每个应用程序中而是由内核统一管理。这些处理和管理硬件控制器的软件就是设备驱动。Linux 核心设备驱动是一组运行在特权级上的内存驻留底层硬件处理共享库。正是它们负责管理各个设备。

设备驱动的一个基本特征是设备处理的抽象概念。所有硬件设备都被看成普通文件;可以通过和操纵普通文件相同的标准系统调用来打开、关闭、读取和写入设备。系统中每个设备都用一种特殊的设备相关文件来表示(device special file),例如系统中第一个IDE硬盘被表示成/dev/hda。块(磁盘)设备和字符设备的设备相关文件可以通过mknod命令来创建,并使用主从设备号来描叙此设备。网络设备也用设备相关文件来表示,但Linux寻找和初始化网络设备时才建立这种文件。由同一个设备驱动控制的所有设备具有相同的主设备号。从设备号则被用来区分具有相同主设备号且由相同设备驱动控制的不同设备。例如主IDE硬盘的每个分区的从设备号都不相同。如/dev/hda2表示主IDE 硬盘的主设备号为3而从设备号为2。Linux通过使用主从设备号将包含在系统调用中的(如将一个文件系统mount到一个块设备)设备相关文件映射到设备的设备驱动以及大量系统表格中,如字符设备表,chrdevs。

Linux支持三类硬件设备:字符、块及网络设备。字符设备指那些无需缓冲直接读写的设备,如系统的串口设备/dev/cua0和/dev/cua1。块设备则仅能以块为单位读写,典型的块大小为512或1024字节。块设备的存取是通过

buffer cache来进行并且可以进行随机访问,即不管块位于设备中何处都可以

对其进行读写。块设备可以通过其设备相关文件进行访问,但更为平常的访问

方法是通过文件系统。只有块设备才能支持可安装文件系统。网络设备可以通

过BSD套接口访问,我们将在网络一章中讨论网络子系统。

Linux核心中虽存在许多不同的设备驱动但它们具有一些共性:

核心代码

设备驱动是核心的一部分,象核心中其它代码一样,出错将导致系统的严

重损伤。一个编写奇差的设备驱动甚至能使系统崩溃并导致文件系统的破坏和

数据丢失。核心接口

设备驱动必须为Linux核心或者其从属子系统提供一个标准接口。例如终

端驱动为Linux核心提供了一个文件I/O接口而SCSI设备驱动为SCSI子系统

提供了一个SCSI设备接口,同时此子系统为核心提供了文件I/O和buffer cache接口。核心机制与服务

设备驱动可以使用标准的核心服务如内存分配、中断发送和等待队列等等。动态可加载

多数Linux设备驱动可以在核心模块发出加载请求时加载,同时在不再使

用时卸载。这样核心能有效地利用系统资源。可配置

Linux设备驱动可以连接到核心中。当核心被编译时,哪些核心被连入核

心是可配置的。动态性

当系统启动及设备驱动初始化时将查找它所控制的硬件设备。如果某个设

备的驱动为一个空过程并不会有什么问题。此时此设备驱动仅仅是一个冗余的

程序,它除了会占用少量系统内存外不会对系统造成什么危害。8.1轮询与中

设备被执行某个命令时,如"将读取磁头移动到软盘的第42扇区上",设备驱动可以从轮询方式和中断方式中选择一种以判断设备是否已经完成此命令。

轮询方式意味着需要经常读取设备的状态,一直到设备状态表明请求已经

完成为止。如果设备驱动被连接进入核心,这时使用轮询方式将会带来灾难性

后果:核心将在此过程中无所事事,直到设备完成此请求。但是轮询设备驱动

可以通过使用系统定时器,使核心周期性调用设备驱动中的某个例程来检查设

备状态。定时器过程可以检查命令状态及Linux软盘驱动的工作情况。使用定

时器是轮询方式中最好的一种,但更有效的方法是使用中断。

基于中断的设备驱动会在它所控制的硬件设备需要服务时引发一个硬件中断。如以太网设备驱动从网络上接收到一个以太数据报时都将引起中断。Linux 核心需要将来自硬件设备的中断传递到相应的设备驱动。这个过程由设备驱动

向核心注册其使用的中断来协助完成。此中断处理例程的地址和中断号都将被

记录下来。在/proc/interrupts文件中你可以看到设备驱动所对应的中断号及

类型:

0:727432 timer 1:20534 keyboard 2:0 cascade 3:79691+serial 4:28258+serial 5:1 sound blaster 11:20868+aic7xxx 13:1 math error 14:247+ide0 15:170+ide1

对中断资源的请求在驱动初始化时就已经完成。作为IBM PC体系结构的遗产,系统中有些中断已经固定。例如软盘控制器总是使用中断6。其它中断,

如PCI设备中断,在启动时进行动态分配。设备驱动必须在取得对此中断的所

有权之前找到它所控制设备的中断号(IRQ)。Linux通过支持标准的PCI BIOS

回调函数来确定系统中PCI设备的中断信息,包括其IRQ号。

如何将中断发送给CPU本身取决于体系结构,但是在多数体系结构中,中

断以一种特殊模式发送同时还将阻止系统中其它中断的产生。设备驱动在其中

断处理过程中作的越少越好,这样Linux核心将能很快的处理完中断并返回中

断前的状态中。为了在接收中断时完成大量工作,设备驱动必须能够使用核心

的底层处理例程或者任务队列来对以后需要调用的那些例程进行排队。

8.2直接内存访问(DMA)

数据量比较少时,使用中断驱动设备驱动程序能顺利地在硬件设备和内存

之间交换数据。例如波特率为9600的modem可以每毫秒传输一个字符。如果硬

件设备引起中断和调用设备驱动中断所消耗的中断时延比较大(如2毫秒)则系

统的综合数据传输率会很低。则9600波特率modem的数据传输只能利用0.002%的CPU处理时间。高速设备如硬盘控制器或者以太网设备数据传输率将更高。SCSI设备的数据传输率可达到每秒40M字节。

直接内存存取(DMA)是解决此类问题的有效方法。DMA控制器可以在不受处

理器干预的情况下在设备和系统内存之间高速传输数据。PC机的ISA DMA控制

器有8个DMA通道,其中七个可以由设备驱动使用。每个DMA通道具有一个16

位的地址寄存器和一个16位的记数寄存器。为了初始化数据传输,设备驱动将设置DMA通道地址和记数寄存器以描叙数据传输方向以及读写类型。然后通知

设备可以在任何时候启动DMA操作。传输结束时设备将中断PC。在传输过程中CPU可以转去执行其他任务。

设备驱动使用DMA时必须十分小心。首先DMA控制器没有任何虚拟内存的

概念,它只存取系统中的物理内存。同时用作DMA传输缓冲的内存空间必须是

连续物理内存块。这意味着不能在进程虚拟地址空间内直接使用DMA。但是你

可以将进程的物理页面加锁以防止在DMA操作过程中被交换到交换设备上去。

另外DMA控制器所存取物理内存有限。DMA通道地址寄存器代表DMA地址的高

16位而页面寄存器记录的是其余8位。所以DMA请求被限制到内存最低16M字

节中。

DMA通道是非常珍贵的资源,一共才有7个并且还不能够在设备驱动间共享。与中断一样,设备驱动必须找到它应该使用那个DMA通道。有些设备使用

固定的DMA通道。例如软盘设备总使用DMA通道2。有时设备的DMA通道可以

由跳线来设置,许多以太网设备使用这种技术。设计灵活的设备将告诉系统它

将使用哪个DMA通道,此时设备驱动仅需要从DMA通道中选取即可。

Linux通过dma_chan(每个DMA通道一个)数组来跟踪DMA通道的使用情况。dma_chan结构中包含有两个域,一个是指向此DMA通道拥有者的指针,另一个

指示DMA通道是否已经被分配出去。当敲入cat/proc/dma打印出来的结果就是dma_chan结构数组。

8.3内存

设备驱动必须谨慎使用内存。由于它属于核心,所以不能使用虚拟内存。系统接收到中断信号时或调度底层任务队列处理过程时,设备驱动将开始运行,

而当前进程会发生改变。设备驱动不能依赖于任何运行的特定进程,即使当前

是为该进程工作。与核心的其它部分一样,设备驱动使用数据结构来描叙它所

控制的设备。这些结构被设备驱动代码以静态方式分配,但会增大核心而引起

空间的浪费。多数设备驱动使用核心中非页面内存来存储数据。

Linux为设备驱动提供了一组核心内存分配与回收过程。核心内存以2的

次幂大小的块来分配。如512或128字节,此时即使设备驱动的需求小于这个

数量也会分配这么多。所以设备驱动的内存分配请求可得到以块大小为边界的

内存。这样核心进行空闲块组合更加容易。

请求分配核心内存时Linux需要完成许多额外的工作。如果系统中空闲内

存数量较少,则可能需要丢弃些物理页面或将其写入交换设备。一般情况下Linux将挂起请求者并将此进程放置到等待队列中直到系统中有足够的物理内

存为止。不是所有的设备驱动(或者真正的Linux核心代码)都会经历这个过程,所以如分配核心内存的请求不能立刻得到满足,则此请求可能会失败。如果设备驱动希望在此内存中进行DMA,那么它必须将此内存设置为DMA使能的。这也

是为什么是Linux核心而不是设备驱动需要了解系统中的DMA使能内存的原因。

8.4设备驱动与核心的接口

Linux核心与设备驱动之间必须有一个以标准方式进行互操作的接口。每

一类设备驱动:字符设备、块设备及网络设备都提供了通用接口以便在需要时

为核心提供服务。这种通用接口使得核心可以以相同的方式来对待不同的设备

及设备驱动。如SCSI和IDE硬盘的区别很大但Linux对它们使用相同的接口。

Linux动态性很强。每次Linux核心启动时如遇到不同的物理设备将需要

不同的物理设备驱动。Linux允许通过配置脚本在核心重建时将设备驱动包含

在内。设备驱动在启动初始化时可能会发现系统中根本没有任何硬件需要控制。其它设备驱动可以在必要时作为核心模块动态加载到。为了处理设备驱动的动

态属性,设备驱动在初始化时将其注册到核心中去。Linux维护着已注册设备

驱动表作为和设备驱动的接口。这些表中包含支持此类设备例程的指针和相关

信息。

8.4.1字符设备

图8.1字符设备

字符设备是Linux设备中最简单的一种。应用程序可以和存取文件相同的

系统调用来打开、读写及关闭它。即使此设备是将Linux系统连接到网络中的PPP后台进程的modem也是如此。字符设备初始化时,它的设备驱动通过在device_struct结构的chrdevs数组中添加一个入口来将其注册到Linux核心上。设备的主设备标志符用来对此数组进行索引(如对tty设备的索引4)。设

备的主设备标志符是固定的。

chrdevs数组每个入口中的device_struct数据结构包含两个元素;一个

指向已注册的设备驱动名称,另一个则是指向一组文件操作指针。它们是位于

此字符设备驱动内部的文件操作例程的地址指针,用来处理相关的文件操作如

打开、读写与关闭。/proc/devices中字符设备的内容来自chrdevs数组。

当打开代表字符设备的字符特殊文件时(如/dev/cua0),核心必须作好准备以便调用相应字符设备驱动的文件操作例程。与普通的目录和文件一样,每个

字符特殊文件用一个VFS节点表示。每个字符特殊文件使用的VFS inode和所

有设备特殊文件一样,包含着设备的主从标志符。这个VFS inode由底层的文

件系统来建立(比如EXT2),其信息来源于设备相关文件名称所在文件系统。

每个VFS inode和一组文件操作相关联,它们根据inode代表的文件系统对象变化而不同。当创建一个代表字符相关文件的VFS inode时,其文件操作被

设置为缺省的字符设备操作。

字符设备只有一个文件操作:打开文件操作。当应用打开字符特殊文件时,通用文件打开操作使用设备的主标志符来索引此chrdevs数组,以便得到那些

文件操作函数指针。同时建立起描叙此字符特殊文件的file结构,使其文件操

作指针指向此设备驱动中的文件操作指针集合。这样所有应用对它进行的文件

操作都被映射到此字符设备的文件操作集合上。

8.4.2块设备

块设备也支持以文件方式访问。系统对块设备特殊文件提供了非常类似于

字符特殊文件的文件操作机制。Linux在blkdevs数组中维护所有已注册的块

设备。象chrdevs数组一样,blkdevs也使用设备的主设备号进行索引。其入

口也是device_struct结构。和字符设备不同的是系统有几类块设备。SCSI设

备是一类而IDE设备则是另外一类。它们将以各自类别登记到Linux核心中并

为核心提供文件操作功能。某类块设备的设备驱动为此类型设备提供了类别相

关的接口。如SCSI设备驱动必须为SCSI子系统提供接口以便SCSI子系统能用它来为核心提供对此设备的文件操作。

和普通文件操作接口一样,每个块设备驱动必须为buffer cache提供接口。每个块设备驱动将填充其在blk_dev数组中的blk_dev_struct结构入口。数组的索引值还是此设备的主设备号。这个blk_dev_struct结构包含请求过程的地址以及指向请求数据结构链表的指针,每个代表一个从buffer cache中来让设备进行数据读写的请求。

图8.2 buffer cache块设备请求

每当buffer cache希望从一个已注册设备中读写数据块时,它会将

request结构添加到其blk_dev_struct中。图8.2表示每个请求有指向一个或

多个buffer_hear结构的指针,每个请求读写一块数据。如buffer cache对buffer_head结构上锁,则进程会等待到对此缓冲的块操作完成。每个request

结构都从静态链表all_requests中分配。如果此请求被加入到空请求链表中,

则将调用驱动请求函数以启动此请求队列的处理,否则该设备驱动将简单地处理请求链表上的request。

一旦设备驱动完成了请求则它必须将每个buffer_heard结构从request结构中清除,将它们标记成已更新状态并解锁之。对buffer_head的解锁将唤醒

所有等待此块操作完成的睡眠进程。如解析文件名称时,EXT2文件系统必须从

包含此文件系统的设备中读取包含下个EXT2目录入口的数据块。在

buffer_head上睡眠的进程在设备驱动被唤醒后将包含此目录入口。request数据结构被标记成空闲以便被其它块请求使用。

8.5硬盘

磁盘驱动器提供了一个永久性存储数据的方式,将数据保存在旋转的盘片上。写入数据时磁头将磁化盘片上的一个小微粒。这些盘片被连接到一个中轴

上并以3000到10,000RPM(每分钟多少转)的恒定速度旋转。而软盘的转速仅

为360RPM。磁盘的读/写磁头负责读写数据,每个盘片的两侧各有一个磁头。

磁头读写时并不接触盘片表面而是浮在距表面非常近的空气垫中(百万分之一英寸)。磁头由一个马达驱动在盘片表面移动。所有的磁头被连在一起,它们同时穿过盘片的表面。

盘片的每个表面都被划分成为叫做磁道的狭窄同心圆。0磁道位于最外面

而最大磁道位于最靠近中央主轴。柱面指一组相同磁道号的磁道。所以每个盘

片上的第五磁道组成了磁盘的第五柱面。由于柱面号与磁道号相等所以我们经

常可以看到以柱面描叙的磁盘布局。每个磁道可进一步划分成扇区。它是硬盘

数据读写的最小单元同时也是磁盘的块大小。一般的扇区大小为512字节并且

这个大小可以磁盘制造出来后格式化时设置。

一个磁盘经常被描绘成有多少各柱面、磁头以及扇区。例如系统启动时Linux将这样描叙一个IDE硬盘:

hdb:Conner Peripherals 540MB-CFS540A,516MB w/64kB

Cache,CHS=1050/16/63

这表示此磁盘有1050各柱面(磁道),16个磁头(8个盘片)且每磁道包含

63个扇区。这样我们可以通过扇区数、块数以及512字节扇区大小计算出磁盘

的存储容量为529200字节。这个容量和磁盘自身声称的516M字节并不相同,

这是因为有些扇区被用来存放磁盘分区信息。有些磁盘还能自动寻找坏扇区并

重新索引磁盘以正常使用。

物理硬盘可进一步划分成分区。一个分区是一大组为特殊目的而分配的扇区。对磁盘进行分区使得磁盘可以同时被几个操作系统或不同目的使用。许多Linux系统具有三个分区:DOS文件系统分区,EXT2文件系统分区和交换分区。硬盘分区用分区表来描叙;表中每个入口用磁头、扇区及柱面号来表示分区的

起始与结束。对于用DOS格式化的硬盘有4个主分区表。但不一定所有的四个

入口都被使用。fdisk支持3中分区类型:主分区、扩展分区及逻辑分区。扩

展分区并不是真正的分区,它只不过包含了几个逻辑分区。扩展和逻辑分区用来打破四个主分区的限制。以下是一个包含两个主分区的fdisk命令的输出:

Disk/dev/sda:64 heads,32 sectors,510 cylinders Units=cylinders of 2048*512 bytes Device Boot Begin Start End Blocks Id System

/dev/sda1 11 478 489456 83 Linux native

/dev/sda2 479 479 510 32768 82 Linux swap Expert command(m for help):p Disk/dev/sda:64 heads,32 sectors,510 cylinders Nr AF Hd Sec Cyl Hd Sec Cyl Start Size ID 100 11 063 32 477 32 978912 83 200 01 478 63 32 509 978944 65536 82 300 00 00 00 00 00 400 00 00 00 00 00

这些内容表明第一个分区从柱面(或者磁道)0,头1和扇区1开始一直到柱面477,扇区22和头63结束。由于每磁道有32个扇区且有64个读写磁头则

此分区在大小上等于柱面数。fdisk使分区在柱面边界上对齐。它从最外面的

柱面0开始并向中间扩展478个柱面。第二个分区:交换分区从478号柱面开始并扩展到磁盘的最内圈。

图8.3磁盘链表

在初始化过程中Linux取得系统中硬盘的拓扑结构映射。它找出有多少中硬盘以及是什么类型。另外Linux还要找到每个硬盘的分区方式。所有这些都用由gendisk_head链指针指向的gendisk结构链表来表示。每个磁盘子系统如IDE在初始化时产生表示磁盘结构的gendisk结构。同时它将注册其文件操作

例程并将此入口添加到blk_dev数据结构中。每个gendisk结构包含唯一的主设备号,它与块相关设备的主设备号相同。例如SCSI磁盘子系统创建了一个主设备号为8的gendisk入口("sd"),这也是所有SCSI硬盘设备的主设备号。图8.3给出了两个gendisk入口,一个表示SCSI磁盘子系统而另一个表示IDE磁盘控制器。ide0表示主IDE控制器。

尽管磁盘子系统在其初始化过程中就建立了gendisk入口,但是只有Linux 作分区检查时才使用。每个磁盘子系统通过维护一组数据结构将物理硬盘上的分区与某个特殊主从特殊设备互相映射。无论何时通过buffer cache或文件操

作对块设备的读写都将被核心定向到对具有某个特定主设备号的设备文件上(如/dev/sda2)。而从设备号的定位由各自设备驱动或子系统来映射。

8.5.1 IDE硬盘

Linux系统上使用得最广泛的硬盘是集成电子磁盘或者IDE硬盘。IDE是一个硬盘接口而不是类似SCSI的I/O总线接口。每个IDE控制器支持两个硬盘,一个为主另一个为从。主从硬盘可以通过盘上的跳线来设置。系统中的第一个IDE控制器成为主IDE控制器而另一个为从属控制器。IDE可以以每秒3.3M字

节的传输率传输数据且最大容量为538M字节。EIDE或增强式IDE可以将磁盘

容量扩展到8.6G字节而数据传输率为16.6M字节/秒。由于IDE和EIDE都比SCSI硬盘便宜,所以大多现代PC机在包含一个或几个板上IDE控制器。

Linux以其发现控制器的顺序来对IDE硬盘进行命名。在主控制器中的主

盘为/dev/hda而从盘为/dev/hdb。/dev/hdc用来表示从属IDE控制器中的主盘。IDE子系统将向Linux核心注册IDE控制器而不是IDE硬盘。主IDE控制器的

主标志符为3而从属IDE控制器的主标志符为22。如果系统中包含两个IDE控

制器则IDE子系统的入口在blk_dev和blkdevs数组的第2和第22处。IDE的

块设备文件反应了这种编号方式,硬盘/dev/hda和/dev/hdb都连接到主IDE控制器上,其主标志符为3。对IDE子系统上这些块相关文件的文件或者buffer cache的操作都通过核心使用主设备标志符作为索引定向到IDE子系统上。当

发出请求时,此请求由哪个IDE硬盘来完成取决于IDE子系统。为了作到这一

点IDE子系统使用从设备编号对应的设备特殊标志符,由它包含的信息来将请

求发送到正确的硬盘上。位于主IDE控制器上的IDE从盘/dev/hdb的设备标志

符为(3,64)。而此盘中第一个分区(/dev/hdb1)的设备标志符为(3,65)。

8.5.2初始化IDE子系统

IDE磁盘与IBM PC关系非常密切。在这么多年中这些设备的接口发生了变化。这使得IDE子系统的初始化过程比看上去要复杂得多。

Linux可以支持的最多IDE控制器个数为4。每个控制器用ide_hwifs数组中的ide_hwif_t结构来表示。每个ide_hwif_t结构包含两个ide_drive_t结

构以支持主从IDE驱动器。在IDE子系统的初始化过程中Linux通过访问系统

CMOS来判断是否有关于硬盘的信息。这种CMOS由电池供电所以系统断电时也

不会遗失其中的内容。它位于永不停止的系统实时时钟设备中。此CMOS内存的位置由系统BIOS来设置,它将通知Linux系统中有多少个IDE控制器与驱动器。Linux使用这些从BIOS中发现的磁盘数据来建立对应此驱动器的ide_hwif_t

结构。许多现代PC系统使用PCI芯片组如Intel 82430 VX芯片组将PCI EIDE

控制器封装在内。IDE子系统使用PCI BIOS回调函数来定位系统中PCI(E)IDE

控制器。然后对这些芯片组调用PCI特定查询例程。

每次找到一个IDE接口或控制器就有建立一个ide_hwif_t结构来表示控制器和与之相连的硬盘。在操作过程中IDE驱动器对I/O内存空间中的IDE命令

寄存器写入命令。主IDE控制器的缺省控制和状态寄存器是0x1F0-0x1F7。这

个地址由早期的IBM PC规范设定。IDE驱动器为每个控制器向Linux注册块缓

冲cache和VFS节点并将其加入到blk_dev和blkdevs数组中。IDE驱动器需

要申请某个中断。一般主IDE控制器中断号为14而从属IDE控制器为15。然

而这些都可以通过命令行选项由核心来重载。IDE驱动器同时还将gendisk入

口加入到启动时发现的每个IDE控制器的gendisk链表中去。分区检查代码知

道每个IDE控制器可能包含两个IDE硬盘。

8.5.3 SCSI硬盘

SCSI(小型计算机系统接口)总线是一种高效的点对点数据总线,它最多可

以支持8个设备,其中包括多个主设备。每个设备有唯一的标志符并可以通过

盘上的跳线来设置。在总线上的两个设备间数据可以以同步或异步方式,在32

位数据宽度下传输率为40M字节来交换数据。SCSI总线上可以在设备间同时传

输数据与状态信息。initiator设备和target设备间的执行步骤最多可以包括

8个不同的阶段。你可以从总线上5个信号来分辨SCSI总线的当前阶段。这8

个阶段是:

BUS FREE

当前没有设备在控制总线且总线上无事务发生。ARBITRATION

一个SCSI设备试图取得SCSI总线的控制权,这时它将其SCSI标志符放置到地址引脚上。具有最高SCSI标志符编号的设备将获得总线控制权。SELECTION

当设备通过仲裁成功地取得了对SCSI总线的控制权后它必须向它准备发送命令的那个SCSI设备发出信号。具体做法是将目标设备的SCSI标志符放置在

地址引脚上进行声明。RESELECTION

在一个请求的处理过程中SCSI设备可能会断开连接。目标(target)设备将再次选择启动设备(initiator)。不是所有的SCSI设备都支持此阶段。COMMAND

此阶段中initiator设备将向target设备发送6、10或12字节命令。DATA IN,DATA OUT

此阶段中数据将在initiator设备和target设备间传输。STATUS

所有命令完毕后将进入此阶段,此时允许target设备向initiator设备发送状态信息以指示操作成功与否。MESSAGE IN,MESSAGE OUT

此阶段附加信息将在initiator设备和target设备间传输。Linux SCSI

子系统由两个基本部分组成,每个由一个数据结构来表示。

host

一个SCSI host即一个硬件设备:SCSI控制权。NCR 810 PCI SCSI控制权即一种SCSI host。在Linux系统中可以存在相同类型的多个SCSI控制权,每

个由一个单独的SCSI host来表示。这意味着一个SCSI设备驱动可以控制多个控制权实例。SCSI host总是SCSI命令的initiator设备。Device

虽然SCSI支持多种类型设备如磁带机、CD-ROM等等,但最常见的SCSI设

备是SCSI磁盘。SCSI设备总是SCSI命令的target。这些设备必须区别对待,例如象CD-ROM或者磁带机这种可移动设备,Linux必须检测介质是否已经移动。不同的磁盘类型有不同的主设备号,这样Linux可以将块设备请求发送到正确

的SCSI设备。初始化SCSI子系统

SCSI子系统的初始化非常复杂,它必须反映处SCSI总线及其设备的动态性。Linux在启动时初始化SCSI子系统。如果它找到一个SCSI控制器(即SCSI hosts)则会扫描此SCSI总线来找出总线上的所有设备。然后初始化这些设备并通过普通文件和buffer cache块设备操作使Linux核心的其它部分能使用这些设备。初始化过程分成四个阶段:

首先Linux将找出在系统核心连接时被连入核心的哪种类型的SCSI主机适配器或控制器有硬件需要控制。每个核心中的SCSI host在

builtin_scsi_hosts数组中有一个Scsi_Host_Template入口。而

Scsi_Host_Template结构中包含执行特定SCSI host操作,如检测连到此SCSI host的SCSI设备的例程的入口指针。这些例程在SCSI子系统进行自我配置时使用同时它们还是支持此host类型的SCSI设备驱动的一部分。每个被检测的SCSI host,即与真正SCSI设备连接的host将其自身的Scsi_Host_Template

结构添加到活动SCSI hosts的scsi_hosts结构链表中去。每个被检测host类型的实例用一个scsi_hostlist链表中的Scsi_Host结构来表示。例如一个包含两个NCR810 PCI SCSI控制器的系统的链表中将有两个Scsi_Host入口,每个控制器对应一个。每个Scsi_Host指向一个代表器设备驱动的

Scsi_Host_Template。

图8.4 SCSI数据结构

现在每个SCSI host已经找到,SCSI子系统必须找出哪些SCSI设备连接

哪个host的总线。SCSI设备的编号是从0到7,对于一条SCSI总线上连接的各个设备,其设备编号或SCSI标志符是唯一的。SCSI标志符可以通过设备上

的跳线来设置。SCSI初始化代码通过在SCSI总线上发送一个TEST_UNIT_READY 命令来找出每个SCSI设备。当设备作出相应时其标志符通过一个ENQUIRY命令来读取。Linux将从中得到生产厂商的名称和设备模式以及修订版本号。SCSI

命令由一个Scsi_Cmnd结构来表示同时这些命令通过调用Scsi_Host_Template 结构中的设备驱动例程传递到此SCSI host的设备驱动中。被找到的每个SCSI 设备用一个Scsi_Device结构来表示,每个指向其父Scsi_Host结构。所有这些Scsi_Device结构被添加到scsi_device链表中。图8.4给出了这些主要数据结构间的关系。

一共有四种SCSI设备类型:磁盘,磁带机,CD-ROM和普通SCSI设备。每

种类型的SCSI设备以不同的主块设备类型单独登记到核心中。如果有多个类型的SCSI设备存在则它们只登记自身。每个SCSI设备类型,如SCSI磁盘维护着其自身的设备列表。它使用这些表将核心块操作(file或者buffer cache)定向到正确的设备驱动或SCSI host上。每种SCSI设备类型用一个

Scsi_Device_Template结构来表示。此结构中包含此类型SCSI设备的信息以

及执行各种任务的例程的入口地址。换句话说,如果SCSI子系统希望连接一个SCSI磁盘设备它将调用SCSI磁盘类型连接例程。如果有多个该种类型的SCSI

设备被检测到则此Scsi_Type_Template结构将被添加到scsi_devicelist链表中。

SCSI子系统的最后一个阶段是为每个已登记的Scsi_Device_Template结

构调用finish函数。对于SCSI磁盘类型设备它将驱动所有SCSI磁盘并记录其磁盘布局。同时还将添加一个表示所有连接在一起的SCSI磁盘的gendisk结构,如图8.3。

发送块设备请求

一旦SCSI子系统初始化完成这些SCSI设备就可以使用了。每个活动的SCSI设备类型将其自身登记到核心以便Linux正确定向块设备请求。这些请求

可以是通过blk_dev的buffer cache请求也可以是通过blkdevs的文件操作。以一个包含多个EXT2文件系统分区的SCSI磁盘驱动器为例,当安装其中一个EXT2分区时系统是怎样将核心缓冲请求定向到正确的SCSI磁盘的呢?

每个对SCSI磁盘分区的块读写请求将导致一个新的request结构被添加到对应此SCSI磁盘的blk_dev数组中的current_request链表中。如果此

request正在被处理则buffer cache无需作任何工作;否则它必须通知SCSI

磁盘子系统去处理它的请求队列。系统中每个SCSI磁盘用一个Scsi_Disk结构来表示。例如/dev/sdb1的主设备号为8而从设备号为17;这样产生一个索引

值1。每个Scsi_Disk结构包含一个指向表示此设备的Scsi_Device结构。这

样反过来又指向拥有它的Scsi_Host结果。这个来自buffer cache的request

结构将被转换成一个描叙SCSI命令的Scsi_Cmd结构,这个SCSI命令将发送到

此SCSI设备同时被排入表示此设备的Scsi_Host结构。一旦有适当的数据块需要读写,这些请求将被独立的SCSI设备驱动来处理。

8.6网络设备

网络设备,即Linux的网络子系统,是一个发送与接收数据包的实体。它一般是一个象以太网卡的物理设备。有些网络设备如loopback设备仅仅是一个用来向自身发送数据的软件。每个网络设备都用一个device结构来表示。网络设备驱动在核心启动初始化网络时将这些受控设备登记到Linux中。device数据结构中包含有有关设备的信息以及用来支持各种网络协议的函数地址指针。这些函数主要用来使用网络设备传输数据。设备使用标准网络支持机制来将接收到的数据传递到适当的协议层。所有传输与接收到的网络数据用一个

sk_buff结构来表示,这些灵活的数据结构使得网络协议头可以更容易的添加与删除。网络协议层如何使用网络设备以及如何使用sk_buff来交换数据将在网络一章中详细描叙。本章只讨论device数据结构及如何发现与初始化网络。

device数据结构包含以下有关网络设备的信息:

Name

与使用mknod命令创建的块设备特殊文件与字符设备特殊文件不同,网络设备特殊文件仅在于系统网络设备发现与初始化时建立。它们使用标准的命名方法,每个名字代表一种类型的设备。多个相同类型设备将从0开始记数。这样以太网设备被命名为/dev/eth0,/dev/eth1,/dev/eth2等等。一些常见的网络设备如下:

/dev/ethN以太网设备

/dev/slNSLIP设备

/dev/pppNPPP设备

/dev/loLoopback设备

Bus Information

这些信息被设备驱动用来控制设备。irq号表示设备使用的中断号。base address指任何设备在I/O内存中的控制与状态寄存器地址。DMA通道指此网络设备使用的DMA通道号。所有这些信息在设备初始化时设置。Interface Flags

它们描叙了网络设备的属性与功能:

IFF_UP接口已经建立并运行

IFF_BROADCAST设备中的广播地址有效

IFF_DEBUG设备调试被使能

IFF_LOOPBACK这是一个loopback设备

IFF_POINTTOPOINT这是点到点连接(SLIP和PPP)

IFF_NOTRAILERS无网络追踪者

IFF_RUNNING资源已被分配

IFF_NOARP不支持ARP协议

IFF_PROMISC设备处于混乱的接收模式,无论包地址怎样它都将接收

IFF_ALLMULTI接收所有的IP多播帧

IFF_MULTICAST可以接收IP多播帧

Protocol Information

每个设备描叙它可以被网络协议层如何使用:

mtu

指不包括任何链路层头在内的,网络可传送的最大包大小。这个值被协议层用来选择适当大小的包进行发送。Family

这个family域表示设备支持的协议族。所有Linux网络设备的族是

AF_INET,互联网地址族。Type

这个硬件接口类型描叙网络设备连接的介质类型。Linux网络设备可以支

持多种不同类型的介质。包括以太网、X.25,令牌环,Slip,PPP和Apple Localtalk。Addresses

结构中包含大量网络设备相关的地址,包括IP地址。Packet Queue

指网络设备上等待传输的sk_buff包队列。Support Functions

每个设备支持一组标准的例程,它们被协议层作为设备链路层的接口而调用。如传输建立和帧传输例程以及添加标准帧头以及收集统计数据的例程。这

些统计数据可以使用ifconfig命令来观察。8.6.1初始化网络设备

网络设备驱动可以象其它Linux设备驱动一样建立到Linux核心中来。每

个潜在的网络设备由一个被dev_base链表指针指向的网络设备链表内部的device结构表示。当网络层需要某个特定工作执行时。它将调用大量网络服务

例程中的一个,这些例程的地址被保存在device结构内部。初始化时每个device结构仅包含一个初始化或者检测例程的地址。

对于网络设备驱动有两个问题需要解决。首先是不是每个连接到核心中的

网络设备驱动都有设备要控制。其次虽然底层的设备驱动迥然不同,但系统中

的以太网设备总是命名为/dev/eth0和/dev/eth1。混淆网络设备这个问题很容

易解决。当每个网络设备的初始化例程被调用时,将得到一个指示是否存在当

前控制器实例的状态信息。如果驱动找不到任何设备,它那个由dev_base指向的device链表将被删除。如果驱动找到了设备则它将用设备相关信息以及网络设备驱动中支撑函数的地址指针来填充此device数据结构。

第二个问题,即为以太网设备动态分配标准名称/dev/ethN设备特殊文件

的工作的解决方法十分巧妙。在设备链表中有8个标准入口;从eth0到eth7。它们使用相同的初始化例程,此初始化过程将依次尝试这些被建立到核心中的

以太网设备驱动直到找到一个设备。当驱动找到其以太网设备时它将填充对应

的ethN设备结构。同时此网络设备驱动初始化其控制的物理硬件并找出使用的

IRQ号以及DMA通道等信息。如果驱动找到了此网络设备的多个实例它将建立多个/dev/ethN device数据结构。一旦所有8个标准/dev/ethN被分配完毕则不会在检测其它的以太网设备。

特别声明:

1:资料来源于互联网,版权归属原作者

2:资料内容属于网络意见,与本账号立场无关

3:如有侵权,请告知,立即删除。

Linux设备驱动程序举例

Linux设备驱动程序设计实例2007-03-03 23:09 Linux系统中,设备驱动程序是操作系统内核的重要组成部分,在与硬件设备之间 建立了标准的抽象接口。通过这个接口,用户可以像处理普通文件一样,对硬件设 备进行打开(open)、关闭(close)、读写(read/write)等操作。通过分析和设计设 备驱动程序,可以深入理解Linux系统和进行系统开发。本文通过一个简单的例子 来说明设备驱动程序的设计。 1、程序清单 //MyDev.c 2000年2月7日编写 #ifndef __KERNEL__ #define __KERNEL__//按内核模块编译 #endif #ifndef MODULE #define MODULE//设备驱动程序模块编译 #endif #define DEVICE_NAME "MyDev" #define OPENSPK 1 #define CLOSESPK 2 //必要的头文件 #include //同kernel.h,最基本的内核模块头文件 #include //同module.h,最基本的内核模块头文件 #include //这里包含了进行正确性检查的宏 #include //文件系统所必需的头文件 #include //这里包含了内核空间与用户空间进行数据交换时的函数宏 #include //I/O访问 int my_major=0; //主设备号 static int Device_Open=0; static char Message[]="This is from device driver"; char *Message_Ptr; int my_open(struct inode *inode, struct file *file) {//每当应用程序用open打开设备时,此函数被调用 printk ("\ndevice_open(%p,%p)\n", inode, file); if (Device_Open) return -EBUSY;//同时只能由一个应用程序打开 Device_Open++; MOD_INC_USE_COUNT;//设备打开期间禁止卸载 return 0; } static void my_release(struct inode *inode, struct file *file)

(整理)嵌入式系统的以太网接口设计及linux内核网络设备驱动.

嵌入式系统的以太网接口设计及linux驱动 1 以太网概述 以太网(Ethernet)是当今局域网采用的最通用的通信协议标准。在以太网中,所有计算机被连接在一条电缆上,采用带冲突检测的载波侦听多路访问(CSMA/CD)方法,采用竞争机制和总线拓扑结构。基本上,以太网由共享传输媒体,如双绞线电缆或同轴电缆、多端口集线器、网桥或交换机构成。 按照OSI(Open System Interconnection Reference Model,开放式系统互联参考模型)7层参考模型,以太网定义的是物理层(PHY)和数据链路层(对应以太网的MAC层)的标准。 2 嵌入式处理器上扩展以太网接口 以太网接口控制器主要包括MAC乘PHY两部分,如图1所示为嵌入式处理器集成MAC层控制器。 MAC层控制器和PHY的连接是通过MII、RMII等接口实现的。在IEEE802的标准系列中,数据链路层包括LLC和MAC两个子层。其中MAC负责完成数据帧的封装、解封、发送和接受功能。PHY层的结构随着传输速率的不同而有一定的差异。对于1OBaseT等网络,从以太网PHY芯片输出的就是传输所需的差分信号。但是还需要一个网络隔离变压器组成图2的结构。网络隔离变压器可起到抑制共模干扰、隔离线路以及阻抗匹配等作用。 本文介绍一种新款网络接口芯片DM9000A,它可以很方便的实现与嵌入式CPU的接口,实现扩展以太网口的功能。DM9000A是中国台湾DAVICOM公司推出的一款高速以太网接口芯片,其基本特征是:集成10/100M物理层接口;内部带有16K字节SRAM用作接收发送的FIFO缓存;支持8/16bit两种主机工作模式:

一个简单的演示用的Linux字符设备驱动程序.

实现如下的功能: --字符设备驱动程序的结构及驱动程序需要实现的系统调用 --可以使用cat命令或者自编的readtest命令读出"设备"里的内容 --以8139网卡为例,演示了I/O端口和I/O内存的使用 本文中的大部分内容在Linux Device Driver这本书中都可以找到, 这本书是Linux驱动开发者的唯一圣经。 ================================================== ===== 先来看看整个驱动程序的入口,是char8139_init(这个函数 如果不指定MODULE_LICENSE("GPL", 在模块插入内核的 时候会出错,因为将非"GPL"的模块插入内核就沾污了内核的 "GPL"属性。 module_init(char8139_init; module_exit(char8139_exit; MODULE_LICENSE("GPL"; MODULE_AUTHOR("ypixunil"; MODULE_DESCRIPTION("Wierd char device driver for Realtek 8139 NIC"; 接着往下看char8139_init( static int __init char8139_init(void {

int result; PDBG("hello. init.\n"; /* register our char device */ result=register_chrdev(char8139_major, "char8139", &char8139_fops; if(result<0 { PDBG("Cannot allocate major device number!\n"; return result; } /* register_chrdev( will assign a major device number and return if it called * with "major" parameter set to 0 */ if(char8139_major == 0 char8139_major=result; /* allocate some kernel memory we need */ buffer=(unsigned char*(kmalloc(CHAR8139_BUFFER_SIZE, GFP_KERNEL; if(!buffer { PDBG("Cannot allocate memory!\n"; result= -ENOMEM;

Linux网络设备驱动开发实验

实验三:Linux网络设备驱动开发实验 一、实验目的 读懂linux网络设备驱动程序例子,并且实际加载驱动程序,加载进操作系统以后,会随着上层应用程序的触发而执行相应动作,具体执行的动作可以通过代码进行改变。 ●读懂源码及makefile ●编译驱动程序 ●加载 ●多种形式触发动作 二、预备知识 熟悉linux驱动基本原理,能读懂简单的makefile。 三、实验预计时间 80-120分钟左右 四、驱动程序部分具体步骤 要求读懂一个最简单的驱动程序,在驱动程序的诸如“xxx_open”、“xxx_read”等标准接口里面加入打印语句。可参考多模式教学网上的驱动样例。 五、用于触发驱动动作的应用程序及命令 驱动程序就是以静态的标准接口库函数形式存在,网络设备驱动会受到两大类情况的触发,一种是linux里面的控制台里面的命令,另一种是套接口应用程序,首先要搞清都有哪些具体的命令和应用程序流程,应用程序参考多模式教学网的例子。 六、运行测试 提示:需要将驱动程序以dll加载进系统中,并且触发应用程序调用各种文件操作的接口函数,使得驱动有所动作,打印出相关信息。 1.编译驱动: cd /某某目录/vnetdev/ make clean make 2.加载驱动与打开网卡: insmod netdrv.ko

ifconfig vnet0 up 3.运行应用程序 ../raw 4.通过命令“修改网卡MTU”触发驱动执行动作: ifconfig vnet0 mtu 1222 5.显示内核打印: cat /var/log/messages 6.卸载: ifconfig vnet0 down rmmod netdrv.ko 7.修改代码中的某些函数中的打印信息,重新试验上述流程。 至此大家都应该真正理解和掌握了驱动程序-操作系统-应用程序的三者联动机制。 七、实验结果 由图可知能正常加载网卡驱动,并且能够打印调试信息。

Linux设备驱动程序学习(18)-USB 驱动程序(三)

Linux设备驱动程序学习(18)-USB 驱动程序(三) (2009-07-14 11:45) 分类:Linux设备驱动程序 USB urb (USB request block) 内核使用2.6.29.4 USB 设备驱动代码通过urb和所有的 USB 设备通讯。urb用 struct urb 结构描述(include/linux/usb.h )。 urb以一种异步的方式同一个特定USB设备的特定端点发送或接受数据。一个USB 设备驱动可根据驱动的需要,分配多个 urb 给一个端点或重用单个 urb 给多个不同的端点。设备中的每个端点都处理一个 urb 队列, 所以多个 urb 可在队列清空之前被发送到相同的端点。 一个 urb 的典型生命循环如下: (1)被创建; (2)被分配给一个特定 USB 设备的特定端点; (3)被提交给 USB 核心; (4)被 USB 核心提交给特定设备的特定 USB 主机控制器驱动; (5)被 USB 主机控制器驱动处理, 并传送到设备; (6)以上操作完成后,USB主机控制器驱动通知 USB 设备驱动。 urb 也可被提交它的驱动在任何时间取消;如果设备被移除,urb 可以被USB 核心取消。urb 被动态创建并包含一个内部引用计数,使它们可以在最后一个用户释放它们时被自动释放。 struct urb

struct list_head urb_list;/* list head for use by the urb's * current owner */ struct list_head anchor_list;/* the URB may be anchored */ struct usb_anchor *anchor; struct usb_device *dev;/* 指向这个 urb 要发送的目标 struct usb_device 的指针,这个变量必须在这个 urb 被发送到 USB 核心之前被USB 驱动初始化.*/ struct usb_host_endpoint *ep;/* (internal) pointer to endpoint */ unsigned int pipe;/* 这个 urb 所要发送到的特定struct usb_device 的端点消息,这个变量必须在这个 urb 被发送到 USB 核心之前被 USB 驱动初始化.必须由下面的函数生成*/ int status;/*当 urb开始由 USB 核心处理或处理结束, 这个变量被设置为 urb 的当前状态. USB 驱动可安全访问这个变量的唯一时间是在 urb 结束处理例程函数中. 这个限制是为防止竞态. 对于等时 urb, 在这个变量中成功值(0)只表示这个 urb 是否已被去链. 为获得等时 urb 的详细状态, 应当检查 iso_frame_desc 变量. */ unsigned int transfer_flags;/* 传输设置*/ void*transfer_buffer;/* 指向用于发送数据到设备(OUT urb)或者从设备接收数据(IN urb)的缓冲区指针。为了主机控制器驱动正确访问这个缓冲, 它必须使用 kmalloc 调用来创建, 不是在堆栈或者静态内存中。对控制端点, 这个缓冲区用于数据中转*/ dma_addr_t transfer_dma;/* 用于以 DMA 方式传送数据到 USB 设备的缓冲区*/ int transfer_buffer_length;/* transfer_buffer 或者 transfer_dma 变量指向的缓冲区大小。如果这是 0, 传送缓冲没有被 USB 核心所使用。对于一个 OUT 端点, 如果这个端点大小比这个变量指定的值小, 对这个USB 设备的传输将被分成更小的块,以正确地传送数据。这种大的传送以连续的 USB 帧进行。在一个 urb 中提交一个大块数据, 并且使 USB 主机控制器去划分为更小的块, 比以连续地顺序发送小缓冲的速度快得多*/

linux设备驱动中常用函数

Linux2.6设备驱动常用的接口函数(一) ----字符设备 刚开始,学习linux驱动,觉得linux驱动很难,有字符设备,块设备,网络设备,针对每一种设备其接口函数,驱动的架构都不一样。这么多函数,要每一个的熟悉,那可多难啦!可后来发现linux驱动有很多规律可循,驱动的基本框架都差不多,再就是一些通用的模块。 基本的架构里包括:加载,卸载,常用的读写,打开,关闭,这是那种那基本的咯。利用这些基本的功能,当然无法实现一个系统。比方说:当多个执行单元对资源进行访问时,会引发竞态;当执行单元获取不到资源时,它是阻塞还是非阻塞?当突然间来了中断,该怎么办?还有内存管理,异步通知。而linux 针对这些问题提供了一系列的接口函数和模板框架。这样,在实际驱动设计中,根据具体的要求,选择不同的模块来实现其功能需求。 觉得能熟练理解,运用这些函数,是写号linux设备驱动的第一步。因为是设备驱动,是与最底层的设备打交道,就必须要熟悉底层设备的一些特性,例如字符设备,块设备等。系统提供的接口函数,功能模块就像是工具,能够根据不同的底层设备的的一些特性,选择不同的工具,方能在linux驱动中游刃有余。 最后就是调试,这可是最头疼的事。在调试过程中,总会遇到这样,那样的问题。怎样能更快,更好的发现并解决这些问题,就是一个人的道行咯!我个人觉得: 发现问题比解决问题更难! 时好时坏的东西,最纠结! 看得见的错误比看不见的错误好解决! 一:Fops结构体中函数: ①ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); 用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以-EINVAL("Invalid argument") 失败. 一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型). ②ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); 发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数 ③loff_t (*llseek) (struct file *, loff_t, int); llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值. loff_t 参数是一个"long offset", 并且就算在 32位平台上也至少 64 位宽. 错误由一个负返回值指示. 如果这个函数指针是 NULL, seek 调用会以潜在地无法预知的方式修改 file 结构中的位置计数器( 在"file 结构" 一节中描述). ④int (*open) (struct inode *, struct file *);

Linux设备驱动模型之platform总线深入浅出

Linux设备驱动模型之platform总线深入浅出 在Linux2.6以后的设备驱动模型中,需关心总线,设备和驱动这三种实体,总线将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。 对于依附在USB、PCI、I2C、SPI等物理总线来这些都不是问题。但是在嵌入式系统里面,在Soc系统中集成的独立外设控制器,挂接在Soc内存空间的外设等却不依附在此类总线。基于这一背景,Linux发明了一种总线,称为platform。相对于USB、PCI、I2C、SPI等物理总线来说,platform总线是一种虚拟、抽象出来的总线,实际中并不存在这样的总线。 platform总线相关代码:driver\base\platform.c 文件相关结构体定义:include\linux\platform_device.h 文件中 platform总线管理下最重要的两个结构体是platform_device和platform_driver 分别表示设备和驱动在Linux中的定义如下一:platform_driver //include\linux\platform_device.h struct platform_driver { int (*probe)(struct platform_device *); //探测函数,在注册平台设备时被调用int (*remove)(struct platform_device *); //删除函数,在注销平台设备时被调用void (*shutdown)(struct platform_device *); int (*suspend)(struct platform_device *, pm_message_t state); //挂起函数,在关机被调用int (*suspend_late)(struct platform_device *, pm_message_t state); int (*resume_early)(struct platform_device *); int (*resume)(struct platform_device *);//恢复函数,在开机时被调用struct device_driver driver;//设备驱动结构}; 1 2 3 4 5 6 7 8

Linux设备驱动程序学习(20)-内存映射和DMA-基本概念

Linux设备驱动程序学习(20)-内存映射和DMA-基本概念 (2011-09-25 15:47) 标签: 虚拟内存设备驱动程序Linux技术分类:Linux设备驱动程序 这部分主要研究 Linux 内存管理的基础知识, 重点在于对设备驱动有用的技术. 因为许多驱动编程需要一些对于虚拟内存(VM)子系统原理的理解。 而这些知识主要分为三个部分: 1、 mmap系统调用的实现原理:它允许设备内存直接映射到一个用户进程地址 空间. 这样做对一些设备来说可显著地提高性能. 2、与mmap的功能相反的应用原理:内核态代码如何跨过边界直接存取用户空间的内存页. 虽然较少驱动需要这个能力. 但是了解如何映射用户空间内存到内 核(使用 get_user_pages)会有用. 3、直接内存存取( DMA ) I/O 操作, 它提供给外设对系统内存的直接存取. 但所有这些技术需要理解 Linux 内存管理的基本原理, 因此我将先学习VM子 系统的基本原理. 一、Linux的内存管理 这里重点是 Linux 内存管理实现的主要特点,而不是描述操作系统的内存管理理论。Linux虚拟内存管理非常的复杂,要写可以写一本书:《深入理解Linux 虚拟内存管理》。学习驱动无须如此深入, 但是对它的工作原理的基本了解是必要的. 解了必要的背景知识后,才可以学习内核管理内存的数据结构. Linux是一个虚拟内存系统(但是在没有MMU的CPU中跑的ucLinux除外), 意味着在内核启动了MMU 之后所有使用的地址不直接对应于硬件使用的物理地址,这些地址(称之为虚拟地址)都经过了MMU转换为物理地址之后再从CPU的内存总线中发出,读取/写入数据. 这样 VM 就引入了一个间接层, 它是许多操作成为可能: 1、系统中运行的程序可以分配远多于物理内存的内存空间,即便单个进程都可拥有一个大于系统的物理内存的虚拟地址空间. 2、虚拟内存也允许程序对进程的地址空间运用多种技巧, 包括映射程序的内存到设备内存.等等~~~ 1、地址类型 Linux 系统处理几种类型的地址, 每个有它自己的含义: 用户虚拟地址:User virtual addresses,用户程序见到的常规地址. 用户地址在长度上是 32 位或者 64 位, 依赖底层的硬件结构, 并且每个进程有它自己 的虚拟地址空间.

Linux网络设备驱动

嵌入式培训专家
Linux网络设备驱动
主讲:宋宝华
https://www.360docs.net/doc/6a6135366.html,

华清远见
今天的内容
vLinux网络设备驱动架构 vLinux网络设备驱动数据流程
? NON-NAPI模式数据接收流程 ? NAPI模式数据接收流程 ? 数据发送流程
vLinux网络协议栈的实现
? TCP/UDP/IP/MAC各层数据传递 ? 网络系统调用与socket

华清远见
Linux网络设备驱动架构

华清远见
net_device
struct net_device_ops { int (*ndo_open)(struct net_device *dev); int (*ndo_start_xmit) (struct sk_buff *skb, struct net_device *dev); int (*ndo_set_mac_address)(struct net_device *dev, void *addr); int (*ndo_do_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd); void (*ndo_tx_timeout) (struct net_device *dev); ... }
struct net_device { struct net_device_stats stats; const struct net_device_ops *netdev_ops; const struct ethtool_ops *ethtool_ops; ... }
struct ethtool_ops { int (*get_settings)(struct net_device *, struct ethtool_cmd *); int (*set_settings)(struct net_device *, struct ethtool_cmd *); void (*get_drvinfo)(struct net_device *, struct ethtool_drvinfo *); int (*get_regs_len)(struct net_device *); ... }

Linux设备驱动程序简介

第一章Linux设备驱动程序简介 Linux Kernel 系统架构图 一、驱动程序的特点 ?是应用和硬件设备之间的一个软件层。 ?这个软件层一般在内核中实现 ?设备驱动程序的作用在于提供机制,而不是提供策略,编写访问硬件的内核代码时不要给用户强加任何策略 o机制:驱动程序能实现什么功能。 o策略:用户如何使用这些功能。 二、设备驱动分类和内核模块 ?设备驱动类型。Linux 系统将设备驱动分成三种类型 o字符设备 o块设备 o网络设备 ?内核模块:内核模块是内核提供的一种可以动态加载功能单元来扩展内核功能的机制,类似于软件中的插件机制。这种功能单元叫内核模块。 ?通常为每个驱动创建一个不同的模块,而不在一个模块中实现多个设备驱动,从而实现良好的伸缩性和扩展性。 三、字符设备 ?字符设备是个能够象字节流<比如文件)一样访问的设备,由字符设备驱动程序来实现这种特性。通过/dev下的字符设备文件来访问。字符设备驱动程序通常至少需要实现 open、close、read 和 write 等系统调用 所对应的对该硬件进行操作的功能函数。 ?应用程序调用system call<系统调用),例如:read、write,将会导致操作系统执行上层功能组件的代码,这些代码会处理内核的一些内部 事务,为操作硬件做好准备,然后就会调用驱动程序中实现的对硬件进 行物理操作的函数,从而完成对硬件的驱动,然后返回操作系统上层功 能组件的代码,做好内核内部的善后事务,最后返回应用程序。 ?由于应用程序必须使用/dev目录下的设备文件<参见open调用的第1个参数),所以该设备文件必须事先创建。谁创建设备文件呢? ?大多数字符设备是个只能顺序访问的数据通道,不能前后移动访问指针,这点和文件不同。比如串口驱动,只能顺序的读写设备。然而,也 存在和数据区或者文件特性类似的字符设备,访问它们时可前后移动访

linux驱动基础试题

L i n u x驱动基础试题(时间:1个小时) 一、选择题(每题4分,共40分,包括单选和多选,多选、少选均不得分) 1.Linux系统中将设备进行分类管理,下列设备中(ACD)属于字符设备,(BC)属于块设备 [A]键盘[B]硬盘[C]闪存设备[D]帧缓存设备[E]网卡 2.Linux系统中,内核以(D)区分设备 [A]设备节点名[B]设备节点号[C]设备名称[D]设备号 3.Linux系统中设备节点可以创建在(A) [A]/dev目录下[B]根目录下[C]/tmp目录下[E]以上都可以 4.Linux驱动程序运行在(A) [A]内核空间[B]用户空间[C]用户空间和内核空间 5.Linux系统中设备驱动程序是以模块形式组织的,编译驱动时可以用哪种方式编译(AB) [A]静态编译进内核[B]动态编译 6.内核中,设备的主设备号用(B)位来表示,次设备号用(D)位来表示 [A]8[B]12[C]16[D]20[E]24[F]32 7.Linux系统中哪些种类的设备有设备节点(BD) [A]定时器[B]字符设备[C]块设备[D]网络设备 8.通常情况下,kmalloc函数能分配的最大内存是(C) [A]4K[B]64K[C]128K[D]4M 9.能保证物理空间上连续的内存分配函数是(AB) [A]__get_free_pages[B]kmalloc[C]vmalloc[D]malloc 10.Linux系统中通过add_timer添加的timer是(A) [A]一次的[B]循环的[C]以上两种都可以 二、简答题(每题6分,共60分) 系统中以模块方式组织设备驱动程序,请列举在一个模块程序中必不可少的组成部分。(可以写个Helloworld模块的程序) 2.请从定义、性质、操作方式等方面对比说明字符设备和块设备。 3.请列举Linux设备驱动程序中,程序延缓执行的机制。 4.简述Linux设备驱动中使用中断的步骤。 5.简述信号量和自旋锁的异同和使用时的注意事项。 6.简述命令mknod/dev/zeroc15的做用和命令各个部分的含义,并写出创建一个块设备节点的命令。 7.简述命令insmod,rmmod,lsmod的功能。 8.驱动程序中采用动态申请设备号的,我们如何得到对应设备的设备号? 9.简述设备驱动程序和普通应用程序的异同点。 10.简述mmap机制的作用和使用mmap的好处。

如何实现Linux设备驱动模型

文库资料?2017 Guangzhou ZHIYUAN Electronics Stock Co., Ltd. 如何实现Linux 设备驱动模型 设备驱动模型,对系统的所有设备和驱动进行了抽象,形成了复杂的设备树型结构,采用面向对象的方法,抽象出了device 设备、driver 驱动、bus 总线和class 类等概念,所有已经注册的设备和驱动都挂在总线上,总线来完成设备和驱动之间的匹配。总线、设备、驱动以及类之间的关系错综复杂,在Linux 内核中通过kobject 、kset 和subsys 来进行管理,驱动编写可以忽略这些管理机制的具体实现。 设备驱动模型的内部结构还在不停的发生改变,如device 、driver 、bus 等数据结构在不同版本都有差异,但是基于设备驱动模型编程的结构基本还是统一的。 Linux 设备驱动模型是Linux 驱动编程的高级内容,这一节只对device 、driver 等这些基本概念作介绍,便于阅读和理解内核中的代码。实际上,具体驱动也不会孤立的使用这些概念,这些概念都融合在更高层的驱动子系统中。对于大多数读者可以忽略这一节内容。 1.1.1 设备 在Linux 设备驱动模型中,底层用device 结构来描述所管理的设备。device 结构在文件中定义,如程序清单错误!文档中没有指定样式的文字。.1所示。 程序清单错误!文档中没有指定样式的文字。.1 device 数据结构定义 struct device { struct device *parent; /* 父设备 */ struct device_private *p; /* 设备的私有数据 */ struct kobject kobj; /* 设备的kobject 对象 */ const char *init_name; /*设备的初始名字 */ struct device_type *type; /* 设备类型 */ struct mutex mutex; /*同步驱动的互斥信号量 */ struct bus_type *bus; /*设备所在的总线类型 */ struct device_driver *driver; /*管理该设备的驱动程序 */ void *platform_data; /*平台相关的数据 */ struct dev_pm_info power; /* 电源管理 */ #ifdef CONFIG_NUMA int numa_node; /*设备接近的非一致性存储结构 */ #endif u64 *dma_mask; /* DMA 掩码 */ u64 coherent_dma_mask; /*设备一致性的DMA 掩码 */ struct device_dma_parameters *dma_parms; /* DMA 参数 */ struct list_head dma_pools; /* DMA 缓冲池 */ struct dma_coherent_mem *dma_mem; /* DMA 一致性内存 */ /*体系结构相关的附加项*/ struct dev_archdata archdata; /* 体系结构相关的数据 */ #ifdef CONFIG_OF

Linux 系统下4G 终端模块驱动的实现

龙源期刊网 https://www.360docs.net/doc/6a6135366.html, Linux 系统下4G 终端模块驱动的实现 作者:邹龙王德志刘忠诚周治坤 来源:《电脑知识与技术》2015年第28期 摘要:文章分析了Linux系统的设备驱动原理,USB接口设备的驱动程序编写与内核编译原理,结合实例完成了4G模块的驱动程序与内核编译,并对编译后的Linux系统进行了验证,验证了系统内核能够正确识别4G模块并分配内存,成功实现了Linux系统的4G模块驱动。 关键词:Linux;设备驱动;4G;USB 中图分类号:TP391 文献标识码:A 文章编号:1009-3044(2015)27-0206-04 Abstract: The device driver of Linux system is analyzed, and the USB interface device driver is compiled with the kernel principle. The 4G module is compiled with an example. The Linux system is verified by the 4G system. The system kernel can correctly identify the 4G module and allocate memory.. Key words: Linux; device driver; 4G; USB Linux系统以其良好的可剪裁性、强稳定性以及易操作等特点,已在物联网,程序控制,电子消费,智能家居等领域得到广泛的使用。4G网络的推广和应用也在各领域展开。因此,将Linux设备与4G网络有机地结合起来,为新一代物联网构造一个更加高速,更加安全,更加稳定的网络通信环境,将会成为一个应用热点。 本文介绍了一种Linux系统驱动4G模块的方法,Linux系统通过USB接口驱动4G终端 模块,实现4G网络的接入。首先,文章介绍了整体的软硬件应用环境,然后分析了Linux系统下的设备驱动以及USB接口设备驱动的编写原理,完成了4G终端模块在Linux系统中的驱动程序编写和内核编译,并且最后对驱动的内核烧入进行了验证性测试。 1 Linux系统设备驱动原理 当一个新的硬件设备接入Linux系统时[1],我们需要加载与其对应的驱动程序,之后驱动程序会根据自己的类型向Linux系统注册,注册成功后系统会为驱动程序配置与其类型相应的软件接口以及反馈一个主设备号给驱动程序,然后驱动程序会根据这个主设备号在/dev目录下创建一个设备文件,这样,我们就可以通过这个设备文件来对接入的硬件设备进行控制了。 1.1 Linux系统设备驱动类型

Linux设备驱动程序学习(10)-时间、延迟及延缓操作

Linux设备驱动程序学习(10)-时间、延迟及延缓操作 Linux设备驱动程序学习(10) -时间、延迟及延缓操作 度量时间差 时钟中断由系统定时硬件以周期性的间隔产生,这个间隔由内核根据HZ 值来设定,HZ 是一个体系依赖的值,在中定义或该文件包含的某个子平台相关文件中。作为通用的规则,即便如果知道HZ 的值,在编程时应当不依赖这个特定值,而始终使用HZ。对于当前版本,我们应完全信任内核开发者,他们已经选择了最适合的HZ值,最好保持HZ 的默认值。 对用户空间,内核HZ几乎完全隐藏,用户HZ 始终扩展为100。当用户空间程序包含param.h,且每个报告给用户空间的计数器都做了相应转换。对用户来说确切的HZ 值只能通过/proc/interrupts 获得:/proc/interrup ts 的计数值除以/proc/uptime 中报告的系统运行时间。 对于ARM体系结构:在文件中的定义如下: 也就是说:HZ 由__KERNEL__和CONFIG_HZ决定。若未定义__KERNEL__,H Z为100;否则为CONFIG_H Z。而CONFIG_HZ是在内核的根目录

的.config文件中定义,并没有在make menuconfig的配置选项中出现。Linux的\arch\arm\configs\s3c2410_defconfig文件中的定义为: 所以正常情况下s3c24x0的HZ为200。这一数值在后面的实验中可以证实。 每次发生一个时钟中断,内核内部计数器的值就加一。这个计数器在系统启动时初始化为0,因此它代表本次系统启动以来的时钟嘀哒数。这个计数器是一个64-位变量( 即便在32-位的体系上)并且称为“jiffies_64”。但是驱动通常访问jiffies 变量(unsigned long)(根据体系结构的不同:可能是jiffies_64 ,可能是jiffies_64 的低32位)。使用jiffies 是首选,因为它访问更快,且无需在所有的体系上实现原子地访问64-位的jiffies_64 值。 使用jiffies 计数器 这个计数器和用来读取它的工具函数包含在,通常只需包含,它会自动放入jiffi es.h 。 jiffies 和jiffies_64 必须被当作只读变量。当需要记录当前jiffies 值(被声明为volatile 避免编译器优化内存读)时,可以简单地访问这个unsigned long 变量,如: 以下是一些简单的工具宏及其定义:

linux设备驱动

Linux设备驱动 操作系统的目的之一就是将系统硬件设备细节从用户视线中隐藏起来。例如虚拟文件系统对各种类型已安装的文件系统提供了统一的视图而屏蔽了具体底层细节。本章将描叙Linux核心对系统中物理设备的管理。 CPU并不是系统中唯一的智能设备,每个物理设备都拥有自己的控制器。键盘、鼠标和串行口由一个高级I/O芯片统一管理,IDE控制器控制IDE硬盘而SCSI控制器控制SCSI硬盘等等。每个硬件控制器都有各自的控制和状态寄存器(CSR)并且各不相同。例如Adaptec 2940 SCSI控制器的CSR与NCR 810 SCSI控制器完全不一样。这些CSR被用来启动和停止,初始化设备及对设备进行诊断。在Linux中管理硬件设备控制器的代码并没有放置在每个应用程序中而是由内核统一管理。这些处理和管理硬件控制器的软件就是设备驱动。Linux 核心设备驱动是一组运行在特权级上的内存驻留底层硬件处理共享库。正是它们负责管理各个设备。 设备驱动的一个基本特征是设备处理的抽象概念。所有硬件设备都被看成普通文件;可以通过和操纵普通文件相同的标准系统调用来打开、关闭、读取和写入设备。系统中每个设备都用一种特殊的设备相关文件来表示(device special file),例如系统中第一个IDE硬盘被表示成/dev/hda。块(磁盘)设备和字符设备的设备相关文件可以通过mknod命令来创建,并使用主从设备号来描叙此设备。网络设备也用设备相关文件来表示,但Linux寻找和初始化网络设备时才建立这种文件。由同一个设备驱动控制的所有设备具有相同的主设备号。从设备号则被用来区分具有相同主设备号且由相同设备驱动控制的不同设备。例如主IDE硬盘的每个分区的从设备号都不相同。如/dev/hda2表示主IDE 硬盘的主设备号为3而从设备号为2。Linux通过使用主从设备号将包含在系统调用中的(如将一个文件系统mount到一个块设备)设备相关文件映射到设备的设备驱动以及大量系统表格中,如字符设备表,chrdevs。 Linux支持三类硬件设备:字符、块及网络设备。字符设备指那些无需缓冲直接读写的设备,如系统的串口设备/dev/cua0和/dev/cua1。块设备则仅能以块为单位读写,典型的块大小为512或1024字节。块设备的存取是通过

Linux设备驱动程序说明介绍

Linux设备驱动程序简介 Linux是Unix操作系统的一种变种,在Linux下编写驱动程序的原理和思想完全类似于其他的Unix系统,但它dos或window环境下的驱动程序有很大的区别。在Linux环境下设计驱动程序,思想简洁,操作方便,功能也很强大,但是支持函数少,只能依赖kernel 中的函数,有些常用的操作要自己来编写,而且调试也不方便。本人这几周来为实验室自行研制的一块多媒体卡编制了驱动程序,获得了一些经验,愿与Linux fans共享,有不当之处,请予指正。 以下的一些文字主要来源于khg,johnsonm的Write linux device driver,Brennan's Guide to Inline Assembly,The Linux A-Z,还有清华BBS上的有关device driver的一些资料. 这些资料有的已经过时,有的还有一些错误,我依据自己的试验结果进行了修正. 一、Linux device driver 的概念 系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口.设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作.设备驱动程序是内核的一部分,它完成以下的功能: 1.对设备初始化和释放. 2.把数据从内核传送到硬件和从硬件读取数据. 3.读取应用程序传送给设备文件的数据和回送应用程序请求的数据. 4.检测和处理设备出现的错误. 在Linux操作系统下有两类主要的设备文件类型,一种是字符设备,另一种是块设备.字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般就紧接着发生了,块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请求能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际的I/O操作.块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间来等待. 已经提到,用户进程是通过设备文件来与实际的硬件打交道.每个设备文件都都有其文件属性(c/b),表示是字符设备还蔤强樯璞?另外每个文件都有两个设备号,第一个是主设备号,标识驱动程序,第二个是从设备号,标识使用同一个设备驱动程序的不同的硬件设备,比如有两个软盘,就可以用从设备号来区分他们.设备文件的的主设备号必须与设备驱动程序在登记时申请的主设备号一致,否则用户进程将无法访问到驱动程序. 最后必须提到的是,在用户进程调用驱动程序时,系统进入核心态,这时不再是抢先式调度.也就是说,系统必须在你的驱动程序的子函数返回后才能进行其他的工作.如果你的驱动程序陷入死循环,不幸的是你只有重新启动机器了,然后就是漫长的fsck. 读/写时,它首先察看缓冲区的内容,如果缓冲区的数据 如何编写Linux操作系统下的设备驱动程序 二、实例剖析 我们来写一个最简单的字符设备驱动程序。虽然它什么也不做,但是通过它可以了解Linux的设备驱动程序的工作原理.把下面的C代码输入机器,你就会获得一个真正的设备驱动程序.不过我的kernel是2.0.34,在低版本的kernel上可能会出现问题,我还没测试过. [code]#define __NO_VERSION__

《LINUX设备驱动程序》阅读笔记全十八章

《LINUX设备驱动程序》阅读笔记 目录 第1章:设备驱动程序简介 (1) 第2章:构造和运行模块 (1) 第3章:字符设备驱动程序 (1) 第4章:调试技术 (2) 第5章:并发和竞态 (2) 第6章:高级字符驱动程序操作 (3) 第7章:时间、延迟及延缓操作 (3) 第8章:分配内存 (3) 第9章:与硬件通信 (4) 第10章:中断处理 (4) 第11章:内核的数据类型 (4) 第12章:PCI 驱动程序 (5) 第13章:USB 驱动程序 (5) 第14章:Linux 设备模型 (5) 第15章:内存映射和 DMA (5) 第16章:块设备驱动程序 (6) 第17章:网络驱动程序 (6) 第18章:TTY 驱动程序 (6) 第1章:设备驱动程序简介 1、“通常,设备驱动程序就是这个进入Linux内核世界的大门”,“设备驱动程序在Linux 内核中扮演着特殊的角色,它们是一个个独立的黑盒子,使某个特定硬件响应一个定义良好的内部编程接口,这些接口完全隐藏了设备的工作细节。用户的操作通过一组标准化的调用执行,而这些调用独立于特定的驱动程序”。 2、Linux系统将设备分成三种基本类型:字符设备、块设备和网络设备。 第2章:构造和运行模块 1、“内核黑客通常拥有一个‘牺牲用的’系统,用于测试新的代码”。 2、模块在被使用之前需要注册,而退出时要仔细撤销初始化函数所做的一切。驱动模块只能调用由内核导出的那些函数。 3、公共内核符号表中包含了所有的全局内核项(即函数和变量)的地址。当模块被装入内核后,它所导出的任何符号都会变成内核符号表的一部分。 第3章:字符设备驱动程序 第一节-主设备号和次设备号。对字符设备的访问都是通过文件系统内的设备名称进行的。通常而言,主设备号标识设备对应的驱动程序,而次设备号用于正确确定设备文件所指的设备。对应的数据结构为dev_t 类型。分配设备号使用函数alloc_chrdev_region() ,释放就使用unregister_chrdev_region() 函数。 第二节-一些重要的数据结构。大部分基本的驱动程序的操作都要涉及到三个重要的内

相关文档
最新文档