scull from 《Linux设备驱动程序》
memdev.c
/* * memdev.c * create at 2015/01/07 * 字符设备驱动程序框架 */ #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/slab.h> #include <linux/cdev.h> #include <asm/io.h> #include <asm/uaccess.h> #include "memdev.h" static mem_major = MEMDEV_MAJOR; module_param(mem_major, int, S_IRUGO); /* 设备结构体指针 */ struct mem_dev *mem_devp; struct cdev cdev; /* 文件打开函数 */ int mem_open(struct inode *inode, struct file *filp) { struct mem_dev *dev; /* 获取次设备号 */ int num = MINOR(inode->i_rdev); if (num > MEMDEV_NR_DEVS) return -ENODEV; dev = &mem_devp[num]; filp->private_data = dev; return 0; } /* 文件释放函数 */ int mem_release(struct inode *inode, struct file *filp) { return 0; } /* 读函数 */ static ssize_t mem_read(struct file *filp, char __user *buf, size size, loff_t *ppos) { unsigned long p = *ppos; unsigned int count = size; int ret = 0; struct mem_dev *dev = filp->private_data; /* 判断读位置是否有效 */ if (p >= MEMDEV_SIZE) return 0; if (count > MEMDEV_SIZE - p) count = MEMDEV_SIZE - p; /*读数据到用户空间*/ if (copy_to_user(buf, (void*)(dev->data + p), count)) { ret = - EFAULT; } else { *ppos += count; ret = count; printk(KERN_INFO "read %u bytes(s) from %lu ", count, p); } return ret; } /*写函数*/ static ssize_t mem_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 mem_dev *dev = filp->private_data; /*获得设备结构体指针*/ /*分析和获取有效的写长度*/ if (p >= MEMDEV_SIZE) return 0; if (count > MEMDEV_SIZE - p) count = MEMDEV_SIZE - p; /*从用户空间写入数据*/ if (copy_from_user(dev->data + p, buf, count)) ret = - EFAULT; else { *ppos += count; ret = count; printk(KERN_INFO "written %u bytes(s) from %lu ", count, p); } return ret; } /* seek文件定位函数 */ static loff_t mem_llseek(struct file *filp, loff_t offset, int whence) { loff_t newpos; switch(whence) { case 0: /* SEEK_SET */ newpos = offset; break; case 1: /* SEEK_CUR */ newpos = filp->f_pos + offset; break; case 2: /* SEEK_END */ newpos = MEMDEV_SIZE -1 + offset; break; default: /* can't happen */ return -EINVAL; } if ((newpos<0) || (newpos>MEMDEV_SIZE)) return -EINVAL; filp->f_pos = newpos; return newpos; } /*文件操作结构体*/ static const struct file_operations mem_fops = { .owner = THIS_MODULE, .llseek = mem_llseek, .read = mem_read, .write = mem_write, .open = mem_open, .release = mem_release, }; /*设备驱动模块加载函数*/ static int memdev_init(void) { int result; int i; dev_t devno = MKDEV(mem_major, 0); /* 静态申请设备号*/ if (mem_major) result = register_chrdev_region(devno, 2, "memdev"); else /* 动态分配设备号 */ { result = alloc_chrdev_region(&devno, 0, 2, "memdev"); mem_major = MAJOR(devno); } if (result < 0) return result; /*初始化cdev结构*/ cdev_init(&cdev, &mem_fops); cdev.owner = THIS_MODULE; cdev.ops = &mem_fops; /* 注册字符设备 */ cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS); /* 为设备描述结构分配内存*/ mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL); if (!mem_devp) /*申请失败*/ { result = - ENOMEM; goto fail_malloc; } memset(mem_devp, 0, sizeof(struct mem_dev)); /*为设备分配内存*/ for (i=0; i < MEMDEV_NR_DEVS; i++) { mem_devp[i].size = MEMDEV_SIZE; mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL); memset(mem_devp[i].data, 0, MEMDEV_SIZE); } return 0; fail_malloc: unregister_chrdev_region(devno, 1); return result; } /*模块卸载函数*/ static void memdev_exit(void) { cdev_del(&cdev); /*注销设备*/ kfree(mem_devp); /*释放设备结构体内存*/ unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*释放设备号*/ } MODULE_AUTHOR("Elvalad"); MODULE_LICENSE("GPL"); module_init(memdev_init); module_exit(memdev_exit);
memdev.h
/* * memdev.h * create at 2015/01/07 */ #ifndef _MEMDEV_H_ #define _MEMDEV_H_ #ifndef MEMDEV_MAJOR #define MEMDEV_MAJOR 260 /*预设的mem的主设备号*/ #endif #ifndef MEMDEV_NR_DEVS #define MEMDEV_NR_DEVS 2 /*设备数*/ #endif #ifndef MEMDEV_SIZE #define MEMDEV_SIZE 4096 #endif /*mem设备描述结构体*/ struct mem_dev { char *data; unsigned long size; }; #endif /* _MEMDEV_H_ */
Makefile
ifneq ($(KERNELRELEASE),) obj-m:=memdev.o else KERNELDIR:=/lib/modules/$(shell uname -r)/build PWD:=$(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean: rm -rf *.o *.mod.c *.mod.o *.ko endif
memdevtest.c
#include <stdio.h> #include <stdlib.h> #include <time.h> #include <unistd.h> #include <linux/i2c.h> #include <linux/fcntl.h> int main() { int fd; //写入memdev设备的内容 char buf[]="it's funny!!!"; //memdev设备的内容读入到该buf中 char buf_read[4096]; if((fd=open("/dev/memdev",O_RDWR))==-1) //打开memdev设备 printf("open memdev ERROR "); else printf("open memdev SUCCESS! "); printf("buf is %s ",buf); write(fd,buf,sizeof(buf)); //把buf中的内容写入memdev设备 lseek(fd,0,SEEK_SET); //把文件指针重新定位到文件开始的位置 read(fd,buf_read,sizeof(buf)); //把memdev设备中的内容读入到buf_read中 printf("buf_read is %s ",buf_read); return 0; }
编译字符驱动之后会产生一个memdev.ko文件,然后执行
sudo insmod memdev.ko lsmod 或者 cat /proc/devices 查看ko是否插入
创建设备节点
sudo mknod memdev c 260 0
编译memdevtest.c程序测试这个字符设备驱动
gcc -o memdevtest memdevtest.c
执行此测试程序,因为设备文件建立在/dev目录下,在执行此程序时因为要打开设备文件,所以需要root权限下执行。