globalmem设备驱动
基于宋宝华ldd中globalmem修改测试。
1)采用container_of()获取设备结构体数据;
2)mutex实现互斥访问。
3)支持子设备(示例中设定为3)。
#include <linux/types.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/delay.h> //#include <linux/ide.h> #include <asm/uaccess.h> #include <linux/cdev.h> #include <linux/slab.h> #define DEVICE_MAJOR 230 #define DEVICE_NUM 3 static int device_major = DEVICE_MAJOR; module_param(device_major, int, S_IRUGO); static char *device_name = "globalmem_chrdev"; #define GLOBALMEM_SIZE 0x1000 //#define MEM_CLEAR 0x1 #define GLOBALMEM_MAGIC 'g' #define MEM_CLEAR _IO(GLOBALMEM_MAGIC, 0) struct globalmem_dev{ struct cdev cdev; unsigned char mem[GLOBALMEM_SIZE]; struct mutex mutex; }; struct globalmem_dev *globalmem_devp; static int globalmem_open(struct inode *inode, struct file *filp) { struct globalmem_dev *dev = container_of(inode->i_cdev, struct globalmem_dev, cdev); filp->private_data = dev; return 0; } static int globalmem_release(struct inode *inode, struct file *filp) { return 0; } static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos) { int ret = 0; unsigned long pos = *ppos; unsigned int count = size; struct globalmem_dev *dev = filp->private_data; if(pos >= GLOBALMEM_SIZE){ return 0; } if(count > (GLOBALMEM_SIZE - pos)){ count = GLOBALMEM_SIZE - pos; } if(mutex_lock_interruptible(&dev->mutex)){ return -ERESTARTSYS; } msleep(10000); if(copy_to_user(buf, dev->mem+pos, count)){ ret = -EFAULT; } else { *ppos += count; ret = count; printk(KERN_INFO "device %s read %u byte(s) from %lu\n", device_name, count, pos); } mutex_unlock(&dev->mutex); return ret; } static ssize_t globalmem_write(struct file *filp, const char __user *buf, size_t size , loff_t *ppos) { int ret = 0; unsigned long pos = *ppos; unsigned int count = size; struct globalmem_dev *dev = filp->private_data; if(pos >= GLOBALMEM_SIZE){ return 0; } if(size > (GLOBALMEM_SIZE-pos)){ size = GLOBALMEM_SIZE-pos; } if(mutex_lock_interruptible(&dev->mutex)){ return -ERESTARTSYS; } msleep(10000); if(copy_from_user(dev->mem+pos, buf, count)){ ret = -EFAULT; } else { *ppos += count; ret = count; printk(KERN_INFO "device %s write %u bytes from %lu\n", device_name, count, pos); } mutex_unlock(&dev->mutex); return ret; } static long globalmem_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct globalmem_dev *dev = filp->private_data; switch(cmd){ case MEM_CLEAR: if(mutex_lock_interruptible(&dev->mutex)){ return -ERESTARTSYS; } memset(dev->mem, 0, GLOBALMEM_SIZE); mutex_unlock(&dev->mutex); printk(KERN_INFO "device %s is set to zero\n", device_name); break; default: return -EINVAL; } return 0; } static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig) { loff_t ret = 0; switch(orig){ case SEEK_SET: if(offset < 0){ ret = -EINVAL; break; } if((unsigned int)offset > GLOBALMEM_SIZE){ ret = -EINVAL; break; } filp->f_pos = (unsigned int) offset; ret = filp->f_pos; break; case SEEK_CUR: if((filp->f_pos + offset) >= GLOBALMEM_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; } static const struct file_operations globalmem_fops = { .owner = THIS_MODULE, .llseek = globalmem_llseek, .unlocked_ioctl = globalmem_ioctl, .read = globalmem_read, .write = globalmem_write, .open = globalmem_open, .release = globalmem_release, }; static int globalmem_setup_cdev(struct globalmem_dev *dev, int index) { int err, devno = MKDEV(device_major, index); cdev_init(&dev->cdev, &globalmem_fops); dev->cdev.owner = THIS_MODULE; err = cdev_add(&dev->cdev, devno, 1); if(err){ printk(KERN_NOTICE "device %s add %d cdev error %d\n", device_name, index, err); return -1; } return 0; } static int __init globalmem_init(void) { int ret = 0; int i = 0, ii = 0; dev_t devno; printk(KERN_INFO "device %s probing...\n", device_name); if(device_major){ devno = MKDEV(device_major, 0); ret = register_chrdev_region(devno, DEVICE_NUM, device_name); } else { ret = alloc_chrdev_region(&devno, 0, DEVICE_NUM, device_name); device_major = MAJOR(devno); } if(ret < 0){ return ret; } globalmem_devp = kzalloc(sizeof(struct globalmem_dev)*DEVICE_NUM, GFP_KERNEL); if(!globalmem_devp){ ret = -ENOMEM; goto fail_malloc; } for(i = 0; i < DEVICE_NUM; i++){ ret = globalmem_setup_cdev(globalmem_devp+i, i); if(ret){ ret = -EFAULT; goto fail_cdev; } } mutex_init(&globalmem_devp->mutex); printk(KERN_INFO "device %s probe successfully!\n", device_name); return 0; fail_cdev: ii = i; for(i = 0; i < ii; i++){ cdev_del(&(globalmem_devp+i)->cdev); } kfree(globalmem_devp); fail_malloc: unregister_chrdev_region(devno, DEVICE_NUM); printk(KERN_INFO "device %s probe failed!\n", device_name); return ret; } static void __exit globalmem_exit(void) { int i = 0; for(i = 0; i < DEVICE_NUM; i++){ cdev_del(&(globalmem_devp+i)->cdev); } kfree(globalmem_devp); unregister_chrdev_region(MKDEV(device_major, 0), DEVICE_NUM); printk(KERN_INFO "device %s exit!\n", device_name); } module_init(globalmem_init); module_exit(globalmem_exit); MODULE_AUTHOR("wqh"); MODULE_LICENSE("Dual BSD/GPL");
pc测试Makefile:
obj-m := globalmem.o CURRENT_PATH := $(shell pwd) LINUX_KERNEL := $(shell uname -r) LINUX_KERNEL_PATH := /lib/modules/$(shell uname -r)/build #LINUX_KERNEL_PATH := /home/wang/kernel/kernel/linux-imx-4.1.15 GCC ?= gcc all: #$(MAKE) -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- $(MAKE) -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules $(GCC) -Wall -o app app.c clean: #$(MAKE) -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- $(MAKE) -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean -rm app
app测试程序
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <string.h> #include <errno.h> int main(int argc, char **argv) { int fd, retvalue; char *filename = NULL; char rbuf[100], wbuf[] = {"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"}; if(argc != 2){ printf("Usage: %s filename\n", argv[0]); exit(0); } filename = argv[1]; fd = open(filename, O_RDWR); if(fd < 0){ //printf("device %s open error(%d): %s\n", filename, fd, strerror(-fd)); printf("device %s open error(%d): %s\n", filename, fd, strerror(errno)); return fd; } retvalue = write(fd, wbuf, strlen(wbuf)); if(retvalue < 0){ printf("device %s write error(%d): %s\n", filename, retvalue, strerror(errno)); return retvalue; } printf("device %s write %d bytes\n", filename, retvalue); retvalue = lseek(fd, 0, SEEK_SET); if(retvalue < 0){ printf("device %s seek error(%d): %s\n", filename, retvalue, strerror(errno)); return -1; } retvalue = read(fd, rbuf, sizeof(rbuf)); if(retvalue < 0){ printf("device %s read error(%d): %s\n", filename, retvalue, strerror(errno)); return retvalue; } printf("device %s read %d bytes, and data: %s\n", filename, retvalue, rbuf); close(fd); return 0; }
测试:
## base sudo mknod /dev/globmem_chrdev0 c 230 0 sudo mknod /dev/globmem_chrdev1 c 230 1 sudo mknod /dev/globmem_chrdev2 c 230 2 sudo mknod /dev/globmem_chrdev3 c 230 3 // DEVICE_NUM=3, total 3, just test error cat /proc/devices // 230 globalmem_chrdev dmesg | tail -n 50 cat /var/log/kern.log | tail -n 50 // su root echo "hello world" > /dev/globalmem_chrdev cat /dev/globalmem_chrdev sudo ./app /dev/globalmem_chrdev2 sudo ./app /dev/globalmem_chrdev3 // just test Error ### mutex 同一子设备互斥访问,不同子设备没有影响(不互斥),因为设备文件的inode不同,取出的设备结构体实际不同,即filp->private_data不同子设备不同。 // same device sudo time ./app /dev/globalmem_chrdev0 // 30s read(10s) + write(10s) + mutex(10s) sudo time ./app /dev/globalmem_chrdev0 // 30s read(10s) + write(10s) + mutex(10s) // different devices sudo time ./app /dev/globalmem_chrdev0 // 30s read(10s) + write(10s) + mutex(10s) sudo time ./app /dev/globalmem_chrdev0 // 30s read(10s) + write(10s) + mutex(10s)
同一子设备互斥访问,不同子设备没有影响(不互斥),因为设备文件的inode不同,取出的设备结构体实际不同,即filp->private_data不同子设备不同。
参考: