字符设备是3大类设备(字符设备、块设备和网络设备)中较简单的一类设备,其驱动程序中完成的主要工作是初始化、添加和删除cdev结构体,申请和释放设备号,以及填充 file_operations结构体中的操作函数,实现file_operations结构体中的read()、write()和ioctl()等函数是驱动设计的主体工作。
参考例程
源码
/* * 虚拟字符设备globalmem实例: *在globalmem字符设备驱动中会分配一片大小为 GLOBALMEM_SIZE(4KB) *的内存空间,并在驱动中提供针对该片内存的读写、控制和定位函数,以供用户空间的进程能通过 *Linux系统调用访问这片内存。 */#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 DEV_NAME "globalmem" /* /dev中显示的设备名 */#define DEV_MAJOR 0 /* 指定主设备号,为0则动态获取 *//* ioctl用的控制字 */#define GLOBALMEM_MAGIC "M"#define MEM_CLEAR _IO(GLOBALMEM_MAGIC, 0)/*--------------------------------------------------------------------- local vars *//*globalmem设备结构体*/typedef struct {struct cdev cdev; /* 字符设备cdev结构体*/#define MEM_SIZE 0x1000 /*全局内存最大4K字节*/unsigned char mem[MEM_SIZE]; /*全局内存*/struct semaphore sem; /*并发控制用的信号量*/} globalmem_dev_t;static int globalmem_major = DEV_MAJOR;static globalmem_dev_t *globalmem_devp; /*设备结构体指针*//*--------------------------------------------------------------------- file operations *//*文件打开函数*/static int globalmem_open(struct inode *inodep, struct file *filep){/* 获取dev指针 */globalmem_dev_t *dev = container_of(inodep->i_cdev, globalmem_dev_t, cdev);filep->private_data = dev;return 0;}/*文件释放函数*/static int globalmem_release(struct inode *inodep, struct file *filep){return 0;}/*读函数*/static ssize_t globalmem_read(struct file *filep, char __user *buf, size_t len, loff_t *ppos){globalmem_dev_t *dev = filep->private_data;unsigned long p = *ppos; int ret = 0;/*分析和获取有效的长度*/if (p > MEM_SIZE) {printk(KERN_EMERG "%s: overflow!
", __func__);return - ENOMEM;}if (len > MEM_SIZE - p) {len = MEM_SIZE - p;}if (down_interruptible(&dev->sem)) /* 获得信号量*/return- ERESTARTSYS;/*内核空间->用户空间*/if (copy_to_user(buf, (void*)(dev->mem + p), len)) {ret = - EFAULT;}else{*ppos += len;printk(KERN_INFO "%s: read %d bytes from %d
", DEV_NAME, (int)len, (int)p);ret = len;}up(&dev->sem); /* 释放信号量*/return ret;}/*写函数*/static ssize_t globalmem_write(struct file *filep, const char __user *buf, size_t len, loff_t *ppos){globalmem_dev_t *dev = filep->private_data;int ret = 0;unsigned long p = *ppos; if (p > MEM_SIZE) {printk(KERN_EMERG "%s: overflow!
", __func__);return - ENOMEM;}if (len > MEM_SIZE - p) {len = MEM_SIZE - p;}if (down_interruptible(&dev->sem)) /* 获得信号量*/return- ERESTARTSYS;/*用户空间->内核空间*/if (copy_from_user(dev->mem + p, buf, len)) {ret =- EFAULT;}else{*ppos += len;printk(KERN_INFO "%s: written %d bytes from %d
", DEV_NAME, (int)len, (int)p);ret = len;}up(&dev->sem); /* 释放信号量*/return ret;}/* seek文件定位函数 */static loff_t globalmem_llseek(struct file *filep, loff_t offset, int start){globalmem_dev_t *dev = filep->private_data;int ret = 0;if (down_interruptible(&dev->sem)) /* 获得信号量*/return- ERESTARTSYS;switch (start) {case SEEK_SET:if (offset < 0 || offset > MEM_SIZE) {printk(KERN_EMERG "%s: overflow!
", __func__);return - ENOMEM;}ret = filep->f_pos = offset;break;case SEEK_CUR:if ((filep->f_pos + offset) < 0 || (filep->f_pos + offset) > MEM_SIZE) {printk(KERN_EMERG "%s: overflow!
", __func__);return - ENOMEM;}ret = filep->f_pos += offset;break;default:return - EINVAL;break;}up(&dev->sem); /* 释放信号量*/printk(KERN_INFO "%s: set cur to %d.
", DEV_NAME, ret);return ret;}/* ioctl设备控制函数 */static long globalmem_ioctl(struct file *filep, unsigned int cmd, unsigned long arg){globalmem_dev_t *dev = filep->private_data;switch (cmd) {case MEM_CLEAR:if (down_interruptible(&dev->sem)) /* 获得信号量*/return- ERESTARTSYS;memset(dev->mem, 0, MEM_SIZE);up(&dev->sem); /* 释放信号量*/printk(KERN_INFO "%s: clear.
", DEV_NAME);break;default:return - EINVAL;}return 0;}/*文件操作结构体*/static const struct file_operations globalmem_fops = {.owner = THIS_MODULE,.open = globalmem_open,.release= globalmem_release,.read = globalmem_read,.write= globalmem_write,.llseek = globalmem_llseek,.compat_ioctl = globalmem_ioctl};/*---------------------------------------------------------------------*//*初始化并注册cdev*/static int globalmem_setup(globalmem_dev_t *dev, int minor){int ret = 0;dev_t devno = MKDEV(globalmem_major, minor);cdev_init(&dev->cdev, &globalmem_fops);dev->cdev.owner = THIS_MODULE;ret = cdev_add(&dev->cdev, devno, 1);if (ret) {printk(KERN_NOTICE "%s: Error %d dev %d.
", DEV_NAME, ret, minor);}return ret;}/*设备驱动模块加载函数*/static int __init globalmem_init(void){int ret = 0;dev_t devno; /* 申请设备号*/if(globalmem_major){devno = MKDEV(globalmem_major, 0);ret = register_chrdev_region(devno, 2, DEV_NAME);}else{ /* 动态申请设备号 */ret = alloc_chrdev_region(&devno, 0, 2, DEV_NAME);globalmem_major = MAJOR(devno);}if (ret < 0) {return ret;}/* 动态申请设备结构体的内存,创建两个设备 */globalmem_devp = kmalloc(2*sizeof(globalmem_dev_t), GFP_KERNEL);if (!globalmem_devp) {unregister_chrdev_region(devno, 2);return - ENOMEM;}ret |= globalmem_setup(&globalmem_devp[0], 0); /* globalmem0 */ret |= globalmem_setup(&globalmem_devp[1], 1); /* globalmem1 */if(ret)return ret;init_MUTEX(&globalmem_devp[0].sem); /*初始化信号量*/init_MUTEX(&globalmem_devp[1].sem);printk(KERN_INFO "globalmem: up %d,%d.
", MAJOR(devno), MINOR(devno));return 0;}/*模块卸载函数*/static void __exit globalmem_exit(void){cdev_del(&globalmem_devp[0].cdev);cdev_del(&globalmem_devp[1].cdev);kfree(globalmem_devp);unregister_chrdev_region(MKDEV(globalmem_major, 0), 2);printk(KERN_INFO "globalmem: down.
");}/* 定义参数 */module_param(globalmem_major, int, S_IRUGO);module_init(globalmem_init);module_exit(globalmem_exit);/* 模块描述及声明 */MODULE_AUTHOR("Archie Xie <archixie@cnblogs.com>");MODULE_LICENSE("Dual BSD/GPL");MODULE_DESCRIPTION("A char device module just for demo.");MODULE_ALIAS("cdev gmem");MODULE_VERSION("1.0");用户空间验证
- 切换到root用户
- 插入模块
insmod globalmem.ko - 创建设备节点(后续例程会展示自动创建节点的方法)
cat /proc/devices 找到主设备号majormknod /dev/globalmem0 c major 0 和 /dev/globalmem1 c major 1 - 读写测试
echo "hello world" > /dev/globalmemcat /dev/globalmem
本文永久更新链接地址:http://www.linuxidc.com/Linux/2016-12/138210.htm