一个简单的字符驱动程序
最近在学习Linux下设备驱动程序,从头开始吧!慢慢总结,先入手,后入门,再提高!
下面是一个简单的字符设备驱动程序,主要是一个模拟设备,使用了系统的内存,相信大家都能看懂,毕竟简单,而且注释清楚,编译测试的方法大家都知道吧,这里要说的就是习惯的测试方法是通过echo向设备文件写入内容,然后通过cat显示内容,但是希望大家看到,该设备只有4字节的内存,测试的时候建议大家可以试一试写入的内容小于4字节和大于4字节,大家自己看看出现什么事,呵呵:
#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 <linux/cdev.h> #include <asm/io.h> #include <asm/system.h> #include <asm/uaccess.h> #define MEMSIZE 4 #define MAJORNUM 2538 static int major = MAJORNUM; /******定义自己的字符设备******/ struct char_mem_t { struct cdev memdev; int device[MEMSIZE]; }char_mem; static ssize_t char_mem_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { /*******取得文件当前偏移量********/ unsigned long p = *ppos; int ret = 0; /*****分析当前要读取操作的读取位置和读取量是否满足要求*****/ if(p > MEMSIZE) return 0; if(count > MEMSIZE - p) count = MEMSIZE - p; /*******读取操作,调用copy_to_user()函数*********/ if(copy_to_user(buf, (void*)(char_mem.device+p), count)) ret = -EFAULT; else //读取成功则需要调整文件指针位置 { *ppos += count; ret = count; printk(KERN_INFO "read %d bytes from %d", count, p); } return ret; } static ssize_t char_mem_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) { /*******取得文件当前偏移量********/ unsigned long p = *ppos; int ret = 0; /*****分析当前要写入操作的写入位置和写入量是否满足要求*****/ if(p >= MEMSIZE) return 0; if(count > MEMSIZE - p) count = MEMSIZE - p; /*******写入操作,调用copy_from_user()函数*********/ if(copy_from_user((void*)(char_mem.device+p), buf, count)) ret = -EFAULT; else //写入成功则需要调整文件指针位置 { *ppos += count; ret = count; printk(KERN_INFO "write %d bytes to %d", count, p); } return ret; } static loff_t char_mem_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 > MEMSIZE) { ret = -EINVAL; break; } filp->f_pos = (unsigned int) offset; ret = filp->f_pos; break; /*******从当前位置偏移*************/ case 1: if((filp->f_pos + offset) > MEMSIZE) { ret = -EINVAL; break; } if((filp->f_pos + offset) < 0) { ret = -EINVAL; break; } filp->f_pos += offset; ret = filp->f_pos; break; default: ret = -EINVAL; } return ret; } static const struct file_operations char_mem_ops = { .owner = THIS_MODULE, .llseek = char_mem_llseek, .read = char_mem_read, .write = char_mem_write, // .ioctl = char_mem_ioctl, }; static int charmem_init(void) { int result = 0; int err = 0; dev_t dev = MKDEV(major, 0); if(major) { result = register_chrdev_region(dev, 1, "charmem"); } else { result = alloc_chrdev_region(&dev, 0, 1, "charmem"); major = MAJOR(dev); } /******申请设备号失败*******/ if(result < 0) return result; /********注册设备********/ cdev_init(&char_mem.memdev, &char_mem_ops); char_mem.memdev.owner = THIS_MODULE; err = cdev_add(&char_mem.memdev, dev, 1); /******注册失败********/ if(err) printk(KERN_INFO "Error %d adding char_mem device", err); } static void charmem_exit(void) { /****删除设备***/ cdev_del(&char_mem.memdev); /*****释放设备编号******/ unregister_chrdev_region(MKDEV(major, 0), 1); } MODULE_AUTHOR("chenlong12580"); MODULE_LICENSE("Dual BSD/GPL"); module_init(charmem_init); module_exit(charmem_exit);