字符设备驱动相关函数及数据结构简介
设备驱动程序简介

设备驱动程序简介1.设备驱动程序的作⽤从⼀个⾓度看,设备驱动程序的作⽤在于提供机制,⽽不是策略。
在编写驱动程序时,程序猿应该特别注意以下这个基本概念:编写訪问硬件的内核代码时,不要给⽤户强加不论什么特定策略。
由于不同的⽤户有不同的需求,驱动程序应该处理如何使硬件可⽤的问题。
⽽将如何使⽤硬件的问题留给上层应⽤程序。
从还有⼀个⾓度来看驱动程序。
它还能够看作是应⽤程序和实际设备之间的⼀个软件层。
总的来说,驱动程序设计主要还是综合考虑以下三个⽅⾯的因素:提供给⽤户尽量多的选项、编写驱动程序要占⽤的时间以及尽量保持程序简单⽽不⾄于错误丛⽣。
2.内核功能划分Unix系统⽀持多进程并发执⾏。
每⼀个进程都请求系统资源。
内核负责处理全部这些请求,依据内核完毕任务的不同,可将内核功能分为例如以下⼏部分:1.进程管理:负责创建和销魂进程。
并处理它们和外部世界之间的连接。
内核进程管理活动就是在单个或多个CPU上实现了多个进程的抽象。
2.内存管理:内存是计算机的主要资源之中的⼀个,⽤来管理内存的策略是决定系统系能的⼀个关键因素。
3.⽂件系统:内核在没有结构的硬件上构造结构化的⽂件系统。
⽽⽂件抽象在整个系统中⼴泛使⽤。
4.设备控制:差点⼉每个系统操作终于都会映射到物理设备上。
5.⽹络功能:⽹络功能也必须由操作系统来管理,系统负责在应⽤程序和⽹络接⼝之间传递数据包,并依据⽹络活动控制程序的运⾏。
全部的路由和地址解析问题都由内核处理。
可装载模块:Linux有⼀个⾮常好的特性:内核提供的特性可在执⾏时进⾏扩展。
可在执⾏时加⼊到内核的代码被称为“模块”。
Linux内核⽀持⼏种模块类型。
包含但不限于设备驱动程序。
每⼀个模块由⽬标代码组成,能够使⽤insmod程序将模块连接到正在执⾏的内核,也能够使⽤rmmod程序移除连接。
3.设备和模块的分类Linux系统将设备分成三个基本类型:字符设备、块设备、⽹络接⼝。
1.字符设备:字符设备驱动程序通常⾄少要实现open、close、read和write系统调⽤。
C语言设备驱动编程入门

C语言设备驱动编程入门C语言设备驱动编程是一项常见的技术,用于编写操作系统的设备驱动程序。
设备驱动程序是操作系统与硬件设备之间的桥梁,它负责将用户操作转化为硬件设备能够理解和执行的指令。
本文将介绍C语言设备驱动编程的基本概念和入门知识,帮助读者了解并入门这一重要的编程技术。
一、设备驱动程序概述设备驱动程序是操作系统的一部分,它与操作系统内核紧密结合,用于实现对硬件设备的控制和管理。
设备驱动程序通常由硬件设备制造商提供,或者由操作系统开发者开发。
它负责处理硬件设备与操作系统之间的通信,使得用户能够方便地操作硬件设备。
设备驱动程序可以分为字符设备驱动和块设备驱动两种类型。
字符设备驱动用于处理流式数据的设备,如键盘、鼠标等;块设备驱动用于处理以块为单位的数据的设备,如硬盘、U盘等。
不同类型的设备驱动程序在实现上有所不同,但都需要用C语言编写。
二、设备驱动程序的基本结构设备驱动程序的基本结构包括设备初始化、设备打开、设备关闭和设备读写等函数。
下面我们逐步介绍这些函数的作用和实现方法。
1. 设备初始化函数设备初始化函数负责对设备进行初始化,包括设备的寄存器配置、中断设置等。
在这个函数中,我们需要了解硬件设备的相关规格和特性,并根据需要进行适当的配置。
2. 设备打开函数设备打开函数在设备被用户程序打开时被调用,它负责向操作系统申请资源,并进行相应的设置,例如打开文件、分配内存等。
3. 设备关闭函数设备关闭函数在设备被用户程序关闭时被调用,它负责释放设备所占用的资源,如释放文件占用的内存、关闭文件等。
4. 设备读写函数设备读写函数是设备驱动程序的核心部分,它负责设备与用户程序之间的数据交换。
设备读函数用于从设备中读取数据,设备写函数用于向设备中写入数据。
三、设备驱动程序的编写步骤编写设备驱动程序需要经过以下几个步骤:1. 了解硬件设备在编写设备驱动程序之前,我们需要详细了解硬件设备的规格和特性,包括硬件寄存器的地址、中断向量等。
实验二:字符设备驱动实验

