linux 内核驱动保存数据,Linux内核驱动之一些重要数据结构

文件操作

到现在,我们已经保留了一些设备编号给我们使用,但是我们还没有连接任何我们设备操作到这些编号上. file_operation结构是一个字符驱动如何建立这个连接.这个结构,定义在,是一个函数指针的集合.每个打开文件(内部用一个file结构来代表,稍后我们会查看)与它自身的函数集合相关连(通过包含一个称为f_op的成员,它指向一个file_operations结构).这些操作大部分负责实现系统调用,因此,命名为open, read,等等.我们可以认为文件是一个"对象"并且其上的函数操作称为它的"方法",使用面向对象编程的术语来表示一个对象声明的用来操作对象的动作.这是我们在Linux内核中看到的第一个面向对象编程的现象

一个file_operation结构或者其一个指针称为fops(或者它的一些变体).结构中的每个成员必须指向驱动中的函数,这些函数实现一个特别的操作,或者对于不支持的操作留置为NULL

struct module *owner

第一个file_operations成员根本不是一个操作;它是一个指向拥有这个结构的模块的指针.这个成员用来在它的操作还在被使用时阻止模块被卸载.几乎所有时间中,它被简单初始化为THIS_MODULE,一个在中定义的宏.

loff_t (*llseek) (struct file *, loff_t, int);

llseek方法用作改变文件中的当前读/写位置,并且新位置作为(正的)返回值. loff_t参数是一个"long offset",并且就算在32位平台上也至少64位宽.错误由一个负返回值指示.如果这个函数指针是NULL, seek调用会以潜在地无法预知的方式修改file结构中的位置计数器(在"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.[]

int (*check_flags)(int)

这个方法允许模块检查传递给fnctl(F_SETFL...)调用的标志.

int (*dir_notify)(struct file *, unsigned long);

这个方法在应用程序使用fcntl来请求目录改变通知时调用.只对文件系统有用;驱动不需要实现dir_notify.

scull设备驱动只实现最重要的设备方法.它的file_operations结构是如下初始化的:

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标记式结构初始化语法.这个语法是首选的,因为它使驱动在结构定义的改变之间更加可移植,并且,有争议地,使代码更加紧凑和可读.标记式初始化允许结构成员重新排序;在某种情况下,真实的性能提高已经实现,通过安放经常使用的成员的指针在相同硬件高速存储行中.

文件结构

struct file,定义于,是设备驱动中第二个最重要的数据结构.文件结构代表一个打开的文件. (它不特定给设备驱动;系统中每个打开的文件有一个关联的struct file在内核空间).它由内核在open时创建,并传递给在文件上操作的任何函数,直到最后的关闭.在文件的所有实例都关闭后,内核释放这个数据结构.

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.所有的标志在头文件中定义.

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结构.

inode结构

inode结构由内核在内部用来表示文件.因此,它和代表打开文件描述符的文件结构是不同的.可能有代表单个文件的多个打开描述符的许多文件结构,但是它们都指向一个单个inode结构.

inode结构包含大量关于文件的信息.作为一个通用的规则,这个结构只有2个成员对于编写驱动代码有用:

dev_t i_rdev;

对于代表设备文件的节点,这个成员包含实际的设备编号.

struct cdev *i_cdev;

struct cdev是内核的内部结构,代表字符设备;这个成员包含一个指针,指向这个结构,当节点指的是一个字符设备文件时.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值