下面开始学习Linux字符设备驱动,也是linux驱动中最简单的驱动模块。
在内存中虚拟出一段空间作为字符设备,并为之编写些列的驱动程序。
字符设备驱动cdev中用到的两个重要的结构体如下,现补充下基本知识
一、cdev
- /*
- *内核源码位置
- *linux2.6.38/include/linux/cdev.h
- */
- struct cdev {
- struct kobject kobj;
- struct module *owner; //一般初始化为:THIS_MODULE
- const struct file_operations *ops; //字符设备用到的例外一个重要的结构体file_operations,cdev初始化时与之绑定
- struct list_head list;
- dev_t dev; //主设备号24位 与次设备号8位,dev_t为32位整形
- unsigned int count;
- };
/*
*内核源码位置
*linux2.6.38/include/linux/cdev.h
*/
struct cdev {
struct kobject kobj;
struct module *owner; //一般初始化为:THIS_MODULE
const struct file_operations *ops; //字符设备用到的例外一个重要的结构体file_operations,cdev初始化时与之绑定
struct list_head list;
dev_t dev; //主设备号24位 与次设备号8位,dev_t为32位整形
unsigned int count;
};
二、file_operations
熟悉C语言文件编程的应该知道 read write等函数,这些函数都在file_operations中声明,在read等函数中实现与硬件相关的操作,这样就让具体的硬件设备与操作系统联系在了一起
- /*
- ~/include/linux/fs.h
- */
- 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 *);
- 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 *, 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 **);
- long (*fallocate)(struct file *file, int mode, loff_t offset,
- loff_t len);
- };
/*
~/include/linux/fs.h
*/
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 *);
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 *, 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 **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
};
光有这cdev与file_operations定义的结构体变量是不行的,显然要让他们做一些初始化工作,然后通过某个函数,让我们定义的这两个结构体变量与内核联系在一起,所以,调用内核的下列函数
文件名:char_dev.c
- void cdev_init(struct cdev *cdev, const struct file_operations *fops)
- {
- memset(cdev, 0, sizeof *cdev);
- INIT_LIST_HEAD(&cdev->list);
- kobject_init(&cdev->kobj, &ktype_cdev_default);
- cdev->ops = fops;
- }
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj, &ktype_cdev_default);
cdev->ops = fops;
}
为cdev开辟内存空间,然后,将file_operations定义的变量fops赋值给cdev中的ops成员变量,这样,他们就紧密的连在一起了
以上才仅仅将cdev初始化,还未将其真正的添加到系统内核中,因此调用下列函数:
文件名:char_dev.c
- int cdev_add(struct cdev *p, dev_t dev, unsigned count)
- {
- p->dev = dev;
- p->count = count;
- return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
- }
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
p->dev = dev;
p->count = count;
return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}
有了以上的字符设备基础时候后,在开始看一下字符设备的基本结构,其实就是在hello word的基础之上添加了设备读、写、控制的函数。
头文件:一般包含下面几个
- #include<linux/cdev.h>
- #include<linux/module.h>
- #include<linux/types.h>
- #include<linux/fs.h>
- #include<linux/errno.h>
- #include<linux/mm.h>
- #include<linux/sched.h>
- #include<linux/init.h>
- #include<asm/io.h>
- #include<asm/system.h>
- #include<asm/uaccess.h>
#include<linux/cdev.h>
定义的cdev结构体与设备空间数据
- /*习惯上将内部数据空间与cdev 绑定,与其封装*/
- struct mychar_dev{
- struct cdev cdev;
- unsigned char mem[MYCHAR_MEM_SIZE];
- };
- /*一个实例*/
- struct mychar_dev* mychar_devp;
/*习惯上将内部数据空间与cdev 绑定,与其封装*/
struct mychar_dev{
struct cdev cdev;
unsigned char mem[MYCHAR_MEM_SIZE];
};
/*一个实例*/
struct mychar_dev* mychar_devp;
然后是读、写、ioctl函数的实现
- /*实现file_operations结构体体的open函数*/
- int mychar_open(struct inode *inode,struct file * filp)
/*实现file_operations结构体体的open函数*/
int mychar_open(struct inode *inode,struct file * filp)
int mychar_release(struct inode *inode,struct file* filp);
/*read*/
ssize_t mychar_read(struct file *filp,char __user *buf,size_t size ,loff_t *ppos );
/*write*/
ssize_t mychar_write(struct file *filp ,const char __user *buf,size_t size,loff_t *ppos);
/*llseek*/
static loff_t mychar_llseek(struct file *filp,loff_t offset,int orig);
/*ioctl*/
int mychar_ioctl(struct inode * inodep ,struct file *filp ,unsigned int cmd ,unsigned long arg);
以上函数实现后将里赋值到file_operations中相应的函数成员变量
- static const struct file_operations mychar_fops = {
- .owner = THIS_MODULE,
- .llseek = mychar_llseek,
- .read = mychar_read,
- .write = mychar_write,
- .ioctl = mychar_ioctl,
- .open = mychar_open,
- .release =mychar_release,
- };
static const struct file_operations mychar_fops = {
.owner = THIS_MODULE,
.llseek = mychar_llseek,
.read = mychar_read,
.write = mychar_write,
.ioctl = mychar_ioctl,
.open = mychar_open,
.release =mychar_release,
};
最后是init 与exit函数
/*init*/
static int __init mychar_init(void);
/*exit*/
static void __exit mychar_exit(void);
最后是
- MODULE_LICENSE(“Dual BSD/GPL”);
- MODULE_AUTHOR(”ghostyu”);
- module_param(mychar_major,int,S_IRUGO);
- module_init(mychar_init);
- module_exit(mychar_exit);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("ghostyu");
module_param(mychar_major,int,S_IRUGO);
module_init(mychar_init);
module_exit(mychar_exit);
下面看一下完整的源码:
- /*在内存中申请1k 大小的内存做为简单的一个设备来访问*/
- /*一般包含的头文件*/
- #include<linux/cdev.h>
- #include<linux/module.h>
- #include<linux/types.h>
- #include<linux/fs.h>
- #include<linux/errno.h>
- #include<linux/mm.h>
- #include<linux/sched.h>
- #include<linux/init.h>
- #include<asm/io.h>
- #include<asm/system.h>
- #include<asm/uaccess.h>
- /*设备空间*/
- #define MYCHAR_MEM_SIZE 0x0400
- /*主设备号*/
- #define MYCHAR_MAJOR 260
- /*自定义的清除内存的命令*/
- #define MYCHAR_MEN_CLR 0x01
- /*主设备号变量*/
- static int mychar_major = MYCHAR_MAJOR;
- /*习惯上将内部数据空间与cdev 绑定,与其封装*/
- struct mychar_dev{
- struct cdev cdev;
- unsigned char mem[MYCHAR_MEM_SIZE];
- };
- /*一个实例*/
- struct mychar_dev* mychar_devp;
- /*实现file_operations结构体体的open函数*/
- int mychar_open(struct inode *inode,struct file * filp)
- {
- filp->private_data = mychar_devp;
- return 0;
- }
- /*同上*/
- int mychar_release(struct inode *inode,struct file* filp)
- {
- return 0;
- }
- /*read*/
- ssize_t mychar_read(struct file *filp,char __user *buf,size_t size ,loff_t *ppos )
- {
- unsigned long p=*ppos;
- unsigned int count = size;
- int ret = 0;
- struct mychar_dev *dev = filp->private_data;
- if(p>MYCHAR_MEM_SIZE)
- return 0;
- if(count > MYCHAR_MEM_SIZE-p)
- count = MYCHAR_MEM_SIZE-p;
- if( copy_to_user(buf,(void*)(dev->mem+p),count)){
- ret= -EFAULT;
- }else{
- *ppos +=count;
- ret = count;
- printk(KERN_INFO ”read %u bytes(s) from %1u\n”,count,p);
- }
- return ret;
- }
- /*write*/
- ssize_t mychar_write(struct file *filp ,const char __user *buf,size_t size,loff_t *ppos)
- {
- unsigned long p=*ppos;
- unsigned int count=size;
- int ret = 0;
- struct mychar_dev *dev = filp->private_data;
- if(p > MYCHAR_MEM_SIZE)
- return 0;
- if(count > MYCHAR_MEM_SIZE-p)
- count = MYCHAR_MEM_SIZE-p;
- if(copy_from_user((void*)(dev->mem),buf,count)){
- ret = -EFAULT;
- }else{
- *ppos +=count;
- ret = count;
- printk(KERN_INFO ”written %u byte(s) from %1u\n”,count,p);
- }
- return ret;
- }
- /*llseek*/
- static loff_t mychar_llseek(struct file *filp,loff_t offset,int orig)
- {
- loff_t ret = 0;
- switch(orig){
- case 0: /*相对于文件开始位置偏移*/
- if(offset < 0)
- ret = -EINVAL;
- break;
- if((unsigned int)offset > MYCHAR_MEM_SIZE){
- ret = -EINVAL;
- break;
- }
- filp->f_pos =(unsigned int )offset;
- ret = filp->f_pos;
- break;
- case 1: /*相对于文件当前位置*/
- if((filp->f_pos+offset)>MYCHAR_MEM_SIZE){
- ret = -EINVAL;
- break;
- }
- if((filp->f_pos+offset)< 0){
- ret = -EINVAL;
- break;
- }
- filp->f_pos +=offset;
- ret = filp->f_pos;
- break;
- default:
- ret = - EINVAL;
- break;
- }
- return ret;
- }
- /*ioctl*/
- int mychar_ioctl(struct inode * inodep ,struct file *filp ,unsigned int cmd ,unsigned long arg)
- {
- struct mychar_dev *dev =filp->private_data;
- switch(cmd){
- case MYCHAR_MEM_CLR:
- memset(dev->mem,0,MYCHAR_MEM_SIZE);
- printk(KERN_INFO ”mychar memery is set to zero\n”);
- break;
- default:
- return -EINVAL;
- }
- return 0;
- }
- static const struct file_operations mychar_fops = {
- .owner = THIS_MODULE,
- .llseek = mychar_llseek,
- .read = mychar_read,
- .write = mychar_write,
- .ioctl = mychar_ioctl,
- .open = mychar_open,
- .release =mychar_release,
- };
- /*cdev结构初始化*/
- static void mychar_setup_cdev(struct mychar_dev *dev,int index)
- {
- int err;
- int devno = MKDEV(mychar_major,index);
- cdev_init(&dev->cdev,&mychar_fops);
- dev->cdev.owner = THIS_MODULE;
- err = cdev_add(&dev->cdev,devno,1);
- if(err)
- printk(KERN_NOTICE ” Error %d adding mychar %d”,err,index);
- }
- /*init*/
- static int __init mychar_init(void)
- {
- int result;
- dev_t devno = MKDEV(mychar_major,0);
- if(mychar_major)
- result = register_chrdev_region(devno,1,”mychar”);
- else{
- result = alloc_chrdev_region(&devno,0,1,”mychar”);
- mychar_major = MAJOR(devno);
- }
- if(result<0)
- return result;
- mychar_devp = kmalloc(sizeof(struct mychar_dev),GFP_KERNEL);
- if(!mychar_devp){
- result = -ENOMEM;
- goto fall_malloc;
- }
- memset(mychar_devp,0,sizeof(struct mychar_dev));
- mychar_setup_cdev(mychar_devp,0);
- return 0;
- fall_malloc:
- unregister_chrdev_region(devno,1);
- return result;
- }
- /*exit*/
- static void __exit mychar_exit(void)
- {
- cdev_del(&mychar_devp->cdev);
- kfree(mychar_devp);
- unregister_chrdev_region(MKDEV(mychar_major,0),1);
- }
- MODULE_LICENSE(”Dual BSD/GPL”);
- MODULE_AUTHOR(”ghostyu”);
- module_param(mychar_major,int,S_IRUGO);
- module_init(mychar_init);
- module_exit(mychar_exit);
/*在内存中申请1k 大小的内存做为简单的一个设备来访问*/
/*一般包含的头文件*/
#include<linux/cdev.h> #include<linux/module.h> #include<linux/types.h> #include<linux/fs.h> #include<linux/errno.h> #include<linux/mm.h> #include<linux/sched.h> #include<linux/init.h> #include<asm/io.h> #include<asm/system.h> #include<asm/uaccess.h> /*设备空间*/ #define MYCHAR_MEM_SIZE 0x0400 /*主设备号*/ #define MYCHAR_MAJOR 260 /*自定义的清除内存的命令*/ #define MYCHAR_MEN_CLR 0x01 /*主设备号变量*/ static int mychar_major = MYCHAR_MAJOR; /*习惯上将内部数据空间与cdev 绑定,与其封装*/ struct mychar_dev{ struct cdev cdev; unsigned char mem[MYCHAR_MEM_SIZE]; }; /*一个实例*/ struct mychar_dev* mychar_devp; /*实现file_operations结构体体的open函数*/ int mychar_open(struct inode *inode,struct file * filp) { filp->private_data = mychar_devp; return 0; } /*同上*/ int mychar_release(struct inode *inode,struct file* filp) { return 0; } /*read*/ ssize_t mychar_read(struct file *filp,char __user *buf,size_t size ,loff_t *ppos ) { unsigned long p=*ppos; unsigned int count = size; int ret = 0; struct mychar_dev *dev = filp->private_data; if(p>MYCHAR_MEM_SIZE) return 0; if(count > MYCHAR_MEM_SIZE-p) count = MYCHAR_MEM_SIZE-p; if( copy_to_user(buf,(void*)(dev->mem+p),count)){ ret= -EFAULT; }else{ *ppos +=count; ret = count; printk(KERN_INFO "read %u bytes(s) from %1u\n",count,p); } return ret; } /*write*/ ssize_t mychar_write(struct file *filp ,const char __user *buf,size_t size,loff_t *ppos) { unsigned long p=*ppos; unsigned int count=size; int ret = 0; struct mychar_dev *dev = filp->private_data; if(p > MYCHAR_MEM_SIZE) return 0; if(count > MYCHAR_MEM_SIZE-p) count = MYCHAR_MEM_SIZE-p; if(copy_from_user((void*)(dev->mem),buf,count)){ ret = -EFAULT; }else{ *ppos +=count; ret = count; printk(KERN_INFO "written %u byte(s) from %1u\n",count,p); } return ret; } /*llseek*/ static loff_t mychar_llseek(struct file *filp,loff_t offset,int orig) { loff_t ret = 0; switch(orig){ case 0: /*相对于文件开始位置偏移*/ if(offset < 0) ret = -EINVAL; break; if((unsigned int)offset > MYCHAR_MEM_SIZE){ ret = -EINVAL; break; } filp->f_pos =(unsigned int )offset; ret = filp->f_pos; break; case 1: /*相对于文件当前位置*/ if((filp->f_pos+offset)>MYCHAR_MEM_SIZE){ ret = -EINVAL; break; } if((filp->f_pos+offset)< 0){ ret = -EINVAL; break; } filp->f_pos +=offset; ret = filp->f_pos; break; default: ret = - EINVAL; break; } return ret; } /*ioctl*/ int mychar_ioctl(struct inode * inodep ,struct file *filp ,unsigned int cmd ,unsigned long arg) { struct mychar_dev *dev =filp->private_data; switch(cmd){ case MYCHAR_MEM_CLR: memset(dev->mem,0,MYCHAR_MEM_SIZE); printk(KERN_INFO "mychar memery is set to zero\n"); break; default: return -EINVAL; } return 0; } static const struct file_operations mychar_fops = { .owner = THIS_MODULE, .llseek = mychar_llseek, .read = mychar_read, .write = mychar_write, .ioctl = mychar_ioctl, .open = mychar_open, .release =mychar_release, }; /*cdev结构初始化*/ static void mychar_setup_cdev(struct mychar_dev *dev,int index) { int err; int devno = MKDEV(mychar_major,index); cdev_init(&dev->cdev,&mychar_fops); dev->cdev.owner = THIS_MODULE; err = cdev_add(&dev->cdev,devno,1); if(err) printk(KERN_NOTICE " Error %d adding mychar %d",err,index); } /*init*/ static int __init mychar_init(void) { int result; dev_t devno = MKDEV(mychar_major,0); if(mychar_major) result = register_chrdev_region(devno,1,"mychar"); else{ result = alloc_chrdev_region(&devno,0,1,"mychar"); mychar_major = MAJOR(devno); } if(result<0) return result; mychar_devp = kmalloc(sizeof(struct mychar_dev),GFP_KERNEL); if(!mychar_devp){ result = -ENOMEM; goto fall_malloc; } memset(mychar_devp,0,sizeof(struct mychar_dev)); mychar_setup_cdev(mychar_devp,0); return 0; fall_malloc: unregister_chrdev_region(devno,1); return result; } /*exit*/ static void __exit mychar_exit(void) { cdev_del(&mychar_devp->cdev); kfree(mychar_devp); unregister_chrdev_region(MKDEV(mychar_major,0),1); } MODULE_LICENSE("Dual BSD/GPL"); MODULE_AUTHOR("ghostyu"); module_param(mychar_major,int,S_IRUGO); module_init(mychar_init); module_exit(mychar_exit);
只要事先先建立一个linux内核源码的工程,在在这个工程中添加自己的linux驱动程序源码。
这样就实现了一个IDE,函数、变量、宏定义不全功能,语法着色等等非常方便,加快开发速度。