Chinaunix首页 | 论坛 | 博客
  • 博客访问: 206034
  • 博文数量: 33
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 1277
  • 用 户 组: 普通用户
  • 注册时间: 2013-03-03 10:03
个人简介

现于杭州电子科技大学攻读硕士学位

文章分类

全部博文(33)

文章存档

2013年(33)

我的朋友

分类: LINUX

2013-09-07 15:30:35

/*
基础知识:
1:一个设备驱动可以只包含 并且引用当前进程.例如, 下面的语句打印了当前进程的进程 ID 和命令名称, 通过存取结构task_struct 中的某些字段.
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              /*IRQ中断相关头文件*/
#include
#include
#include /*GPIO操作相关头文件*/
#include
#include /*硬件平台相关头文件*/
#include       /*内核相关的头文件*/
#include     /*所有模块都需要的头文件,此头文件包含了可装载模块需要的大量的符号和函数定义*/
#include           /*设备驱动初始化相关的头文件,包含该函数的目的是指定初始化和清除函数*/
#include         /*内存管理相关的头文件*/
#include            /*文件系统相关的头文件,它是编写设备驱动程序必需的头文件,其中声明了许多重要的函数和数据结构*/
#include       /*设备类型定义*/
#include       /*延时处理相关的头文件*/
#include   /*大部分模块都包含的头文件,这样我们就可以在装载模块时向模块传递参数*/
#include
#include       /*错误处理*/
#include        /*IO操作
#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**********************************************************************/

阅读(2034) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~