当前位置:首页 » 操作系统 » linux文件驱动

linux文件驱动

发布时间: 2022-11-15 19:47:17

1. 如何玩转linux驱动



我们很明白Linux 设备驱动的学习是一项浩大的工程,正是由于这个原因,一些人不免望而生畏,其实,只要我们有足够的积累和全面的知识,玩转驱动,也是早晚的事。闲话少说,开始来干货。
对于驱动工程师来说,首先要明白驱动在整个系统中的作用,

大家从上图中可以看出,linux驱动②在这个构架中起到承上硬件①启下应用程序③的作用。在程序的编写中,我们常用高内聚低耦合的标准,因此,驱动的引入显得意义更加重大:一方面,使嵌入式应用工程师不用考虑过多的硬件差异,另一方面,通过将设备驱动融入内核,面向操作系统内核的接口,这样的接口由操作系统规定,对一类设备而言结构一致,独立于具体的设备。同时由于linux操作系统有内存管理和进程管理,因此对于多任务并发的要求时,操作系统和驱动的引入使得任务变得简单。但是对于不需要多任务调度、文件系统、内存管理等复杂功能时,在一个大while(1)循环中既可以完成相关的任务。
上面分析了驱动的意义,那么,玩转linux驱动需要那方面的知识呢,现在罗列下:
 第一、Linux 驱动工程师要有良好的硬件基础。
这个要求不言而喻,linux驱动工程师的主要任务就是隐藏硬件的差异,给应用工程师一个统一的接口,因此需要能看懂电路图,理解SRAM、Flash、SDRAM、磁盘等模块的读写方式,知道UART、I2C、USB 等设备的接口以及常规操作,了解轮询、中断、DMA 的原理,PCI 总线的工作方式以及CPU 的内存管理单元(MMU)等。不过对于这种常见的模块,linux内核中有相关的配置,因此需要有阅读linux内核的能力和修改linux内核的能力。
 第二 、Linux驱动工程师具有良好的C 语言基础。
作为一个面向硬件底层和应用层的关键人物,C语言功底是必须要牢固的。在编写linux的字符设备和块设备驱动中常用的fopen()、fwrite()、fread()、fclose()以及内存分配中经常使用结构体和指针。因此能灵活地运用C 语言的结构体、指针、函数指针及内存动态申请和释放显现的尤为重要。
例如字符设备驱动中的读函数函数的定义
/* 读设备*/
ssize_t xxx_read(struct file *filp, char _ _user *buf, size_t count,loff_t*f_pos)
{
...
_to_user(buf, ..., ...);
...
}
从中看出C语言功底的重要性。
第三、 Linux 驱动工程师具有一定的Linux 内核基础,虽然并不要求工程师对内核各个部分有深入的研究,但至少要了解设备驱动与内核的接口,尤其是对于块设备、网络设备、Flash设备、串口设备等复杂设备。
现在工作起来,嵌入式驱动工程师的工作量相对会小一点,因为一般常见的硬件设备供应商都会提供相应的linux版本驱动,驱动工程师的任务就是调试这些驱动能正常运行在自己的系统中,同时保证系统的稳定。
 第四、 Linux 驱动工程师具有良好的操作系统知识。
这个要求对于没有学习过操作系统的人来说唯一的痛苦之处就是对于专有名词不是很理解,例如上半部,下半部,原子操作等。其实刚开始或许是个痛苦的过程,但是只要认真的分析了一个或者几个驱动程序后,你就会发现其中的规律。毕竟linux驱动大体分为字符设备驱动、块设备驱动和网络设备驱动三类,正所谓抓其纲要,举一反三,便可融会贯通。因此linux中多任务并发控制和同步等基础很重要,因为在设备驱动中会大量使用自旋锁、互斥、信号量、等待队列等并发与同步机制。
第五、动手能力。
纸上得来终觉浅,因此,看再多的书也没有真正的调试一个驱动来的认识深刻。这时你需要搭建宿主机平台,购买开发板。不要好大喜功,从简单的小驱动开始一步一步走,以蚂蚁啃骨头的精神进行学习,收获会很大。
经历了痛苦的折磨,现在看下嵌入式驱动工程师的甜蜜吧,工作个三五年,你已经是大师了,可以去招聘网站浏览下,这方面的待遇都是面议奖金都是大大的,红色票票也随心所愿了。想到这些,你还不下定决心来经受linux驱动的虐待,相信只要以“驱动虐我千百遍,我待驱动如初恋”的决心,相信你可以玩转linux驱动。

2. linux如何加载驱动

linux操作系统下,加载驱动的方式有二:

  1. 静态加载驱动;

  2. 动态加载驱动;

作为前者,静态加载驱动是通过将驱动程序编译到内核而进行的一系列配置操作;对于后者而言则是向内核注册设备信息,从而在kernel启动后,再通过insmod指令,关联好主、次设备号,从而以模块的形式进行加载的;

二者各有优点,所以应用的场合也是不一样的;

3. Linux设备文件与设备驱动程序之间的关系

设各驱动程序在系统中的位置如图1所示。由于设各驱动程序是直接与外部设各的寄存器打交道的,并且由于外部设各的多样性及其快速的发展,设各驱动程序常常是由外部设各供应厂商或者是需要挂接外部设备的计算机开发人员提供的,因此,驱动程序不便与linux内核编制在一起形成一个一体化的结构。于是,linux允许把外部设备以内核模块的形式来提供设各驱动程序。这样就可使用户根据需要'动态地向linux内核插入设各

