linux 字符驱动
字符驱动
·
1 结构体说明:
struct cdev {
struct kobject kobj; // 每个 cdev 都是一个 kobject
struct module *owner; // 指向实现驱动的模块
const struct file_operations *ops; // 操纵这个字符设备文件的方法
struct list_head list; // 与 cdev 对应的字符设备文件的 inode->i_devices 的链表头
dev_t dev; // 起始设备编号
unsigned int count; // 设备范围号大小
};
内核中每个字符设备都对应一个 cdev 结构的变量。
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 **);
};
下面是常用的一些方法的介绍:
loff_t (*llseek) (struct file * filp , loff_t p, int orig);
指针参数filp为进行读取信息的目标文件结构体指针;参数 p 为文件定位的目标偏移量(根据orig指定的位置的偏移量,这个值可以为负值);参数orig为对文件定位的起始地址,这个值可以为文件开头(SEEK_SET,0,当前位置(SEEK_CUR,1),文件末尾(SEEK_END,2)llseek 方法用作改变文件中的当前读/写位置,
该方法执行成功返回新位置,返回值小于0,表示执行失败。
这个方法对应应用程序中的seek函数。
ssize_t (*read) (struct file * filp, char __user * buffer, size_t size , loff_t * p);
指针参数 filp 为进行读取信息的目标文件,指针参数buffer 为对应放置信息的缓冲区(即用户空间内存地址),参数size为要读取的数据的长度,参数 p 为读的位置相对于文件开头的偏移,在读取信息后,这个指针一般都会移动,移动的值为要读取信息的长度值
该方法执行成功返回实际读取的字节数,返回负值表示执行失败。
该方法对应应用程序中的read函数。
最后需要注意的是buffer是用户空间的地址,所以不能用memcpy进行拷贝,必须要使用copy_to_user来进行内核空间到用户空间的拷贝。示例如下
copy_to_user(buffer ,ptr ,len) ; buffer为用户空间的地址,ptr为内核空间的地址,len为要拷贝的长度。
ssize_t (*write) (struct file * filp, const char __user * buffer, size_t count, loff_t * ppos);
参数filp为目标文件结构体指针,buffer为要写入文件的信息缓冲区,count为要写入信息的长度,ppos为当前的偏移位置,这个值通常是用来判断写文件是否越界
函数执行成功返回实际发送的字节数,失败返回负值。
该方法对应应用程序中的write函数。
同read一样buffer是用户空间的地址,所以不能直接拷贝,需要用copy_from_user来进行拷贝,示例如下:
copy_from_user(ptr , buffer ,len) ; buffer为用户空间的地址,ptr为内核空间的地址,len为要拷贝的长度。
unsigned int (*poll) (struct file *, struct poll_table_struct *);
这是一个设备驱动中的轮询函数,第一个参数为file结构指针,第二个为轮询表指针.
这个函数资源可用时返回设备资源的可获取状态,即POLLIN,POLLOUT,POLLPRI,POLLERR,POLLNVAL等宏的位“或”结果。否则返回0 。该函数详细的用法可以参考《select poll epoll使用示例》里面的介绍。
int (*unlocked_ioctl) ( struct file *filp, unsigned int cmd, unsigned long arg);
注意在2.6.36以后ioctl已经不再存在了,所以这里我们只说明unlocked_ioctl。unlocked_ioctl和ioctl的区别在于ioctl多了一个struct inode *的参数。这个在移植老代码的时候要注意修改,否则编译不过的,unlocked_ioctl和ioctl对应的都是应用程序中的ioctl函数,对于应用,不论是unlocked_ioctl还是ioctl都是不需要修改的。
unlocked_ioctl主要用于对硬件设备的控制。第一个参数为file结构指针, 第二个参数cmd是用户传进来的操作码,驱动根据这个数据来决定执行什么操作。第三个参数arg是参数,这个参数可以是一个整数,或者是一个指针,如果是指针的话,在内核空间要通过 copy_to_user或者 copy_from_user拷贝数据。
下面详细说明下cmd,cmd由4部分组成,设备类型(幻数),方向,序号,数据大小,linux提供了下面的宏进行操作
_IO(type,nr)
_IOR(type,nr,size)
_IOW(type,nr,size)
_IOWR(type,nr,size)
还有下面的宏可以获取一个cmd设备类型(幻数),方向,序号,数据大小
_IOC_DIR(nr)
_IOC_TYPE(nr)
_IOC_NR(nr)
_IOC_SIZE(nr)
注意:对于我们定义的mcd,最好用根据方向、数据大小等用_IO、_IOR、_IOW、_IOWR来定义,而不要直接用一个数值,例如1、2、3这些数据。
直接定义一个常数在早期的linux不会出错,但是在3.0以后,可能会造成某些cmd根本不会执行,但是返回的还是0。例如我曾经移植2.6.2的驱动到3.0下,结果该驱动有一个cmd定义的是2,结果之前没有问题,到3.0 应用调用ioctl返回0,没有报错,但是看串口信息,驱动中的unlocked_ioctl根本没有调用,将这个cmd用_IOR重新定义执行就正常了,
unlocked_ioctl执行成功返回0 ,失败返回负值。
int (*mmap) (struct file *, struct vm_area_struct *);
mmap 用来请求将设备内存映射到进程的地址空间.这个函数对应的是应用程序中的mmap.
函数执行成功返回0,失败返回负值。
下面是示例代码:
static int test_mmap( struct file *filp ,struct vm_area_struct *vma)
{
unsigned long size ;
size = vma->vm_end-vma->vm_start;
//这里的MAX_SIZE 是我们分配的用于映射的内存的大小,是我们在驱动中定义的
//vm_pgoff是页偏移。也就是说如果应用中设置的偏移是4096(linux一页时4096字节) 那么驱动中vm_pgoff就是1,如果应用中是8192 那么驱动对应的就是2。
if( (vma->vm_pgoff<<PAGE_SHIFT)+size > MAX_SIZE )
{
return -EAGAIN;
}
/*标记这段虚拟内存映射为IO区域,并阻止系统将该区域包含在进程的存放转存中*/
vma->vm_flags|=VM_IO;
/*标记这段区域不能被换出*/
vma->vm_flags|=VM_RESERVED;
if(remap_pfn_range(vma,vma->vm_start,(virt_to_phys(ptr_mem)>>PAGE_SHIFT)+vma->vm_pgoff,size,vma->vm_page_prot))
return -EAGAIN;
return 0;
}
另外我们需要注意的是mmap映射的地址是按页对齐的,也就是低12位的地址要为0。我们可以通过alloc_pages来分配内存用于映射。示例如下:
struct page *page;
page = alloc_pages(GFP_KERNEL,1);//alloc_pages的第二个参数是分配的页数,为2的n次方页。
//例如 为0 表示分配1页(4096),为1表示分配两页(8192),为2表示分配4页
if(page==NULL)
{
printk(KERN_ERR"alloc_pages return error\r\n");
ptr_mem=NULL;
}
else
{
ptr_mem= page_address(page);
if(ptr_mem==NULL)
{
printk(KERN_ERR"page_address return error\r\n");
free_pages((unsigned long)page, 1);
}
}
驱动卸载的时候调用
if(ptr_mem)
{
free_pages(ptr_mem, 1);
}
释放分配的页。
在应用中我们调用mmap函数获取映射地址的指针。
mmap声明如下:
void * mmap(void *addr,size_t length,int prot, int flags,int fd,off_t offset);
该函数执行成功返回一个指针,应用可以直接对这个指针进行操作,就可以操作驱动中映射的内存了。
参数说明如下:
void *addr 是程序员所希望的虚拟地址作为起始映射地址,通常为NULL,内核自动分配。
size_t length当然是指需要映射的区域大小。
int flags是指对这段区域的保护方式。具体的可以参看内核源码的linux/mm.h。常用的是PROT_EXEC,PROT_READ,PROT_WRITE,PROT_NONE。
int flags主要是指对这段区域的映射方式,主要分为两种方式MAP_SHARE,MAP_PRIVATE.其中的MAP_SHARE是指对映射区域的写操作会更新到文件中,这样就相当于直接操作文件。而MAP_PRIVATE通常采用一种称为"写时保护的机制"实现映射,对映射区的写操作不会更新到文件中,实现方法是将需要被写操作的页复制到重新分配的新页中,然后再对新的页进行写操作。原来的映射并没有改变,但是读操作并不会重新分配物理内存空间。具体的参考深入理解计算机系统。
int fd是指将被映射的文件描述符,映射需要保证文件描述符的正确性。
off_t offset是指从文件的具体位置开始映射,通常情况下可以设置为0,即从开头映射。这里需要注意的是这个偏移必须要页对齐的,就是说必须是4096的倍数,否则mmap将失败。这个对应的就是驱动中的vm_pgoff。
下面是应用调用的例子:
fd1= open("/dev/filename", O_RDWR );
mmap_ptr = (unsigned char *)mmap(0, 4096, PROT_READ |PROT_WRITE , MAP_SHARED , fd1, 0);
mmap_ptr2 = (unsigned char *)mmap(0, 4096, PROT_READ |PROT_WRITE , MAP_SHARED , fd1, 8192);
。。。。。。。
munmap(mmap_ptr ,4096);
munmap(mmap_ptr2 ,4096);
int (*open) (struct inode * inode , struct file * filp ) ;
inode 为文件节点,这个节点只有一个,无论用户打开多少个文件,都只是对应着一个inode结构;
但是filp就不同,只要打开一个文件,就对应着一个file结构体,file结构体通常用来追踪文件在运行时的状态信息,所以我们通常会在open中分配针对该设备的将结构,然后将这个结构的地址赋值给struct file的private_data,这样在read、write等其他的方法中就可以使用这个分配的结构了。
尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知.
int (*release) (struct inode *, struct file *);
release ()函数当最后一个打开设备的用户进程执行close()系统调用的时候,内核将调用驱动程序release()函数.release函数的主要任务是清理未结束的输入输出操作,释放资源,用户自定义排他标志的复位等。在文件结构被释放时引用这个操作. 如同 open, release 可以为 NULL.
struct file {
/*
* fu_list becomes invalid after file_free is called and queued via
* fu_rcuhead for RCU freeing
*/
union {
struct list_headfu_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;
/*
* Protects f_ep_links, f_flags, f_pos vs i_size in lseek SEEK_CUR.
* Must not be taken from IRQ context.
*/
spinlock_t f_lock;
#ifdef CONFIG_SMP
int f_sb_list_cpu;
#endif
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode;
loff_t f_pos;
struct fown_structf_owner;
const struct cred*f_cred;
struct file_ra_statef_ra;
u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_headf_ep_links;
struct list_headf_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space*f_mapping;
#ifdef CONFIG_DEBUG_WRITECOUNT
unsigned long f_mnt_write_state;
#endif
};
这个结构体代表一个打开的文件,系统中的每个打开的文件在内核空间都有一个关联的struct file。它由内核在打开文件时创建,并传递给在文件上进行操作的任何函数。
下面介绍下我们需要关注的成员变量
unsigned int f_flags; 当打开文件时指定的标志,对应系统调用open的int flags参数。驱动程序为了支持非阻塞型操作需要检查这个标志。
mode_t f_mode; 对文件的读写模式,对应系统调用open的mod_t mode参数。如果驱动程序需要这个值,可以直接读取这个字段。
void *private_data;指向私有数据的指针,用户在open的时候可以创建自己的数据结构,用这个指针保存地址,在对设备操作的时候使用这个结构。
struct inode {
umode_t i_mode;
unsigned shorti_opflags;
uid_t i_uid;
gid_t i_gid;
unsigned int i_flags;
#ifdef CONFIG_FS_POSIX_ACL
struct posix_acl*i_acl;
struct posix_acl*i_default_acl;
#endif
const struct inode_operations*i_op;
struct super_block*i_sb;
struct address_space*i_mapping;
#ifdef CONFIG_SECURITY
void *i_security;
#endif
/* Stat data, not accessed from path walking */
unsigned long i_ino;
/*
* Filesystems may only read i_nlink directly. They shall use the
* following functions for modification:
*
* (set|clear|inc|drop)_nlink
* inode_(inc|dec)_link_count
*/
union {
const unsigned int i_nlink;
unsigned int __i_nlink;
};
dev_t i_rdev;
struct timespeci_atime;
struct timespeci_mtime;
struct timespeci_ctime;
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
unsigned short i_bytes;
blkcnt_t i_blocks;
loff_t i_size;
#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount;
#endif
/* Misc */
unsigned long i_state;
struct mutex i_mutex;
unsigned long dirtied_when; /* jiffies of first dirtying */
struct hlist_nodei_hash;
struct list_headi_wb_list;
/* backing dev IO list */
struct list_headi_lru;
/* inode LRU list */
struct list_headi_sb_list;
union {
struct list_headi_dentry;
struct rcu_headi_rcu;
};
atomic_t i_count;
unsigned int i_blkbits;
u64 i_version;
atomic_t i_dio_count;
atomic_t i_writecount;
const struct file_operations*i_fop;
/* former ->i_op->default_file_ops */
struct file_lock*i_flock;
struct address_spacei_data;
#ifdef CONFIG_QUOTA
struct dquot *i_dquot[MAXQUOTAS];
#endif
struct list_headi_devices;
union {
struct pipe_inode_info*i_pipe;
struct block_device*i_bdev;
struct cdev *i_cdev;
};
__u32 i_generation;
#ifdef CONFIG_FSNOTIFY
__u32 i_fsnotify_mask; /* all events this inode cares about */
struct hlist_headi_fsnotify_marks;
#endif
#ifdef CONFIG_IMA
atomic_t i_readcount; /* struct files open RO */
#endif
void *i_private; /* fs or device private pointer */
};
每一个设备文件对应一个inode ,struct inode 和struct file 的区别在于,例如我有一个设备/dev/mytest,那么这个/dev/mytest就对应一个struct inode,而我每次打开/dev/mytest(可以同时打开多次),都会分配一个struct file。也就是一个设备文件只对应一个struct inode(同时打开多次,也只有一个),而如果这个设备文件被打开多次,那么就对应多个struct file。
下面是我们需要关注的成员:
dev_t i_rdev;该设备文件对应的设备号。
struct cdev *i_cdev;该设备的cdev结构的指针。
2 相关的函数:
int alloc_chrdev_region(dev_t *dev,unsigned int firstminor,unsigned int count,char *name);
该函数的功能是动态分配一个设备号,通过dev带回。
参数说明:dev是用来带回动态分配的设备号
firstminor第一个次设备号,通常设置为0
count 要分配的设备数
name 分配的设备号对应的名字,这个名字我们可以在/proc/devices里面看到。可以用cat /proc/devices查看
返回值: 0执行成功 小于0 失败。
int register_chrdev_region(dev_t first, unsigned int count, char *name);
该函数的功能是注册一个(或者一组)已经定义的设备号。
参数说明:first 是要分配的起始设备编号.first 的次编号部分常常是 0。
count 是请求的连续设备编号的总数
name 注册的设备号对应的名字,这个名字我们可以在/proc/devices里面看到。可以用cat /proc/devices查看
返回值: 0执行成功 小于0 失败。
以上这两个函数功能类似,都是注册一个或者一组设备号,但是不同的是alloc_chrdev_region是动态分配的,系统会自动在没有分配的主设备号中找一个可用的来分配。而register_chrdev_region是用户自己定义一个主设备号,来注册的。调用这个函数我们必须要确保我们定义的设备号没有被使用,否则这个函数会失败。为了避免冲突和便于移植到其他平台,建议最好使用alloc_chrdev_region动态分配一个设备号。
void unregister_chrdev_region(dev_t first, unsigned int count);
该函数的功能是释放注册的设备号
参数说明:first为第一个设备号 。
count为申请释放的设备数量
设备号操作的相关的宏:
MKDEV(ma,mi) 该宏的功能是根据主设备号和次设备号得到一个设备号。ma为主设备号 ,mi为次设备号
MAJOR(dev) 该宏的功能是获取主设备号 。dev为设备号
MINOR(dev) 该宏的功能为获取次设备号。参数dev是设备号
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
函数的功能是初始化一个cdev的结构。
参数说明:struct cdev *cdev 已经定义的cdev结构的指针 ,
const struct file_operations *fops 已经定义的file_operations 的结构的指针,该结构定义了设备文件的一系列操作。
示例代码如下:
struct cdev my_cdev;
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;
struct cdev *cdev_alloc(void);
函数的功能和cdev_init类似,但是这个函数是动态分配的一个struct cdev的结构。
返回值:分配成功返回struct cdev的结构的地址失败返回NULL。
示例代码如下:
struct cdev *my_cdev = cdev_alloc();
if(my_cdev)
{
my_cdev->ops = &fops;
my_cdev->owner = THIS_MODULE;
}
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
函数的功能是将字符设备驱动程序注册到系统中。
参数说明:struct cdev *p; 将要加入系统的cdev ,p是之前调用cdev_init或者cdev_alloc初始化好的。
dev_t dev;已经注册的设备号。这个是通过alloc_chrdev_region或者register_chrdev_region注册的设备号。
unsinged int count;要注册的设备的数目。
返回值:执行成功返回0,失败返回负值
void cdev_del(struct cdev *p);
函数功能是释放之前注册的cdev。
函数参数:struct cdev *p,之前用cdev_add注册的cdev的指针。
struct class *class_create(struct module *owner, const char *name);
函数功能是分配一个struct class的结构。
参数说明:struct module *owner 指向模块owner的指针,通常设置为THIS_MODULE
const char *name class的名字,这个名字可以在/sys/class/看到,系统将在/sys/class/下创建参数name指定的目录。
返回值:执行成功返回struct class的地址,失败返回NULL。
说明:实际上class_create不是一个函数而是一个宏,
#define class_create(owner, name) \
({ \
static struct lock_class_key __key;\
__class_create(owner, name, &__key);\
})
不过对于我们在实际调用中,可以不需要关注的。
void class_destroy(struct class *cls);
函数功能是注销一个class。
参数 cls就是我们之前用class_create分配的一个struct class的指针。
这个函数和class_create成对使用的。
int class_register(struct class *cls);
函数功能和class_create类似,也是注册一个class,实际上class_create内部也会调用__class_register。不同的是这个函数的参数是已经分配好的结构。
参数cls是struct class的指针
返回值执行成功返回0,失败返回负值。
实际上这个也是一个宏
#define class_register(class) \
({ \
static struct lock_class_key __key;\
__class_register(class, &__key);\
} )
下面是class_register的示例代码:
struct class my_class;
my_class->name = "myclass";
my_class->owner = THIS_MODULE;
my_class->class_release = class_create_release;
// 将class注册到内核中,同时会在/sys/class/下创建class对应的节点
int retval = class_register(my_class);
void class_unregister(struct class *cls);
注销一个class,这个函数和class_register成对使用。
struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void * drvdata, const char *fmt, ...);
函数功能是用于动态的建立逻辑设备,并对新的逻辑设备类进行相应初始化,将其与函数的第一个参数所代表的逻辑类关联起来,然后将此逻辑设备加到linux内核系统的设备驱动程序模型中。函数能够自动在/sys/devices/virtual目录下创建新的逻辑设备目录,在/dev目录下创建于逻辑类对应的设备文件。
参数说明:cls struct class 指针,必须在本函数调用之前先被调用class_register或者class_create注册的。
parent 该设备的父设备的指针,如果没有就设置为NULL
devt 该设备的设备号
drvdata 传递给该设备的私有数据的指针,如果没有就直接用NULL
fmt 设备名称,就是在dev目录下显示的设备名
void device_destroy(struct class *cls, dev_t devt);
函数功能:用于从linux内核系统设备驱动程序模型中移除一个设备,并删除/sys/devices/virtual目录下对应的设备目录及/dev/目录下对应的设备文件
参数说明:cls struct class 指针
devt 设备号
3 下面是示例代码:
在头文件中定义
#define SET1 _IOR('O', 1, int)
#define SET2 _IOR('O', 2, int)
#define SET3 _IOR('O', 3, int)
在C代码中
#define TEST_DEV_NAME "mytest"
#define MAX_DEV 3
typedef struct
{
dev_t test_dev;
struct cdev test_cdev;
struct class *test_class;
}test_DEV_struct;
typedef struct
{
char info[100];
int offset;
}test_data;
static ssize_t test_write(struct file *filp, const char __user *data,size_t len, loff_t *ppos)
{
test_data * ptr = filp->private_data;
if(copy_from_user(ptr->info ,data ,len) == 0 )
{
ptr->offset=0;
return len;
}
else
{
return 0;
}
}
static ssize_t test_read(struct file *filp, char __user *data, size_t len, loff_t *ppos)
{
test_data * ptr = filp->private_data;
int length =strlen( ptr->info);
if(length<=ptr->offset)
{
return 0;
}
if(ptr->offset+len>length)
{
len = length-ptr->offset;
}
copy_to_user(data ,ptr->info+ptr->offset ,len+1) ;
ptr->offset += len;
return len;
}
static loff_t test_seek(struct file *filp, loff_t pos , int mod)
{
return 0;
}
static int test_open (struct inode *inode, struct file *filp)
{
static int count = 0;
dev_t dev = inode->i_rdev;
filp->f_pos = 16;
test_data * ptr=kmalloc(sizeof(test_data), GFP_KERNEL);
sprintf(ptr->info ,"NO %d called major=0x%x minor=0x%x",count,MAJOR(dev),MINOR(dev));
ptr->offset = 0;
count++;
filp->private_data = ptr;//注意这里的filp每个打开的设备(同一个设备代开多次)都会有一个独立的private_data
return 0;
}
static int test_release (struct inode *inode, struct file *filp)
{
kfree( filp->private_data );
return 0;
}
static long test_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
test_data * ptr = filp->private_data;
char * p = (char*)arg;
char buf[100];
if(copy_from_user(buf ,p ,100) == 0 )
{
}
else
{
printk(KERN_ERR"copy arg error\r\n");
return -1;
}
switch(cmd)
{
case SET1:
sprintf(ptr->info ,"%s cmd=%d",buf,cmd);
break;
case SET2:
sprintf(ptr->info ,"%s cmd=%d",buf,cmd);
break;
case SET3:
sprintf(ptr->info ,"%s cmd=%d",buf,cmd);
break;
}
ptr->offset = 0;
return 0;
}
struct file_operations test_fops =
{
.owner = THIS_MODULE,
.open = test_open,
.release = test_release,
.read = test_read,
.write = test_write,
.llseek = test_seek,
.unlocked_ioctl = test_ioctl,
};
test_DEV_struct *test_dev;
static int __init test_init(void)
{
int err = -ENOMEM;
int ret;
int i;
int major;
test_dev = kmalloc(sizeof( test_DEV_struct), GFP_KERNEL);
if(test_dev == NULL)
return -ENOMEM;
// alloc_chrdev_region\class_create\device_create这三个函数中指定的名字,可以是不一样的,也不影响驱动的执行
ret = alloc_chrdev_region(&(test_dev->test_dev), 0, MAX_DEV, TEST_DEV_NAME);
if( ret<0 )
{
goto fail1 ;
}
major = MAJOR(test_dev->test_dev);
memset(&(test_dev->test_cdev), 0, sizeof(struct cdev));
cdev_init(&(test_dev->test_cdev), &test_fops);
ret = cdev_add(&(test_dev->test_cdev), test_dev->test_dev, MAX_DEV );
if(ret<0)
{
goto fail2;
}
test_dev->test_class = class_create(THIS_MODULE, TEST_DEV_NAME);
for(i=0;i<MAX_DEV;i++)
{
device_create(test_dev->test_class, NULL, MKDEV(major ,i), NULL, "%s%d",TEST_DEV_NAME,i);
}
printk("test driver is successfully loaded\n");
return 0;
fail2:
unregister_chrdev_region(test_dev->test_dev, MAX_DEV);
fail1:
kfree(test_dev);
return err;
}
static void __exit test_exit(void)
{
int i;
//这里要注意注销的顺序,如果先注销了class在调用device_destroy,会造成异常
for(i=0;i<MAX_DEV;i++)
{
device_destroy(test_dev->test_class, test_dev->test_dev+i);
}
cdev_del(&(test_dev->test_cdev));
unregister_chrdev_region(test_dev->test_dev, MAX_DEV);
class_destroy( test_dev->test_class );
kfree(test_dev);
}
module_init(test_init);
module_exit(test_exit);
MODULE_AUTHOR("xxx Inc.");
MODULE_DESCRIPTION("testfor application");
//MODULE_LICENSE是定义模块许可的,这个一定不能漏掉,否则在加载模块的时候可能会出错,造成模块无法加载。
MODULE_LICENSE("GPL");
运行上面个的代码,我们就可以在/dev目录下看到mytest0 、mytest1 、mytest2三个设备节点了,在应用中就可以通过open close read write 对这些设备进行操作了。
可能大家还有一个疑问,对于这个驱动,我们对应了三个设备,那么在open的时候我们怎么知道打开的到底是mytest0 还是mytest1 或者mytest2呢?
这个问题我们可以通过inode->i_rdev来区分了,inode->i_rdev就是设备的设备号,我们创建的三个设备的次设备号分别是0、1、2,所以我们只要读一下次设备号就知道对应的设备了。
struct cdev {
struct kobject kobj; // 每个 cdev 都是一个 kobject
struct module *owner; // 指向实现驱动的模块
const struct file_operations *ops; // 操纵这个字符设备文件的方法
struct list_head list; // 与 cdev 对应的字符设备文件的 inode->i_devices 的链表头
dev_t dev; // 起始设备编号
unsigned int count; // 设备范围号大小
};
内核中每个字符设备都对应一个 cdev 结构的变量。
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 **);
};
下面是常用的一些方法的介绍:
loff_t (*llseek) (struct file * filp , loff_t p, int orig);
指针参数filp为进行读取信息的目标文件结构体指针;参数 p 为文件定位的目标偏移量(根据orig指定的位置的偏移量,这个值可以为负值);参数orig为对文件定位的起始地址,这个值可以为文件开头(SEEK_SET,0,当前位置(SEEK_CUR,1),文件末尾(SEEK_END,2)llseek 方法用作改变文件中的当前读/写位置,
该方法执行成功返回新位置,返回值小于0,表示执行失败。
这个方法对应应用程序中的seek函数。
ssize_t (*read) (struct file * filp, char __user * buffer, size_t size , loff_t * p);
指针参数 filp 为进行读取信息的目标文件,指针参数buffer 为对应放置信息的缓冲区(即用户空间内存地址),参数size为要读取的数据的长度,参数 p 为读的位置相对于文件开头的偏移,在读取信息后,这个指针一般都会移动,移动的值为要读取信息的长度值
该方法执行成功返回实际读取的字节数,返回负值表示执行失败。
该方法对应应用程序中的read函数。
最后需要注意的是buffer是用户空间的地址,所以不能用memcpy进行拷贝,必须要使用copy_to_user来进行内核空间到用户空间的拷贝。示例如下
copy_to_user(buffer ,ptr ,len) ; buffer为用户空间的地址,ptr为内核空间的地址,len为要拷贝的长度。
ssize_t (*write) (struct file * filp, const char __user * buffer, size_t count, loff_t * ppos);
参数filp为目标文件结构体指针,buffer为要写入文件的信息缓冲区,count为要写入信息的长度,ppos为当前的偏移位置,这个值通常是用来判断写文件是否越界
函数执行成功返回实际发送的字节数,失败返回负值。
该方法对应应用程序中的write函数。
同read一样buffer是用户空间的地址,所以不能直接拷贝,需要用copy_from_user来进行拷贝,示例如下:
copy_from_user(ptr , buffer ,len) ; buffer为用户空间的地址,ptr为内核空间的地址,len为要拷贝的长度。
unsigned int (*poll) (struct file *, struct poll_table_struct *);
这是一个设备驱动中的轮询函数,第一个参数为file结构指针,第二个为轮询表指针.
这个函数资源可用时返回设备资源的可获取状态,即POLLIN,POLLOUT,POLLPRI,POLLERR,POLLNVAL等宏的位“或”结果。否则返回0 。该函数详细的用法可以参考《select poll epoll使用示例》里面的介绍。
int (*unlocked_ioctl) ( struct file *filp, unsigned int cmd, unsigned long arg);
注意在2.6.36以后ioctl已经不再存在了,所以这里我们只说明unlocked_ioctl。unlocked_ioctl和ioctl的区别在于ioctl多了一个struct inode *的参数。这个在移植老代码的时候要注意修改,否则编译不过的,unlocked_ioctl和ioctl对应的都是应用程序中的ioctl函数,对于应用,不论是unlocked_ioctl还是ioctl都是不需要修改的。
unlocked_ioctl主要用于对硬件设备的控制。第一个参数为file结构指针, 第二个参数cmd是用户传进来的操作码,驱动根据这个数据来决定执行什么操作。第三个参数arg是参数,这个参数可以是一个整数,或者是一个指针,如果是指针的话,在内核空间要通过 copy_to_user或者 copy_from_user拷贝数据。
下面详细说明下cmd,cmd由4部分组成,设备类型(幻数),方向,序号,数据大小,linux提供了下面的宏进行操作
_IO(type,nr)
_IOR(type,nr,size)
_IOW(type,nr,size)
_IOWR(type,nr,size)
还有下面的宏可以获取一个cmd设备类型(幻数),方向,序号,数据大小
_IOC_DIR(nr)
_IOC_TYPE(nr)
_IOC_NR(nr)
_IOC_SIZE(nr)
注意:对于我们定义的mcd,最好用根据方向、数据大小等用_IO、_IOR、_IOW、_IOWR来定义,而不要直接用一个数值,例如1、2、3这些数据。
直接定义一个常数在早期的linux不会出错,但是在3.0以后,可能会造成某些cmd根本不会执行,但是返回的还是0。例如我曾经移植2.6.2的驱动到3.0下,结果该驱动有一个cmd定义的是2,结果之前没有问题,到3.0 应用调用ioctl返回0,没有报错,但是看串口信息,驱动中的unlocked_ioctl根本没有调用,将这个cmd用_IOR重新定义执行就正常了,
unlocked_ioctl执行成功返回0 ,失败返回负值。
int (*mmap) (struct file *, struct vm_area_struct *);
mmap 用来请求将设备内存映射到进程的地址空间.这个函数对应的是应用程序中的mmap.
函数执行成功返回0,失败返回负值。
下面是示例代码:
static int test_mmap( struct file *filp ,struct vm_area_struct *vma)
{
unsigned long size ;
size = vma->vm_end-vma->vm_start;
//这里的MAX_SIZE 是我们分配的用于映射的内存的大小,是我们在驱动中定义的
//vm_pgoff是页偏移。也就是说如果应用中设置的偏移是4096(linux一页时4096字节) 那么驱动中vm_pgoff就是1,如果应用中是8192 那么驱动对应的就是2。
if( (vma->vm_pgoff<<PAGE_SHIFT)+size > MAX_SIZE )
{
return -EAGAIN;
}
/*标记这段虚拟内存映射为IO区域,并阻止系统将该区域包含在进程的存放转存中*/
vma->vm_flags|=VM_IO;
/*标记这段区域不能被换出*/
vma->vm_flags|=VM_RESERVED;
if(remap_pfn_range(vma,vma->vm_start,(virt_to_phys(ptr_mem)>>PAGE_SHIFT)+vma->vm_pgoff,size,vma->vm_page_prot))
return -EAGAIN;
return 0;
}
另外我们需要注意的是mmap映射的地址是按页对齐的,也就是低12位的地址要为0。我们可以通过alloc_pages来分配内存用于映射。示例如下:
struct page *page;
page = alloc_pages(GFP_KERNEL,1);//alloc_pages的第二个参数是分配的页数,为2的n次方页。
//例如 为0 表示分配1页(4096),为1表示分配两页(8192),为2表示分配4页
if(page==NULL)
{
printk(KERN_ERR"alloc_pages return error\r\n");
ptr_mem=NULL;
}
else
{
ptr_mem= page_address(page);
if(ptr_mem==NULL)
{
printk(KERN_ERR"page_address return error\r\n");
free_pages((unsigned long)page, 1);
}
}
驱动卸载的时候调用
if(ptr_mem)
{
free_pages(ptr_mem, 1);
}
释放分配的页。
在应用中我们调用mmap函数获取映射地址的指针。
mmap声明如下:
void * mmap(void *addr,size_t length,int prot, int flags,int fd,off_t offset);
该函数执行成功返回一个指针,应用可以直接对这个指针进行操作,就可以操作驱动中映射的内存了。
参数说明如下:
void *addr 是程序员所希望的虚拟地址作为起始映射地址,通常为NULL,内核自动分配。
size_t length当然是指需要映射的区域大小。
int flags是指对这段区域的保护方式。具体的可以参看内核源码的linux/mm.h。常用的是PROT_EXEC,PROT_READ,PROT_WRITE,PROT_NONE。
int flags主要是指对这段区域的映射方式,主要分为两种方式MAP_SHARE,MAP_PRIVATE.其中的MAP_SHARE是指对映射区域的写操作会更新到文件中,这样就相当于直接操作文件。而MAP_PRIVATE通常采用一种称为"写时保护的机制"实现映射,对映射区的写操作不会更新到文件中,实现方法是将需要被写操作的页复制到重新分配的新页中,然后再对新的页进行写操作。原来的映射并没有改变,但是读操作并不会重新分配物理内存空间。具体的参考深入理解计算机系统。
int fd是指将被映射的文件描述符,映射需要保证文件描述符的正确性。
off_t offset是指从文件的具体位置开始映射,通常情况下可以设置为0,即从开头映射。这里需要注意的是这个偏移必须要页对齐的,就是说必须是4096的倍数,否则mmap将失败。这个对应的就是驱动中的vm_pgoff。
下面是应用调用的例子:
fd1= open("/dev/filename", O_RDWR );
mmap_ptr = (unsigned char *)mmap(0, 4096, PROT_READ |PROT_WRITE , MAP_SHARED , fd1, 0);
mmap_ptr2 = (unsigned char *)mmap(0, 4096, PROT_READ |PROT_WRITE , MAP_SHARED , fd1, 8192);
。。。。。。。
munmap(mmap_ptr ,4096);
munmap(mmap_ptr2 ,4096);
int (*open) (struct inode * inode , struct file * filp ) ;
inode 为文件节点,这个节点只有一个,无论用户打开多少个文件,都只是对应着一个inode结构;
但是filp就不同,只要打开一个文件,就对应着一个file结构体,file结构体通常用来追踪文件在运行时的状态信息,所以我们通常会在open中分配针对该设备的将结构,然后将这个结构的地址赋值给struct file的private_data,这样在read、write等其他的方法中就可以使用这个分配的结构了。
尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知.
int (*release) (struct inode *, struct file *);
release ()函数当最后一个打开设备的用户进程执行close()系统调用的时候,内核将调用驱动程序release()函数.release函数的主要任务是清理未结束的输入输出操作,释放资源,用户自定义排他标志的复位等。在文件结构被释放时引用这个操作. 如同 open, release 可以为 NULL.
struct file {
/*
* fu_list becomes invalid after file_free is called and queued via
* fu_rcuhead for RCU freeing
*/
union {
struct list_headfu_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;
/*
* Protects f_ep_links, f_flags, f_pos vs i_size in lseek SEEK_CUR.
* Must not be taken from IRQ context.
*/
spinlock_t f_lock;
#ifdef CONFIG_SMP
int f_sb_list_cpu;
#endif
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode;
loff_t f_pos;
struct fown_structf_owner;
const struct cred*f_cred;
struct file_ra_statef_ra;
u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_headf_ep_links;
struct list_headf_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space*f_mapping;
#ifdef CONFIG_DEBUG_WRITECOUNT
unsigned long f_mnt_write_state;
#endif
};
这个结构体代表一个打开的文件,系统中的每个打开的文件在内核空间都有一个关联的struct file。它由内核在打开文件时创建,并传递给在文件上进行操作的任何函数。
下面介绍下我们需要关注的成员变量
unsigned int f_flags; 当打开文件时指定的标志,对应系统调用open的int flags参数。驱动程序为了支持非阻塞型操作需要检查这个标志。
mode_t f_mode; 对文件的读写模式,对应系统调用open的mod_t mode参数。如果驱动程序需要这个值,可以直接读取这个字段。
void *private_data;指向私有数据的指针,用户在open的时候可以创建自己的数据结构,用这个指针保存地址,在对设备操作的时候使用这个结构。
struct inode {
umode_t i_mode;
unsigned shorti_opflags;
uid_t i_uid;
gid_t i_gid;
unsigned int i_flags;
#ifdef CONFIG_FS_POSIX_ACL
struct posix_acl*i_acl;
struct posix_acl*i_default_acl;
#endif
const struct inode_operations*i_op;
struct super_block*i_sb;
struct address_space*i_mapping;
#ifdef CONFIG_SECURITY
void *i_security;
#endif
/* Stat data, not accessed from path walking */
unsigned long i_ino;
/*
* Filesystems may only read i_nlink directly. They shall use the
* following functions for modification:
*
* (set|clear|inc|drop)_nlink
* inode_(inc|dec)_link_count
*/
union {
const unsigned int i_nlink;
unsigned int __i_nlink;
};
dev_t i_rdev;
struct timespeci_atime;
struct timespeci_mtime;
struct timespeci_ctime;
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
unsigned short i_bytes;
blkcnt_t i_blocks;
loff_t i_size;
#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount;
#endif
/* Misc */
unsigned long i_state;
struct mutex i_mutex;
unsigned long dirtied_when; /* jiffies of first dirtying */
struct hlist_nodei_hash;
struct list_headi_wb_list;
/* backing dev IO list */
struct list_headi_lru;
/* inode LRU list */
struct list_headi_sb_list;
union {
struct list_headi_dentry;
struct rcu_headi_rcu;
};
atomic_t i_count;
unsigned int i_blkbits;
u64 i_version;
atomic_t i_dio_count;
atomic_t i_writecount;
const struct file_operations*i_fop;
/* former ->i_op->default_file_ops */
struct file_lock*i_flock;
struct address_spacei_data;
#ifdef CONFIG_QUOTA
struct dquot *i_dquot[MAXQUOTAS];
#endif
struct list_headi_devices;
union {
struct pipe_inode_info*i_pipe;
struct block_device*i_bdev;
struct cdev *i_cdev;
};
__u32 i_generation;
#ifdef CONFIG_FSNOTIFY
__u32 i_fsnotify_mask; /* all events this inode cares about */
struct hlist_headi_fsnotify_marks;
#endif
#ifdef CONFIG_IMA
atomic_t i_readcount; /* struct files open RO */
#endif
void *i_private; /* fs or device private pointer */
};
每一个设备文件对应一个inode ,struct inode 和struct file 的区别在于,例如我有一个设备/dev/mytest,那么这个/dev/mytest就对应一个struct inode,而我每次打开/dev/mytest(可以同时打开多次),都会分配一个struct file。也就是一个设备文件只对应一个struct inode(同时打开多次,也只有一个),而如果这个设备文件被打开多次,那么就对应多个struct file。
下面是我们需要关注的成员:
dev_t i_rdev;该设备文件对应的设备号。
struct cdev *i_cdev;该设备的cdev结构的指针。
2 相关的函数:
int alloc_chrdev_region(dev_t *dev,unsigned int firstminor,unsigned int count,char *name);
该函数的功能是动态分配一个设备号,通过dev带回。
参数说明:dev是用来带回动态分配的设备号
firstminor第一个次设备号,通常设置为0
count 要分配的设备数
name 分配的设备号对应的名字,这个名字我们可以在/proc/devices里面看到。可以用cat /proc/devices查看
返回值: 0执行成功 小于0 失败。
int register_chrdev_region(dev_t first, unsigned int count, char *name);
该函数的功能是注册一个(或者一组)已经定义的设备号。
参数说明:first 是要分配的起始设备编号.first 的次编号部分常常是 0。
count 是请求的连续设备编号的总数
name 注册的设备号对应的名字,这个名字我们可以在/proc/devices里面看到。可以用cat /proc/devices查看
返回值: 0执行成功 小于0 失败。
以上这两个函数功能类似,都是注册一个或者一组设备号,但是不同的是alloc_chrdev_region是动态分配的,系统会自动在没有分配的主设备号中找一个可用的来分配。而register_chrdev_region是用户自己定义一个主设备号,来注册的。调用这个函数我们必须要确保我们定义的设备号没有被使用,否则这个函数会失败。为了避免冲突和便于移植到其他平台,建议最好使用alloc_chrdev_region动态分配一个设备号。
void unregister_chrdev_region(dev_t first, unsigned int count);
该函数的功能是释放注册的设备号
参数说明:first为第一个设备号 。
count为申请释放的设备数量
设备号操作的相关的宏:
MKDEV(ma,mi) 该宏的功能是根据主设备号和次设备号得到一个设备号。ma为主设备号 ,mi为次设备号
MAJOR(dev) 该宏的功能是获取主设备号 。dev为设备号
MINOR(dev) 该宏的功能为获取次设备号。参数dev是设备号
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
函数的功能是初始化一个cdev的结构。
参数说明:struct cdev *cdev 已经定义的cdev结构的指针 ,
const struct file_operations *fops 已经定义的file_operations 的结构的指针,该结构定义了设备文件的一系列操作。
示例代码如下:
struct cdev my_cdev;
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;
struct cdev *cdev_alloc(void);
函数的功能和cdev_init类似,但是这个函数是动态分配的一个struct cdev的结构。
返回值:分配成功返回struct cdev的结构的地址失败返回NULL。
示例代码如下:
struct cdev *my_cdev = cdev_alloc();
if(my_cdev)
{
my_cdev->ops = &fops;
my_cdev->owner = THIS_MODULE;
}
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
函数的功能是将字符设备驱动程序注册到系统中。
参数说明:struct cdev *p; 将要加入系统的cdev ,p是之前调用cdev_init或者cdev_alloc初始化好的。
dev_t dev;已经注册的设备号。这个是通过alloc_chrdev_region或者register_chrdev_region注册的设备号。
unsinged int count;要注册的设备的数目。
返回值:执行成功返回0,失败返回负值
void cdev_del(struct cdev *p);
函数功能是释放之前注册的cdev。
函数参数:struct cdev *p,之前用cdev_add注册的cdev的指针。
struct class *class_create(struct module *owner, const char *name);
函数功能是分配一个struct class的结构。
参数说明:struct module *owner 指向模块owner的指针,通常设置为THIS_MODULE
const char *name class的名字,这个名字可以在/sys/class/看到,系统将在/sys/class/下创建参数name指定的目录。
返回值:执行成功返回struct class的地址,失败返回NULL。
说明:实际上class_create不是一个函数而是一个宏,
#define class_create(owner, name) \
({ \
static struct lock_class_key __key;\
__class_create(owner, name, &__key);\
})
不过对于我们在实际调用中,可以不需要关注的。
void class_destroy(struct class *cls);
函数功能是注销一个class。
参数 cls就是我们之前用class_create分配的一个struct class的指针。
这个函数和class_create成对使用的。
int class_register(struct class *cls);
函数功能和class_create类似,也是注册一个class,实际上class_create内部也会调用__class_register。不同的是这个函数的参数是已经分配好的结构。
参数cls是struct class的指针
返回值执行成功返回0,失败返回负值。
实际上这个也是一个宏
#define class_register(class) \
({ \
static struct lock_class_key __key;\
__class_register(class, &__key);\
} )
下面是class_register的示例代码:
struct class my_class;
my_class->name = "myclass";
my_class->owner = THIS_MODULE;
my_class->class_release = class_create_release;
// 将class注册到内核中,同时会在/sys/class/下创建class对应的节点
int retval = class_register(my_class);
void class_unregister(struct class *cls);
注销一个class,这个函数和class_register成对使用。
struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void * drvdata, const char *fmt, ...);
函数功能是用于动态的建立逻辑设备,并对新的逻辑设备类进行相应初始化,将其与函数的第一个参数所代表的逻辑类关联起来,然后将此逻辑设备加到linux内核系统的设备驱动程序模型中。函数能够自动在/sys/devices/virtual目录下创建新的逻辑设备目录,在/dev目录下创建于逻辑类对应的设备文件。
参数说明:cls struct class 指针,必须在本函数调用之前先被调用class_register或者class_create注册的。
parent 该设备的父设备的指针,如果没有就设置为NULL
devt 该设备的设备号
drvdata 传递给该设备的私有数据的指针,如果没有就直接用NULL
fmt 设备名称,就是在dev目录下显示的设备名
void device_destroy(struct class *cls, dev_t devt);
函数功能:用于从linux内核系统设备驱动程序模型中移除一个设备,并删除/sys/devices/virtual目录下对应的设备目录及/dev/目录下对应的设备文件
参数说明:cls struct class 指针
devt 设备号
3 下面是示例代码:
在头文件中定义
#define SET1 _IOR('O', 1, int)
#define SET2 _IOR('O', 2, int)
#define SET3 _IOR('O', 3, int)
在C代码中
#define TEST_DEV_NAME "mytest"
#define MAX_DEV 3
typedef struct
{
dev_t test_dev;
struct cdev test_cdev;
struct class *test_class;
}test_DEV_struct;
typedef struct
{
char info[100];
int offset;
}test_data;
static ssize_t test_write(struct file *filp, const char __user *data,size_t len, loff_t *ppos)
{
test_data * ptr = filp->private_data;
if(copy_from_user(ptr->info ,data ,len) == 0 )
{
ptr->offset=0;
return len;
}
else
{
return 0;
}
}
static ssize_t test_read(struct file *filp, char __user *data, size_t len, loff_t *ppos)
{
test_data * ptr = filp->private_data;
int length =strlen( ptr->info);
if(length<=ptr->offset)
{
return 0;
}
if(ptr->offset+len>length)
{
len = length-ptr->offset;
}
copy_to_user(data ,ptr->info+ptr->offset ,len+1) ;
ptr->offset += len;
return len;
}
static loff_t test_seek(struct file *filp, loff_t pos , int mod)
{
return 0;
}
static int test_open (struct inode *inode, struct file *filp)
{
static int count = 0;
dev_t dev = inode->i_rdev;
filp->f_pos = 16;
test_data * ptr=kmalloc(sizeof(test_data), GFP_KERNEL);
sprintf(ptr->info ,"NO %d called major=0x%x minor=0x%x",count,MAJOR(dev),MINOR(dev));
ptr->offset = 0;
count++;
filp->private_data = ptr;//注意这里的filp每个打开的设备(同一个设备代开多次)都会有一个独立的private_data
return 0;
}
static int test_release (struct inode *inode, struct file *filp)
{
kfree( filp->private_data );
return 0;
}
static long test_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
test_data * ptr = filp->private_data;
char * p = (char*)arg;
char buf[100];
if(copy_from_user(buf ,p ,100) == 0 )
{
}
else
{
printk(KERN_ERR"copy arg error\r\n");
return -1;
}
switch(cmd)
{
case SET1:
sprintf(ptr->info ,"%s cmd=%d",buf,cmd);
break;
case SET2:
sprintf(ptr->info ,"%s cmd=%d",buf,cmd);
break;
case SET3:
sprintf(ptr->info ,"%s cmd=%d",buf,cmd);
break;
}
ptr->offset = 0;
return 0;
}
struct file_operations test_fops =
{
.owner = THIS_MODULE,
.open = test_open,
.release = test_release,
.read = test_read,
.write = test_write,
.llseek = test_seek,
.unlocked_ioctl = test_ioctl,
};
test_DEV_struct *test_dev;
static int __init test_init(void)
{
int err = -ENOMEM;
int ret;
int i;
int major;
test_dev = kmalloc(sizeof( test_DEV_struct), GFP_KERNEL);
if(test_dev == NULL)
return -ENOMEM;
// alloc_chrdev_region\class_create\device_create这三个函数中指定的名字,可以是不一样的,也不影响驱动的执行
ret = alloc_chrdev_region(&(test_dev->test_dev), 0, MAX_DEV, TEST_DEV_NAME);
if( ret<0 )
{
goto fail1 ;
}
major = MAJOR(test_dev->test_dev);
memset(&(test_dev->test_cdev), 0, sizeof(struct cdev));
cdev_init(&(test_dev->test_cdev), &test_fops);
ret = cdev_add(&(test_dev->test_cdev), test_dev->test_dev, MAX_DEV );
if(ret<0)
{
goto fail2;
}
test_dev->test_class = class_create(THIS_MODULE, TEST_DEV_NAME);
for(i=0;i<MAX_DEV;i++)
{
device_create(test_dev->test_class, NULL, MKDEV(major ,i), NULL, "%s%d",TEST_DEV_NAME,i);
}
printk("test driver is successfully loaded\n");
return 0;
fail2:
unregister_chrdev_region(test_dev->test_dev, MAX_DEV);
fail1:
kfree(test_dev);
return err;
}
static void __exit test_exit(void)
{
int i;
//这里要注意注销的顺序,如果先注销了class在调用device_destroy,会造成异常
for(i=0;i<MAX_DEV;i++)
{
device_destroy(test_dev->test_class, test_dev->test_dev+i);
}
cdev_del(&(test_dev->test_cdev));
unregister_chrdev_region(test_dev->test_dev, MAX_DEV);
class_destroy( test_dev->test_class );
kfree(test_dev);
}
module_init(test_init);
module_exit(test_exit);
MODULE_AUTHOR("xxx Inc.");
MODULE_DESCRIPTION("testfor application");
//MODULE_LICENSE是定义模块许可的,这个一定不能漏掉,否则在加载模块的时候可能会出错,造成模块无法加载。
MODULE_LICENSE("GPL");
运行上面个的代码,我们就可以在/dev目录下看到mytest0 、mytest1 、mytest2三个设备节点了,在应用中就可以通过open close read write 对这些设备进行操作了。
可能大家还有一个疑问,对于这个驱动,我们对应了三个设备,那么在open的时候我们怎么知道打开的到底是mytest0 还是mytest1 或者mytest2呢?
这个问题我们可以通过inode->i_rdev来区分了,inode->i_rdev就是设备的设备号,我们创建的三个设备的次设备号分别是0、1、2,所以我们只要读一下次设备号就知道对应的设备了。
更多推荐
已为社区贡献2条内容
所有评论(0)