分类: LINUX
2013-09-07 15:30:35
/*
基础知识:
1:一个设备驱动可以只包含
printk(KERN_INFO "The process is \"%s\" (pid %i)\n", current->comm,current->pid);
存储于 current->comm 的命令名称是由当前进程执行的程序文件的基本名称( 截短到 15 个字符, 如果需要 ).
2:lsmod 通过读取 /proc/modules 虚拟文件工作. 当前加载的模块的信息也可在位于 /sys/module 的 sysfs 虚拟文件系统找到.
/***************************************************Makefile*****************************************************************/
/*
KERN_DIR = /work/system/linux-2.6.30.4
all:
make -C $(KERN_DIR) M=`pwd` modules #-C 指定进入指定的目录即KERN_DIR,是内核源代码目录,调用该顶层目录下的Makefile,M=选项让该makefile在构造modules目标之前返回到模块源代码目录
clean: #然后modules目标指向obj-m变量中设定的模块
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += xxx.o
xxx-objs :=xxx1.o xxx2.o ..... #模块是由多个C文件构成
*/
/****************************************************end**********************************************************************/
/***************************************************相关头文件*****************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/****************************************************end**********************************************************************/
/************************************************模块相关的说明与许可证*******************************************************/
MODULE_AUTHOR("xxxx"); /*描述模块作者*/
MODULE_LICENSE("Dual BSD/GPL");/*指定代码使用双重许可证*/
MODULE_VERSION("v1.0"); /*模块版本*/
MODULE_DESCRIPTION("xxxx"); /*说明模块用途*/
MODULE_ALIAS("xxx"); /*模块别名*/
MODULE_DEVICE_TABLE(); /*该宏用于将内核的变量导出到用户空间,以支持热插拔事件*/
/****************************************************end**********************************************************************/
/************************************************模块加载与卸载函数***********************************************************/
static int __init xxx_init(void) /*初始化函数 */
{
/*注意错误的处理,goto就比较有效*/
return 0;
}
static void __exit xxx_exit(void) /*清除函数*/
{
}
module_init(xxx_init);/*设备加载函数*/
module_exit(xxx_exit);/*设备卸载函数*/
/*
在注册设备时, 注册可能失败. 即便最简单的动作常常需要内存分配, 分配的内存可能不可用. 因此模块代码必须一直检查返回值,
并且确认要求的操作实际上已经成功.当模块的初始化出现错误后,模块必须自行撤销已注册的设施,错误恢复处理有时goto语
句比较有效
/****************************************************end**********************************************************************/
/****************************************************字符设备驱动**************************************************************/
/*----------------------------------------------设备号相关操作------------------------------------------------------------------*/
/*
通常而言,主设备号标识设备对应的驱动,次设备号由内核使用,用于正确确定设备文件所指的设备文件所指的设备,
我们可通过次设备号获得指向内核设备的直接指针,也可将次设备号当作设备本地数组的引索。
同一类设备使用相同的主设备号,不同类的设备使用不同的主设备号,用次设备号来描述使用该驱动的设备的序号,
序号一般从0开始在调用cdev_add()函数向系统注册字符设备之前,该首先分配设备号
可通过命令:cat /proc/devices 来查看系统使用的设备号*/
MAJOR(dev_t dev); /*将设备编号转换为主设备号 ,dev是 32 位的数, 高12 位用作主编号, 低20位用作次编号.(该函数实际是将dev右移了20位)*/
MINOR(dev_t dev); /*将设备编号转换为次设备号(该函数实际是屏蔽了dev的高12位)*/
MKDEV(int major, int minor)/*将主设备号转换为设备编号,minor一般取0,可查看Documentation/device.txt文件确定可用的设备号*/
int register_chrdev_region(dev_t first,unsigned int count, char *name)
/*分配从first开始count个设备编号,first为预先设定的设备号,成功返回0,失败返回负的错误码,
name 是应当连接到这个编号范围的设备的名子; 它会出现在 /proc/devices 和 sysfs 中*/
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,unsigned int count, char *name)/*动态分配设备号, firstminor通常为0,分配的结果保存在第一个参数里*/
void unregister_chrdev_region(dev_t first, unsigned int count) /*卸载设备号*/
/*驱动程序需要将设备编号和内部函数连接起来,这些内部函数用来实现设备的操作
一般采用一些方式分配设备号:*/
if(xxx_major)
{
xxx_devno=MKDEV(xxx_major,xxx_minor); /*将主设备号和此设备号转换成设备编号*/
result=register_chrdev_region(xxx_devno,1,DEVICE_NAME); /*在指定设备主次设备号的情况下,通过这种方式分配设备编号*/
}
else
{
result=alloc_chrdev_region(&xxx_devno,0,1,DEVICE_NAME); /*如果没有指定主次设备号,则采用这种方式进行动态分配主*/
xxx_major=MAJOR(led_devno);
}
----------------------------------------------------------------------------------------------------------------------------
/*-----------------------------------------------------file_operations结构----------------------------------------------------*/
struct file_operations { /*文件操作*/
struct module *owner; /*它并不是一个操作,它指向“拥有”该结构的模块的指针,内核使用这个字段以避免在模块操作正在被
使用时卸载该模块,几乎所有的情况下,该成员被初始化为THIS_MODULE,它是定义在
loff_t (*llseek) (struct file *file, loff_t offset, int origin); /*llseek用来修改文件的当前读写位置,并将新位置(正的)作为返回值返回,
参数loff_t是一个长偏移量,即使在32位平台上也至少占用64位的数据宽度,
出错返回负的返回值,如果这个函数指针为NULL,对seek的调用将会以某种
不可预期的方式修改file结构中的位置计数器*/
ssize_t (*read) (struct file *filp, char __user *buf, size_t count, loff_t *f_pos); /*用来从设备读取数据,该函数指针被赋为NULL时,将导致read系统调
用出错并返回-EINVAL,函数返回非负值表示成功读取的字节数
注意更新文件的位置f_pos如果返回值传输的字节数小于count,
则应该重新传输数据返回0表示已经到达文件尾
如果数据暂时没有到达则应该阻塞*/
ssize_t (*write) (struct file *filp,const char __user *buf, size_t count, loff_t *f_pos); /*向设备发送数据,如果没有这个函数,将导致write系统调用出错并返
回-EINVAL,函数返回非负值表示成功写入的字节数注意更新文件的位
置f_pos */
ssize_t (*aio_read) (struct kiocb *iocb, const struct iovec *iov,unsigned long nr_segs, loff_t pos); /*初始化一个异步的读取操作---即在函数返回之前可能不会完
成的读取操作,如果该方法为NULL,所有的操作将通过read(
同步)处理(异步非阻塞)*/
ssize_t (*aio_write) (struct kiocb *iocb, const struct iovec *iov,unsigned long nr_segs, loff_t pos);/*初始化一个异步的写入操作(异步非阻塞)*/
unsigned int (*poll) (struct file *filp, poll_table *wait);/*poll方法是poll、epoll、select这三个系统调用的后端实现,这三个系统调用可用查询某个或
多个文件描述符上读写是否阻塞,poll方法应该返回一位掩码,用来指定非阻塞的读取
或写入是否可能,并且也会向内核提供将调用进程至于休眠状态直至I/O变 可能时的信息,
如果驱动程序将poll方法定义为NULL,则设备被认为即可读也可写,并且不会阻塞*/
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); /*系统调用ioctl提供了一钟执行设备特定命令的方法(如格式化磁盘,
这个既不是读操作也不是写操作),另外,内核 还能识别一部分
ioctl命令, 而不必调用fops表中的ioctl,如果设备不提供ioctl入口点,
对于任何内核未预先定义的请求,ioctl系统调用将返回错误*/
long (*unlocked_ioctl) (struct file *filp, unsigned int ioctl, unsigned long arg); /*不使用BLK的文件系统,将使用此种函数指针代替ioctl*/
long (*compat_ioctl) (struct file *file, unsigned cmd, unsigned long arg); /*在64位系统上,32位的ioctl调用,将调用此函数指针代替*/
int (*mmap) (struct file *file, struct vm_area_struct *vma); /*mmap用于请求将设备内存映射到进程空间,如果设备没有实现这个方法,
那么mmap系统调用将返回-ENODEV*/
int (*open) (struct inode *inode,struct file *filp); /*尽管这是对设备文件的第一个操作,然而却并不要求驱动程序一定要声明一个相应的
方法,如果这个入口为NULL,设备的打开操作永远成功,但系统不会通知驱动程序*/
int (*flush) (struct file *filp, fl_owner_t id); /*对flush操作的调用发生在进程关闭设备文件描述符副本的时候,它应该执行(并等待)设备上
尚未完结的操作,目前,flush仅仅用于少数几个驱动程序
如果flush被设置为NULL,内核将简单低忽略用于的应用程序请求*/
int (*release) (struct inode *inode, struct file *filp); /*当file结构被释放时即关闭设备文件,将调用这个操作,与open相仿,
也可以将release设置为NULL*/
int (*fsync) (struct file *filp, struct dentry *dentry, int datasync); /*该方法是fsync系统调用的后端实现,用户调用它来刷新待处理的数据,
如果驱动程序没有实现这一方法,fsync系统调用将返回-EINVAL*/
int (*aio_fsync) (struct kiocb *iocb, int datasync); /*这是fsync方法的异步版本*/
int (*fasync) (int fd, struct file *filp, int mode); /*这个操作用来通知设备其FASYNC标志发生了变化,如果设备不支持异步通知,
该字段可以使NULL*/
int (*lock) (struct file *filp, int cmd, struct file_lock *fl); /*lock方法用于实现文件锁定,锁定是常规文件不可缺少的特性,
但设备驱动程序几乎不会实现这个方法*/
unsigned long (*get_unmapped_area)(struct file *file,unsigned long addr, unsigned long len,unsigned long pgoff,unsigned long flags);
/*该方法的目的是在进程的地址空间找到一个合适的位置,
以便将底层设备中的内存 映射到该位置,
大部分驱动程序可设置改方法为NULL */
int (*check_flags)(int); /*该方法允许模块检查传递给fcntl调用的标志*/
ssize_t (*splice_write)(struct pipe_inode_info *pipe, struct file *out,loff_t *ppos, size_t len, unsigned int flags); /*由VFS调用,将管道数据粘接到文件*/
ssize_t (*splice_read)(struct file *in, loff_t *ppos,struct pipe_inode_info *pipe, size_t len,unsigned int flags); /*由VFS调用,将文件数据粘接到管道*/
};
struct file_operations xxx_fops={
.owner=THIS_MODULE,
.open=xxx_open,
.read=xxx_read,
.write=xxx_write,
.ioctl=xxx_ioctl,
.poll=xxx_poll,
.release=xxx_close,
......
};
/*------------------------------------------------------------------------------------------------------------------------------------*/
/*-----------------------------------------------------------file 结构----------------------------------------------------------------*/
/*
*文件结构代表一个打开的文件(文件描述符). (它不特定给设备驱动; 系统中每个打开的文件有一个关联的 struct file 在内核空间).
它由内核在 open 时创建, 并传递给在文件上操作的任何函数, 直到最后的关闭. 在文件的所有实例都关闭后,内核释放这个数据结构
*
.*/
struct file { /*file结构,指向该结构的指针为filep/file(文件指针)*/
/*
* fu_list becomes invalid after file_free is called and queued via
* fu_rcuhead for RCU freeing
*/
union {
struct list_head fu_list;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
#define f_dentry f_path.dentry
#define f_vfsmnt f_path.mnt
const struct file_operations *f_op; /*与文件相关操作,内核在执行open操作时对这个指针赋值,以后需要处理这些操作时就读取这个指针,
filep->f_op中的值决不会为方便引用而保存起来也就是说,我们可以在任何时候修改文件的关联操作,
在返回给调用者之后,新的操作方法就会立即生效*/
spinlock_t f_lock; /* f_ep_links, f_flags, no IRQ */
atomic_long_t f_count;
unsigned int f_flags; /*文件标志,如O_RDONLY O_NONBLOCK 和O_SYNC,为了检查用户请求的是否是非阻塞式的操作,
驱动程序需要检查O_NONBLOCK标志,而其他标志很少用到,所有的标志在
fmode_t f_mode; /*文件模式,它通过FMODE_READ和FMODE_WRITE位来标识文件是否可读或可写或可读写,驱动程序无需为
此而作额外的判断*/
loff_t f_pos; /*当前的读/写位置,loff_t是一个64位数(gcc中long long定义),如果驱动程序需要知道文件中的当前位置,
可以读取这个值,但不需要修改它,read/write会使用它们接收到最后的那个指针参数来更新这一位置,
而不是直接对filep->f_pos进行操作*/
struct fown_struct f_owner;/*用来保存异步通知的属主进程的进程的ID号,目的是为了让内核知道应该通知哪一个进程*/
const struct cred *f_cred;
struct file_ra_state f_ra;
u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data; /*open系统调用在调用驱动程序的open方法之前将这个指针设置为NULL,private_data在跨系统调用时保存状态
信息是非常有用的资源,我们大部分示例都使用它struct xxx_dev *xxx_dev; dev=container_of(inode->i_cdev,struct xxx_dev,cdev)
xxx_dev是我们自己定义的设备私有结构体,其中包含 struct cdev cdev成员,这样今后就可以方便对该指针的访问了
container_of是通过结构体内部成员的地址来获取整个结构体的地址*/
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
#ifdef CONFIG_DEBUG_WRITECOUNT
unsigned long f_mnt_write_state;
#endif
};
-----------------------------------------------------------------------------------------------------------------------------------
/*-----------------------------------------------------------inode结构---------------------------------------------------------------*/
//inode 结构由内核在内部用来表示文件. 因此, 它和代表打开文件描述符的文件结构是不同的. 可能有代表单个文件的多个打开的
//文件描述符的file结构, 但是它们都指向一个单个 inode 结构
struct inode { /*inode结构,此结构包含了大量的文件信息*/
struct hlist_node i_hash;
struct list_head i_list;
struct list_head i_sb_list;
struct list_head i_dentry;
unsigned long i_ino;
atomic_t i_count;
unsigned int i_nlink;
uid_t i_uid;
gid_t i_gid;
dev_t i_rdev; /*对表示设备文件的inode结构,该字段包含了真正的设备编号*/
u64 i_version;
loff_t i_size;
#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount;
#endif
struct timespec i_atime;
struct timespec i_mtime;
struct timespec i_ctime;
unsigned int i_blkbits;
blkcnt_t i_blocks;
unsigned short i_bytes;
umode_t i_mode;
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
struct mutex i_mutex;
struct rw_semaphore i_alloc_sem;
const struct inode_operations *i_op;
const struct file_operations *i_fop; /* former ->i_op->default_file_ops */
struct super_block *i_sb;
struct file_lock *i_flock;
struct address_space *i_mapping;
struct address_space i_data;
#ifdef CONFIG_QUOTA
struct dquot *i_dquot[MAXQUOTAS];
#endif
struct list_head i_devices; /* 链表头,struct cdev通过list字段链入链表 */
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;/*若是块设备,其对应的是block_device结构体指针*/
struct cdev *i_cdev; /*struct cdev是表示字符设备的内核的内部结构,当inode指向一个字符设备文件时,该字段包含了指向
struct cdev结构的指针(指向我们初始化并注册的struct cdev结构)*/
};
//从inode中获得主设备号和次设备号:
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);
/*---------------------------------------------------------------------------------------------------------------------------------------*/
/*-----------------------------------------------------------字符设备注册----------------------------------------------------------------*/
struct xxx_dev{ /*描述设备结构体*/
struct cdev *cdev; /*字符设备cdev结构体*/
unsigned char button_value; /*状态标志,=1表示有按键按下*/
struct semaphore sem; /*信号量 */
wait_queue_head_t outq;
struct fasync_struct *async_quene;
};
struct xxx_dev *xxxx_dev;
xxxx_dev=kmalloc(sizeof(struct xxx_dev), GFP_KERNEL); /*为设备描述结构体分配内存*/
//如果直接定义了该结构则可用下面函数来分配该结构:*/
struct cdev *cdev_alloc(void); /*分配一个struct cdev结构体*/
if(!xxxx_dev)
{
ret=-ENOMEM;
goto fail_malloc;
}
memset(xxxx_dev,0,sizeof(struct xxx_dev)); /*将分配的内存清零 */
void cdev_init(struct cdev *cdev, struct file_operations *fops) /*初始化已分配的设备结构*/
cdev.owner=THIS_MODULE;
cdev.ops=&file_operations xxx
int cdev_add(struct cdev *dev, dev_t num, unsigned int count) /*注册设备,num为设备号,count是应该和该设备关联的设备编号的数量,通常取1
成功返回0,否则返回负数*/
void cdev_del(struct cdev *dev) /*卸载设备*/
//早期的方法:
int register_chrdev(unsigned int major, const char *name,struct file_operations *fops) /*注册字符设备*/
int unregister_chrdev(unsigned int major, const char *name) /*卸载设备*/
//可以在入口函数里面使用下面函数来自动创建设备节点:
static struct class *xxx_class;
static struct device *xxxclass_dev;
class_create(owner, name);/*创建一个类,然后使用下面函数来在类里面创建设备节点*/
struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...);
//可以在出口函数里面使用下面函数来删除设备节点:
void device_destroy(struct class *class, dev_t devt)
void class_destroy(struct class *cls)
/****************************************************end**********************************************************************/
/*********************************************用户空间和内核空间的数据传输***************************************************/
unsigned long copy_to_user(void __user *to, const void *from, unsigned long count); /*返回值是没有拷贝成功的字节数*/
unsigned long copy_from_user(void *to, const void __user *from, unsigned long count); /*返回值是没有拷贝成功的字节数*/
//如果要复制的内存是简单类型,如 char、int、long等,则可以使用下面简单函数:
int val; /*内核空间整型变量*/
get_user(val,(int *)arg); /*用户空间到内核空间传输数据,arg是用户空间的地址*/
put_user(val,(int *)arg); /*内核空间到用户空间传输数据,arg是用户空间的地址*/
/****************************************************end**********************************************************************/
/****************************************************mmap方法*****************************************************************/
/*
应用程序通过内存映射可以直接访问设备的I/O存储区或DMA缓冲,内存映射使用用户空间的一段地址关联到设备内存上,程序在映射的地址范围内
进行读写,实际上就是对设备访问,mmap函数原型如下:*/
unsigned long mmap(unsigned long addr,unsigned long len,int prot,int flags,int fd,long off);
/*
参数说明:
参数addr为内存块的建立位置,不能确保mmap函数一定使用这块内存区域,因此通常将其设置为NULL
参数len为映射到调用进程地址空间的字节数,它从被映射文件开头off个字节算起
参数prot为指定了共享内存的访问权限,可取如下几个值:PROT_READ PROT_WRITE PROT_EXEC PROT_NONE(不可访问)
参数flags为由以下几个常值指定:MAP_SHARED、MAP_PRIVATE、MAP_FIEXD,其中MAP_SHARED和MAP_PRIVATE必选其一,而MAP_FIEXD不推荐使用;
如果指定为MAP_SHARED则对映射内存所做的修改同样映射到文件,如果是MAP_PRIVATE则对映射内存所做的修改仅对该进程可见对文件
没有影响。
参数fd为文件描述符
参数off一般设为0,表示从文件头开始映射。
*/
/*取消映射可以使用下面函数:*/
int munmap(void *addr,size_t len)
/*****************************************************************************************************************************/
/****************************************************poll机制(非阻塞)*******************************************************/
/*
用户空间poll--->系统调用sys_poll---> SYSCALL_DEFINE3--->do_sys_poll--->do_poll--->do_pollfd
异步阻塞I/O:
在用户空间,poll、select和epoll的原型声明分别是:
*/
int poll(struct pollfd *ufds, unsigned int nfds,int timeout); /*nfds表示等待的文件描述符的个数;timeout表示超时限制,单位是毫秒,函数的返回值大于0则表示集合中可以进行读或写的
文件描述符的个数;返回值等于0则表明集合中所有文件描述符尚无状态变化时,timeout指定的时间到,函数超时;返回值小 */ //于0则表示调用失败
int select(int nfds,fd_set *writeset,fd_set *exceptset,struct timeval *timeout);
int epoll_create(int size);
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);
int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);
/*在用户空间,应用程序将要操作的一组文件的描述符加入到一个集合中,然后在这个集合的基础上使用这些函数来监控其中
的每个文件描述符:倘若集合中的每个文件目前都不可以进行读取写入操作,进程也许会因此而被阻塞,直到该集合中的任
一文件可读或者可写。*/
struct pollfd{
int fd; /*文件描述符*/
short events;/* 等待的事件,通常是POLLLIN等宏的组合*/
short revent;/*返回的事件即驱动程序中实际发生的事件*/
};
//原型:
unsigned int xxx_poll(struct file filp, poll_table *wait); /* poll_table结构用于实现poll、select和epoll系统调用*/
#include
//(1)
void poll_wait(struct file *filp,wait_queue_head_t queue, poll_table *wait); /*向poll_table结构添加一个等待队列,不会休眠,而是会在系统调用直到
超时时间时,超时才会休眠*/
//(2)poll的第二项任务是返回描述哪个操作可以立即执行的位掩码
// 常用的位掩码有:
POLLIN /*如果设备可以无阻塞的读取,就设置该位*/
POLLRDNORM /*如果通常的数据已经就绪,可以读取,就设置该位一个可读设备返回(POLLLIN|POLLRDNORM)*/
POLLHUP /* 当读取设备的进程到达文件尾时,驱动程序必须设置该(挂起)位,依照select功能描述,调用select的进程会被
告知设备是可读的*/
POLLERR /*设备发生了错误*/
POLLOUT /*如果设备可以无阻塞的写入,就设置该位*/
POLLWRNORM /*如果通常的数据已经就绪,可以写入,就设置该位一个可读设备返回(POLLOUT|POLLWRNORM)*/
/*一般程序设计模板:*/
unsigned int xxx_poll(struct file filp, poll_table *wait)
{
unsigned int mask;
poll_wait(filp,&xxx_dev->inq,wait); /*将等待节点对象加入到自己管理的等待队列中,注意等待队列wait需要先初始化 */
if(/*可读*/)
mask=POLLIN|POLLRDNORM;
if(/*可写*/)
mask=POLLOUT|POLLWRNORM;
if(/*无数据可获取*/)
mask=POLLHUP;
return mask;
}
/*注意:需要在驱动程序的其他地方唤醒阻塞在poll上的进程:wake_up(&xxx_dev->inq)*/
/****************************************************end**********************************************************************/
/****************************************************异步通知*****************************************************************/
/*
*先了解下fcntl函数的执行流程:fcntl--->sys_fcntl--->SYSCALL_DEFINE3---> do_fcntl--->setfl--->驱动程序的fasync函数
*用户空间应用程序:
*为了启动文件的异步通知机制,用户应用程序必须执行以下两个步骤:
*(1)他们指定一个进程作为文件的属主,当进程使用fcntl系统调用执行F_SETOWN命令时,属主进程的进程ID号就被保存在filp->f_owner中,
这一步是必须的,目的是为了 让内核知道应该通知那个进程,也即把进程ID号告诉驱动,
* 这样当驱动程序发现设备的数据就绪时才知道要通知哪个进程
*
* int fcntl(int fd,int cmd,int arg); //该函数用于对已经打开的文件描述符执行各种控制操作,根据参数cmd的值决定是否要第3个附加参数,
* //F_SETOWN:设置进程或进程组接收SIGIO或SIGURG信号,进程组则以负值返回
* //F_GETFL:获取文件的打开方式,返回所有的标志位,标志位的含义与open相同
* //F_SETFL:设置文件打开的方式,设置文件打开方式为参数arg指定的方式
* //F_GETOWN:获取当前接收SIGIO或SIGURG信号,进程组则以负值返回
*
*(2)为了真正启动异步通知,用户应用程序还必须通过fcntl的F_SETFL命令设置FASYNC标志让驱动程序启动异步通知机制,
执行完这两步输入文件就可以在新数据到达时请求发送一个SIGIO信号该信号被发送到存在filp->f_owner中的进程.
*/
//综上所知:用户的应用程序必须执行下面的语句
int oflags;
signal(SIGIO, &input_handler); /*input_handler为信号处理函数*/
fcntl(fd, F_SETOWN, getpid());
oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, oflags | FASYNC);
//不是所有的设备都支持异步通知,应用程序通常假设只有套接字和终端才有异步通知能力
/*
*驱动程序:
*(1) 当 F_SETOWN被调用时, 除了一个值被赋值给filp->f_owner外什么都也不做
*(2) 在执行 F_SETFL启动 FASYNC时, 驱动的 fasync 方法被调用,只要 filp->f_flags 中的FASYNC 标志发生了变化,就会调用该方法,
以便把这个变化通知驱动程序使其能正确响应,文件打开时,FASYNC标志被默认为是清除的
*(3) 当数据到达, 所有的注册异步通知的进程必须被发出一个 SIGIO 信号
*
* 驱动程序为实现fasync例程,需要维护一个struct fasync_struct链表,链表的每个节点代表着一个需要通知的进程
*/
//程序设计如下:
struct xxx_dev{
........;
........;
........;
struct fasync_struct *async_quene;
};
static int xxx_fasync (int fd, struct file *filp, int mode) /*当打开的文件的FASYNC标志被修改时,调用fasync_helper以便从相关进程列表中增加
或删除文件*/
{
struct xxx_dev *dev=(struct xxx_dev *)filp->private_data; /*获取执行设备描述结构的指针,在open函数中设置*/
return fasync_helper(fd,filp,mode,&dev->async_quene) ; /*该函数主要将当前要通知的进程加入一个链表或从链表中删除,
这取决于应用程序调用fcntl时是否设置了FASYNC标志 */
}
static struct file_operations xxx_fops={
.............
.............
.fasync=xxx_fasync,
};
if(dev->async_quene)
kill_fasync(&xxx_dev->async_quene,SIGIO,POLL_IN(/*或POLL_OUT(对设备可读)*/));/*当数据到达时,执行此语句来异步通知进程可读或可写,
即释放信号(向驱动程序维护的一个 struct fasync_struct链表,
链表的每个等待通知进程发送通知信号)*/
xxx_fasync(-1,filp,0); /*将文件从异步通知列表中删除 ,一般在release中实现*/
/****************************************************end**********************************************************************/