[必要的头文件]
/* * Asimple character driver for learn */ #include <linux/module.h> #include <linux/types.h> #include <linux/fs.h> #include <linux/errno.h> #include <linux/mm.h> #include <linux/init.h> #include <linux/sched.h> #include <linux/cdev.h> #include <asm/io.h> #include <asm/system.h> #include <asm/uaccess.h> #include <linux/slab.h> /* kmalloc */ #include <linux/device.h> /* device_create, class_create */
[宏定义]
#define MAX_SIZE 1024 #define SIMPLE_MAJOR 256 #define CLASS_NAME "simple_class" #define CHRDEV_NAME"simple_chrdev"
[定义simple_chrdev结构体来表示一个简单的字符设备]
struct simple_chrdev { structcdev cdev; charmem[MAX_SIZE]; };
struct simple_chrdev *dev; static unsigned int major_no = 0; static struct class *simple_class;
[open方法]
static int simple_open(struct inode *inode,struct file *filp) { structsimple_chrdev *dev; dev= container_of(inode->i_cdev, struct simple_chrdev, cdev); filp->private_data= dev; return0; }
填写filp->private_data里的数据结构,后面的read,write等方法可能会用到
container_of(pointer, containter_type, container_field):inode参数在其i_cdev字段中包含了我们所需要的信息,即我们先前设置的cdev。然而我们不需要cdev结构体本身,而是希望得到包含cdev结构体的simple_chrdev结构体。
[release 方法]
static int simple_release(struct inode*inode, struct file *filp) { return0; }
[read 方法]
static ssize_t simple_read(struct file*filp, char __user *buf, size_t count, loff_t*f_pos) { structsimple_chrdev *dev = filp->private_data; unsignedlong pos = *f_pos; intret = 0; if(pos >= MAX_SIZE) return- EFAULT; if(count > MAX_SIZE - pos) count= MAX_SIZE - pos; if(copy_to_user(buf, (void *)(dev->mem + pos), count)) { ret= -EFAULT; gotoout; } *f_pos+= count; ret= count; out: returnret; }
[write 方法]
static ssize_t simple_write(struct file*filp, const char __user *buf, size_tcount, loff_t *f_pos) { structsimple_chrdev *dev = filp->private_data; unsignedlong pos = *f_pos; intret = 0; if(pos >= MAX_SIZE) return- EFAULT; if(count > MAX_SIZE - pos) count= MAX_SIZE - pos; if(copy_from_user(dev->mem + pos, buf, count)) { ret= - EFAULT; gotoout; } *f_pos+= count; ret= count; out: returnret; }
read和write代码要做的工作就是在用户地址空间和内核地址空间之间进行整段数据的拷贝。注意:buf参数是用户空间的指针,内核代码不能直接引用其中的内容。
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)
返回值:复制成功时,返回0;失败时,返回未复制的字节数。
这两个函数并不限于在内核空间和用户空间之间拷贝数据,它们还会检查用户空间的指针是否有效。
[llseek 方法]
loff_t simple_llseek(struct file *filp,loff_t off, int whence) { loff_tnewpos; switch(whence) { case0: /*SEEK_SET*/ newpos= off; break; case1: /*SEEK_CUR*/ newpos= filp->f_pos + off; break; case2: /*SEEK_END*/ newpos= MAX_SIZE + off; break; default: return-EINVAL; } if(newpos < 0) return-EINVAL; filp->f_pos= newpos; returnnewpos; }
struct file_operations simple_fops = { .owner = THIS_MODULE, .llseek = simple_llseek, .read = simple_read, .write = simple_write, .open = simple_open, .release = simple_release, };
[模块初始化函数]
static int __init simple_chrdev_init(void) { intminor_no = 0; /* 次设备号 */ dev_tdev_no; /* 设备号 */ intret; if(major_no) { dev_no= MKDEV(major_no, minor_no); ret= register_chrdev_region(dev_no, 1, CLASS_NAME); }else { ret= alloc_chrdev_region(&dev_no, minor_no, 1, CHRDEV_NAME); major_no= MAJOR(dev_no); } if(ret) { printk("cannotget major %d\n", major_no); returnret; } printk("getmajor %d\n", major_no); dev= kmalloc(sizeof(struct simple_chrdev), GFP_KERNEL); if(dev == NULL) { ret= - ENOMEM; gotoerr_malloc; } memset(dev,0, sizeof(struct simple_chrdev)); cdev_init(&dev->cdev,&simple_fops); dev->cdev.owner= THIS_MODULE; dev->cdev.ops= &simple_fops; ret= cdev_add(&dev->cdev, dev_no, 1); if(ret) { printk("cannotcdev add\n"); gotoerr_cdev_add; } /*autocreate device inode file*/ simple_class= class_create(THIS_MODULE, CHRDEV_NAME); if(IS_ERR(simple_class)) { printk("ERR:cannot create a simple_class"); gotoerr_class_crt; } device_create(simple_class,NULL, MKDEV(major_no, 0), 0, CHRDEV_NAME); printk("Iam in\n"); return 0; /* 这个return 0;不能少 */ err_class_crt: cdev_del(&dev->cdev); err_cdev_add: kfree(dev); err_malloc: unregister_chrdev_region(MKDEV(major_no,0), 1); returnret; }
模块加载函数所做的工作:
1、申请主设备号;
静态分配设备号 int register_chrdev_region(dev_t dev_no, unsigned int count, char *dev_name);
动态分配设备号 int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *dev_name);
2、注册字符设备;
初始化字符设备 cdev_init();
注册字符设备 cdev_add();
3、创建设备节点文件
当使用udev制作的文件系统时,内核为我们提供了一组函数,可以用来在模块加载的时候自动在/dev目录下创建设备文件OR在卸载时自动删除设备文件。
主要涉及两个函数
class_create(owner, class_name)
用来在sysfs创建一个类,设备信息导出在这下面;
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
用来在/dev目录下创建一个设备文件;
加载模块时,用户空间的udev会自动响应device_create()函数,在sysfs下寻找对应的类从而创建设备节点。
[模块卸载函数]
static void __exit simple_chrdev_exit(void) { device_destroy(simple_class,MKDEV(major_no, 0)); class_destroy(simple_class); cdev_del(&dev->cdev); kfree(dev); unregister_chrdev_region(MKDEV(major_no,0), 1); printk("Iam exit\n"); }
module_init(simple_chrdev_init); module_exit(simple_chrdev_exit); MODULE_AUTHOR("CJOK<[email protected]>"); MODULE_LICENSE("Dual BSD/GPL"); MODULE_DESCRIPTION("A simple characterdriver for learn");
if you have any questions, please contact me<[email protected]> or leave a comment, we will exchange views, it's good for us, so great!