实验二:字符设备驱动实验一、实验目的通过本实验的学习,了解Linux操作系统中的字符设备驱动程序结构,并能编写简单的字符设备的驱动程序以及对所编写的设备驱动程序进行测试,最终了解Linux操作系统如何管理字符设备。
二、准备知识字符设备驱动程序主要包括初始化字符设备、字符设备的I/O调用和中断服务程序。
在字符设备驱动程序的file_operations结构中,需要定义字符设备的基本入口点。
open()函数;release()函数read()函数write()函数ioctl()函数select()函数。
另外,注册字符设备驱动程序的函数为register_chrdev()。
register_chrdev() 原型如下:int register_chrdev(unsigned int major, //主设备号const char *name, //设备名称struct file_operations *ops); //指向设备操作函数指针其中major是设备驱动程序向系统申请的主设备号。
如果major为0,则系统为该驱动程序动态分配一个空闲的主设备号。
name是设备名称,ops是指向设备操作函数的指针。
注销字符设备驱动程序的函数是unregister_chrdev(),原型如下:int unregister_chrdev(unsigned int major,const char *name);字符设备注册后,必须在文件系统中为其创建一个设备文件。
该设备文件可以在/dev目录中创建,每个设备文件代表一个具体的设备。
使用mknod命令来创建设备文件。
创建设备文件时需要使用设备的主设备号和从设备号作为参数。
阅读教材相关章节知识,了解字符设备的驱动程序结构。
三、实验内容根据教材提供的实例。
编写一个简单的字符设备驱动程序。
要求该字符设备包括open()、write()、read()、ioctl()和release()五个基本操作,并编写一个测试程序来测试所编写的字符设备驱动程序。
PCI驱动编程

目录一、字符设备和块设备 (2)二、设备驱动程序接口 (2)三、设备驱动程序模块 (3)四、设备驱动程序结构 (4)1.驱动程序的注册与注销 (4)2.设备的打开与释放 (4)3.设备的读写操作 (4)4.设备的控制操作 (5)5.设备的中断和轮询处理 (5)五、PCI驱动程序框架 (5)1.关键数据结构 (5)a. pci_driver (5)b. pci_dev (6)2.基本框架 (9)六、框架的具体实现之模块操作 (12)1.struct pci_device_id (12)2.初始化设备模块 (12)3.卸载设备模块: (15)4.中断处理: (16)七、框架的具体实现之设备文件操作 (16)1.设备文件操作接口 (16)2.打开设备模块 (17)3.释放设备模块 (17)4.设备数据读写和控制信息模块 (18)5.内存映射模块 (19)八、附录 (19)1.PCI设备私有数据结构 (19)2.PCI配置寄存器 (20)参考资料: (21)一、字符设备和块设备Linux抽象了对硬件的处理,所有的硬件设备都可以像普通文件一样来看待:它们可以使用和操作文件相同的、标准的系统调用接口来完成打开、关闭、读写和I/O控制操作,而驱动程序的主要任务也就是要实现这些系统调用函数。
Linux系统中的所有硬件设备都使用一个特殊的设备文件来表示,例如,系统中的第一个IDE硬盘使用/dev/hda表示。
每个设备文件对应有两个设备号:一个是主设备号,标识该设备的种类,也标识了该设备所使用的驱动程序;另一个是次设备号,标识使用同一设备驱动程序的不同硬件设备。
设备文件的主设备号必须与设备驱动程序在登录该设备时申请的主设备号一致,否则用户进程将无法访问到设备驱动程序。
在Linux操作系统下有两类主要的设备文件:一类是字符设备,另一类则是块设备。
字符设备是以字节为单位逐个进行I/O操作的设备,在对字符设备发出读写请求时,实际的硬件I/O紧接着就发生了,一般来说字符设备中的缓存是可有可无的,而且也不支持随机访问。
mtd介绍——精选推荐

mtd介绍MTD,Memory Technology Device即内存技术设备字符设备和块设备的区别在于前者只能被顺序读写,后者可以随机访问;同时,两者读写数据的基本单元不同。
字符设备,以字节为基本单位,在Linux中,字符设备实现的⽐较简单,不需要缓冲区即可直接读写,内核例程和⽤户态API⼀⼀对应,⽤户层的Read函数直接对应了内核中的Read例程,这种映射关系由字符设备的file_operations维护。
块设备,则以块为单位接受输⼊和返回输出。
对这种设备的读写是按块进⾏的,其接⼝相对于字符设备复杂,read、write API没有直接到块设备层,⽽是直接到⽂件系统层,然后再由⽂件系统层发起读写请求。
同时,由于块设备的IO性能与CPU相⽐很差,因此,块设备的数据流往往会引⼊⽂件系统的Cache机制。
MTD设备既⾮块设备也不是字符设备,但可以同时提供字符设备和块设备接⼝来操作它。
MTD总概述Linux中MTD的所有源码位于/drivers/mtd⼦⽬录下,MTD设备通常可分为四层这四层从上到下依次是:设备节点、MTD设备层、MTD原始设备层和硬件驱动层。
⼀、Flash硬件驱动层硬件驱动层负责在init时驱动Flash硬件并建⽴从具体设备到MTD原始设备映射关系tip: 映射关系通常包括分区信息、I/O映射及特定函数的映射drivers/mtd/chips : CFI/jedec接⼝通⽤驱动drivers/mtd/nand : nand通⽤驱动和部分底层驱动程序drivers/mtd/maps : nor flash映射关系相关函数drivers/mtd/devices: nor flash底层驱动⼆、MTD原始设备⽤于描述MTD原始设备的是mtd_info,它定义了⼤量的关于MTD的数据和操作函数。
mtdcore.c : MTD原始设备接⼝相关实现mtdpart.c : MTD分区接⼝相关实现三、MTD设备层基于MTD原始设备,linux系统可以定义出MTD的块设备(主设备号31)和字符设备(设备号90)。
register_chrdev函数

register_chrdev函数regiter_chrdev函数是Linux内核提供的一个用于注册字符设备驱动程序的函数。
它的原型如下:```cint register_chrdev(unsigned int major, const char *name, struct file_operations *fops);```该函数会在Linux内核的字符设备表中创建一个新的字符设备,并将其与相应的驱动程序函数关联起来。
下面是对register_chrdev函数的详细解析。
1.参数说明:- `major`:表示字符设备的主设备号。
主设备号用于标识设备驱动程序,一个设备对应一个主设备号。
如果传递参数为0,内核会动态分配一个主设备号,并将其作为函数返回值。
- `name`:表示字符设备的名称。
通常以字符串的形式表示。
- `fops`:表示设备驱动程序的操作函数集。
file_operations结构体中定义了字符设备的各种操作函数,例如open、release、read、write等。
2.返回值说明:-如果返回值为负数,表示注册失败。
返回值为正数,则表示返回的主设备号。
- 当major参数为0时,返回的主设备号由内核动态分配。
3.函数功能:- register_chrdev函数的主要功能是将字符设备的操作函数和设备的主设备号关联起来,以便内核能够在用户空间请求操作设备时,找到相应的驱动程序函数进行处理。
-在函数内部,会首先获取一个未使用的主设备号,如果传入的主设备号参数为0,则由内核分配。
成功获取主设备号后,会在字符设备表中创建一个新的字符设备项。
-然后,将传入的操作函数集与该字符设备项关联起来,并将主设备号返回,以便用户空间可以使用该号码来打开、关闭、读取或写入设备。
4.注意事项:- 在使用register_chrdev函数注册字符设备驱动程序之前,需要在内核空间中编写相应的设备操作函数,并将这些函数封装到file_operations结构体中。
精Vxworks教程

06 VxWorks内存管 理编程实践
动态内存分配策略
分段内存管理
将内存划分为不同大小的段,根据需求动态分配和释 放内存段。
内存池管理
创建多个内存池,每个内存池管理特定大小的内存块 ,提高内存分配效率。
自定义内存分配器
根据应用需求,实现自定义的内存分配器,以满足特 定场景下的内存管理需求。
内存泄漏检测工具使用
优化内存使用技巧
减少全局变量使用
尽量避免使用全局变量,以减少内存占用和 提高程序可维护性。
合理使用指针和引用
在传递数据时,尽量使用指针和引用而非直 接传递数据,以降低内存消耗。
及时释放不再使用的内存
在程序运行过程中,及时释放不再使用的内 存资源,避免造成不必要的内存浪费。
使用内存对齐和压缩技术
合理利用内存对齐和压缩技术,提高内存使 用效率并降低内存碎片化的风险。
01
根据目标硬件平台和开发需求选择合适的编译器,如GNU
Compiler Collection (GCC) 或 Wind River Diab Compiler。
设置编译器选项
02
在Workbench中配置编译器的选项,如优化级别、警告级别、
语言标准等。
编译项目
03
使用选定的编译器对项目进行编译,生成可在目标硬件上运行
同步与互斥机制实现
互斥锁
条件变量
互斥锁是一种用于实现互斥访问共享 资源的同步机制。在VxWorks中,互 斥锁通过`mutexCreate()`函数创建, 并通过`mutexLock()`和 `mutexUnlock()`函数进行锁的获取 和释放。当一个任务获取了互斥锁时 ,其他试图获取该锁的任务将被阻塞 ,直到锁被释放。
驱动函数ioctl,register_chrdev,request_irq

(1)Ioctl方法
Ioctl方法主要用于对设备进行读写之外的其他控制,比如配置设备、进入或退出某种操作模式,这些操作一般都无法通过read/write文件操作来完成,无法通过write操作控制,这就是ioctl操作的功能。
inode 和 file两个指针对应应用程序传递的文件描述符fd(即在用户态下,调用ioctl时,就是把fd映射到内核态的前两个参数),cmd不会被修改地传递给驱动程序,可选的参数arg则无论用户应用程序使用的是指针还是其他类型值,都以unsigned long的形式传递给驱动。
(2)驱动程序的注册,这个工作是和驱动模块获 得主设备号时初始化一同进行的。你可以使用头文件 linux/fs.h中的函数register_chrdev来实现。
用户空间的ioctl函数的原型为:
int ioctl(inf fd,int cmd,…)
其中的…代表可变数目的参数表,实际中是一个可选参数,一般定义为:
int ioctl(inf fd,int cmd,char *argp)
驱动程序中定义的ioctl 方法原型为:
int (*ioctl) (struct i,unsigned int cmd, unsigned long arg)
(4) devfs_register函数
其原型为:
devfs_register(devfs_handle_t dir, const char *name, unsigned int flags,
unsigned int major, unsigned int minor,
umode_t mode, void *ops, void *info)
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
1.设备号分为主次设备号,看上去像是两个号码,但在内核中用dev_t(<linux/types.h>)一种结构表示,同时不应该自己去假设赋值设备号,而是使用宏(<linux/kdev_t.h>)来取得.MAJOR(dev_t dev);MINOR(dev_t dev);即使你有确定的主,次设备号也要用dev=MKDEV(int major, int minor);1.1分配设备号<linux/fs.h>静态分配int register_chrdev_region(dev_t first, unsigned int count, char *name);first 是你要分配的起始设备编号. first 的次编号部分常常是0, 但是没有要求是那个效果.count 是你请求的连续设备编号的总数. 注意, 如果count 太大, 你要求的范围可能溢出到下一个次编号;但是只要你要求的编号范围可用, 一切都仍然会正确工作.name 是应当连接到这个编号范围的设备的名子; 它会出现在/proc/devices 和sysfs 中动态分配int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);dev 是一个只输出的参数, 它在函数成功完成时持有你的分配范围的第一个数.fisetminor 应当是请求的第一个要用的次编号; 它常常是0.count 和name 参数如同给request_chrdev_region 的一样>>>应该始终使用动态分配,但最好为定制设备号留有接口,以参数形式,以name_major=0做为默认值,可能的操作如下:if(scull_major){dev = MKDEV(scull_major, scull_minor);result = register_chrdev_region(dev, scull_nr_devs,"scull");}else{result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,"scull");scull_major = MAJOR(dev);}if(result < 0){printk(KERN_WARNING "scull: can't get major %d\n", scull_major);return result;}1.2释放设备号void unregister_chrdev_region(dev_t first, unsigned int count);2.重要的数据结构2.1文件操作<linux/fs.h>中的file_operation结构,其成员struct module *owner第一个file_operations 成员根本不是一个操作; 几乎所有时间中, 它被简单初始化为THIS_MODULE,一个在<linux/module.h> 中定义的宏.loff_t (*llseek) (struct file *, loff_t, int);llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值. loff_t 参数是一个"long offset", 并且就算在32位平台上也至少64 位宽. 错误由一个负返回值指示. 如果这个函数指针是NULL, seek 调用会以潜在地无法预知的方式修改file 结构中的位置计数器ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);用来从设备中获取数据. 在这个位置的一个空指针导致read 系统调用以-EINVAL("Invalid argument") 失败. 一个非负返回值代表了成功读取的字节数( 返回值是一个"signed size" 类型, 常常是目标平台本地的整数类型).ssize_t (*aio_read)(struct kiocb *, char __user *, size_t, loff_t);初始化一个异步读-- 可能在函数返回前不结束的读操作. 如果这个方法是NULL, 所有的操作会由read代替进行(同步地).ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);发送数据给设备. 如果NULL, -EINVAL 返回给调用write 系统调用的程序. 如果非负, 返回值代表成功写的字节数.ssize_t (*aio_write)(struct kiocb *, const char __user *, size_t, loff_t *);初始化设备上的一个异步写.int (*readdir) (struct file *, void *, filldir_t);对于设备文件这个成员应当为NULL; 它用来读取目录, 并且仅对文件系统有用.unsigned int (*poll) (struct file *, struct poll_table_struct *);poll 方法是3 个系统调用的后端: poll, epoll, 和select, 都用作查询对一个或多个文件描述符的读或写是否会阻塞. poll 方法应当返回一个位掩码指示是否非阻塞的读或写是可能的, 并且, 可能地, 提供给内核信息用来使调用进程睡眠直到I/O 变为可能. 如果一个驱动的poll 方法为NULL, 设备假定为不阻塞地可读可写.int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);ioctl 系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不是写). 另外, 几个ioctl 命令被内核识别而不必引用fops 表. 如果设备不提供ioctl 方法, 对于任何未事先定义的请求(-ENOTTY, "设备无这样的ioctl"), 系统调用返回一个错误.int (*mmap) (struct file *, struct vm_area_struct *);mmap 用来请求将设备内存映射到进程的地址空间. 如果这个方法是NULL, mmap 系统调用返回-ENODEV.int (*open) (struct inode *, struct file *);尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是NULL, 设备打开一直成功, 但是你的驱动不会得到通知.int (*flush) (struct file *);flush 操作在进程关闭它的设备文件描述符的拷贝时调用; 它应当执行(并且等待)设备的任何未完成的操作. 这个必须不要和用户查询请求的fsync 操作混淆了. 当前, flush 在很少驱动中使用; SCSI 磁带驱动使用它, 例如, 为确保所有写的数据在设备关闭前写到磁带上. 如果flush 为NULL, 内核简单地忽略用户应用程序的请求.int (*release) (struct inode *, struct file *);在文件结构被释放时引用这个操作. 如同open, release 可以为NULL.int (*fsync) (struct file *, struct dentry *, int);这个方法是fsync 系统调用的后端, 用户调用来刷新任何挂着的数据. 如果这个指针是NULL, 系统调用返回-EINVAL.int (*aio_fsync)(struct kiocb *, int);这是fsync 方法的异步版本.int (*fasync) (int, struct file *, int);这个操作用来通知设备它的FASYNC 标志的改变. 异步通知是一个高级的主题, 在第 6 章中描述. 这个成员可以是NULL 如果驱动不支持异步通知.int (*lock) (struct file *, int, struct file_lock *);lock 方法用来实现文件加锁; 加锁对常规文件是必不可少的特性, 但是设备驱动几乎从不实现它.ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *,const struct iovec *, unsigned long, loff_t *);这些方法实现发散/汇聚读和写操作. 应用程序偶尔需要做一个包含多个内存区的单个读或写操作; 这些系统调用允许它们这样做而不必对数据进行额外拷贝. 如果这些函数指针为NULL, read 和write 方法被调用( 可能多于一次).ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *);这个方法实现sendfile 系统调用的读, 使用最少的拷贝从一个文件描述符搬移数据到另一个. 例如, 它被一个需要发送文件内容到一个网络连接的web 服务器使用. 设备驱动常常使sendfile 为NULL.ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);sendpage 是sendfile 的另一半; 它由内核调用来发送数据, 一次一页, 到对应的文件. 设备驱动实际上不实现sendpage.unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long,unsigned long);这个方法的目的是在进程的地址空间找一个合适的位置来映射在底层设备上的内存段中. 这个任务通常由内存管理代码进行; 这个方法存在为了使驱动能强制特殊设备可能有的任何的对齐请求. 大部分驱动可以置这个方法为NULL.[10]int (*check_flags)(int)这个方法允许模块检查传递给fnctl(F_SETFL...) 调用的标志.int (*dir_notify)(struct file *, unsigned long);这个方法在应用程序使用fcntl 来请求目录改变通知时调用. 只对文件系统有用; 驱动不需要实现dir_notify.>>>下面是一个实现的可能例子,重要的函数被实现struct file_operations scull_fops ={.owner = THIS_MODULE,.llseek = scull_llseek,.read= scull_read,.write= scull_write,.ioctl = scull_ioctl,.open= scull_open,.release = scull_release,};这个声明使用标准的 C 标记式结构初始化语法.2.2文件结构struct file, 定义于<linux/fs.h>其成员:mode_t f_mode;文件模式确定文件是可读的或者是可写的(或者都是), 通过位FMODE_READ 和FMODE_WRITE. 你可能想在你的open 或者ioctl 函数中检查这个成员的读写许可, 但是你不需要检查读写许可, 因为内核在调用你的方法之前检查. 当文件还没有为那种存取而打开时读或写的企图被拒绝, 驱动甚至不知道这个情况.loff_t f_pos;当前读写位置. loff_t 在所有平台都是64 位( 在gcc 术语里是long long ). 驱动可以读这个值, 如果它需要知道文件中的当前位置, 但是正常地不应该改变它; 读和写应当使用它们作为最后参数而收到的指针来更新一个位置, 代替直接作用于filp->f_pos. 这个规则的一个例外是在llseek 方法中, 它的目的就是改变文件位置.unsigned int f_flags;这些是文件标志, 例如O_RDONLY, O_NONBLOCK, 和O_SYNC. 驱动应当检查O_NONBLOCK 标志来看是否是请求非阻塞操作( 我们在第一章的"阻塞和非阻塞操作"一节中讨论非阻塞I/O ); 其他标志很少使用. 特别地, 应当检查读/写许可, 使用f_mode 而不是f_flags. 所有的标志在头文件<linux/fcntl.h>中定义.struct file_operations *f_op;和文件关联的操作. 内核安排指针作为它的open 实现的一部分, 接着读取它当它需要分派任何的操作时. filp->f_op 中的值从不由内核保存为后面的引用; 这意味着你可改变你的文件关联的文件操作, 在你返回调用者之后新方法会起作用. 例如, 关联到主编号 1 (/dev/null, /dev/zero, 等等)的open 代码根据打开的次编号来替代filp->f_op 中的操作. 这个做法允许实现几种行为, 在同一个主编号下而不必在每个系统调用中引入开销. 替换文件操作的能力是面向对象编程的"方法重载"的内核对等体.void *private_data;open 系统调用设置这个指针为NULL, 在为驱动调用open 方法之前. 你可自由使用这个成员或者忽略它; 你可以使用这个成员来指向分配的数据, 但是接着你必须记住在内核销毁文件结构之前, 在release 方法中释放那个内存. private_data 是一个有用的资源, 在系统调用间保留状态信息, 我们大部分例子模块都使用它.struct dentry *f_dentry;关联到文件的目录入口( dentry )结构. 设备驱动编写者正常地不需要关心dentry 结构, 除了作为filp->f_dentry->d_inode 存取inode 结构.2.3inode结构其成员:dev_t i_rdev;对于代表设备文件的节点, 这个成员包含实际的设备编号.struct cdev *i_cdev;struct cdev 是内核的内部结构, 代表字符设备; 这个成员包含一个指针, 指向这个结构, 当节点指的是一个字符设备文件时.i_rdev 类型在2.5 开发系列中改变了, 破坏了大量的驱动. 作为一个鼓励更可移植编程的方法, 内核开发者已经增加了 2 个宏, 可用来从一个inode 中获取主次编号:unsigned int iminor(struct inode *inode); unsigned int imajor(struct inode *inode);为了不要被下一次改动抓住, 应当使用这些宏代替直接操作i_rdev3.字符设备的注册3.1添加struct cdev *my_cdev = cdev_alloc(); my_cdev->ops = &my_fops;但是, 偶尔你会想将cdev 结构嵌入一个你自己的设备特定的结构; scull 这样做了. 在这种情况下, 你应当初始化你已经分配的结构, 使用:void cdev_init(struct cdev *cdev, struct file_operations *fops);任一方法, 有一个其他的struct cdev 成员你需要初始化. 象file_operations 结构, struct cdev 有一个拥有者成员, 应当设置为THIS_MODULE. 一旦cdev 结构建立, 最后的步骤是把它告诉内核, 调用:int cdev_add(struct cdev *dev, dev_t num, unsigned int count);这里, dev 是cdev 结构, num 是这个设备响应的第一个设备号, count 是应当关联到设备的设备号的数目. 常常count 是1, 但是有多个设备号对应于一个特定的设备的情形.>>>在使用cdev_add 是有几个重要事情要记住. 第一个是这个调用可能失败. 如果它返回一个负的错误码, 你的设备没有增加到系统中. 它几乎会一直成功, 但是, 并且带起了其他的点: cdev_add 一返回, 你static void scull_setup_cdev(struct scull_dev *dev,int index) {int err, devno = MKDEV(scull_major, scull_minor + index);cdev_init(&dev->cdev,&scull_fops);dev->cdev.owner = THIS_MODULE;dev->cdev.ops =&scull_fops;err = cdev_add (&dev->cdev, devno, 1);/* Fail gracefully if need be */if(err)printk(KERN_NOTICE "Error %d adding scull%d", err, index);}。