曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 7629|回复: 1
打印 上一主题 下一主题

字符设备的运作方式

[复制链接]

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
跳转到指定楼层
楼主
发表于 2009-8-22 16:04:39 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
字符设备驱动程序利用应用程序中的低级文件函数在设备文件上读取或写入数据,此时,可以调用相应的设备驱动程序定义的函数,如下图所示:

如图中,为了从设备文件读取数据,应用程序调用了 read() 函数,这时系统就调出字符设备驱动程序为 read() 所定义的 dev_read() 函数,从而为硬件指定特征,并向应用程序返回结果。而内核则是利用了设备文件上记录的设备类型和主设备号链接到注册在内核上的设备驱动程序函数。

下图表示字符设备驱动程序的运作方式:

由上图可见,在应用程序和设备驱动之间打交道的一个重要桥梁是一个名为 struct file_operation 的结构体。该结构体记录了字符设备驱动程序使用其注册函数(xxx_open()、xxx_read()等)设定相应低级文件输入输出函数(open()、read()等)的内容。也就是说,利用设备文件类型信息和主设备号查找内核内部的设备驱动程序。

file_operations 结构体中的成员函数是字符设备驱动程序设计的主题内容。这些函数实际会在应用程序进行 linux 的 open()、write()、read()、close() 等系统调用时最终被调用。也就是说,这是一个 1 : 1 的链接方式。

低级文件输入输出函数对应某个硬件时,字符设备驱动程序可以看作是控制硬件的内核函数的集合。在设备驱动程序的函数中,除了对应于低级输入输出函数的内核函数外,还有两个与应用程序相关的设备驱动程序请求函数: 一个是内核上加载模块时调用的模块初始化函数;另一个是从内核中注销模块时调用的模块卸载函数。另外,设备驱动程序中不可忽视被硬件设备调用的中断函数。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
沙发
 楼主| 发表于 2009-8-22 17:05:36 | 只看该作者

file_operation 结构体

file_operations 结构体定义在 include/linux/fs.h 中。2.4 内核的定义如下:
struct file_operations {
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
        int (*readdir) (struct file *, void *, filldir_t);
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, struct dentry *, int datasync);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_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 *);
        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
};
2.6 内核的定义如下(2.6.30)
struct file_operations {
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
        ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        int (*readdir) (struct file *, void *, filldir_t);
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *, fl_owner_t id);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, struct dentry *, int datasync);
        int (*aio_fsync) (struct kiocb *, int datasync);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_lock *);
        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
        int (*check_flags)(int);
        int (*flock) (struct file *, int, struct file_lock *);
        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
        int (*setlease)(struct file *, long, struct file_lock **);
};

按照一般的惯例,一个 file_operations 结构体或其指针,都被称为 fops .

在结构体里面,除了 owner 域外,其它域都是函数的指针。假如结构体中对应的函数位置设为 NULL ,则表示不支持此操作。 另外,在这些方法的(操作函数)列表里,注意到不少函数的参数里包含字符串 __user 。这种注解是一种文档形式(a form of documentation)。注意,一个指针是一个不能被直接解引用的用户空间地址。对于正常情况下的编译,__user 没有影响,但它可以被外部检查软件用来找出对用户空间地址的滥用。

struct module *owner
一个指向拥有这个结构的模块指针(表示 file_operations 的拥有者)。2.4 内核中只有定义没有实际作用。这个成员的作用是,如果对设备的操作还在使用中,则阻止模块被卸载。它一般总是被简单的初始化为 THIS_MODULE --- 这是一个宏,定义在 <linux/module.h> 中。它的定义如下:
extern struct module __this_module;
#define THIS_MODULE             (&__this_module)
2.6 内核因为内核直接管理设备驱动程序的使用次数,所以必须指定该域。
........... ............. ............. ............. ............. ............. ............. ............. ............. ............. .............

loff_t (*llseek) (struct file *, loff_t, int);
llseek() 函数用来修改一个文件的当前读写位置(文件指针),并将新位置返回,出错时返回一个负值。如果此处定义为 NULL,那么在调用 seek() 时会改变 file struct 这个结构体中位置计数器的值,那么可能带来潜在的且不可预知的错误!字符设备驱动程序的编译者可定义不同的文件指针。例如,对于控制内存的字符设备驱动程序可以定义成内存的地址。其中,loff_t 是一个 "长偏移 “(long offset) 类型,即使是在 32 位的平台上,它至少也是 64 位宽。
........... ............. ............. ............. .......................... ............. ............. ............. ..........................

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
定义设备驱动程序的读取函数。函数用来从设备中读取数据,成功时返回读取的字节数,出错时返回一个负值。如果这里被定义为 NULL,那么用户程序在调用 read() 时会导致失败,错误返回为 -EINVAL 。
........... ............. ............. ............. .......................... ............. ............. ............. ..........................

ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
初始化一个异步读操作,在函数返回之前,这个读操作可能还没能完成。如果这里设为 NULL ,则所有的处理(同步处理)都由 read 取而代之。
........... ............. ............. ....................................... ............. ............. .......................................

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
写数据到设备。若此处设为 NULL ,那么在应用程序里调用 write() 时,则会返回 -EINVAL 。如果返回值为非负数,则代表写成功,并且表示成功写入的字节数。
........... ............. ............. ....................................... ............. ............. .......................................

ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
在设备上初始化一个异步写操作。
........... ............. ............. ....................................... ............. ............. .......................................

