Linux下PCI设备驱动开发详解 下载本文

解释一下:

llseek: llseek对应着用户空间里的lseek,它的作用是改变文件结构中的操作位置,说的明确一些就是修改file->f_pos;成功时,它返回一个新位置,失败时返回一个负数值;

read: read是从应用程序的角度看的说法,所以read实际上就是把数据写到用户空间,如果返回一个正数,就是实际读到的字节数;负数返回值表示错误;

write:write的作用是向设备馈送数据,返回值方面与read相同;

readdir:readdir只有文件系统才能使用,它的作用是将读取某个子目录里的内容;

poll: poll允许应用程序响应来自设备的给定事件。它在BSD UNIX里的对应函数是select, 但Linux不推荐使用select,所以 应该用poll代替它;

ioctl: 它的含义是I/O控制,它允许应许程序通过ioctl系统调用控制设备的行为或者从设备取得数据;

mmap:mmap实现了设备地址空间到用户空间的地址的映射。它可以用来提供对设备驱动程序的内部缓冲区或者外设内存空间进行直接访问的功能

Open:Open是应用程序打开设备时将要调用的文件操作。它是唯一一个对字符设备与块设备都有缺省实现的函数。因此,如果你不需要或不想知道设备会在何时被打开,就可以不对这个文件操作进行定义

flush:flush的作用是把缓冲区数据“冲”出去。由于字符设备不使用缓冲区,所以这个条目只对块设备有意义

release:release是在设备关闭时将被调用的文件操作

fsync: fsync的作用是同步内存中与磁盘上的数据状态,把输出缓冲区里尚未写到磁盘上去的数据写出去。它在结束操作之前是不应该返回的。这个条目也只有与块设备有关;

fasync:fasync将在应用程序通过fcntl改变设备行为时调用;

check_media_change: check_media_change检查自从上次访问之后介质是否经过了更换。因此它只对处理可更换介质(比如:CD-ROM和软盘)的块设备有意义;

revalidate:revalidate和check_media_change是密切相关的。如果检测出更换了盘算,就需要调用revalidate来更新设备内部的信息。revalidate也是只对可更换介质的块设备有意义;

lock:lock使用户可以锁定一个文件。它也是只对文件系统有意义

任何设备都不太可能用到上一节所描述的那些方法。你只需要定义那些你用到的函数,不用的全部置NULL,当字符设备注册时,设备的 file_operation结构和设备的名字将添加到一个全局性的chrdevs数组里去,这个数组是由一些device_struct结构组成的,数 组的下标就是设备的主编号,这个数组被字符设备切换表。device_struct结构的定义如下所示:

struct device_struct{

const char *name; struct file_operations *fops; },

这样,通过查看chrdev[YOUR_MAJOR]->fops,内核就知道如何与设备进行交谈以及设备都支持那些入口点;

下面就写一个例子: // forward declarations for _fops

static ssize_t schar_read(struct file *file, char *buf, size_t count, loff_t *offset);

static ssize_t schar_write(struct file *file, const *buf, size_t count, loff_t *offset);

static unsigned int schar_poll (struct file *file, poll_table *wait)

static int schar_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg);

static int schar_mmap(struct file *file, struct vm_area_area_struct *vma);

static int schar_open(struct inode *inode, struct file *file); static int schar_release(struct inode *inode, struct file *file);

static struct file_operations schar_fops = {

NULL, //llseek

schar_read, schar_write, NULL, //readdir schar_poll, schar_ioctl, NULL, //mmap schar_open, NULL, //flush schar_release, NULL, //fsync NULL, //fasync NULL, //lock }

这段定义,可以个人风格,放在相应的地方。 下面定义一些简便方式: #define DEBUG

#ifndef DEBUG

#define MSG(string, args...) printk(KERN_DEBUG \ #else

#define MSG(string, args) #endif //定义主设备号

#define SCHAR_MAJOR 42

//程序的注册定义入口点 int init_module(void) { int res;

if(schar_name ==NULL) schar_name= \

//register device with kernel

res = register_chrdev(SCHAR_MAJOR, schar_name, &schar_fops);

if(res){

MSG(\ return res; } }

//若注册没有错误,你就会在/proc/devices的字符设备里能看到这个设备名称了

下面稍微讲一下,模块的使用计数

内核需要记录加载到系统里的每一个模块的使用情况,如果不是这样的话,这统无法知道什么卸载一个模块是安全的,如果你正在往硬盘中写数据,突然去掉硬盘的驱动程序的话,什么后果会发生呢? 我想你会知道它的厉害的!!!

修改模块需要用到两个宏命令,MOD_INC_USE_COUNT和MOD_DEC_USE_COUNT,一个加一,一个是减一;维护模块使用 计数器是由程序员来完成的,即要保证模块不会被意外加载,也不要让模块在使用过程中被卸载, 用MOD_IN_USE宏命令可以救出模块当前的使用计数, 但这也没有什么是必要的,因为内核在尝试卸载一个模块时,系统都会对这个数字进行检查的。

OPEN和RELEASE:设备的打开与关闭

现在模块已经被加载,它会在系统上等等有人进程来打开与之关联的设备。当设备被一个进程打开的时候,schar_open就会被调用。模块的使用计数就是在这里得到增加的,如下代码: static int schar_open(struct inode *inode, struct file *file) {

//increment usage count MOD_INC_USE_COUNT;

//这样可以保证模块不被意外卸载,会返回一个“-EBUSY”错误。

//传递给schar_open的file参数是内核对返回给应用程序的文件描述符的一个内部描述。file结构里有关设备打开模式的信息,如下面代码,测试是否以读方式打开 if(file->f_mode & FMODE_READ){ MSG(*open for reading\\n\ ...

file结构里还有一个读操作发生位置等其他信息。下面给出的是该结构与我们这个模块有关的项

struct file{ ....

mode_t f_mode; loff_t f_pos; unsigned int f_flags;