一、Virtualdisk虚拟文件系统
1、Virtualdisk虚拟文件系统介绍
Virtualdisk虚拟文件系统为一虚拟磁盘设备,在这个设备中分配了8K的连续内存空间,并定义了两个端口数据(port1和port2)。驱动程序可以对设备进行读写、控制和定位操作,用户空间的程序可以通过Linux系统调用访问virtualdisk设备中的数据。
参考《Linux驱动开发入门与实战》第二版这本书的虚拟文件教程,并在自己硬件开发平台讯为iTOP4412上进行实际验证,期间对遇到的问题进行记录与修改,最终在该基础上实现了驱动加载与使用。
2、开发环境
电脑端:window10家庭中文版,虚拟机VMware12,Ubuntu14.04,交叉编译环境使用讯为自带 arm-none-linux-gnueabi-交叉编译环境。
板子端:u-boot、内核使用讯为自带,文件系统使用busybox自建文件系统,具体创建在上一篇《busybox文件系统与简单驱动学习(1)》(https://blog.csdn.net/yongwenn163/article/details/80024833)已经介绍。
硬件环境:使用讯为精英版POP开发板。
3、参考代码出处
清华大学出版社网站www.tup.com.cn,搜索“Linux驱动开发入门与实战”关键词进入进行下载。
二、编译中问题解决
1、makefile文件修改
将makefile文件自带路径修改为目前自己itop412内核所在路径,确保内核被成功编译过,我的路径为
KERNELDIR ?= /home/ntu/iTOP4412/iTop4412_Kernel_3.0
2、编译指令
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
(1)问题:没有定义“devno”变量报错
解决:查看驱动代码中涉及“devno”的部分发现
static void VirtualDisk_setup_cdev(struct VirtualDisk *dev, int minor)
{
int err;
devno = MKDEV(VirtualDisk_major, minor);
与
int VirtualDisk_init(void)
{
int result;
dev_t devno = MKDEV(VirtualDisk_major, 0);
中都使用了“devno”变量,可能作者使用的内核系统版本不同的原因,在“VirtualDisk_setup_cdev”函数中直接使用变量是正确的,但在我自己的版本编译中出现问题,该指令意思为获取驱动设备号,经过分析最终只需要在“VirtualDisk_setup_cdev”函数中加上变量devno定义即可,修改后如下所示:
static void VirtualDisk_setup_cdev(struct VirtualDisk *dev, int minor)
{
int err;
dev_t devno = MKDEV(VirtualDisk_major, minor);
(2)问题:implicit declaration of function ‘kmalloc’
解决:缺少头文件,添加即可,参考:https://blog.csdn.net/tiany524/article/details/6358771
#include <linux/slab.h>
(3)问题:error: unknown field ‘ioctl’ specified in initializer
解决:该问题原因出在内核版本变化,ioctl定义有变化,细节不再深究。参考:https://blog.csdn.net/u012210613/article/details/50478962
ioctl函数第一个参数删除,删除后如下所示:
static int VirtualDisk_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
然后
.ioctl = VirtualDisk_ioctl,
修改为
.unlocked_ioctl = VirtualDisk_ioctl,
三、加载使用过程
1、手动加载
(1)将生成的ko文件通过nfs共享到板子之后,修改权限为可执行,然后insmod VirtualDisk.ko加载驱动进入内核,通过lsmod指令可以查阅到驱动已经进入内核,但在/dev文件下却无法看到。
(2)通过查阅资料,要想让驱动可以识别需要手动加载或者自动加载进去到/dev文件下,先讲手动模式。
(3)下面指令即可手动加载成功,其中 c 代表字符设备,200为主设备号,0为次设备号,参考https://blog.csdn.net/u011202336/article/details/9172109
mknod /dev/VirtualDisk c 200 0
2、自动加载
(1)自动加载需要在驱动中利用udev(mdev)来实现设备节点的自动创建。参考http://blog.sina.com.cn/s/blog_6405313801019xv9.html
https://blog.csdn.net/u013937069/article/details/51318610
(2)驱动代码开头定义类结构体
static struct class* Virtualdisk_class = NULL;
static struct device* Virtualdisk_device = NULL;
(3)VirtualDisk_init函数中添加类与设备创建
Virtualdisk_class = class_create(THIS_MODULE,"VirtualDisk");
if(IS_ERR(Virtualdisk_class))
{
printk("failed to create virtualdisk module class.\n");
unregister_chrdev_region(MKDEV(VirtualDisk_major, 0), 1);
return -1;
}
Virtualdisk_device = device_create(Virtualdisk_class,NULL,MKDEV(VirtualDisk_major, 0),NULL,"VirtualDisk");
if(IS_ERR(Virtualdisk_device))
{
printk("failed to create virtualdisk module device.\n");
unregister_chrdev_region(MKDEV(VirtualDisk_major, 0), 1);
return -1;
}
(4)VirtualDisk_exit设备卸载
device_destroy(Virtualdisk_class,MKDEV(VirtualDisk_major, 0));
class_destroy(Virtualdisk_class);
(5)编译问题:implicit declaration of function ‘class_create’ implicit declaration of function ‘class_device_
解决:添加头文件
#include <linux/device.h>
(6)然后继续insmod、ls /dev就可以成功自动加载设备到/dev文件下,可以进行使用。
(7)执行结果:./VirtualDiskTest
[ 749.210073] written 27 bytes(s) from 0
[ 749.212376] read 1024 bytes(s) from 0
[ 749.216144] read 1024 bytes(s) from 4
one two three four five six
two three four five six
四、所有代码
上干货,包括驱动代码、makefile、测试代码
1、VirualDisk.c
/*======================================================================
A VirtualDisk driver as an example of char device drivers
======================================================================*/
#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 <linux/device.h>
#define VIRTUALDISK_SIZE 0x2000 /*È«ŸÖÄÚŽæ×îŽó8K×ÖœÚ*/
#define MEM_CLEAR 0x1 /*È«ŸÖÄÚŽæÇåÁã*/
#define PORT1_SET 0x2 /*œ«port1¶Ë¿ÚÇåÁã*/
#define PORT2_SET 0x3 /*œ«port2¶Ë¿ÚÇåÁã*/
/*
#define VirtualDisk_MAGIC 100
#define MEM_CLEAR _IO(VirtualDisk_MAGIC,0)
#define PORT1_SET _IO(VirtualDisk_MAGIC,1)
#define PORT2_SET _IO(VirtualDisk_MAGIC,2)
*/
#define VIRTUALDISK_MAJOR 200 /*Ô€ÉèµÄVirtualDiskµÄÖ÷É豞ºÅΪ200*/
static int VirtualDisk_major = VIRTUALDISK_MAJOR;
/*VirtualDiskÉ豞œá¹¹Ìå*/
struct VirtualDisk
{
struct cdev cdev; /*cdevœá¹¹Ìå*/
unsigned char mem[VIRTUALDISK_SIZE]; /*È«ŸÖÄÚŽæ8K*/
int port1; /*Áœžö²»Í¬ÀàÐ͵Ķ˿Ú*/
long port2;
long count; /*ŒÇÂŒÉ豞Ŀǰ±»¶àÉÙÉ豞Žò¿ª*/
};
static struct class* Virtualdisk_class = NULL;
static struct device* Virtualdisk_device = NULL;
struct VirtualDisk *Virtualdisk_devp; /*É豞œá¹¹ÌåÖžÕë*/
/*ÎÄŒþŽò¿ªº¯Êý*/
int VirtualDisk_open(struct inode *inode, struct file *filp)
{
/*œ«É豞œá¹¹ÌåÖžÕëž³ÖµžøÎÄŒþËœÓÐÊýŸÝÖžÕë*/
filp->private_data = Virtualdisk_devp;
struct VirtualDisk *devp = filp->private_data;/*»ñµÃÉ豞œá¹¹ÌåÖžÕë*/
devp->count++;/*ÔöŒÓÉ豞Žò¿ªŽÎÊý*/
return 0;
}
/*ÎÄŒþÊͷź¯Êý*/
int VirtualDisk_release(struct inode *inode, struct file *filp)
{
struct VirtualDisk *devp = filp->private_data;/*»ñµÃÉ豞œá¹¹ÌåÖžÕë*/
devp->count--;/*ŒõÉÙÉ豞Žò¿ªŽÎÊý*/
return 0;
}
/* ioctlÉ豞¿ØÖƺ¯Êý */
//static int VirtualDisk_ioctl(struct inode *inodep, struct file
static int VirtualDisk_ioctl(struct file
*filp, unsigned
int cmd, unsigned long arg)
{
struct VirtualDisk *devp = filp->private_data;/*»ñµÃÉ豞œá¹¹ÌåÖžÕë*/
switch (cmd)
{
case MEM_CLEAR:/*É豞ÄÚŽæÇåÁã*/
memset(devp->mem, 0, VIRTUALDISK_SIZE);
printk(KERN_INFO "VirtualDisk is set to zero\n");
break;
case PORT1_SET:/*œ«¶Ë¿Ú1ÖÃ0*/
devp->port1=0;
break;
case PORT2_SET:/*œ«¶Ë¿Ú2ÖÃ0*/
devp->port2=0;
break;
default:
return - EINVAL;
}
return 0;
}
/*¶Áº¯Êý*/
static ssize_t VirtualDisk_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 VirtualDisk *devp = filp->private_data; /*»ñµÃÉ豞œá¹¹ÌåÖžÕë*/
/*·ÖÎöºÍ»ñÈ¡ÓÐЧµÄ¶Á³€¶È*/
if (p >= VIRTUALDISK_SIZE) /*Òª¶ÁÈ¡µÄÆ«ÒÆŽóÓÚÉ豞µÄÄÚŽæ¿ÕŒä*/
return count ? - ENXIO: 0;/*¶ÁÈ¡µØÖ·ŽíÎó*/
if (count > VIRTUALDISK_SIZE - p)/*Òª¶ÁÈ¡µÄ×֜ڎóÓÚÉ豞µÄÄÚŽæ¿ÕŒä*/
count = VIRTUALDISK_SIZE - p;/*œ«Òª¶ÁÈ¡µÄ×ÖœÚÊýÉèΪʣÓàµÄ×ÖœÚÊý*/
/*Äں˿Ռä->Óû§¿ÕŒäœ»»»ÊýŸÝ*/
if (copy_to_user(buf, (void*)(devp->mem + p), count))
{
ret = - EFAULT;
}
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "read %d bytes(s) from %d\n", count, p);
}
return ret;
}
/*ÐŽº¯Êý*/
static ssize_t VirtualDisk_write(struct file *filp, const char __user *buf,
size_t size, loff_t *ppos)
{
unsigned long p = *ppos; /*ŒÇÂŒÎÄŒþÖžÕëÆ«ÒÆλÖÃ*/
int ret = 0; /*·µ»ØÖµ*/
unsigned int count = size;/*ŒÇÂŒÐèҪЎÈëµÄ×ÖœÚÊý*/
struct VirtualDisk *devp = filp->private_data; /*»ñµÃÉ豞œá¹¹ÌåÖžÕë*/
/*·ÖÎöºÍ»ñÈ¡ÓÐЧµÄÐŽ³€¶È*/
if (p >= VIRTUALDISK_SIZE)/*ҪЎÈëµÄÆ«ÒÆŽóÓÚÉ豞µÄÄÚŽæ¿ÕŒä*/
return count ? - ENXIO: 0;/*ÐŽÈëµØÖ·ŽíÎó*/
if (count > VIRTUALDISK_SIZE - p)/*ҪЎÈëµÄ×֜ڎóÓÚÉ豞µÄÄÚŽæ¿ÕŒä*/
count = VIRTUALDISK_SIZE - p;/*œ«ÒªÐŽÈëµÄ×ÖœÚÊýÉèΪʣÓàµÄ×ÖœÚÊý*/
/*Óû§¿ÕŒä->Äں˿Ռä*/
if (copy_from_user(devp->mem + p, buf, count))
ret = - EFAULT;
else
{
*ppos += count;/*ÔöŒÓÆ«ÒÆλÖÃ*/
ret = count;/*·µ»ØʵŒÊµÄÐŽÈë×ÖœÚÊý*/
printk(KERN_INFO "written %d bytes(s) from %d\n", count, p);
}
return ret;
}
/* seekÎÄŒþ¶šÎ»º¯Êý */
static loff_t VirtualDisk_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = 0;/*·µ»ØµÄλÖÃÆ«ÒÆ*/
switch (orig)
{
case SEEK_SET: /*Ïà¶ÔÎÄŒþ¿ªÊŒÎ»ÖÃÆ«ÒÆ*/
if (offset < 0)/*offset²»ºÏ·š*/
{
ret = - EINVAL; /*ÎÞЧµÄÖžÕë*/
break;
}
if ((unsigned int)offset > VIRTUALDISK_SIZE)/*Æ«ÒÆŽóÓÚÉ豞ÄÚŽæ*/
{
ret = - EINVAL; /*ÎÞЧµÄÖžÕë*/
break;
}
filp->f_pos = (unsigned int)offset; /*žüÐÂÎÄŒþÖžÕëλÖÃ*/
ret = filp->f_pos;/*·µ»ØµÄλÖÃÆ«ÒÆ*/
break;
case SEEK_CUR: /*Ïà¶ÔÎÄŒþµ±Ç°Î»ÖÃÆ«ÒÆ*/
if ((filp->f_pos + offset) > VIRTUALDISK_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 VirtualDisk_fops =
{
.owner = THIS_MODULE,
.llseek = VirtualDisk_llseek,/*¶šÎ»Æ«ÒÆÁ¿º¯Êý*/
.read = VirtualDisk_read,/*¶ÁÉ豞º¯Êý*/
.write = VirtualDisk_write,/*ÐŽÉ豞º¯Êý*/
//.ioctl = VirtualDisk_ioctl,/*¿ØÖƺ¯Êý*/
.unlocked_ioctl = VirtualDisk_ioctl,/*¿ØÖƺ¯Êý*/
.open = VirtualDisk_open,/*Žò¿ªÉ豞º¯Êý*/
.release = VirtualDisk_release,/*ÊÍ·ÅÉ豞º¯Êý*/
};
/*³õÊŒ»¯²¢×¢²ácdev*/
static void VirtualDisk_setup_cdev(struct VirtualDisk *dev, int minor)
{
int err;
dev_t devno = MKDEV(VirtualDisk_major, minor);/*¹¹ÔìÉ豞ºÅ*/
cdev_init(&dev->cdev, &VirtualDisk_fops);/*³õÊŒ»¯cdevÉ豞*/
dev->cdev.owner = THIS_MODULE;/*ʹÇý¶¯³ÌÐòÊôÓÚžÃÄ£¿é*/
dev->cdev.ops = &VirtualDisk_fops;/*cdevÁ¬œÓfile_operationsÖžÕë*/
err = cdev_add(&dev->cdev, devno, 1);/*œ«cdev×¢²áµœÏµÍ³ÖÐ*/
if (err)
printk(KERN_NOTICE "Error in cdev_add()\n");
}
/*É豞Çý¶¯Ä£¿éŒÓÔغ¯Êý*/
int VirtualDisk_init(void)
{
int result;
dev_t devno = MKDEV(VirtualDisk_major, 0); /*¹¹œšÉ豞ºÅ*/
/* ÉêÇëÉ豞ºÅ*/
if (VirtualDisk_major) /* Èç¹û²»Îª0£¬ÔòŸ²Ì¬ÉêÇë*/
result = register_chrdev_region(devno, 1, "VirtualDisk");
else /* ¶¯Ì¬ÉêÇëÉ豞ºÅ */
{
result = alloc_chrdev_region(&devno, 0, 1, "VirtualDisk");
VirtualDisk_major = MAJOR(devno);/* ŽÓÉêÇëÉ豞ºÅÖеõœÖ÷É豞ºÅ */
}
if (result < 0)
return result;
Virtualdisk_class = class_create(THIS_MODULE,"VirtualDisk");
if(IS_ERR(Virtualdisk_class))
{
printk("failed to create virtualdisk module class.\n");
unregister_chrdev_region(MKDEV(VirtualDisk_major, 0), 1);
return -1;
}
Virtualdisk_device = device_create(Virtualdisk_class,NULL,MKDEV(VirtualDisk_major, 0),NULL,"VirtualDisk");
if(IS_ERR(Virtualdisk_device))
{
printk("failed to create virtualdisk module device.\n");
unregister_chrdev_region(MKDEV(VirtualDisk_major, 0), 1);
return -1;
}
/* ¶¯Ì¬ÉêÇëÉ豞œá¹¹ÌåµÄÄÚŽæ*/
Virtualdisk_devp = kmalloc(sizeof(struct VirtualDisk), GFP_KERNEL);
if (!Virtualdisk_devp) /*ÉêÇëʧ°Ü*/
{
result = - ENOMEM;
goto fail_kmalloc;
}
memset(Virtualdisk_devp, 0, sizeof(struct VirtualDisk));/*œ«ÄÚŽæÇåÁã*/
/*³õÊŒ»¯²¢ÇÒÌíŒÓcdevœá¹¹Ìå*/
VirtualDisk_setup_cdev(Virtualdisk_devp, 0);
return 0;
fail_kmalloc:
unregister_chrdev_region(devno, 1);
return result;
}
/*Ä£¿éжÔغ¯Êý*/
void VirtualDisk_exit(void)
{
cdev_del(&Virtualdisk_devp->cdev); /*×¢Ïúcdev*/
kfree(Virtualdisk_devp); /*ÊÍ·ÅÉ豞œá¹¹ÌåÄÚŽæ*/
device_destroy(Virtualdisk_class,MKDEV(VirtualDisk_major, 0));
class_destroy(Virtualdisk_class);
unregister_chrdev_region(MKDEV(VirtualDisk_major, 0), 1); /*ÊÍ·ÅÉ豞ºÅ*/
}
MODULE_AUTHOR("Zheng Qiang");
MODULE_LICENSE("Dual BSD/GPL");
module_param(VirtualDisk_major, int, S_IRUGO);
module_init(VirtualDisk_init);
module_exit(VirtualDisk_exit);
2、makefile
# To build modules outside of the kernel tree, we run "make"
# in the kernel source tree; the Makefile these then includes this
# Makefile once again.
# This conditional selects whether we are being included from the
# kernel Makefile or not.
ifeq ($(KERNELRELEASE),)
# Assume the source tree is where the running kernel was built
# You should set KERNELDIR in the environment if it's elsewhere
KERNELDIR ?= /home/ntu/iTOP4412/iTop4412_Kernel_3.0
# The current directory is passed to sub-makes as argument
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
# called from kernel build system: just declare what our modules are
obj-m := VirtualDisk.o
endif
3、VirtualDiskTest.c
/*
* File Name: VirtualDiskTest.c
*/
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
void main()
{
int fileno;/*ÓÃÓÚÎÄŒþ±íÊö·û*/
int number;
char data[]="one two three four five six";/*ÐŽÈëVirtualDiskµÄÊýŸÝ*/
char str[1024];/*Óû§¿ÕŒä»º³å*/
int len;
fileno = open("/dev/VirtualDisk",O_RDWR);/*ÒÔ¶ÁÐŽ·œÊœŽò¿ªÉ豞ÎÄŒþ*/
if (fileno == -1)/*Žò¿ªÎÄŒþʧ°Ü*/
{
printf("open VirtualDisk device errr!\n");
return 0;
}
write(fileno,data,strlen(data));/*œ«ÊýŸÝÐŽÈëÉ豞*/
close(fileno);/*¹Ø±ÕÉ豞ÎÄŒþ*/
fileno=open("/dev/VirtualDisk",O_RDWR);/*ÒÔ¶ÁÐŽ·œÊœŽò¿ªÉ豞ÎÄŒþ*/
len=read(fileno,str,1024);/*¶Á³öÉ豞ÖеÄÊýŸÝ*/
str[len]='\0';
printf("%s\n",str);/*ÏÔÊŸÉ豞µÄÊýŸÝ*/
close(fileno);/*¹Ø±ÕÉ豞ÎÄŒþ*/
fileno=open("/dev/VirtualDisk",O_RDWR);/*ÒÔ¶ÁÐŽ·œÊœŽò¿ªÉ豞ÎÄŒþ*/
lseek(fileno,4,SEEK_SET);/*œ«ÎÄŒþÖžÕëºóÒÆ4×֜ڣ¬µ±Ç°Î»ÖõÄ×Ö·ûΪt*/
len=read(fileno,str,1024);/*¶Á³öÉ豞ÖеÄÊýŸÝ*/
str[len]='\0';
printf("%s\n",str);/*ÏÔÊŸÉ豞µÄÊýŸÝ*/
close(fileno);/*¹Ø±ÕÉ豞ÎÄŒþ*/
return 0;
}