int
(*readdir) (struct file *, void *, filldir_t);
对于设备文件,这里应该设置为 NULL,此函数用来读取一个目录,这仅对文件系统有用。
........... ............. ............. ....................................... ............. ............. .......................................

unsigned int (*poll) (struct file *, struct poll_table_struct *);
poll 方法是 3 种系统调用 : poll() , epoll() 以及 select() 对应的后台程序,这 3 种系统调用都用来查询读写一个或多个的文件描述符是否会被阻塞。
poll 方法返回一个位掩码(bit mask),这个掩码表示一个非阻塞的读或写操作是否可能;或者是,提供给内核信息,用来把调用进程置于睡眠态直到 I/O 可用。如果此处设置为 NULL , 则设备假定为无阻塞的可读可写。
........... ............. .................................................... ............. ....................................................

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
ioctl 系统调用提供了一种可定制指定设备命令的方法。此外,少数的 ioctl 命令也能被内核直接识别,而无需与 fops 表相关联。如果设备没有提供一个 ioctl 方法,则对于任何非预定义的请求系统调用都返回错误信息。( -ENOTTY, "No such ioctl for device" )。
........... ............. .................................................... ............. ....................................................

int (*mmap) (struct file *, struct vm_area_struct *);
mmap 将设备内存映射到进程地址空间中。如果此处设为 NULL ,那 mmap 系统调用返回 -ENODEV 。这个函数对于帧缓冲等设备特别有意义。
........... ............. .................................................... ............. ....................................................

int (*open) (struct inode *, struct file *);
对设备文件来说,open 是第一步要做的操作,然而驱动并没有强制要求要对此操作方法来一个声明。如果这里为 NULL ,则打开设备操作永远都会提示成功,但是驱动不会被通知到它有被要求打开。
........... ............. .................................................... ............. ....................................................

int (*flush) (struct file *, fl_owner_t id);
在一个进程关闭了它对一个设备的文件描述符的拷贝后,flush 操作被调用;它执行(并等待)设备上任何一个仍未执行的操作。换句话说,它在应用程序关闭设备之前,将需要写入写入到设备程序内部的缓存内容全部运行到设备上。目前,flush 只用在极少数的设备上,如 SCSI 。例如,要在设备关闭之前确保所有的要写的数据都写往磁带上,则此时 flush 就有用了。如果 flush 被设置为 NULL , 那么内核会简单的忽略用户程序的请求。
........... ................................................................. .................................................................

int (*release) (struct inode *, struct file *);
当文件结构被释放时,此操作被调用。和 open 类似,release 也可以为 NULL 。注意,release 并不是每次调用 close() 时就会被调用。不论何时只要一个文件结构被共享着(比如在 fork() 或 dup() 后),而且在所有的拷贝未关闭之前,release 都不会被调用。
.............................................................................................................................................

int (*fsync) (struct file *, struct dentry *, int datasync);
此方法是 fsync() 系统调用的后端程序,用户用它来将婚存的数据全部写入到硬件上。如果此处被设置为 NULL ,则相应的系统调用返回 -EINVAL 。
.............................................................................................................................................

int (*aio_fsync) (struct kiocb *, int datasync);
这是 fsync 方法的异步版本。
.............................................................................................................................................

int (*fasync) (int, struct file *, int);
此操作用来通知设备它的 FASYNC 标志的改变。如果驱动不支持异步通知,则此处可设置为 NULL 。
.............................................................................................................................................

int (*lock) (struct file *, int, struct file_lock *);
lock 方法用来实现文件的锁定;锁定对于普通文件来说是一种必须的特性,但设备驱动几乎从不去实现它。
.............................................................................................................................................

ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
sendpage 是 sendfile 的另一半;它被内核调用来发送数据到相应的文件,一次一个页(page),驱动程序一般不实现 sendpage 方法。
.............................................................................................................................................

unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
此方法的目的是在用进程地址空间找出一个合适的位置并将其映射进底层设备上的内存段(memory segment)中。这个任务一般情况下由内存管理代码执行;此方法允许驱动程序强制一个特殊设备可能有的任一种对齐要求。大多数驱动程序对此留空。
.............................................................................................................................................

int (*check_flags)(int);
此方法允许一个模块检查传入 fcntl(F_SETFL...) 调用的标志。
.............................................................................................................................................

ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
这两个方法实现 分散(scatter)/聚集(gather) 读写操作。应用程序偶尔需要对多个内存区域进行读写操作;上面的系统调用允许它们这么做而不需要额外的数据拷贝。如果此处的函数指针为 NULL ,则 read 和 write 方法被调用(可能多于一次)。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2025-5-3 00:33 , Processed in 0.086924 second(s), 22 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表