2-6案列分析: memdev虚拟内存设备驱动
1、memdev虚拟内存字符设备:在驱动中分配一片指定大小的内存空间,作为虚拟字符设备。并在驱动中提供只对该片内存的读写、控制和定位函数seek,以供用户空间的进程能通过Linux系统调用访问这片内存
实例一:
2、测试源代码:
2.1globalmem_test.c代码
#include <stdio.h>
#include <string.h>
int main()
{
FILE *fp0 = NULL;
char Buf[4096];
int result;
/*初始化Buf*/
strcpy(Buf,"Mem is char dev!");
printf("BUF: %s\n",Buf);
/*打开设备文件*/
fp0 = fopen("/dev/globalmem","r+");/*驱动对应的文件名为globalmen,以已读的方式打开*/
if (fp0 == NULL)
{
perror("Open globalmem Error!\n");
return -1;
}
/*写入设备*/
result = fwrite(Buf, sizeof(Buf), 1, fp0);
if (result == -1)
{
perror("fwrite Error!\n");
return -1;
}
/*重新定位文件位置(思考没有该指令,会有何后果)*/
fseek(fp0,0,SEEK_SET);/*定位到从文件头开始读,写完后读写指针在文件的尾部*/
/*清除Buf*/
strcpy(Buf,"Buf is NULL!");
printf("BUF: %s\n",Buf);
sleep(1);
/*读出设备*/
result = fread(Buf, sizeof(Buf), 1, fp0);
if (result == -1)
{
perror("fread Error!\n");
return -1;
}
/*检测结果*/
printf("BUF: %s\n",Buf);
return 0;
}
2.2 Makefile文件
KERNELDIR ?=/home/student/linux-2.6.32.2
all: globalmem_test
globalmem_test : globalmem_test.c
#arm-linux-gcc -I$(KERNELDIR) -s -Wl,-warn-common --static -o $@ $^
arm-linux-gcc -I$(KERNELDIR) -o $@ $^
clean :
rm globalmem_test
3、驱动源代码:
3.1 globalmem.c代码:
#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>
#include <linux/slab.h>
#define GLOBALMEM_SIZE 0x1000 /*全局内存最大4K字节*/
#define MEM_CLEAR 0x1 /*清0全局内存*/
#define GLOBALMEM_MAJOR 0 /*预设的globalmem的主设备号*/
static int globalmem_major = GLOBALMEM_MAJOR;
/*globalmem设备结构体*/
struct globalmem_dev
{
struct cdev cdev; /*cdev结构体*/
unsigned char mem[GLOBALMEM_SIZE]; /*全局内存*/
};
struct globalmem_dev *globalmem_devp; /*设备结构体指针*/
/*文件打开函数*/
int globalmem_open(struct inode *inode, struct file *filp)
{
/*将设备结构体指针赋值给文件私有数据指针*/
filp->private_data = globalmem_devp;
return 0;
}
/*文件释放函数*/
int globalmem_release(struct inode *inode, struct file *filp)/*file结构描述一个正在打开的设备文件*/
{
return 0;
}
/* ioctl设备控制函数 */
static int globalmem_ioctl(struct inode *inodep, 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; /*file结构中保存的loff_t f_pos:当前读写位置*/
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
/*分析和获取有效的写长度*/
if (p >= GLOBALMEM_SIZE)
return count ? - ENXIO: 0; /* return ENXIO: No such device or address */
if (count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;
/*内核空间->用户空间*/
if (copy_to_user(buf, (void*)(dev->mem + p), count))
{
ret = - EFAULT; /* Bad address 拷贝不成功*/
}
else/*拷贝成功*/
{
*ppos += count;
ret = count;
printk(KERN_INFO "read %d bytes(s) from position %d\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 count ? - ENXIO: 0; //ENXIO: No such device or addres
if (count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;
/*用户空间->内核空间*/
if (copy_from_user(dev->mem + p, buf, count))
ret = - EFAULT; /*写失败*/
else/*写成功*/
{
*ppos += count;
ret = count;
printk(KERN_INFO "written %d bytes(s) to position %d\n", count, p);
}
return ret;
}
/* seek文件定位函数 */
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = 0;
switch (orig)
{
case 0: /*相对文件头开始 SEEK_SET 位置偏移 */
if (offset < 0)
{
ret = - EINVAL; /* Invalid argument */
break;
}
if ((unsigned int)offset > GLOBALMEM_SIZE)
{
ret = - EINVAL;
break;
}
filp->f_pos = (unsigned int)offset; /*file结构的成员loff_t f_pos:当前读写位置*/
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,
.ioctl = globalmem_ioctl,
.open = globalmem_open,
.release = globalmem_release,
};
/*初始化并注册cdev*/
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;
dev->cdev.ops = &globalmem_fops;
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk(KERN_NOTICE "Error %d adding LED%d", err, index);
}
/*设备驱动模块加载函数*/
int globalmem_init(void)
{
int result;
dev_t devno = MKDEV(globalmem_major, 0);
/* 申请设备号*/
if (globalmem_major)
result = register_chrdev_region(devno, 1, "globalmem");
else /* 动态申请设备号 */
{
result = alloc_chrdev_region(&devno, 0, 1, "globalmem");
globalmem_major = MAJOR(devno);
}
if (result < 0)
return result;
/* 动态申请设备结构体的内存*/
globalmem_devp = kmalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
if (!globalmem_devp) /*申请失败*/
{
result = - ENOMEM;KERNELDIR ?=/home/student/linux-2.6.32.2
all: globalmem_test
globalmem_test : globalmem_test.c
#arm-linux-gcc -I$(KERNELDIR) -s -Wl,-warn-common --static -o $@ $^
arm-linux-gcc -I$(KERNELDIR) -o $@ $^
clean :
rm globalmem_test
goto fail_malloc;
}
memset(globalmem_devp, 0, sizeof(struct globalmem_dev));
globalmem_setup_cdev(globalmem_devp, 0);
printk("globalmem driver installed!\n");
printk("globalmem_major is:%d\n",globalmem_major);
printk("the device name is %s\n", "globalmem");
return 0;
fail_malloc: unregister_chrdev_region(devno, 1);
return result;
}
/*模块卸载函数*/
void globalmem_exit(void)
{
cdev_del(&globalmem_devp->cdev); /*注销cdev*/
kfree(globalmem_devp); /*释放设备结构体内存*/
unregister_chrdev_region(MKDEV(globalmem_major, 0), 1); /*释放设备号*/
printk("globalmem driver uninstalled!\n");
}
MODULE_AUTHOR("Song Baohua");
MODULE_LICENSE("Dual BSD/GPL");
module_param(globalmem_major, int, S_IRUGO);
module_init(globalmem_init);
module_exit(globalmem_exit);
3.2Makefile文件:
ifeq ($(KERNELRELEASE),)
#KERNELDIR ?= /your/target/source/directory/
KERNELDIR ?=/home/student/linux-2.6.32.2
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
.PHONY: modules modules_install clean
else
obj-m := globalmem.o
endif
实例二:(两个设备组成,主设备号是相同的,但他们的此设备号不同)
4、测试代码
4.1memdev_test.c代码:
#include <stdio.h>
#include <string.h>
int main()
{
FILE *fp0 = NULL;
char Buf[4096];
int result;
/*初始化Buf*/
strcpy(Buf,"Mem is char dev!");
printf("BUF: %s\n",Buf);
/*打开设备文件*/
fp0 = fopen("/dev/memdev0","r+");/*文件名为memdev0 其中0为此设备号*/
if (fp0 == NULL)
{
perror("Open Memdev0 Error!\n");ifeq ($(KERNELRELEASE),)
#KERNELDIR ?= /your/target/source/directory/
KERNELDIR ?=/home/student/linux-2.6.32.2
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
.PHONY: modules modules_install clean
else
obj-m := globalmem.o
endif
return -1;
}
/*写入设备*/
result = fwrite(Buf, sizeof(Buf), 1, fp0);
if (result == -1)
{
perror("fwrite Error!\n");
return -1;
}
/*重新定位文件位置(思考没有该指令,会有何后果)*/
fseek(fp0,0,SEEK_SET);
/*清除Buf*/
strcpy(Buf,"Buf is NULL!");
printf("BUF: %s\n",Buf);
sleep(1);
/*读出设备*/
result = fread(Buf, sizeof(Buf), 1, fp0);
if (result == -1)
{
perror("fread Error!\n");
return -1;
}
/*检测结果*/
printf("BUF: %s\n",Buf);
return 0;
}
4.2Makefile文件:
KERNELDIR ?=/home/student/linux-2.6.32.2
all: memdev_test
memdev_test : memdev_test.c
#arm-linux-gcc -I$(KERNELDIR) -s -Wl,-warn-common --static -o $@ $^
arm-linux-gcc -I$(KERNELDIR) -o $@ $^
clean :
rm memdev_test
5、驱动代码:
5.1头文件memdev.h代码
#ifndef _MEMDEV_H_
#define _MEMDEV_H_
#ifndef MEMDEV_MAJOR
#define MEMDEV_MAJOR 0 /*预设的mem的主设备号*/
#endif
#ifndef MEMDEV_NR_DEVS
#define MEMDEV_NR_DEVS 2 /*设备数*/
#endif
#ifndef MEMDEV_SIZE
#define MEMDEV_SIZE 4096
#endif
/*mem设备描述结构体*/
struct mem_dev /*这里的mem_devp结构设计的挺好留意内存分配问题*/
{
char *data;
unsigned long size;
};
#endif /* _MEMDEV_H_ */
5.2驱动代码:
#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>
#include <linux/slab.h>
#include "memdev.h"
static mem_major = MEMDEV_MAJOR;
module_param(mem_major, int, S_IRUGO);
struct mem_dev *mem_devp; /*设备?ifndef _MEMDEV_H_
#define _MEMDEV_H_
#ifndef MEMDEV_MAJOR
#define MEMDEV_MAJOR 0 /*预设的mem的主设备号*/
#endif
#ifndef MEMDEV_NR_DEVS
#define MEMDEV_NR_DEVS 2 /*设备数*/
#endif
#ifndef MEMDEV_SIZE
#define MEMDEV_SIZE 4096
#endif
/*mem设备描述结构体*/
struct mem_dev /*这里的mem_devp结构设计的挺好留意内存分配问题*/
{
char *data;
unsigned long size;
};
#endif /* _MEMDEV_H_ */
峁固逯刚?/
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; /*传递的是mem_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_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_to_user(buf, (void*)(dev->data + p), count))
{
ret = - EFAULT;
}
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "read %d bytes(s) from postion %d\n", 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 %d bytes(s) to postion %d\n", 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, MEMDEV_NR_DEVS, "memdev");
else /* 动态分配设备号 */
{
result = alloc_chrdev_region(&devno, 0, MEMDEV_NR_DEVS, "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); /*为mem_devp结构对象分配内存*/
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结构设计的挺好,注意指针*data的内存分配*/
{
mem_devp[i].size = MEMDEV_SIZE;
mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);
memset(mem_devp[i].data, 0, MEMDEV_SIZE);
}
printk("memdev driver installed, with major %d\n", mem_major);
printk("the device name is %s\n","memdev");
return 0;
fail_malloc:
unregister_chrdev_region(devno, 1);
return result;
}
/*模块卸载函数*/
static void memdev_exit(void)
{
int i;
cdev_del(&cdev); /*注销设备*/
for (i=0; i < MEMDEV_NR_DEVS; i++)
kfree(mem_devp[i].data);
kfree(mem_devp); /*释放设备结构体内存*/
unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*释放设备号*/
printk("memdev driver uninstalled\n");
}
MODULE_AUTHOR("Hanson He");
MODULE_LICENSE("GPL");
module_init(memdev_init);
module_exit(memdev_exit);
注意:这里只有int mem_open(struct inode *inode, struct file *filp)和int mem_release(struct inode *inode, struct file *filp)两个函数中是带 struct inode ,而mem_open()中的才对其他有作用的。下面是取得对那个设备进行操作,然后是通过struct file结构中的成员private_data进行传递而其他ioctl成员中的函数都有带struct file参数,这样就很好传递了men_dev结构,便于以后进行各种操作。
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; /*传递的是mem_dev结构数组中其中一个的地址*/
5.3Makefile文件:
ifeq ($(KERNELRELEASE),)
#KERNELDIR ?= /your/target/source/directory/
KERNELDIR ?=/home/student/linux-2.6.32.2
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
.PHONY: modules modules_install clean
else
obj-m := memdev.o#include <linux/module.h>
演示过程:
声明:本文非原创,整理自申嵌