设各驱动程序在系统中的位置如图1所示。

由于设各驱动程序是直接与外部设各的寄存器打交道的,并且由于外部设各的多样性及其快速的发展,设各驱动程序常常是由外部设各供应厂商或者是需要挂接外部设备的计算机开发人员提供的,因此,驱动程序不便与linux内核编制在一起形成一个一体化的结构。于是,linux允许把外部设备以内核模块的形式来提供设各驱动程序。这样就可使用户根据需要'动态地向linux内核插入设各驱动模块,从而大大提高了内核的灵活性。设备驱动程序与文件系统及应用程序的关系如图2所示。

4. linux驱动有哪些

1、将驱动程序文件bcm5700src.rpm复制到一个临时目录中,并在此目录中运行以下命令;

2、运行以下命令切换到驱动目录中;

3、此目录中会生成一个名字为bcm5700.spec的文件,运行以下命令对驱动程序进行编译;

4、运行以下命令切换到RPM目录中;

5、运行以下命令安装驱动程序;

6、运行以下命令加载驱动模块;

7、运行kudzu命令,系统会自动搜索到硬件,进行配置即可。

linux是文件型系统,在linux中,一切皆文件,所有硬件都会在对应的目录(/dev)下面用相应的文件表示。 文件系统的linux下面,都有对于文件与这些设备关联的,访问这些文件就可以访问实际硬件。 通过访问文件去操作硬件设备,一切都会简单很多,不需要再调用各种复杂的接口。 直接读文件,写文件就可以向设备发送、接收数据。 按照读写存储数据方式,我们可以把设备分为以下几种:字符设备(character device)、块设备(Block device)和网络设备( network interface)。

字符设备(character device):指应用程序采用字符流方式访问的设备。这些设备节点通常为传真、虚拟终端和串口调制解调器、键盘之类设备提供流通信服务, 它通常只支持顺序访问。字符设备在实现时,大多不使用缓存器。系统直接从设备读取/写入每一个字符。

块设备(Block device):通常支持随机存取和寻址,并使用缓存器,支持mount文件系统。典型的块设备有硬盘、SD卡、闪存等,但此类设备一般不需要自己开发,linux对此提过了大部分的驱动。

网络设备(network interface):是一种特殊设备,它并不存在于/dev下面,主要用于网络数据的收发。网络驱动同块驱动最大的不同在于网络驱动异步接受外界数据,而块驱动只对内核的请求作出响应。

上述设备中,字符设备驱动程序适合于大多数简单的硬件设备,算是各类驱动程序中最简单的一类,一般也是从这类驱动开始学习,然后再开始学习采用IIC、SPI等通讯接口的一些设备驱动。可以基于此类驱动调试LKT和LCS系列加密芯片。注意7位IIC地址是0x28。

5. linux怎样加载文件过滤驱动

文件系统过滤驱动是一种可选的,为文件系统提供具有附加值功能的驱动程序。文件系统过滤驱动是一种核心模式组件,它作为Windows NT执行体的一部分运行。
文件系统过滤驱动可以过滤一个或多个文件系统或文件系统卷的I/O操作。按不同的种类划分,文件系统过滤驱动可以分成日志记录、系统监测、数据修改或事件预防几类。通常,以文件系统过滤驱动为核心的应用程序有防毒软件、加密程序、分级存储管理系统等。
二、文件系统过滤驱动并不是设备驱动
设备驱动是用来控制特定硬件I/O设备的软件组件。例如:DVD存储设备驱动是一个DVD驱动。
相反,文件系统过滤驱动与一个或多个文件系统协同工作来处理文件I/O操作。这些操作包括:创建、打开、关闭、枚举文件和目录;获取和设置文件、目录、卷的相关信息;向文件中读取或写入数据。另外,文件系统过滤驱动必须支持文件系统特定的功能,例如缓存、锁定、稀疏文件、磁盘配额、压缩、安全、可恢复性、还原点和卷装载等。
下面两部分详细的阐述了文件系统过滤驱动和设备驱动之间的相似点与不同点。

