Linux字符设备驱动实例—globalmem驱动
1、globalmem虚拟设备实例
globalmem为“全局内存”的意思,在globalmem字符设备中会分配一片大小为GLOBALMEM_SIZE(4KB)的内存空间,并在驱动中提供对这片内存的读写、控制和定位函数,供用户空间的进程能通过Linux系统调用获取和设置这片内存。
(1)头文件、宏以及设备结构体
#include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/slab.h> #include <linux/uaccess.h> #define GLOBALMEM_SIZE 0x1000 #define MEM_CLEAR 0x1 #define GLOBALMEM_MAJOR 230 static int globalmem_major = GLOBALMEM_MAJOR; module_param(globalmem_major, int, S_IRUGO); struct globalmem_dev { struct cdev cdev; unsigned char mem[GLOBALMEM_SIZE]; }; struct globalmem_dev *globalmem_devp;
定义的globalmem_dev结构体中,包含了对应于globalmem字符设备的cdev,使用的内存mem[GLOBALMEM_SIZE]。
(2)globalmem设备驱动模块的加载和卸载函数
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index) { int err, devno = MKDEV(globalmem_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 "Error %d adding globalmem %d", err, index); } } static int __init globalmem_init(void) { int ret; dev_t devno = MKDEV(globalmem_major, 0); if (globalmem_major) { ret = register_chrdev_region(devno, 1, "globalmem"); } else { ret = alloc_chrdev_region(&devno, 0, 1, "globalmem"); globalmem_major = MAJOR(devno); } if (ret < 0) return ret; globalmem_devp = kzalloc(sizeof(struct globalmem_dev), GFP_KERNEL); if (!globalmem_devp) { ret = -ENOMEM; goto fail_malloc; } globalmem_setup_cdev(globalmem_devp, 0); return 0; fail_malloc: unregister_chrdev_region(devno, 1); return ret; } static void __exit globalmem_exit(void) { cdev_del(&globalmem_devp->cdev); kfree(globalmem_devp); unregister_chrdev_region(MKDEV(globalmem_major, 0), 1); }
globalmem_setup_cdev()函数完成cdev的初始化化和添加,kzalloc()申请了一份globalmem_dev结构体的内存,并将其清0,在cdev_init()函数中,与globalmem的cdev关联的file_operations结构体如下所示:
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, .unlocked_ioctl = globalmem_ioctl, };
(3)读写函数的实现
首先是读函数,函数的实现如下所示:
static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos) { unsigned long p = *ppos; unsigned int count = size; int ret = 0; struct globalmem_dev *dev = filp->private_data; if (p > GLOBALMEM_SIZE) return 0; if (count > GLOBALMEM_SIZE - p) count = GLOBALMEM_SIZE - p; if (copy_to_user(buf, dev->mem + p, count)) { ret = -EFAULT; } else { *ppos += count; ret = count; printk(KERN_INFO "read %u bytes(s) from %lu\n", count, p); } return ret; }
其中*ppos是读的位置相对于文件开头的漂移,如果该漂移大于或等于GLOBALMEM_SIZE,表示文件已经到了末尾,返回0(EOF)。
写函数的实现如下所示:
static ssize_t globalmem_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 globalmem_dev *dev = filp->private_data; if (p > GLOBALMEM_SIZE) return 0; if (count > GLOBALMEM_SIZE - p) count = GLOBALMEM_SIZE - p; if (copy_from_user(dev->mem + p, buf, count)) { return -EFAULT; } else { *ppos += count; ret = count; printk(KERN_INFO "written %u bytes(s) from %lu\n", count, p); } return ret; }
(4)seek函数的实现
seek()函数对文件定位的起始地址可以是文件开头(SEEK_SET,0)、当前位置(SEEK_CUR,1)和文件末尾(SEEK_END,2),在定位的时候,要检查用户请求的合法性,若不合法,函数返回错误号,若合法,更新文件的当前位置,并返回新的位置,实现如下所示:
static loff_t globalmem_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 > GLOBALMEM_SIZE) { ret = -EINVAL; break; } filp->f_pos = (unsigned int)offset; ret = filp->f_pos; break; case 1: 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; }
(5)ioctl函数实现
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: memset(dev->mem, 0, GLOBALMEM_SIZE); printk(KERN_INFO "globalmem is set to zero\n"); break; default: return -EINVAL; } return 0; }
(6)使用文件的私有数据
将文件的私有数据private_data指向设备的结构体,然后使用read()、write()、ioctl()、llseek()等函数通过private_data访问设备结构体,如下所示:
static int globalmem_open(struct inode *inode, struct file *filp) { filp->private_data = globalmem_devp; return 0; } static int globalmem_release(struct inode *inode, struct file *filp) { return 0; }
(7)完整的globalmem驱动代码
#include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/slab.h> #include <linux/uaccess.h> #define GLOBALMEM_SIZE 0x1000 #define MEM_CLEAR 0x1 #define GLOBALMEM_MAJOR 230 static int globalmem_major = GLOBALMEM_MAJOR; module_param(globalmem_major, int, S_IRUGO); struct globalmem_dev { struct cdev cdev; unsigned char mem[GLOBALMEM_SIZE]; }; struct globalmem_dev *globalmem_devp; static int globalmem_open(struct inode *inode, struct file *filp) { filp->private_data = globalmem_devp; return 0; } static int globalmem_release(struct inode *inode, struct file *filp) { return 0; } 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: memset(dev->mem, 0, GLOBALMEM_SIZE); printk(KERN_INFO "globalmem is set to zero\n"); break; default: return -EINVAL; } return 0; } static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos) { unsigned long p = *ppos; unsigned int count = size; int ret = 0; struct globalmem_dev *dev = filp->private_data; if (p > GLOBALMEM_SIZE) return 0; if (count > GLOBALMEM_SIZE - p) count = GLOBALMEM_SIZE - p; if (copy_to_user(buf, dev->mem + p, count)) { ret = -EFAULT; } else { *ppos += count; ret = count; printk(KERN_INFO "read %u bytes(s) from %lu\n", count, p); } return ret; } static ssize_t globalmem_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 globalmem_dev *dev = filp->private_data; if (p > GLOBALMEM_SIZE) return 0; if (count > GLOBALMEM_SIZE - p) count = GLOBALMEM_SIZE - p; if (copy_from_user(dev->mem + p, buf, count)) { return -EFAULT; } else { *ppos += count; ret = count; printk(KERN_INFO "written %u bytes(s) from %lu\n", count, p); } return ret; } static loff_t globalmem_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 > GLOBALMEM_SIZE) { ret = -EINVAL; break; } filp->f_pos = (unsigned int)offset; ret = filp->f_pos; break; case 1: 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, .read = globalmem_read, .write = globalmem_write, .unlocked_ioctl = globalmem_ioctl, .open = globalmem_open, .release = globalmem_release, }; static void globalmem_setup_cdev(struct globalmem_dev *dev, int index) { int err, devno = MKDEV(globalmem_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 "Error %d adding globalmem %d", err, index); } } static int __init globalmem_init(void) { int ret; dev_t devno = MKDEV(globalmem_major, 0); if (globalmem_major) { ret = register_chrdev_region(devno, 1, "globalmem"); } else { ret = alloc_chrdev_region(&devno, 0, 1, "globalmem"); globalmem_major = MAJOR(devno); } if (ret < 0) return ret; globalmem_devp = kzalloc(sizeof(struct globalmem_dev), GFP_KERNEL); if (!globalmem_devp) { ret = -ENOMEM; goto fail_malloc; } globalmem_setup_cdev(globalmem_devp, 0); return 0; fail_malloc: unregister_chrdev_region(devno, 1); return ret; } static void __exit globalmem_exit(void) { cdev_del(&globalmem_devp->cdev); kfree(globalmem_devp); unregister_chrdev_region(MKDEV(globalmem_major, 0), 1); } module_init(globalmem_init); module_exit(globalmem_exit); MODULE_AUTHOR("HLY"); MODULE_LICENSE("GPL");
(8)globalmem驱动验证
使用make命令将源文件编译出驱动模块globalmem.ko文件,编译需要的Makefile如下所示:
# Makefile for globalmem driver obj-m += globalmem.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
然后使用驱动模块命令加载模块,如下:
$ sudo insmod globalmem.ko $ lsmod
然后使用下面的命令查看globalmem虚拟设备的设备号:
$ cat /proc/devices
然后,使用mknod创建设备节点:
# mknod /dev/globalmem c 230 0 # ls -al /dev/globalmem
接下来使用命令对该文件进行读写以测试:
# echo “Hello World” > /dev/globalmem # cat /dev/globalmem
也可以使用系统调用函数open、write和read进行该虚拟设备的测试,测试的app.c文件如下:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <string.h> #define LENGTH 100 int main(int argc, char *argv[]) { int fd,len; char str[LENGTH]; fd = open("/dev/globalmem", O_RDWR); if (fd) { write(fd, "Hello World", strlen("Hello World")); close(fd); } fd = open("/dev/globalmem", O_RDWR); len = read(fd, str, LENGTH); str[len] = '\0'; printf("str:%s\n", str); close(fd); return 0; }
编写该app.c的Makefile文件,如下:
# Makefile by HLY all: myapp # Which compiler CC = gcc # Where are include files INCLUDE = . # Options for development CFLAGS = -g -Wall -ansi myapp: app.o $(CC) -o myapp app.o clean: rm -rf *.o myapp
使用make命令将app.c编译成可执行文件myapp,然后执行程序,即可完成globalmem虚拟设备/dev/globalmem的读写测试。
参考:
《Linux设备驱动开发详解:基于最新的Linux 4.0内核》