linux设备驱动程序
Linux系统关系族谱图:应用程序、内核、驱动程序、硬件详解
![Linux系统关系族谱图:应用程序、内核、驱动程序、硬件详解](https://img.taocdn.com/s3/m/588e760ea5e9856a561260df.png)
Linux系统关系族谱图:应用程序、内核、驱动程序、硬件详解目前,Linux软件工程师大致可分为两个层次:01Linux应用软件工程师(ApplicaTIon Software Engineer):主要利用C库函数和Linux API 进行应用软件的编写;从事这方面的开发工作,主要需要学习:符合linux posix标准的API函数及系统调用,linux 的多任务编程技巧:多进程、多线程、进程间通信、多任务之间的同步互斥等,嵌入式数据库的学习,UI编程:QT、miniGUI等。
02Linux固件工程师(Firmware Engineer):主要进行Bootloader、Linux的移植及Linux设备驱动程序的设计工作。
一般而言,固件工程师的要求要高于应用软件工程师的层次,而其中的Linux设备驱动编程又是Linux程序设计中比较复杂的部分,究其原因,主要包括如下几个方面:1 )设备驱动属于Linux内核的部分,编写Linux设备驱动需要有一定的Linux操作系统内核基础;需要了解部分linux内核的工作机制与系统组成2)编写Linux设备驱动需要对硬件的原理有相当的了解,大多数情况下我们是针对一个特定的嵌入式硬件平台编写驱动的,例如:针对特定的主机平台:可能是三星的2410、2440,也可能是atmel的,或者飞思卡尔的等等3 )Linux设备驱动中广泛涉及到多进程并发的同步、互斥等控制,容易出现bug;因为linux本身是一个多任务的工作环境,不可避免的会出现在同一时刻对同一设备发生并发操作4 )由于属于内核的一部分,Linux设备驱动的调试也相当复杂。
linux设备驱动没有一个很好的IDE环境进行单步、变量查看等调试辅助工具;linux驱动跟linux内核工作在同一层次,一旦发生问题,很容易造成内核的整体崩溃。
在任何一个计算机系统中,大至服务器、PC机、小至手机、mp3/mp4播放器,无论是复杂的大型服务器系统还是一个简单的流水灯单片机系统,都离不开驱动程序的身影,没有硬件的软件是空中楼阁,没有软件的硬件只是一堆废铁,硬件是底层的基础,是所有软件。
设备驱动程序在嵌入式Linux系统中的实现分析
![设备驱动程序在嵌入式Linux系统中的实现分析](https://img.taocdn.com/s3/m/f476c214650e52ea55189844.png)
1 引言
设备驱 动程 序是操 作系 统 内核 和机器硬 件之 间 的接 口 , 为应用 和设备 间 的软件层 , 作 为应用 程序屏 蔽 了硬 件 的细 节 。在 Lnx系统 中 , 件 设 备 只是 iu 硬
一
备 的操作 和控 制 , 须 分析 驱 动程 序 的结构 和 实 现 必
原理。
4 N G n Yi g
【bt c】 T ippr e r e t pr ne f egi i ri t bde L u s m a w l sh s A s at r h ae d c b em o ac o ds n g re e m e d i x yt , s e e ac s s i s h i t i n d v sn h e d n s e l t bi a
・
48 ・
第 1 ・ 2期 0卷 第
王莹 : 设备驱动程序 在嵌入式 Ln x系统 中的实现分析 iu
21 0 0年 4月
可 以分 为 5个部 分 :
件 f. s h里定 义 的 fe oeai s 构 , i — Drt n 结 l o 它包 含 一 系 列 函数指 针 , 这些 函数 指针指 向对设备 的各 种操 作 。
设备 。
2 设备驱动程序实现原理
设备 驱动程 序设计 是嵌 入式 Ln x开发 中重要 iu
பைடு நூலகம்
的部分 , 驱动程序是应用程序与硬件之间的一个中
间软件层 , 应该 为应用 程序展 现硬件 的所有 功能 , 不
2. 驱动程 序 的基 本结构 2
嵌 入式 Ln x 备 驱 动 程 序 都有 一 些 共 性 , iu 设 编 写所有类 型 的驱动 程序 都 是 通用 的 , 作 系统 提 供 操
设计Linux系统网络设备驱动程序
![设计Linux系统网络设备驱动程序](https://img.taocdn.com/s3/m/fc30c3f2fab069dc50220163.png)
1 n u x / i in c 1U d e / 1 n u x / i
net vi e. 。 de c h
■
网络物 理 设备 媒介
2 初 始 化
网 络 设 备 的 初 始 化 主 要 是 由
设 备 媒 介 层
图 1 iu L n x网络驱 动程 序体 系结 构 图
开藏豸 统世界奠 ; ≯
维普资讯
开 放 系统 世界
一 [ i u ] Ln x 技 术 开 发 一
・
序 会 很 轻 松 , 并 且 能 够 形 成 固 定 的 模
备 接 口 f r (hi_ v = 0: t s de < o t s de hi _ v
数 h — a rd
s art x m  ̄ t _ t,
d vc e i e数 据 结 构 中 的 i i n t函 数 指 针 所 指 的 初 始 化 函 数 来 完 成 的 。当 内 核 启 动 或 加 载 网 络 驱 动 模 块 的 时 候 ,就 会 调 用 初 始 化 过 程 。这 个 过 程 将 首 先 检 测 网 络 物
计 Li nux 防 火 墙 和 网 络 入 侵 检 测 系 统 时, 可 以在 网络 驱 动程 序 的基 础上 拦 截 网 络 数 据 包 ,继 而 对 其 进 行 分 析 。由 于 Li nux是 开 放 源 代 码 的 ,所 以 给 我 们 提 供 了一个分析 和改造 网络驱r c e i e tu td vc
网络 设 备接 口层
ne _de c t vie)
的 详 细 内容 , 请 参 看 /
数 据包 发 送 h r tr mi a d sa tx t O
中 断处 理 ( 据 包 数 接收)
LINUX设备驱动程序(4)
![LINUX设备驱动程序(4)](https://img.taocdn.com/s3/m/fbdf88a35727a5e9846a6135.png)
协议简介
对于网络的正式介绍一般都采用 OSI (Open Systems Interconnection)模型, 但是Linux 中网络栈的介绍一般分为四层的 Internet 模型。
协议栈层次对比
OSI七层网络模型 应用层 表示层 会话层 传输层 网络层
数据链路层 物理层
Linux TCP/IP 四层概念模型
网络协议
网络协议层用于实现各种具体的网络协议, 如: TCP、UDP 等。
设备无关接口
设备无关接口将协议与各种网络设备驱动连接在一起。 这一层提供一组通用函数供底层网络设备驱动程序使用,让 它们可以对高层协议栈进行操作。
首先,设备驱动程序可能会通过调用 register_netdevice 或 unregister_netdevice 在内核中 进行注册或注销。调用者首先填写 net_device 结构,然后 传递这个结构进行注册。内核调用它的 init 函数(如果定义 了这种函数),然后执行一组健全性检查,并将新设备添加 到设备列表中(内核中的活动设备链表)。
驱动程序
网络栈底部是负责 管理物理网络设备 的设备驱动程序。
第二节 网卡驱动程序设计
设备注册
设备描述:
每个网络接口都由一个 net_device结构来描述
注册: 网络接口驱动的注册方式与字符驱动不同之处在于 它没有主次设备号,并使用如下函数注册。
int register_netdev(struct net_device *dev)
Linux网络子系统架构
Linux协议架构
Linux 网络子系统的顶部是系统调用接口。它为用 户空间的应用程序提供了一种访问内核网络子系统 的方法。位于其下面的是一个协议无关层,它提供 了一种通用方法来使用传输层协议。然后是具体协 议的实现,在 Linux 中包括内嵌的协议 TCP、 UDP,当然还有 IP。然后是设备无关层,它提供了 协议与设备驱动通信的通用接口,最下面是设备驱 动程序。
Linux下的硬件驱动——USB设备
![Linux下的硬件驱动——USB设备](https://img.taocdn.com/s3/m/63a7448fa0c7aa00b52acfc789eb172dec639949.png)
Linux下的硬件驱动——USB设备什么是USB设备?USB即Universal Serial Bus,翻译过来就是通用串行总线。
它是一种规范化的、快速的、热插拔的串行输入/输出接口。
USB接口常被用于连接鼠标、键盘、打印机、扫描仪、音频设备、存储设备等外围设备。
Linux下的USB驱动在Linux系统中,每个USB设备都需要一个相应的驱动程序来驱动。
从Linux 2.4开始,内核提供了完整的USB设备支持。
对于每个USB设备,内核都会自动加载对应的驱动程序。
Linux下的USB设备驱动程序主要分为以下几个部分:USB核心驱动程序USB核心驱动程序是操作系统内核中处理USB设备的核心模块,负责与各种类型的USB设备进行通信,包括主机控制器、USB总线、USB设备等。
它与驱动程序和应用程序之间起到了桥梁的作用,为驱动程序提供了USB设备的基础支持。
USB设备驱动程序USB设备驱动程序是与特定USB设备相对应的驱动程序,为USB设备提供具体的读写功能和其他控制功能。
USB核心驱动程序和USB设备驱动程序之间的接口USB核心驱动程序和USB设备驱动程序之间的接口是指USB层和应用程序层之间的接口,负责传递各种USB操作的命令和数据。
如何编译一个USB设备驱动编译一个USB设备驱动程序需要按照以下步骤进行:步骤一:安装必要的软件包首先需要安装编译和调试USB设备驱动所需的软件包,包括编译工具链、内核源代码、内核头文件等。
sudo apt-get install build-essential linux-source linux-headers-`una me -r`步骤二:编写代码现在可以编写USB设备驱动程序的代码,此处不做详细介绍。
步骤三:编译代码在终端窗口中进入USB设备驱动程序所在的目录下,输入以下命令进行编译:make此命令将会编译USB设备驱动程序,并生成一个将驱动程序与内核进行连接的模块文件。
如何在Linux系统中安装驱动程序
![如何在Linux系统中安装驱动程序](https://img.taocdn.com/s3/m/e1e63f0d2a160b4e767f5acfa1c7aa00b52a9dab.png)
如何在Linux系统中安装驱动程序Linux系统作为一个开源的操作系统,广泛应用于各种设备和领域。
而安装驱动程序是在Linux系统中使用外部硬件设备的关键步骤之一。
在本文中,我们将学习如何在Linux系统中安装驱动程序的方法和步骤。
1. 检查硬件设备在安装驱动程序之前,首先需要确定硬件设备的型号和制造商。
可以通过查询设备的型号或者查看设备的相关文档来获取这些信息。
这是非常重要的,因为不同的设备可能需要不同的驱动程序来正确地工作。
2. 更新系统在安装驱动程序之前,确保你的Linux系统已经是最新的状态。
可以通过在终端中运行以下命令来更新系统:```sudo apt-get updatesudo apt-get upgrade```更新系统可以确保你拥有最新的软件包和驱动程序,以获得更好的兼容性和性能。
3. 查找合适的驱动程序一般来说,大部分硬件设备的驱动程序都可以在Linux系统的软件仓库中找到。
可以通过使用包管理器(如apt、yum等)来查找并安装合适的驱动程序。
运行以下命令来搜索并安装特定的驱动程序:```sudo apt-cache search 驱动程序名称sudo apt-get install 驱动程序名称```注意替换“驱动程序名称”为具体的驱动程序名称。
安装驱动程序可能需要输入管理员密码和确认安装。
如果你无法在软件仓库中找到合适的驱动程序,可以转向设备的制造商网站或者开源社区来获取。
下载驱动程序后,根据驱动程序提供的文档和说明来安装。
4. 编译和安装驱动程序有些驱动程序可能需要手动编译和安装。
在这种情况下,你需要确保你的系统已经安装了编译工具(如GCC、make等)。
在终端中切换到驱动程序所在的目录,并按照以下步骤进行编译和安装:```./configuremakesudo make install```以上命令将分别进行配置、编译和安装驱动程序。
在进行安装之前,可能需要输入一些配置选项或者确认安装。
linux pnp描述
![linux pnp描述](https://img.taocdn.com/s3/m/5fdb85cfd1d233d4b14e852458fb770bf78a3bc7.png)
linux pnp描述Linux PnP (Plug-and-Play) 是一种操作系统级别的技术,它使得计算机能够自动识别和配置设备,例如硬盘、显卡、网卡等。
Linux PnP 的主要目标是使计算机硬件更易于使用和管理,从而减少用户的麻烦。
Linux PnP 通常通过以下几种方式实现:1.设备驱动程序:Linux 内核包含了许多设备驱动程序,这些程序能够识别和配置各种硬件设备。
当设备插入系统时,驱动程序会自动加载并配置设备。
2.设备文件:Linux 系统使用设备文件来表示硬件设备。
这些设备文件通常位于 /dev 目录下,并且可以使用 mknod 命令创建。
当设备插入系统时,Linux 内核会自动创建相应的设备文件。
3.sysfs:sysfs 是一个虚拟文件系统,它提供了内核对象和用户空间程序之间的接口。
通过 sysfs,用户空间程序可以读取和修改内核对象的状态。
Linux PnP 使用 sysfs 来管理和配置设备。
4.udev:udev 是一个动态设备管理工具,它可以管理设备节点和设备规则。
udev 能够自动创建和管理设备文件,并可以根据设备的属性自动分配设备的名称和权限。
Linux PnP 的优点包括:1.易于使用:Linux PnP 使得硬件设备的配置和管理变得更加简单和直观。
用户不再需要手动配置硬件设备的参数,系统会自动识别并配置设备。
2.提高稳定性:Linux PnP 可以自动检测和修复硬件设备的错误,从而提高系统的稳定性。
3.支持多种设备:Linux PnP 支持多种硬件设备,包括许多非标准的硬件设备。
这使得 Linux 系统更加灵活和通用。
4.减少维护成本:Linux PnP 可以减少系统管理员的维护成本,因为系统可以自动管理硬件设备的配置和状态。
linux驱动开发知识点总结
![linux驱动开发知识点总结](https://img.taocdn.com/s3/m/0cafcf1c4a35eefdc8d376eeaeaad1f3469311e6.png)
linux驱动开发知识点总结Linux驱动开发是指在Linux操作系统下开发和编写设备驱动程序的过程。
Linux作为一种开源操作系统,具有广泛的应用领域,因此对于驱动开发的需求也非常重要。
本文将从驱动程序的概念、驱动开发的基本步骤、常用的驱动类型以及驱动开发的注意事项等方面进行总结。
一、驱动程序的概念驱动程序是指控制计算机硬件和软件之间通信和交互的程序。
在Linux系统中,驱动程序负责与硬件设备进行交互,实现对硬件的控制和管理。
二、驱动开发的基本步骤1. 确定驱动的类型:驱动程序可以分为字符设备驱动、块设备驱动和网络设备驱动等。
根据具体的硬件设备类型和需求,选择合适的驱动类型。
2. 编写设备注册函数:设备注册函数用于向系统注册设备,使系统能够识别和管理该设备。
3. 实现设备的打开、关闭和读写操作:根据设备的具体功能和使用方式,编写设备的打开、关闭和读写操作函数。
4. 实现设备的中断处理:如果设备需要进行中断处理,可以编写中断处理函数来处理设备的中断请求。
5. 编写设备的控制函数:根据设备的需求,编写相应的控制函数来实现对设备的控制和配置。
6. 编译和安装驱动程序:将编写好的驱动程序进行编译,并将生成的驱动模块安装到系统中。
三、常用的驱动类型1. 字符设备驱动:用于控制字符设备,如串口、打印机等。
字符设备驱动以字符流的方式进行数据传输。
2. 块设备驱动:用于控制块设备,如硬盘、U盘等。
块设备驱动以块为单位进行数据传输。
3. 网络设备驱动:用于控制网络设备,如网卡。
网络设备驱动实现了数据包的收发和网络协议的处理。
4. 触摸屏驱动:用于控制触摸屏设备,实现触摸操作的识别和处理。
5. 显示驱动:用于控制显示设备,实现图像的显示和刷新。
四、驱动开发的注意事项1. 熟悉硬件设备的规格和寄存器的使用方法,了解硬件设备的工作原理。
2. 确保驱动程序的稳定性和可靠性,避免出现系统崩溃或死机等问题。
3. 对于需要频繁访问的设备,要考虑性能问题,尽量减少对硬件的访问次数。
Linux设备驱动程序加载卸载方法insmod和modprobe命令
![Linux设备驱动程序加载卸载方法insmod和modprobe命令](https://img.taocdn.com/s3/m/495ce0681fd9ad51f01dc281e53a580216fc5029.png)
Linux设备驱动程序加载卸载⽅法insmod和modprobe命令linux加载/卸载驱动有两种⽅法。
1.modprobe注:在使⽤这个命令加载模块前先使⽤depmod -a命令⽣成modules.dep⽂件,该⽂件位于/lib/modules/$(uname -r)⽬录下;modprobe命令智能地向内核中加载模块或者从内核中移除模块,可载⼊指定的个别模块,或是载⼊⼀组相依的模块。
modprobe会根据depmod所产⽣的依赖关系,决定要载⼊哪些模块。
若在载⼊过程中出错,modprobe会卸载整组的模块。
载⼊模块的命令:(1) 载⼊指定的模块:modprobe drv.ko(2) 载⼊全部模块:modprobe -a卸载模块的命令:modprobe -r drv.komodprobe命令⽤于智能地向内核中加载模块或者从内核中移除模块。
modprobe可载⼊指定的个别模块,或是载⼊⼀组相依的模块。
modprobe会根据depmod所产⽣的相依关系,决定要载⼊哪些模块。
若在载⼊过程中发⽣错误,在modprobe会卸载整组的模块。
选项-a或--all:载⼊全部的模块;-c或--show-conf:显⽰所有模块的设置信息;-d或--debug:使⽤排错模式;-l或--list:显⽰可⽤的模块;-r或--remove:模块闲置不⽤时,即⾃动卸载模块;-t或--type:指定模块类型;-v或--verbose:执⾏时显⽰详细的信息;-V或--version:显⽰版本信息;-help:显⽰帮助。
参数模块名:要加载或移除的模块名称。
实例查看modules的配置⽂件:modprobe -c这⾥,可以查看modules的配置⽂件,⽐如模块的alias别名是什么等。
会打印许多⾏信息,例如其中的⼀⾏会类似如下:alias symbol:ip_conntrack_unregister_notifier ip_conntrack列出内核中所有已经或者未挂载的所有模块:modprobe -l这⾥,我们能查看到我们所需要的模块,然后根据我们的需要来挂载;其实modprobe -l读取的模块列表就位于/lib/modules/`uname -r`⽬录中;其中uname -r是内核的版本,例如输出结果的其中⼀⾏是:/lib/modules/2.6.18-348.6.1.el5/kernel/net/netfilter/xt_statistic.ko挂载vfat模块:modprobe vfat这⾥,使⽤格式modprobe 模块名来挂载⼀个模块。
platform driver使用方法
![platform driver使用方法](https://img.taocdn.com/s3/m/337fd2521fb91a37f111f18583d049649b660e2d.png)
platform driver使用方法一、Platform Driver的基本概念Platform Driver是Linux内核中的一种设备驱动程序,用于管理特定硬件平台上的设备。
它通过与设备的Platform Device进行匹配,并提供设备的初始化、注册和卸载等功能。
Platform Driver通常由两部分组成:Platform Driver的结构体和Platform Driver的注册函数。
1. Platform Driver的结构体Platform Driver的结构体是一个包含了与设备驱动相关信息的数据结构,它通常包含了设备的名称、设备的ID、设备的资源信息等。
在编写Platform Driver时,需要定义一个Platform Driver的结构体,并在其中填写相关信息。
2. Platform Driver的注册函数Platform Driver的注册函数用于将Platform Driver的结构体与对应的硬件设备进行匹配,并注册到Linux内核中。
在注册函数中,需要填写Platform Driver的结构体,并调用相应的函数进行注册。
二、Platform Driver的使用步骤使用Platform Driver的步骤一般包括以下几个步骤:1. 定义Platform Driver的结构体需要定义一个Platform Driver的结构体,并填写相关信息。
在结构体中,需要包含设备的名称、设备的ID、设备的资源信息等。
2. 实现Platform Driver的初始化函数在定义了Platform Driver的结构体后,需要实现Platform Driver 的初始化函数。
在初始化函数中,可以进行设备的初始化操作,如初始化设备的寄存器、分配设备的内存等。
3. 实现Platform Driver的probe函数Platform Driver的probe函数用于设备的注册和初始化。
在probe函数中,需要填写设备的相关信息,并调用相应的函数进行设备的注册和初始化。
LINUX设备驱动开发详解
![LINUX设备驱动开发详解](https://img.taocdn.com/s3/m/43154dccbdeb19e8b8f67c1cfad6195f302be85f.png)
LINUX设备驱动开发详解概述LINUX设备驱动开发是一项非常重要的任务,它使得硬件设备能够与操作系统进行有效地交互。
本文将详细介绍LINUX设备驱动开发的基本概念、流程和常用工具,帮助读者了解设备驱动开发的要点和技巧。
设备驱动的基本概念设备驱动是连接硬件设备和操作系统的桥梁,它负责处理硬件设备的输入和输出,并提供相应的接口供操作系统调用。
设备驱动一般由设备驱动程序和设备配置信息组成。
设备驱动程序是编写解决设备驱动的代码,它负责完成设备初始化、IO操作、中断处理、设备状态管理等任务。
设备驱动程序一般由C语言编写,使用Linux内核提供的API函数进行开发。
设备配置信息是定义硬件设备的相关参数和寄存器配置的文件,它告诉操作系统如何与硬件设备进行交互。
设备配置信息一般以设备树或者直接编码在设备驱动程序中。
设备驱动的开发流程设备驱动的开发流程包括设备初始化、设备注册、设备操作函数编写和设备驱动注册等几个主要步骤。
下面将详细介绍这些步骤。
设备初始化设备初始化是设备驱动开发的第一步,它包括硬件初始化和内存分配两个主要任务。
硬件初始化是对硬件设备进行基本的初始化工作,包括寄存器配置、中断初始化等。
通过操作设备的寄存器,将设备设置为所需的状态。
内存分配是为设备驱动程序分配内存空间以便于执行。
在设备初始化阶段,通常需要为设备驱动程序分配一块连续的物理内存空间。
设备注册设备注册是将设备驱动程序与设备对象进行关联的过程,它使得操作系统能够正确地管理设备。
设备注册包括设备号分配、设备文件创建等操作。
设备号是设备在系统中的唯一标识符,通过设备号可以找到设备对象对应的设备驱动程序。
设备号分配通常由操作系统负责,设备驱动程序通过注册函数来获取设备号。
设备文件是用户通过应用程序访问设备的接口,它是操作系统中的一个特殊文件。
设备文件的创建需要通过设备号和驱动程序的注册函数来完成。
设备操作函数编写设备操作函数是设备驱动程序的核心部分,它包括设备打开、设备关闭、读和写等操作。
Linux设备驱动程序学习(10)-时间、延迟及延缓操作
![Linux设备驱动程序学习(10)-时间、延迟及延缓操作](https://img.taocdn.com/s3/m/602d7ccfa1c7aa00b52acb57.png)
Linux设备驱动程序学习(10)-时间、延迟及延缓操作Linux设备驱动程序学习(10)-时间、延迟及延缓操作度量时间差时钟中断由系统定时硬件以周期性的间隔产生,这个间隔由内核根据HZ 值来设定,HZ 是一个体系依赖的值,在<linux/param.h>中定义或该文件包含的某个子平台相关文件中。
作为通用的规则,即便如果知道HZ 的值,在编程时应当不依赖这个特定值,而始终使用HZ。
对于当前版本,我们应完全信任内核开发者,他们已经选择了最适合的HZ值,最好保持HZ 的默认值。
对用户空间,内核HZ几乎完全隐藏,用户HZ 始终扩展为100。
当用户空间程序包含param.h,且每个报告给用户空间的计数器都做了相应转换。
对用户来说确切的HZ 值只能通过/proc/interrupts 获得:/proc/interrup ts 的计数值除以/proc/uptime 中报告的系统运行时间。
对于ARM体系结构:在<linux/param.h>文件中的定义如下:也就是说: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位)。
linux下devicedriver
![linux下devicedriver](https://img.taocdn.com/s3/m/6a1d3fed0975f46527d3e18f.png)
发信人: olly (剑胆琴心), 信区: Linux标题: LINUX下的设备驱动程序三、UNIX系统下的设备驱动程序3.1、UNIX下设备驱动程序的基本结构在UNIX系统里,对用户程序而言,设备驱动程序隐藏了设备的具体细节,对各种不同设备提供了一致的接口,一般来说是把设备映射为一个特殊的设备文件,用户程序可以象对其它文件一样对此设备文件进行操作。
UNIX对硬件设备支持两个标准接口:块特别设备文件和字符特别设备文件,通过块(字符)特别设备文件存取的设备称为块(字符)设备或具有块(字符)设备接口。
块设备接口仅支持面向块的I/O操作,所有I/O操作都通过在内核地址空间中的I/O缓冲区进行,它可以支持几乎任意长度和任意位置上的I/O请求,即提供随机存取的功能。
字符设备接口支持面向字符的I/O操作,它不经过系统的快速缓存,所以它们负责管理自己的缓冲区结构。
字符设备接口只支持顺序存取的功能,一般不能进行任意长度的I/O请求,而是限制I/O请求的长度必须是设备要求的基本块长的倍数。
显然,本程序所驱动的串行卡只能提供顺序存取的功能,属于是字符设备,因此后面的讨论在两种设备有所区别时都只涉及字符型设备接口。
设备由一个主设备号和一个次设备号标识。
主设备号唯一标识了设备类型,即设备驱动程序类型,它是块设备表或字符设备表中设备表项的索引。
次设备号仅由设备驱动程序解释,一般用于识别在若干可能的硬件设备中,I/O请求所涉及到的那个设备。
设备驱动程序可以分为三个主要组成部分:(1) 自动配置和初始化子程序,负责检测所要驱动的硬件设备是否存在和是否能正常工作。
如果该设备正常,则对这个设备及其相关的、设备驱动程序需要的软件状态进行初始化。
这部分驱动程序仅在初始化的时候被调用一次。
(2) 服务于I/O请求的子程序,又称为驱动程序的上半部分。
调用这部分是由于系统调用的结果。
这部分程序在执行的时候,系统仍认为是和进行调用的进程属于同一个进程,只是由用户态变成了核心态,具有进行此系统调用的用户程序的运行环境,因此可以在其中调用sleep()等与进程运行环境有关的函数。
如何编写Linux设备驱动程序
![如何编写Linux设备驱动程序](https://img.taocdn.com/s3/m/81f1e4ba69dc5022aaea0010.png)
Ke wo d y rs: L n x S s e i u y t m;D v c r v r e i e D i e
0 引言
w i e c oe等。如何把系统调用和驱动 程序关联起来 , rt 、l s 这 需要 了解一个 非常关键的数 据结 构:i e o ea i n 。 f l— p r t o s 这个
—
样对硬件设备进行操作。设备驱动程序是 内核的一 部分 ,
①对设备初始化和释放 ;
它完成以下功 能: ②把数据传送到硬 件和从硬件读取数据 ; ④读 取应用程序传送给设备 文件的数据和 回送 应用程 序请求 的数据 ; ④检查和处理设备 出现 的错误。
2 实 例 剖 析
f l ’i ec a b f i tc u t ( ie f l ,hr u ,n o n )
关 键 词 :Ln x 作 系统 ; 备 驱 动 程 序 iu 操 设
中图分类号 :T 3 4 P1
文献标识码 : A
文章编号 : 6 1 49 一 20 )— 09 0 1 7 — 7 2 (088 09 — 2
Ab t c : T e o c p o i u d v c d i e s a i t o u e , a d h n h i t r a m c a i m a d h s r t h c n e t f L n x e i e r v r w s n r d c d a n t e t e n e n l e h n s s n t e
1 iu e i r e 的概 念 l xd vc di r n e v
结构的每一个成员的名字都对应着一个系统调用。 用户进程 利用系统调用在对设备 文件进行诸如 ra/rt edwie操作时, 系统调 用通过设备文件 的主设 备号找 到相应 的设备 驱动程
linux设备驱动程序的设计与实现
![linux设备驱动程序的设计与实现](https://img.taocdn.com/s3/m/da3b1b06b207e87101f69e3143323968011cf40f.png)
linux设备驱动程序的设计与实现
Linux设备驱动程序的设计与实现是一个涉及底层系统编程和硬件交互的复杂过程。
下面是一个简单的步骤指南,以帮助你开始设计和实现Linux设备驱动程序:
1. 了解硬件:首先,你需要熟悉你要驱动的硬件设备的规格和特性。
这包括硬件的内存空间、I/O端口、中断请求等。
2. 选择驱动程序模型:Linux支持多种设备驱动程序模型,包括字符设备、块设备、网络设备等。
根据你的硬件设备和需求,选择合适的驱动程序模型。
3. 编写Makefile:Makefile是一个文本文件,用于描述如何编译和链接你的设备驱动程序。
它告诉Linux内核构建系统如何找到并编译你的代码。
4. 编写设备驱动程序:在Linux内核源代码树中创建一个新的驱动程序模块,并编写相应的C代码。
这包括设备注册、初始化和卸载函数,以及支持读写和配置硬件的函数。
5. 测试和调试:编译你的设备驱动程序,并将其加载到运行中的Linux内核中。
使用各种测试工具和方法来验证驱动程序的正确性和稳定性。
6. 文档和发布:编写清晰的文档,描述你的设备驱动程序的用途、用法和已知问题。
发布你的代码以供其他人使
用和改进。
2-Linux驱动和内核模块编程
![2-Linux驱动和内核模块编程](https://img.taocdn.com/s3/m/a334b3bdc77da26925c5b0f3.png)
设备驱动的Hello World模块 设备驱动的 模块
模块卸载函数
static void __exit cleanup_function(void) { /* 释放资源 */ } module_exit(cleanup_function);
在模块被移除前注销接口并 释放所有所占用的系统资源
标识这个代码是只用于模块卸载( 标识这个代码是只用于模块卸载 通过使编译器把它放在 特殊的 ELF 段) 原型: 原型:#define __exit __attribute__ ((__section__(“.exit.text”)))
查看已加载模块
lsmod cat /proc/modules.
卸载驱动模块 卸载模块
从内核中卸载模块可以用rmmod工具.
注意,如果内核认为该模块任然在使用状态, 注意,如果内核认为该模块任然在使用状态,或 者内核被禁止移除该模块,则无法移除该模块。 者内核被禁止移除该模块,则无法移除该模块。
内核打印函数
隐藏硬件细节,提高应用软件的可移植性 提供安全性 开发模式 内核态驱动 用户态驱动
提供机制,而不是提供策略
机制:驱动程序能实现什么功能 策略:用户如何使用这些功能
设备的分类和特点Biblioteka 设备分类字符设备(char device) 字符设备 块设备(block device) 块设备 网络设备(network device) 网络设备
MODULE_LICENSE()---模块许可证声明 模块许可证声明
模块许可证(LICENSE)声明描述内核模块的许可权限 如果不声明LICENSE,模块被加载时,将收到内核被污染(kernel tainted)的警告
动手写一个内核模块
linux设备驱动spi详解5-应用到驱动的完整流程
![linux设备驱动spi详解5-应用到驱动的完整流程](https://img.taocdn.com/s3/m/3a9a29c085254b35eefdc8d376eeaeaad1f316f4.png)
linux设备驱动spi详解5-应⽤到驱动的完整流程所有的应⽤程序使⽤dev/⽬录下创建的设备,这些字符设备的操作函数集在⽂件spidev.c中实现。
1static const struct file_operations spidev_fops = {2 .owner = THIS_MODULE,3/* REVISIT switch to aio primitives, so that userspace4 * gets more complete API coverage. It'll simplify things5 * too, except for the locking.6*/7 .write = spidev_write,8 .read = spidev_read,9 .unlocked_ioctl = spidev_ioctl,10 .open = spidev_open,11 .release = spidev_release,12 };1 spidev_ioctl函数以上是所有应⽤程序所能够做的所有操作,由此开始追踪spi 驱动程序的完整执⾏流程其中,最重要的就是ioctl, 从这⾥开始先重点剖析ioctl函数1 spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)2 {3int err = 0;4int retval = 0;5struct spidev_data *spidev;6struct spi_device *spi;7 u32 tmp;8 unsigned n_ioc;9struct spi_ioc_transfer *ioc;10//ioctl cmd 检查11/* Check type and command number */12if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC)13return -ENOTTY;1415/* Check access direction once here; don't repeat below.16 * IOC_DIR is from the user perspective, while access_ok is17 * from the kernel perspective; so they look reversed.18*/19if (_IOC_DIR(cmd) & _IOC_READ)20 err = !access_ok(VERIFY_WRITE,21 (void __user *)arg, _IOC_SIZE(cmd));22if (err == 0 && _IOC_DIR(cmd) & _IOC_WRITE)23 err = !access_ok(VERIFY_READ,24 (void __user *)arg, _IOC_SIZE(cmd));25if (err)26return -EFAULT;2728/* guard against device removal before, or while,29 * we issue this ioctl.30*/31//通过以下⽅式获取spi_device-----spi(是之后操作的基础)32 spidev = filp->private_data;33 spin_lock_irq(&spidev->spi_lock);34 spi = spi_dev_get(spidev->spi);35 spin_unlock_irq(&spidev->spi_lock);3637if (spi == NULL)38return -ESHUTDOWN;3940/* use the buffer lock here for triple duty:41 * - prevent I/O (from us) so calling spi_setup() is safe;42 * - prevent concurrent SPI_IOC_WR_* from morphing43 * data fields while SPI_IOC_RD_* reads them;44 * - SPI_IOC_MESSAGE needs the buffer locked "normally".45*/46 mutex_lock(&spidev->buf_lock);47//以上是进⾏check,检查命令有效性,以及进⾏初始化数据,这⾥不在多做说明48switch (cmd) {49/* read requests */50case SPI_IOC_RD_MODE://获取模式信息,将信息发送给⽤户51 retval = __put_user(spi->mode & SPI_MODE_MASK,52 (__u8 __user *)arg);53break;54case SPI_IOC_RD_LSB_FIRST://获取spi最低有效位55 retval = __put_user((spi->mode & SPI_LSB_FIRST) ? 1 : 0,56 (__u8 __user *)arg);57break;58case SPI_IOC_RD_BITS_PER_WORD:59 retval = __put_user(spi->bits_per_word, (__u8 __user *)arg);61case SPI_IOC_RD_MAX_SPEED_HZ:62 retval = __put_user(spi->max_speed_hz, (__u32 __user *)arg);63break;6465/* write requests */66case SPI_IOC_WR_MODE://设置数据传输模式,这⾥只是把设置的数据保存在spi 中,但并没有对spi device做任何操作,对spi device的操作⼀并在最后进⾏ 67 retval = __get_user(tmp, (u8 __user *)arg);68if (retval == 0) {69 u8 save = spi->mode;7071if (tmp & ~SPI_MODE_MASK) {72 retval = -EINVAL;73break;74 }7576 tmp |= spi->mode & ~SPI_MODE_MASK;77 spi->mode = (u8)tmp;78 retval = spi_setup(spi);79if (retval < 0)80 spi->mode = save;81else82 dev_dbg(&spi->dev, "spi mode %02xn", tmp);83 }84break;85case SPI_IOC_WR_LSB_FIRST://设置设置spi写最低有效位,同上86 retval = __get_user(tmp, (__u8 __user *)arg);87if (retval == 0) {88 u8 save = spi->mode;8990if (tmp)91 spi->mode |= SPI_LSB_FIRST;92else93 spi->mode &= ~SPI_LSB_FIRST;94 retval = spi_setup(spi);95if (retval < 0)96 spi->mode = save;97else98 dev_dbg(&spi->dev, "%csb firstn",99 tmp ? 'l' : 'm');100 }101break;102case SPI_IOC_WR_BITS_PER_WORD://设置spi写每个字含多个个位,同上103 retval = __get_user(tmp, (__u8 __user *)arg);104if (retval == 0) {105 u8 save = spi->bits_per_word;106107 spi->bits_per_word = tmp;108 retval = spi_setup(spi);109if (retval < 0)110 spi->bits_per_word = save;111else112 dev_dbg(&spi->dev, "%d bits per wordn", tmp);113 }114break;115case SPI_IOC_WR_MAX_SPEED_HZ://设置spi写最⼤速率,同上116 retval = __get_user(tmp, (__u32 __user *)arg);117if (retval == 0) {118 u32 save = spi->max_speed_hz;119120 spi->max_speed_hz = tmp;121 retval = spi_setup(spi);122if (retval < 0)123 spi->max_speed_hz = save;124else125 dev_dbg(&spi->dev, "%d Hz (max)n", tmp);126 }127break;128129default:130/* segmented and/or full-duplex I/O request */131if (_IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0))//查看是否为数据write命令132 || _IOC_DIR(cmd) != _IOC_WRITE) {133 retval = -ENOTTY;134break;135 }136//pay more time on understanding below method137 tmp = _IOC_SIZE(cmd);//从命令参数中解析出⽤户数据⼤⼩138if ((tmp % sizeof(struct spi_ioc_transfer)) != 0) {//数据⼤⼩必须是struct spi_ioc_transfer的整数倍139 retval = -EINVAL;140break;141 }142 n_ioc = tmp / sizeof(struct spi_ioc_transfer);//将要传输的数据分成n个传输数据段143if (n_ioc == 0)145146/* copy into scratch area */147 ioc = kmalloc(tmp, GFP_KERNEL);//获取n个数据段的数据管理结构体的内存空间148if (!ioc) {149 retval = -ENOMEM;150break;151 }152if (__copy_from_user(ioc, (void __user *)arg, tmp)) {//从⽤户空间获取数据管理结构体的初始化值153 kfree(ioc);154 retval = -EFAULT;155break;156 }157158/* translate to spi_message, execute */159 retval = spidev_message(spidev, ioc, n_ioc);//数据传输,这是整个流程中的核⼼160 kfree(ioc);161break;162 }163164 mutex_unlock(&spidev->buf_lock);165 spi_dev_put(spi);166return retval;167 }通过调⽤函数spi->master->setup()来设置SPI模式。
Linux设备驱动程序DF
![Linux设备驱动程序DF](https://img.taocdn.com/s3/m/d2fd0e1a59eef8c75fbfb3a1.png)
Linux设备驱动程序
04/05/2006 应忍冬
内容
• • • • • • • • • 设备分类 设备驱动程序的框架 字符型设备 网络设备 文件系统
– User Spacuffer例子和使用 Debug原理和Debug方法 常用设备/fb/ram/loopback/zero
设备驱动程序内访问设备地址
• 设备驱动程序可以通过指针访问设备地址 • 设备驱动程序接触到的还是虚拟地址,但 对于外界设备有固定的设备地址映射(设 备的地址在移植Linux时候确定) 设备驱动程序
虚拟地址映射
设备地址映射
设备驱动程序
虚拟地址映射
设备地址映射
物理内存地址空间
设备地址空间
直接访问IO端口 vs 设备驱动程序
设备驱动程序的任务
• • • • 设备初始化 硬件操作和管理 外部硬件和内核空间的数据传递 内核空间和用户空间的数据传递
设备驱动程序的功能
用 户 空 间 内 核 空 间
用户程序
程序
用户态程序 vs 内核态程序
用户程序 • 权限受限 • 虚拟运行环境
–逻辑地址 –关键资源访问受监管
内核程序 • 最高权限 • 实际的运行环境
生成o文件
设备装载和设备文件建立
• chmod +x /tmp/LED.o • /sbin/insmod -f ./LED.o • cat /proc/devices得到装入内核的主 设备号 • mknod /dev/Lamp c Num1 Num2 Num1为主设备号 Num2为次设备号 强制安装,忽略版本检查
Linux系统分析——devicedrivers
![Linux系统分析——devicedrivers](https://img.taocdn.com/s3/m/de4f2031eefdc8d376ee326b.png)
kfree_s()
设备号由主、次设备号拼接而成。
#define MAJOR(dev) ((dev)>>8) #define MINOR(dev) ((dev)&0xff)
辅助函数——设备的注册和注销
int register_chrdev(unsigned int major, const char *name,
ISA VESA EISA PCI
驱动程序基础——命名空间
并行设备:lp
软盘:fd SCSI盘:sd IDE硬盘:hda1, hda2, hdb等 网络设备:ethn, slipn, pppn等 在写驱动程序的时候,需要给函数名加 上选择的前缀来避免任何混淆。如: foo_read(),foo_write()等。
驱动程序基础——中断vs轮询
工作机制的区别
编程上的区别:
UNIX的系统调用:执行模式的改变 内核模式下的进程访问进程原来所在的用户空间的存 储:get_fs_*()和memcpy_fromfs()读用户空间, put_fs_*()和memcpy_tofs()写入用户空间内存。在进程 运行时调用,不需要考虑地址的问题。 在中断发生时,这些宏不能使用。因为它们可能覆盖 其他运行着的进程的随机空间。必须提供临时空间存 放信息。 对于块设备,由cache缓冲机制自动提供;字符设备需 要驱动程序分配。
ioctl()函数:处理ioctl调用。
结构:首先差错检查,然后用一个大的switch语句来处
理所有可能的ioct。
参数:
Struct inode *inode Struct file *file Unsigned int cmd :ioctl命令。一般用于做case语句的 switch参数。 Unsigned int arg 这是此命令的参数,由用户定义。
嵌入式Linux操作系统设备驱动程序设计与实现
![嵌入式Linux操作系统设备驱动程序设计与实现](https://img.taocdn.com/s3/m/ec6c07224b73f242336c5f1e.png)
Q i — ig LU T o U Xa pn ,I a o
(nom t n S i c n eh ooyC lg , i in nvr t, i giJ j n 3 0 5 Ifr ai ce e ad T c nlg o ee J j g U i sy J nx i i g3 2 0 ) o n l ua ei a ua
钟 函数 。
信、 数码产 品、 网络设备 、 全系统等领域 。越来越 多的公 司 、 安 研 究单位 、 大专 院校 、 以及个 人开始 进行嵌入 式系统 的研究 , 嵌入 式系统设计将是未来相 当长一段时 间内研究 的热点 。
1 Ln x设 备 驱动 程序 概述 iu
嵌人式 Lnx以其可应用于多种 硬件平 台 、内核高效稳定 、 iu
源码开放 、软件丰富 、网络通信和文件管理机 制完善等优 良特
性, 成为嵌入式系统领域 中的一个研究热点 。嵌入式 Lnx系统 iu
中 ,内核提供保 护机 制 ,用户空间 的进程一般不 能直 接访 问硬
件。 进行嵌入式系统的开发 , 很大的工作量是为各种设 备编写驱
动程序 , 除非系统不使用操作系统 。 iu 设备驱动程序在 Lnx Ln x iu 内核源代码 中占有很 大比例 , 20 2 从 .、. 24版本的 内核 , 2到 . 源代 码 的长度 t益增加 , 3 其实主要是设备驱动程序在 增加 。 设备驱 动程序在 Ln x内核 中占有极其重要的位置 , iu 它是 内 核用于完成对物理设备 的控制操作 的功能模块 。 除了 C U、 P 内存 以及其他很少的几个部分之外 ,所有 的设备 控制操作都必须 由 与被控设备相关 的代码 , 也就是驱 动程序来完成 。内核必须包括 与系统 中的每个外部设备对应 的驱动程序 。否则设备 就无法在 Ln x i 下正常工作。这就是驱 动程序开发成为 Ln x内核开发 的 u iu
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
主设备号和次设备号
通过访问文件系统的名字(或“节点” )访问字符设备,通常这些文件位于 /dev 目录。设备 文件是特殊文件,这一点可以通过 ls -l 输出的第一列中的“c”标明,它说明它们是字符节点。 /dev 下还有块设备, 但它们的第一列是 “b” ; 尽管如下介绍的某些内容也同样适用于块设备, 现在我们只关注字符设备。如果你执行 ls 命令,在设备文件条目的最新修改日期前你会看 到两个数(用逗号分隔) ,这个位置通常显示文件长度。这些数就是相应设备的主设备号和 次设备号。下面的列表给出了我使用的系统上的一些设备。它们的主设备号是 10,1 和 4, 而次设备号是 0,3,5,64-65 和 128-129。 (代码) 主设备号标识设备对应的驱动程序。例如,/dev/null 和/dev/zero 都有驱动程序 1 管理,而所 有的 tty 和 pty 都由驱动程序 4 管理。 内核利用主设备号将设备与相应的驱动程序对应起来。 次设备号只由设备驱动程序使用;内核的其他部分不使用它,仅将它传递给驱动程序。一个 驱动程序控制若干个设备并不为奇(如上面的例子所示) ――次顺便号提供了一种区分它们 的方法。 向系统增加一个驱动程序意味着要赋予它一个主设备号。 这一赋值过程应该在驱动程序 (模 块)的初始化过程中完成,它调用如下函数,这个函数定义在<linux/fs.h>: (代码) 返回值是错误码。当出错时返回一个负值;成功时返回零或正值。参数 major 是所请求的主 设备号,name 是你的设备的名字,它将在/proc/devices 中出现,fops 是一个指向跳转表的指 针,利用这个跳转表完成对设备函数的调用,本章稍后将在“文件操作”一节中介绍这些函 数。 主设备号是一个用来索引静态字符设备数组的整数。在 1.2.13 和早期的 2.x 内核中,这个数 组 有 64 项 , 而 2.0.6 到 2.1.11 的 内 核 则 升 至 128 。 由 于 只 有 设 备 才 处 理 次 设 备 号 , register_chrdev 不传递次设备号。 一旦设备已经注册到内核表中, 无论何时操作与你的设备驱动程序的主设备号匹配的设备文 件,内核都会通过在 fops 跳转表索引调用驱动程序中的正确函数。 接下来的问题就是如何给程序一个它们可以请求你的设备驱动程序的名字。 这个名字必须插 入到/dev 目录中,并与你的驱动程序的主设备号和次设备号相连。 在文件系统上创建一个设备节点的命令是 mknod,而且你必须是超级用户才能创建设备。 除 了要创建的节点名字外,该命令还带三个参数。例如,命令: (代码) 创建一个字符设备(c) ,主设备号是 127,次设备号是 0。由于历史原因,次设备号应该在 0-255 范围内,有时它们存储在一个字节中。存在很多原因扩展可使用的次设备号的范围, 但就现在而言,仍然有 8 位限制。
字符设备驱动程序设计
本章的目标是编写一个完整的字符设备驱动程序。 由于这类驱动程序适合于大多数简单的硬 件设备,我们首先开放一个字符设备驱动程序。字符也相对比较好理解,比如说块设备驱动 程序。 我们的最终目标是写一个模块化的字符设备驱动程序, 但本章我们不再讲述有关模块 化的问题。 本章通篇都是从一个真实的设备驱动程序截取出的代码块:这个设备就是 scull,是“Simple Character Utility for Loading Localities”的缩写。尽管 scull 是一个设备,但它却是操作内存 的字符设备。这种情况的一个副作用就是,只要涉及 scull, “设备”这个词就可以同“scull 使用的内存区”互换使用。 scull 的优点是,由于每台电脑都有内存,所以它与硬件无关。 scull 用 kmalloc 分配内存, 而 且仅仅操作内存。任何人都可以编译和运行 scull,而且 scull 可以移植到所有 Linux 支持的 平台上。但另一方面,除了演示内核于字符设备驱动程序间的交互过程,可以让用户运行某 些测试例程外,scad 脚本,用户可以在 scull_load 中命令行中将参数传递给 insmod。 这里是我在 scull.c 中使用的获取主设备号的代码: (代码)
从系统中删除设备驱动程序
当从系统中卸载一个模块时,应该释放主设备号。这一操作可以在 cleanup_module 中调用 如下函数完成: (代码) 参数是要释放的主设备号和相应的设备名。内核对这个名字和设备号对应的名字进行比较: 如果不同,返回-ENINVAL。如果主设备号超出了所允许的范围或是并未分配给这个设备, 内核一样返回-EINVAL。 在 cleanup_module 中注销资源失败会有非常不号的后果。 下次读取 /proc/devices 时,由于其中一个 name 字串仍然指向模块内存,而那片内存已经不存在了, 系统将产生一次失效。这种失效称为 Oops*,内核在访问无效地址时将打印这样的消息。 当你卸载驱动程序而又无法注销主设备号时, 这种情况是无法恢复的, 即便为此专门写一个 “补救”模块也无济于事,因为 unregister_chrdev 中调用了 strcmp,而 strcmp 将使用未映射 的 name 字串,当释放设备时就会使系统 Oops。无需说明,任何视图打开这个异常的设备号 对应的设备的操作都会 Oops。 除了卸载模块, 你还经常需要在卸载驱动程序时删除设备节点。 如果设备节点是在加载时创 建的,可以写一个简单的脚本在卸载时删除它们。对于我们的样例设备,脚本 scull_unload 完成这个工作。如果动态节点没有从/dev 中删除,就会有可能造成不可预期的错误:如果动 态分配的主设备号相同,开发者计算机上的一个空闲/dev/framegrabber 就有可能在一个月后 引用一个火警设备。 “没有这个文件或目录”要比这个新设备所产生的后果要好得多。
一个用户锁住了设备,前者返回-EBUSY,而后者则实现为阻塞型 open。通过这些可以展示 如何实现不同的访问策略。 每一个 scull 设备都展示了驱动程序不同的功能,而且都不同的难度。本章主要讲解 scull0-3 的内部结构;第 5 章,字符设备驱动程序的扩展操作,将介绍更复杂的设备: “一个样例 实现:scullpipe”介绍 scullpipe, “设备文件的访问控制”介绍其他设备。
scull 的设计
编写设备驱动程序的第一步就是定义驱动程序提供给用户程序的能力( “机制” ) 。由于我们 的“设备”是电脑内存的一部分,我做什么都可以。它可以是顺便存取设备,也可以是随机 存取设备,可以是一个设备,也可以是多个,等等。 为了是 scull 更有用,可以成为编写真实设备的驱动程序的模板,我将向你展示如何在电脑 的内存之上实现若干设备抽象操作,每一种操作都有自己的特点。 scull 的源码实现如下设备。由模块实现的每一种设备都涉及一种类型: scull0-3 4 个设备,共保护了 4 片内存区,都是全局性的和持久性的。 “全局性”是指,如果打开设 备多次,所有打开它的文件描述符共享其中的数据。 “持久性”是指,如果设备关闭后再次 打开,数据不丢失。由于可以使用常用命令访问这个设备,如 cp,cat 以及 shell I/O 重定向 等,这个设备操作非常有趣;本章将深入探讨它的内部结构。 scullpipe0-3 4 个“fifo”设备,操作起来有点象管道。一个进程读取另一个进程写入的数据。如果有多 个进程读同一个设备,他们彼此间竞争数据。通过 scullpipe 的内部结构可以了解阻塞型和 非阻塞型读/写是如何实现的;没有中断也会出现这样的情况。尽管真实的驱动程序利用中 断与它们的设备同步, 但阻塞型和非阻塞型操作是非常重要的内容, 从概念上讲与中断处理 (第 9 章,中断处理,介绍)无关。 scullsingle scullpriv sculluid scullwuid 这些设备与 scull0 相似,但在何时允许 open 操作时都不同方式的限制。第一个(scullsingle) 只允许一次一个进程使用驱动程序, 而 scullpriv 对每个虚拟控制台是私有的 (每个设备对虚 拟控制台是私有的) 。sculluid 和 scullwuid 可以多次打开,但每次只能有一个用户;如果另
动态分配主设备号
某些主设备号已经静态地分配给了大部分公用设备。在内核源码树 的 Documentation/device.txt 文件中可以找到这些设备的列表。由于许多数字已经分配了,为新 设备选择一个唯一的号码是很困难的――不同的设备要不主设备号多得多。 很幸运(或是感谢某些人天才) ,你可以动态分配主设备号了。如果你调用 register_chrdev 时的 major 为零的话,这个函数就会选择一个空闲号码并做为返回值返回。主设备号总是正 的,因此不会和错误码混淆。 我强烈推荐你不要随便选择一个一个当前不用的设备号做为主设备号, 而使用动态分配机制 获取你的主设备号。 动态分配的缺点是, 由于分配给你的主设备号不能保证总是一样的, 无法事先创建设备节点。 然而这不是什么问题,这是因为一旦分配了设备号,你就可以从/proc/devices 读到。为了加 载一个设备驱动程序, 对 insmod 的调用被替换为一个简单的脚本, 它通过/proc/devices 获得 新分配的主设备号,并创建节点。 /proc/devices 一般如下所示: (代码) 加载动态分配主设备号驱动程序的脚本可以利用象 awk 这类工具从 /proc/devices 中获取信 息,并在/dev 中创建文件。 下面这个脚本,scull_load,是 scull 发行中的一部分。使用以模块形式发行的驱动程序的用 户可以在/etc/rc.d/rc.local 中调用这个脚本,或是在需要模块时手工调用。此外还有另一种方 法:使用 kerneld。这个方法和其他模块的高级功能将在第 11 章“Kerneld 和高级模块化” 中介绍。 (代码) 这个脚本同样可以适用于其他驱动程序, 只要重新定义变量和调整 mknod 那几行就可以了。 上面那个脚本创建 4 个设备,4 是 scull 源码中的默认值。 脚本的最后两行看起来有点怪怪的:为什么要改变设备的组和权限呢?原因是这样的,由 root 创建的节点自然也属于 root。默认权限位只允许 root 对其有写访问权,而其他只有读权 限。正常情况下,设备节点需要不同的策略,因此需要进行某些修改。通常允许一组用户访 问对设备, 但实现细节却依赖于设备和系统管理员。 安全是个大问题, 这超出了本书的范围。 scull_load 中的 chmod 和 chgrp 那两行仅仅是最为处理权限问题的一点提示。稍后,在第 5 章的“设备文件的访问控制”一节中将介绍 sculluid 源码,展示设备驱动程序如何实现自己 的设备访问授权。 如果重复地创建和删除/dev 节点似乎有点过分的话, 有一个解决的方法。如果你看了内核源 码 fs/devices.c 的话,你可以看到动态设备号是从 127(或 63)之后开始的,你可以用 127 做为主设备号创建一个长命节点, 同时可以避免在每次相关设备加载时调用脚本。 如果你使 用了几个动态设备,或是新版本的内核改变了动态分配的特性,这个技巧就不能用了。 (如 果内核发生了修改,基于内核内部结构编写的代码并不能保证继续可以工作。 )不管怎样, 由于开发期间模块要不断地加载和卸载,你会发现这一技术在开发期间还是很有用的。 就我看来,分配主设备号的最佳方式是,默认采用动态分配,同时留给你在加载时,甚至是 编译时,指定主设备号的余地。使用我建议的代码将与自动端口探测的代码十分相似。scull 的实现使用了一个全局变量, scull_major ,来保存所选择的设备号。该变量的默认值 是 SCULL_MAJOR,在所发行的源码中为 0,即“选择动态分配” 。用户可以使用这个默认值 或选择某个特定的主设备号, 既可以在编译前修改宏定义, 也可以在 ins_mod 命令行中指定。