三、安装文件系统过滤驱动
对于Windows XP和后续操作系统来说,可以通过INI文件或安装应用程序来安装文件系统过滤驱动(对于Windows 2000和更早的操作系统,过滤驱动通常通过服务控制管理器Service Control Manager来进行安装)。
四、初始化文件系统过滤驱动
与设备驱动类似,文件系统过滤驱动也使用DriverEntry例程进行初始化工作。在驱动程序加载后,加载驱动相同的组件将通过调用驱动程序的 DriverEntry例程来对驱动程序进行初始化工作。对于文件系统过滤驱动来说,加载和初始化过滤驱动的系统组件为I/O管理器。
DriverEntry例程运行于系统线程上下文中,其IRQL = PASSIVE_LEVEL。本例程可分页,详细信息参见MmLockPagableCodeSection。
DriverEntry例程定义如下:
NTSTATUS
DriverEntry (
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
本例程有两个输入参数。第一个参数,DriverObject为系统在文件系统过滤驱动加载时所创建的驱动对象;第二个参数,RegistryPath为包含驱动程序注册键路径的Unicode字符串。
文件系统过滤驱动按如下顺序执行DriverEntry例程:

01、创建控制设备对象:

文件系统过滤驱动的DriverEntry例程通常以创建控制设备对象作为该例程的起始。创建控制设备对象的目的在于允许应用程序即使在过滤驱动加载到文件系统或卷设备对象之前也能够直接与过滤驱动进行通信。
注意:文件系统也会创建控制设备对象。当文件系统过滤驱动将其自身附加到文件系统之上时(而不是附加到某一特定文件系统卷),过滤驱动同样将其自身附加到文件系统的控制设备对象之上。

在FileSpy驱动范例中,控制设备对象按如下方式创建:

RtlInitUnicodeString(&nameString, FILESPY_FULLDEVICE_NAME);
status = IoCreateDevice(
DriverObject, //DriverObject
0, //DeviceExtensionSize
&nameString, //DeviceName
FILE_DEVICE_DISK_FILE_SYSTEM, //DeviceType
FILE_DEVICE_SECURE_OPEN, //DeviceCharacteristics
FALSE, //Exclusive
&gControlDeviceObject); //DeviceObject

RtlInitUnicodeString(&linkString, FILESPY_DOSDEVICE_NAME);
status = IoCreateSymbolicLink(&linkString, &nameString);

与文件系统不同,文件系统过滤驱动并不是一定要为其控制设备对象命名。如果传递给DeviceName参数一个非空(Non-NULL)值,该值将作为控制设备对象的名称。接下来,在前面的代码范例中DriverEntry可以调用IoCreateSymbolicLink例程来将该对象的核心模式名称与应用程序可见的用户模式名称关联到一起(同样可以通过调用IoRegisterDeviceInterface来使设备对象对应用程序可见)。
注意:由于控制设备对象是唯一不会附加到设备堆栈中的设备对象,因此控制设备对象是唯一的可安全命名的设备对象。由此,是否为文件系统过滤驱动的控制设备对象是否命名是可选的。
注意:文件系统的控制设备对象必须命名。过滤设备对象从不命名。

6. linux驱动程序如何调用

1、进入到Ubuntu桌面后,打开终端,快捷键为ctrl+alt+T。

注意事项:

在很多企业网络中,为了追求速度和安全,Linux操作系统不仅仅是被网络运维人员当作服务器使用,Linux既可以当作服务器,又可以当作网络防火墙是Linux的 一大亮点。

7. 如何编写Linux 驱动程序

以装载和卸载模块为例:

1、首先输入代码

#include <linux/init.h>

#include <linux/mole.h>

8. linux中驱动放在哪个目录下

这些文件在正常操作中不会被改变的。这个目录也包含你的Linux发行版本的主要的应用程序,譬如,Netscape。 /var 目录包含在正常操作中被改变的文件:假脱机文件、记录文件、加锁文件、临时文件和页格式化文件等。 /home 目录包含用户的文件:参数设置文件、个性化文件、文档、数据、EMAIL、缓存数据等。这个目录在系统省级时应该保留。 /proc 目录整个包含虚幻的文件。它们实际上并不存在磁盘上,也不占用任何空间。(用ls –l 可以显示它们的大小)当查看这些文件时,实际上是在访问存在内存中的信息,这些信息用于访问系统 /bin 系统启动时需要的执行文件(二进制),这些文件可以被普通用户使用。 /sbin 系统执行文件(二进制),这些文件不打算被普通用户使用。(普通用户仍然可以使用它们,但要指定目录。) /etc 操作系统的配置文件目录。 /root 系统管理员(也叫超级用户或根用户)的Home目录。 /dev 设备文件目录。LINUX下设备被当成文件,这样一来硬件被抽象化,便于读写、网络共享以及需要临时装载到文件系统中。正常情况下,设备会有一个独立的子目 录。这些设备的内容会出现在独立的子目录下。LINUX没有所谓的驱动符。 /lib 根文件系统目录下程序和核心模块的共享库。 /boot 用于自举加载程序(LILO或GRUB)的文件。当计算机启动时(如果有多个操作系统,有可能允许你选择启动哪一个操作系统),这些文件首先被装载。这个目录也会包含LINUX核(压缩文件vmlinuz),但LINUX核也可以存在别处,只要配置LILO并且LILO知道LINUX核在哪儿。 /opt 可选的应用程序,譬如,REDHAT 5.2下的KDE (REDHAT 6.0下,KDE放在其它的XWINDOWS应用程序中,主执行程序在/usr/bin目录下) /tmp 临时文件。该目录会被自动清理干净。 /lost+found 在文件系统修复时恢复的文件 “/usr”目录下比较重要的部分有: /usr/X11R6 X-WINDOWS系统(version 11, release 6) /usr/X11 同/usr/X11R6 (/usr/X11R6的符号连接) /usr/X11R6/bin 大量的小X-WINDOWS应用程序(也可能是一些在其它子目录下大执行文件的符号连接)。 /usr/doc LINUX的文档资料(在更新的系统中,这个目录移到/usr/share/doc)。 /usr/share 独立与你计算机结构的数据,譬如,字典中的词。 /usr/bin和/usr/sbin 类似与“/”根目录下对应的目录(/bin和/sbin),但不用于基本的启动(譬如,在紧急维护中)。大多数命令在这个目录下。 /usr/local 本地管理员安装的应用程序(也可能每个应用程序有单独的子目录)。在“main”安装后,这个目录可能是空的。这个目录下的内容在重安装或升级操作系统后应该存在。 /usr/local/bin 可能是用户安装的小的应用程序,和一些在/usr/local目录下大应用程序的符号连接。 /proc目录的内容: /proc/cpuinfo 关于处理器的信息,如类型、厂家、型号和性能等。 /proc/devices 当前运行内核所配置的所有设备清单。 /proc/dma 当前正在使用的DMA通道。/proc/filesystems 当前运行内核所配置的文件系统。 /proc/interrupts 正在使用的中断,和曾经有多少个中断。 /proc/ioports 当前正在使用的I/O端口。 举例,使用下面的命令能读出系统的CPU信息。 cat /proc/cpuinfo /bin bin是binary的缩写。这个目录沿袭了UNIX系统的结构,存放着使用者最经常使用的命令。例如cp、ls、cat,等等。 /boot 这里存放的是启动Linux时使用的一些核心文件。 /dev dev是device(设备)的缩写。这个目录下是所有Linux的外部设备,其功能类似DOS下的.sys和Win下的.vxd。在Linux中设备和文件是用同种方法访问的。例如:/dev/hda代表第一个物理IDE硬盘。 /etc 这个目录用来存放系统管理所需要的配置文件和子目录。 /home 用户的主目录,比如说有个用户叫wang,那他的主目录就是/home/wang也可以用~wang表示。 /lib 这个目录里存放着系统最基本的动态链接共享库,其作用类似于Windows里的.dll文件。几乎所有的应用程序都须要用到这些共享库。 /lost+found 这个目录平时是空的,当系统不正常关机后,这里就成了一些无家可归的文件的避难所。对了,有点类似于DOS下的.chk文件。 /mnt 这个目录是空的,系统提供这个目录是让用户临时挂载别的文件系统。 /proc 这个目录是一个虚拟的目录,它是系统内存的映射,我们可以通过直接访问这个目录来获取系统信息。也就是说,这个目录的内容不在硬盘上而是在内存里。 /root 系统管理员(也叫超级用户)的主目录。作为系统的拥有者,总要有些特权啊!比如单独拥有一个目录。 /sbin s就是Super User的意思,也就是说这里存放的是系统管理员使用的管理程序。 /tmp 这个目录不用说,一定是用来存放一些临时文件的地方了。 /usr 这是最庞大的目录,我们要用到的应用程序和文件几乎都存放在这个目录下。其中包含以下子目录; /usr/X11R6 存放X-Window的目录; /usr/bin 存放着许多应用程序; /usr/sbin 给超级用户使用的一些管理程序就放在这里; /usr/doc 这是Linux文档的大本营; /usr/include Linux下开发和编译应用程序需要的头文件,在这里查找; /usr/lib 存放一些常用的动态链接共享库和静态档案库; /usr/local 这是提供给一般用户的/usr目录,在这里安装软件最适合; /usr/man man在Linux中是帮助的同义词,这里就是帮助文档的存放目录; /usr/src Linux开放的源代码就存在这个目录,爱好者们别放过哦! /var 这个目录中存放着那些不断在扩充着的东西,为了保持/usr的相对稳定,那些经常被修改的目录可以放在这个目录下,实际上许多系统管理员都是这样干的。顺带说一下系统的日志文件就在/var/log目录中。 总结来说: · 用户应该将文件存在/home/user_login_name目录下(及其子目录下)。 · 本地管理员大多数情况下将额外的软件安装在/usr/local目录下并符号连接在/usr/local/bin下的主执行程序。 · 系统的所有设置在/etc目录下。 · 不要修改根目录(“/”)或/usr目录下的任何内容,除非真的清楚要做什么。这些目录最好和LINUX发布时保持一致。 · 大多数工具和应用程序安装在目录:/bin, /usr/sbin, /sbin, /usr/x11/bin,/usr/local/bin。 · 所有的文件在单一的目录树下。没有所谓的“驱动符”

9. LINUX设备驱动程序如何与硬件通信

LINUX设备驱动程序是怎么样和硬件通信的?下面将由我带大家来解答这个疑问吧,希望对大家有所收获!

LINUX设备驱动程序与硬件设备之间的通信

设备驱动程序是软件概念和硬件电路之间的一个抽象层,因此两方面都要讨论。到目前为止,我们已经讨论详细讨论了软件概念上的一些细节,现在讨论另一方面,介绍驱动程序在Linux上如何在保持可移植性的前提下访问I/O端口和I/O内存。

我们在需要示例的场合会使用简单的数字I/O端口来讲解I/O指令,并使用普通的帧缓冲区显存来讲解内存映射I/O。

I/O端口和I/O内存

计算机对每种外设都是通过读写它的寄存器进行控制的。大部分外设都有几个寄存器,不管是在内存地址空间还是在I/O地址空间,这些寄存器的访问地址都是连续的。

I/O端口就是I/O端口,设备会把寄存器映射到I/O端口,不管处理器是否具有独立的I/O端口地址空间。即使没有在访问外设时也要模拟成读写I/O端口。

I/O内存是设备把寄存器映射到某个内存地址区段(如PCI设备)。这种I/O内存通常是首先方案,它不需要特殊的处理器指令,而且CPU核心访问内存更有效率。

I/O寄存器和常规内存

尽管硬件寄存器和内存非常相似,但程序员在访问I/O寄存器的时候必须注意避免由于CPU或编译器不恰当的优化而改变预期的I/O动作。

I/O寄存器和RAM最主要的区别就是I/O操作具有边际效应,而内存操作则没有:由于内存没有边际效应,所以可以用多种 方法 进行优化,如使用高速缓存保存数值、重新排序读/写指令等。

编译器能够将数值缓存在CPU寄存器中而不写入内存,即使储存数据,读写操作也都能在高速缓存中进行而不用访问物理RAM。无论是在编译器一级或是硬件一级,指令的重新排序都有可能发生:一个指令序列如果以不同于程序文本中的次序运行常常能执行得更快。

在对常规内存进行这些优化的时候,优化过程是透明的,而且效果良好,但是对I/O操作来说这些优化很可能造成致命的错误,这是因为受到边际效应的干扰,而这却是驱动程序访问I/O寄存器的主要目的。处理器无法预料某些 其它 进程(在另一个处理器上运行,或在在某个I/O控制器中发生的操作)是否会依赖于内存访问的顺序。编译器或CPU可能会自作聪明地重新排序所要求的操作,结果会发生奇怪的错误,并且很难调度。因此,驱动程序必须确保不使用高速缓冲,并且在访问寄存器时不发生读或写指令的重新排序。

由硬件自身引起的问题很解决:只要把底层硬件配置成(可以是自动的或是由Linux初始化代码完成)在访问I/O区域(不管是内存还是端口)时禁止硬件缓存即可。

由编译器优化和硬件重新排序引起的问题的解决办法是:对硬件(或其他处理器)必须以特定顺序的操作之间设置内存屏障(memory barrier)。Linux提供了4个宏来解决所有可能的排序问题:

#include <linux/kernel.h>

void barrier(void)

这个函数通知编译器插入一个内存屏障,但对硬件没有影响。编译后的代码会把当前CPU寄存器中的所有修改过的数值保存到内存中,需要这些数据的时候再重新读出来。对barrier的调用可避免在屏障前后的编译器优化,但硬件完成自己的重新排序。

#include <asm/system.h>

void rmb(void);

void read_barrier_depends(void);

void wmb(void);

void mb(void);

这些函数在已编译的指令流中插入硬件内存屏障;具体实现方法是平台相关的。rmb(读内存屏障)保证了屏障之前的读操作一定会在后来的读操作之前完成。wmb保证写操作不会乱序,mb指令保证了两者都不会。这些函数都是barrier的超集。

void smp_rmb(void);

void smp_read_barrier_depends(void);

void smp_wmb(void);

void smp_mb(void);

上述屏障宏版本也插入硬件屏障,但仅仅在内核针对SMP系统编译时有效;在单处理器系统上,它们均会被扩展为上面那些简单的屏障调用。

设备驱动程序中使用内存屏障的典型形式如下:

writel(dev->registers.addr, io_destination_address);

writel(dev->registers.size, io_size);

writel(dev->registers.operation, DEV_READ);

wmb();

writel(dev->registers.control, DEV_GO);

在这个例子中,最重要的是要确保控制某种特定操作的所有设备寄存器一定要在操作开始之前已被正确设置。其中的内存屏障会强制写操作以要求的顺序完成。

因为内存屏障会影响系统性能,所以应该只用于真正需要的地方。不同类型的内存屏障对性能的影响也不尽相同,所以最好尽可能使用最符合需要的特定类型。

值得注意的是,大多数处理同步的内核原语,如自旋锁和atomic_t操作,也能作为内存屏障使用。同时还需要注意,某些外设总线(比如PCI总线)存在自身的高速缓存问题,我们将在后面的章节中讨论相关问题。

在某些体系架构上,允许把赋值语句和内存屏障进行合并以提高效率。内核提供了几个执行这种合并的宏,在默认情况下,这些宏的定义如下:

#define set_mb(var, value) do {var = value; mb();} while 0

#define set_wmb(var, value) do {var = value; wmb();} while 0

#define set_rmb(var, value) do {var = value; rmb();} while 0

在适当的地方,<asm/system.h>中定义的这些宏可以利用体系架构特有的指令更快的完成任务。注意只有小部分体系架构定义了set_rmb宏。

使用I/O端口

I/O端口是驱动程序与许多设备之间的通信方式——至少在部分时间是这样。本节讲解了使用I/O端口的不同函数,另外也涉及到一些可移植性问题。

I/O端口分配

下面我们提供了一个注册的接口,它允允许驱动程序声明自己需要操作的端口:

#include <linux/ioport.h>

struct resource *request_region(unsigned long first, unsigned long n, const char *name);

它告诉内核,我们要使用起始于first的n个端口。name是设备的名称。如果分配成功返回非NULL,如果失败返回NULL。

所有分配的端口可从/proc/ioports中找到。如果我们无法分配到我们要的端口集合,则可以查看这个文件哪个驱动程序已经分配了这些端口。

如果不再使用这些端口,则用下面函数返回这些端口给系统:

void release_region(unsigned long start, unsigned long n);

下面函数允许驱动程序检查给定的I/O端口是否可用:

int check_region(unsigned long first, unsigned long n);//不可用返回负的错误代码

我们不赞成用这个函数,因为它返回成功并不能确保分配能够成功,因为检查和其后的分配并不是原子操作。我们应该始终使用request_region,因为这个函数执行了必要的锁定,以确保分配过程以安全原子的方式完成。

操作I/O端口

当驱动程序请求了需要使用的I/O端口范围后,必须读取和/或写入这些端口。为此,大多数硬件都会把8位、16位、32位区分开来。它们不能像访问系统内存那样混淆使用。

因此,C语言程序必须调用不同的函数访问大小不同的端口。那些只支持映射的I/O寄存器的计算机体系架构通过把I/O端口地址重新映射到内存地址来伪装端口I/O,并且为了易于移植,内核对驱动程序隐藏了这些细节。Linux内核头文件中(在与体系架构相关的头文件<asm/io.h>中)定义了如下一些访问I/O端口的内联函数:

unsigned inb(unsigned port);

void outb(unsigned char byte, unsigned port);

字节读写端口。

unsigned inw(unsigned port);

void outw(unsigned short word, unsigned port);

访问16位端口

unsigned inl(unsigned port);

void outl(unsigned longword, unsigned port);

访问32位端口

在用户空间访问I/O端口

上面这些函数主要是提供给设备驱动程序使用的,但它们也可以用户空间使用,至少在PC类计算机上可以使用。GNU的C库在<sys/io.h>中定义了这些函数。如果要要用户空间使用inb及相关函数,则必须满足正下面这些条件:

编译程序时必须带有-O选项来强制内联函数的展开。

必须用ioperm(获取单个端口的权限)或iopl(获取整个I/O空间)系统调用来获取对端口进行I/O操作的权限。这两个函数都是x86平台特有的。

必须以root身份运行该程序才能调用ioperm或iopl。或者进程的祖先进程之一已经以root身份获取对端口的访问。

如果宿主平台没有以上两个系统调用,则用户空间程序仍然可以使用/dev/port设备文件访问I/O端口。不过要注意,该设备文件的含义与平台密切相关,并且除PC平台以处,它几乎没有什么用处。

串操作

以上的I/O操作都是一次传输一个数据,作为补充,有些处理器实现了一次传输一个数据序列的特殊指令,序列中的数据单位可以是字节、字、双字。这些指令称为串操作指令,它们执行这些任务时比一个C语言编写的循环语句快得多。下面列出的宏实现了串I/O:

void insb(unsigned port, void *addr, unsigned long count);

void outsb(unsigned port, void *addr, unsigned long count);从内存addr开始连续读/写count数目的字节。只对单一端口port读取或写入数据

void insw(unsigned port, void *addr, unsigned long count);

void outsw(unsigned port, void *addr, unsigned long count);对一个16位端口读写16位数据

void insl(unsigned port, void *addr, unsigned long count);

void outsl(unsigned port, void *addr, unsigned long count);对一个32位端口读写32位数据

在使用串I/O操作函数时,需要铭记的是:它们直接将字节流从端口中读取或写入。因此,当端口和主机系统具有不同的字节序时,将导致不可预期的结果。使用inw读取端口将在必要时交换字节,以便确保读入的值匹配于主机的字节序。然而,串函数不会完成这种交换。

暂停式I/O

在处理器试图从总线上快速传输数据时,某些平台(特别是i386)就会出现问题。当处理器时钟比外设时钟(如ISA)快时就会出现问题,并且在设备板上特别慢时表现出来。为了防止出现丢失数据的情况,可以使用暂停式的I/O函数来取代通常的I/O函数,这些暂停式的I/O函数很像前面介绍的那些I/O函数,不同之处是它们的名字用_p结尾,如inb_p、outb_p等等。在linux支持的大多数平台上都定义了这些函数,不过它们常常扩展为非暂停式I/O同样的代码,因为如果不使用过时的外设总线就不需要额外的暂停。

平台相关性

I/O指令是与处理器密切相关的。因为它们的工作涉及到处理器移入移出数据的细节,所以隐藏平台间的差异非常困难。因此,大部分与I/O端口相关的源代码都与平台相关。

回顾前面函数列表可以看到有一处不兼容的地方,即数据类型。函数的参数根据各平台体系架构上的不同要相应地使用不同的数据类型。例如,port参数在x86平台上(处理器只支持64KB的I/O空间)上定义为unsigned short,但在其他平台上定义为unsigned long,在这些平台上,端口是与内存在同一地址空间内的一些特定区域。

感兴趣的读者可以从io.h文件获得更多信息,除了本章介绍的函数,一些与体系架构相关的函数有时也由该文件定义。

值得注意的是,x86家族之外的处理器都不为端口提供独立的地址空间。

I/O操作在各个平台上执行的细节在对应平台的编程手册中有详细的叙述;也可以从web上下载这些手册的PDF文件。

I/O端口示例

演示设备驱动程序的端口I/O的示例代码运行于通用的数字I/O端口上,这种端口在大多数计算机平台上都能找到。

数字I/O端口最常见的一种形式是一个字节宽度的I/O区域,它或者映射到内存,或者映射到端口。当把数字写入到输出区域时,输出引脚上的电平信号随着写入的各位而发生相应变化。从输入区域读取到的数据则是输入引脚各位当前的逻辑电平值。

这类I/O端口的具体实现和软件接口是因系统而异的。大多数情况下,I/O引脚由两个I/O区域控制的:一个区域中可以选择用于输入和输出的引脚,另一个区域中可以读写实际的逻辑电平。不过有时情况简单些,每个位不是输入就是输出(不过这种情况下就不能称为“通用I/O"了);在所有个人计算机上都能找到的并口就是这样的非通用的I/O端口。

并口简介

并口的最小配置由3个8位端口组成。第一个端口是一个双向的数据寄存器,它直接连接到物理连接器的2~9号引脚上。第二个端口是一个只读的状态寄存器;当并口连接打印机时,该寄存器 报告 打印机状态,如是否是线、缺纸、正忙等等。第三个端口是一个只用于输出的控制寄存器,它的作用之一是控制是否启用中断。

如下所示:并口的引脚

示例驱动程序

while(count--) {

outb(*(ptr++), port);

wmb();

}

使用I/O内存

除了x86上普遍使的I/O端口之外,和设备通信的另一种主要机制是通过使用映射到内存的寄存器或设备内存,这两种都称为I/O内存,因为寄存器和内存的差别对软件是透明的。

I/O内存仅仅是类似RAM的一个区域,在那里处理器可以通过总线访问设备。这种内存有很多用途,比如存放视频数据或以太网数据包,也可以用来实现类似I/O端口的设备寄存器(也就是说,对它们的读写也存在边际效应)。

根据计算机平台和所使用总线的不同,i/o内存可能是,也可能不是通过页表访问的。如果访问是经由页表进行的,内核必须首先安排物理地址使其对设备驱动程序可见(这通常意味着在进行任何I/O之前必须先调用ioremap)。如果访问无需页表,那么I/O内存区域就非常类似于I/O端口,可以使用适当形式的函数读取它们。

不管访问I/O内存是否需要调用ioremap,都不鼓励直接使用指向I/O内存的指针。相反使用包装函数访问I/O内存,这一方面在所有平台上都是安全的,另一方面,在可以直接对指针指向的内存区域执行操作的时候,这些函数是经过优化的。并且直接使用指针会影响程序的可移植性。

I/O内存分配和映射

在使用之前,必须首先分配I/O区域。分配内存区域的接口如下(在<linux/ioport.h>中定义):

struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);

该函数从start开始分配len字节长的内存区域。如果成功返回非NULL,否则返回NULL值。所有的I/O内存分配情况可从/proc/iomem得到。

不再使用已分配的内存区域时,使用如下接口释放:

void release_mem_region(unsigned long start, unsigned long len);

下面函数用来检查给定的I/O内存区域是否可用的老函数:

int check_mem_region(unsigned long start, unsigned long len);//这个函数和check_region一样不安全,应避免使用

分配内存之后我们还必须确保该I/O内存对内存而言是可访问的。获取I/O内存并不意味着可引用对应的指针;在许多系统上,I/O内存根本不能通过这种方式直接访问。因此,我们必须由ioremap函数建立映射,ioremap专用于为I/O内存区域分配虚拟地址。

我们根据以下定义来调用ioremap函数:

#include <asm/io.h>

void *ioremap(unsigned long phys_addr, unsigned long size);

void *ioremap_nocache(unsigned long phys_addr, unsigned long size);在大多数计算机平台上,该函数和ioremap相同:当所有I/O内存已属于非缓存地址时,就没有必要实现ioremap的独立的,非缓冲版本。

void iounmap(void *addr);

记住,由ioremap返回的地址不应该直接引用,而应该使用内核提供的accessor函数。

访问I/O内存

在某些平台上我们可以将ioremap的返回值直接当作指针使用。但是,这种使用不具有可移植性,访问I/O内存的正确方法是通过一组专用于些目的的函数(在<asm/io.h>中定义)。

从I/O内存中读取,可使用以下函数之一:

unsigned int ioread8(void *addr);

unsigned int ioread16(void *addr);

unsigned int ioread32(void *addr);

其中,addr是从ioremap获得的地址(可能包含一个整数偏移量);返回值是从给定I/O内存读取到的值。

写入I/O内存的函数如下:

void iowrite8(u8 value, void *addr);

void iowrite16(u16 value, void *addr);

void iowrite32(u32 value, void *addr);

如果必须在给定的I/O内存地址处读/写一系列值,则可使用上述函数的重复版本:

void ioread8_rep(void *addr, void *buf, unsigned long count);

void ioread16_rep(void *addr, void *buf, unsigned long count);

void ioread32_rep(void *addr, void *buf, unsigned long count);

void iowrite8_rep(void *addr, const void *buf, unsigned long count);

void iowrite16_rep(void *addr, const void *buf, unsigned long count);

void iowrite32_rep(void *addr, const void *buf, unsigned long count);

上述函数从给定的buf向给定的addr读取或写入count个值。count以被写入数据的大小为单位。

上面函数均在给定的addr处执行所有的I/O操作,如果我们要在一块I/O内存上执行操作,则可以使用下面的函数:

void memset_io(void *addr, u8 value, unsigned int count);

void memcpy_fromio(void *dest, void *source, unsigned int count);

void memcpy_toio(void *dest, void *source, unsigned int count);

上述函数和C函数库的对应函数功能一致。

像I/O内存一样使用I/O端口

某些硬件具有一种有趣的特性:某些版本使用I/O端口,而其他版本则使用I/O内存。导出给处理器的寄存器在两种情况下都是一样的,但访问方法却不同。为了让处理这类硬件的驱动程序更加易于编写,也为了最小化I/O端口和I/O内存访问这间的表面区别,2.6内核引入了ioport_map函数:

void *ioport_map(unsigned long port, unsigned int count);

该函数重新映射count个I/O端口,使其看起来像I/O内存。此后,驱动程序可在该函数返回的地址上使用ioread8及其相关函数,这样就不必理会I/O端口和I/O内存之间的区别了。

当不需要这种映射时使用下面函数一撤消:

void ioport_unmap(void *addr);

这些函数使得I/O端口看起来像内存。但需要注意的是,在重新映射之前,我们必须通过request_region来分配这些I/O端口。

为I/O内存重用short

前面介绍的short示例模块访问的是I/O端口,它也可以访问I/O内存。为此必须在加载时通知它使用I/O内存,另外还要修改base地址以使其指向I/O区域。

下例是在MIPS开发板上点亮调试用的LED:

mips.root# ./short_load use_mem=1 base = 0xb7ffffc0

mips.root# echo -n 7 > /dev/short0

下面代码是short写入内存区域时使用的循环:

while(count--) {

iowrite8(*ptr++, address);

wmb();

}

1MB地址空间之下的ISA内存

最广为人知的I/O内存区之一就是个人计算机上的ISA内存段。它的内存范围在64KB(0xA0000)到1MB(0x100000)之间,因此它正好出现在常规系统RAM的中间。这种地址看上去有点奇怪,因为这个设计决策是20世纪80年代早期作出的,在当时看来没有人会用到640KB以上的内存。

10. linux如何安装驱动

在Intel网站直接下载的Linux驱动是e1000-5.2.52.tar.gz(版本可能会有改变),这个压缩包里面没有编译好的.o的文件,需要在Linux系统下编译之后才能使用,
因为网卡需要编译,所以要先确认将内核源文件安装好,下面是关于内核源文件的安装
● Linux下添加内核源文件
1. 用rpm –qa|grep kernel-source查看是否安装了这个包;
如果返回结果中有kernel-source-xxx(其中xxx为当前redhat的内核版本,如rhel3为2.4.21-4EL), 即已经 安装。如无返回结果则需要安装kernel-source包。到安装光盘中找到kernel-source-xxx.i386.rpm,用下面命令安装此rpm包:
2.如果安装了用rpm -V kernel-source校验是否有文件丢失,如果没有输出,表示文件完整;
3.如果有丢失用rpm -ivh --force kernel-source-xxxx...把包重新安装一下;
这个kernel-source包,在您的RH安装光盘中,在Redhat/RPMS中,如果以前没有安装过这个包,那么用rpm -ivh kernel-source-xxxx...来安装,如果安装过,需要覆盖安装,使用rpm -ivh --force kernel-source-xxxx...这个命令强制安装。
注:AS 4 开始,没有kernel-source这个包了,取而代之的是kernel-dev这个包,检查这个包有没有安装的方法同上
● 驱动安装步骤:
1. 把这个tar文件拷贝到用户自己定义的目录中,例如:
/home/username/e1000 or /usr/local/src/e1000
2. 用tar命令解这个压缩包:
tar zxf e1000-5.2.52.tar.gz
3. 切换到驱动的src目录下:
cd e1000-5.2.52/src/
4. 编译这个驱动模块:
make
然后安装这个模块
make install
这个二进制元将被安装到如下位置:
/lib/moles//kernel/drivers/net/e1000.o
以上的路径是默认的安装位置,在某些linux版本中可能是其他位置,具体信息可以查看在驱动的 tar压缩包中的ldistrib.txt文件.
5. 安装模块:
insmod e1000 (2.6以上的版本最好使用全路径安装 P insmod /lib/moles//kernel/drivers/net/e1000/e1000.ko)
6. 设定网卡IP地址:
ifconfig ethx <IP_address> x是网卡接口的号
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
若多个网卡的芯片相同可以cp ifcfg-eth0 ifcfg-eth1~~~~~~
修改下里面的drive名称就OK
!!!!!!!!!!!!!!!!!!!
在网卡的编译中很可能不能进行下去~这个原因除了kernel的开发包没有安装外还可能是由于开发环境不完全所引起的!
这时就需要你讲开发环境安装完成,最简单的办法就是通过 sysconfig-config-packet 安装gcc
安装完成后继续执行 make ;make install

了解更多开源相关,去LUPA社区看看吧

热点内容
内置存储卡可以拆吗 发布:2025-05-18 04:16:35 浏览:336
编译原理课时设置 发布:2025-05-18 04:13:28 浏览:378
linux中进入ip地址服务器 发布:2025-05-18 04:11:21 浏览:612
java用什么软件写 发布:2025-05-18 03:56:19 浏览:32
linux配置vim编译c 发布:2025-05-18 03:55:07 浏览:107
砸百鬼脚本 发布:2025-05-18 03:53:34 浏览:944
安卓手机如何拍视频和苹果一样 发布:2025-05-18 03:40:47 浏览:740
为什么安卓手机连不上苹果7热点 发布:2025-05-18 03:40:13 浏览:803
网卡访问 发布:2025-05-18 03:35:04 浏览:511
接收和发送服务器地址 发布:2025-05-18 03:33:48 浏览:371