[《[arm驱动]linux设备地址映射到用户空间》触及内核驱动函数二个,内核结构体二个,剖析了内核驱动函数二个;可参阅的相关运用程序模板或内核驱动模板二个,可参阅的相关运用程序模板或内核驱动四个
一、问题描绘:一般情况下,用户空间是不行能也不应该直接拜访设备的,可是,设备驱动程序中可完成mmap()函数,这个函数可运用户空间直接拜访设备的物理地址。
1、mmap()函数作业原理:mmap()完成了这样的一个映射进程,它将用户的内存空间的一般内存(精确来说是碑文mmap进程的映射区域内存)与设备内存相关,当用户拜访用户空间的这段地址规模时,实践上会转化为对设备的拜访(linux上全部皆文件)。
文件内存映射原理图示 a
2、mmap长处:1、关于设备文件,最大的有点便是用户空间能够直接拜访设备内存;2、一般文件被映射到进程地址空间后,进程进程拜访文件的速度也变块,不用再调read(),write(),能够用memcpy,strcpy等操作写文件,写完后用msync()同步一下。(感觉仍是很笼统,看了后边的实例一就理解了)
3、运用场景:mmap()的这种才能用于显现适配器一类的设备,屏幕帧的像素不再需求从一个用户空间到内核空间的仿制进程。
二、运用程序相关函数
1、树立映射:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
a) 参数意义:
addr:指定映射的开端地址, 一般设为NULL, 由体系指定。
length: 映射到内存的文件长度。
prot: 映射区的维护方法, 能够是:
PROT_EXEC: 映射区可被碑文
PROT_READ: 映射区可被读取
PROT_WRITE: 映射区可被写入
PROT_NONE 映射区不行拜访.
flags: 映射区的特性, 能够是:
MAP_SHARED:对此区域所做的修正内容奖写入文件内;答应其他映射该文件的进程同享,意思是:n个mmap.out程序在运转,这n个进程的“虚拟内存区域”的物理空间空间都相同。详看《虚拟内存同享原理图b》
虚拟内存同享原理图b
MAP_PRIVATE:对此区域所做的修正不会更改本来的文件内容,对映射区的写入操作会发生一个映射区的仿制(copy-on-write);意思是:n个mmap.out程序在运转,可是虚拟内存区域的物理地址会被内核别的分配。详看《虚拟内存同享原理图c》
虚拟内存同享原理图c
fd: 由open回来的文件描绘符, 代表要映射的文件。
offset: 以文件开端处的偏移量, 有必要是分页巨细的整数倍, 一般为0, 一共从文件头开端映射。
b)回来值:回来成功—-函数的回来值为最终文件映射到进程空间的地址(参照文件内存映射原理图示 a),进程可直接操作开端地址为该值的有用地址。回来失利回来MAP_FAILED(-1),过错原因存于errno 中。
2、免除映射:
int munmap(void *addr, size_t length);
3、 同步回写函数:
int msync(const void *start, size_t length, int flags);
如果您期望立行将数据写入文件中,可运用msync。
a)参数
start为记忆体开端方位(mmap函数回来的值—地址),length为长度。
flags则有三个:
MS_ASYNC : 请Kernel快将材料写入,宣布回写恳求后当即回来
MS_SYNC : 在msync完毕回来前,将材料写入。
MS_INVALIDATE运用回写的内容更新该文件的其它映射
实例一)mmap一般文件被映射到进程地址空间实例
mmapfile.c
#include
#include
#include
#include
#include
#include
#include
#include
void printfMapChar(char *nameChar, char *mapChar){//打印mapChar的内容
printf(“%s = %s\n\n”, nameChar,mapChar);
}
void printfMmapAddr(char *nameChar, char *mmapChar){//打印mmapChar的地址
printf(“%saddress = %p\n”,nameChar, mmapChar);
}
void printfDivLine(char *desc){
printf(“********%s*******\n”, desc);
}
int main(){
int fd;
char *mapChar;//mapchar寄存虚拟内存地址
char *checkChar;//验证是否mapChar是映射地址
printf(“mypid is %d\n”,getpid());//输出本pid
/*取得映射区域地址,赋值mapChar*/
fd = open(“/tmp/test.txt”,O_RDWR);
mapChar = mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//取得映射区域地址MAP_SHARED更改mapchar后改动fd文件内容
/*****************/
Tip:此刻mapchar便是虚拟内存区域的物理地址部分的首地址;也便是《文件内存映射原理图示 a》中的fd文件存储映射部分对应的的首地址,当进车拜访mapchar这段地址规模时,实践上会转化为对文件fd的拜访
/********打印映射区域内容;和mapChar*********/
printfDivLine(“打印映射区域内容;和mapChar”);
printfMapChar(“mapChar”, mapChar);
/**************/
/*******经过mapChar将数据写入映射区域*******/
strcpy(mapChar, “writeSrc,writeSrc,writeSrc,writeSrc,writeSrc,writeSrc,”);//写入映射区域
printfDivLine(“经过mapChar将数据写入映射区域”);
printfMapChar(“mapChar”, mapChar);
/**********checkChar验证*********/
checkChar = mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//取得映射区域地址
close(fd);//不运用fd时就能够close
printfDivLine(“checkChar验证”);
printfMmapAddr(“mapChar”, mapChar);
printfMmapAddr(“checkChar”, checkChar);
printfMapChar(“checkChar”, checkChar);
munmap(mapChar, 100);//开释mapchar的映射,此刻文件的映射在内存内然存在
munmap(checkChar, 100);
return 0;
}
运转成果:
mypid is 28529
********打印映射区域内容;和mapChar*******
mapChar = this is a just test temp file
********经过mapChar将数据写入映射区域*******
mapChar = writeSrc,writeSrc,writeSrc,writeSrc,writeSrc,writeSrc,
********checkChar验证*******
mapCharaddress = 0x7f356eaaa000
checkCharaddress = 0x7f356eaa9000
checkChar = writeSrc,writeSrc,writeSrc,writeSrc,writeSrc,writeSrc,
Tip:一个进程的内存映象由下面几部分组成:程序代码、数据、BSS和栈区域,以及内存映射的区域。一个进程的内存区域能够经过检查/proc/pid/maps
三、给驱动设备添加mmap虚拟内存映射
内核函数一)1、 驱动中的mmap(内核空间):
int(*mmap)(struct file *,struct vm_area_struct *);
2、在struct file_operations中与mmap接口函数相关
static struct file_operations module_drv_fops = {
//…………
.mmap = memdev_mmap,
//……………
}
结构体一)3、struct vm_area_struct(VMA)结构体如下
struct vm_area_struct {
struct mm_struct * vm_mm; /* The address space we belong to. */
unsigned long vm_start; /* Our start address within vm_mm. */
unsigned long vm_end; /* The first byte after our end address within vm_mm. */
/* linked list of VM areas per task, sorted by address */
struct vm_area_struct *vm_next;
pgprot_t vm_page_prot; /* Access permissions of this VMA. */
unsigned long vm_flags; /* Flags, listed below. */
struct rb_node vm_rb;
struct vm_operations_struct * vm_ops;
/* Information about our backing store: */
unsigned long vm_pgoff; /* Offset (within vm_file) in PAGE_SIZE units, *not* PAGE_CACHE_SIZE */
struct file * vm_file; /* File we map to (can be NULL). */
void * vm_private_data; /* was vm_pte (shared mem) */
unsigned long vm_truncate_count;/* truncate_count or restart_addr */
//………………
};
4、struct vm_area_struct(VMA)结构体flag参数
VM_IO将该VMA标记为内存映射的IO区域,VM_IO会阻挠体系将该区域包含在进程的寄存转存(core dump )中
VM_RESERVED标志内存区域不能被换出
内核函数二)5、内核mmap中创立页表函数:remap_pfn_range();
效果用“addr ~ addr + size之间的虚拟地址”结构页表,参阅《虚拟内存同享原理图b》和《虚拟内存同享原理图c》
int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
unsigned long pfn, unsigned long size, pgprot_t prot)
a)参数
1) vma: 虚拟内存区域指针(默许运用vma)
2)addr: 虚拟地址的开端值(默许运用vma->vm_start)
3)pfn:总的来说(pfn = virt_to_phys(void *mem))>>PAGE_SHIFT(常用);或运用默许方法vma->vm_pgoff)
推理:pfn是虚拟地址应该映射到的物理地址地点的物理页帧号便是物理地址右移PAGE_SHIFT位,若PAGE_SIZE为4k则PAGE_SHIFT为12(2的12次方为4k),因而PAGE_SIZE为1<
4)size: 要映射的区域的巨细。(默许运用vma->vm_end – vma->vm_start)
5)prot: VMA的维护特点。(默许运用vma->vm_page_prot)
模板一)6、mmap驱动模板
static int memdev_mmap(struct file*file, struct vm_area_struct *vma){
struct VirtualDisk *devp = file->private_data; /*鑾峰緱璁惧缁撴瀯浣撴寚閽?/
vma->vm_flags |= VM_IO;
vma->vm_flags |= VM_RESERVED;
if (remap_pfn_range(vma,vma->vm_start,virt_to_phys(devp->mem)>>PAGE_SHIFT, vma->vm_end – vma->vm_start, vma->vm_page_prot))
return -EAGAIN;
return 0;
}
file_oprations中添加或修正.mmap
static struct file_operations module_drv_fops = {
//…………
.mmap = memdev_mmap,
//……………
}
实例二)7、完好实例
a)驱动部分
//“module_drv”,”module_”,”module”
#include
#include
#include
#include
#include //在内核和用户空间中移动数据的函数
#include
#include
#include
#include
#define VIRTUALDISK_SIZE 0x1000//4k
#define MEM_CLEAR 0x1
#define VIRTUALDISK_MAJOR 250
int VirtualDisk_major = VIRTUALDISK_MAJOR;
struct VirtualDisk{
struct cdev cdev;//详细看cdev机制
unsigned char mem[VIRTUALDISK_SIZE ];
long count; /*记载设备现在被多少设备翻开*/
};
static struct class *module_class;
static struct class_device *module_class_dev;
struct VirtualDisk *VirtualDiskp;
static int module_drv_open(struct inode *inode, struct file *file)
{
printk(“module_dev read\n”);
file->private_data = VirtualDiskp;
VirtualDiskp->count++; /*添加设备翻开次数*/
return 0;
}
static int module_drv_release(struct inode *inode, struct file *file)
{
printk(“module_dev release\n”);
VirtualDiskp->count–; /*削减设备翻开次数*/
return 0;
}
/*seek文件定位函数:seek()函数对文件定位的开端地址能够是文件最初(SEEK_SET,0)、当时方位(SEEK_CUR,1)、文件尾(SEEK_END,2)*/
static loff_t module_drv_llseek(struct file *file, loff_t offset, int origin){
loff_t ret = 0;/*回来的方位偏移*/
switch (origin)
{
case SEEK_SET: /*相对文件开端方位偏移*/
if (offset < 0)/*offset不合法*/
{
ret = – EINVAL; /*无效的指针*/
break;
}
if ((unsigned int)offset > VIRTUALDISK_SIZE)/*偏移大于设备内存*/
{
ret = – EINVAL; /*无效的指针*/
break;
}
file->f_pos = (unsigned int)offset; /*更新文件指针方位*/
ret = file->f_pos;/*回来的方位偏移*/
break;
case SEEK_CUR: /*相对文件当时方位偏移*/
if ((file->f_pos + offset) > VIRTUALDISK_SIZE)/*偏移大于设备内存*/
{
ret = – EINVAL;/*无效的指针*/
break;
}
if ((file->f_pos + offset) < 0)/*指针不合法*/
{
ret = – EINVAL;/*无效的指针*/
break;
}
file->f_pos += offset;/*更新文件指针方位*/
ret = file->f_pos;/*回来的方位偏移*/
break;
default:
ret = – EINVAL;/*无效的指针*/
break;
}
return ret;
}
/*设备操控函数:ioctl()函数承受的MEM_CLEAR指令,这个指令将大局内存的有用数据长度清零,关于设备不支持的指令,ioctl()函数应该回来-EINVAL*/
static int module_drv_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg){
struct VirtualDisk *devp = file->private_data;/*取得设备结构体指针*/
switch (cmd)
{
case MEM_CLEAR:/*设备内存清零*/
memset(devp->mem, 0, VIRTUALDISK_SIZE);
printk(KERN_INFO “VirtualDisk is set to zero\n”);
break;
default:
return – EINVAL;
}
return 0;
}
/*读函数:读写函数主要是让设备结构体的mem[]数组与用户空间交互数据,并跟着拜访字节数改动回来用户的文件读写偏移方位*/
static ssize_t module_drv_read(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
unsigned long p = *ppos; /*记载文件指针偏移方位*/
unsigned int countt = count;/*记载需求读取的字节数*/
int ret = 0; /*回来值*/
struct VirtualDisk *devp = file->private_data; /*取得设备结构体指针*/
printk(“module_dev read\n”);
/*剖析和获取有用的读长度*/
if (p >= VIRTUALDISK_SIZE ) /*要读取的偏移大于设备的内存空间*/
return countt ? – ENXIO: 0;/*读取地址过错*/
if (countt > VIRTUALDISK_SIZE – p)/*要读取的字节大于设备的内存空间*/
countt = VIRTUALDISK_SIZE – p;/*酿制读取的字节数设为剩下的字节数*/
/*内核空间->用户空间交流数据*/
if (copy_to_user(buf, (void*)(devp->mem + p), countt))
{
ret = – EFAULT;
}
else
{
*ppos += countt;
ret = countt;
printk(“read %d bytes(s) is %ld\n”, countt, p);
}
printk(“bytes(s) is %s\n”, buf);
return ret;
}
/*
file 是文件指针,count 是恳求的传输数据长度,buff 参数是指向用户空间的缓冲区,这个缓冲区或许保存要写入的数据,或许是一个寄存新读入数据的空缓冲区,该地址在内核空间不能直接读写,ppos 是一个指针指向一个”long offset type”目标, 它指出用户正在存取的文件方位. 回来值是一个”signed size type。写的方位相关于文件最初的偏移。
*/
static ssize_t module_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
unsigned long p = *ppos; /*记载文件指针偏移方位*/
int ret = 0; /*回来值*/
unsigned int countt = count;/*记载需求写入的字节数*/
struct VirtualDisk *devp = file->private_data; /*取得设备结构体指针*/
printk(“module_dev write\n”);
/*剖析和获取有用的写长度*/
if (p >= VIRTUALDISK_SIZE )/*要写入的偏移大于设备的内存空间*/
return countt ? – ENXIO: 0;/*写入地址过错*/
if (countt > VIRTUALDISK_SIZE – p)/*要写入的字节大于设备的内存空间*/
countt = VIRTUALDISK_SIZE – p;/*酿制写入的字节数设为剩下的字节数*/
/*用户空间->内核空间*/
if (copy_from_user(devp->mem + p, buf, countt))
ret = – EFAULT;
else
{
*ppos += countt;/*添加偏移方位*/
ret = countt;/*回来实践的写入字节数*/
printk(“written %d bytes(s) from%ld\n”, countt, p);
}
return ret;
return 0;
}
static int memdev_mmap(struct file*file, struct vm_area_struct *vma){
struct VirtualDisk *devp = file->private_data; /*取得设备结构体指针*/
vma->vm_flags |= VM_IO;
vma->vm_flags |= VM_RESERVED;
if (remap_pfn_range(vma,vma->vm_start,virt_to_phys(devp->mem)>>PAGE_SHIFT, vma->vm_end – vma->vm_start, vma->vm_page_prot))
return -EAGAIN;
return 0;
}
static struct file_operations module_drv_fops = {
.owner = THIS_MODULE, /* 这是一个宏,面向编译模块时主动创立的__this_module变量 */
.open = module_drv_open,
.read = module_drv_read,
.write = module_drv_write,
.release = module_drv_release,
.llseek = module_drv_llseek,
.ioctl = module_drv_ioctl,
.mmap = memdev_mmap,
};
/*将 cdev 结构嵌入一个你自己的设备特定的结构,你应当初始化你现已分配的结构运用以上函数,有一个其他的 struct cdev 成员你需求初始化. 象 file_operations 结构,struct cdev 有一个具有者成员,应当设置为 THIS_MODULE,一旦 cdev 结构树立, 最终的过程是把它告知内核, 调用:
cdev_add(&dev->cdev, devno, 1);*/
static void VirtualDisk_setup_cdev(struct VirtualDisk *dev, int minorIndex){
int err;
int devno = MKDEV(VirtualDisk_major, minorIndex);
cdev_init(&dev->cdev, &module_drv_fops);
dev->cdev.owner = THIS_MODULE;
err = cdev_add(&dev->cdev, devno, 1);
if(err){
printk(“error %d cdev file added\n”, err);
}
}
static int module_drv_init(void)
{
int result;
dev_t devno = MKDEV(VirtualDisk_major, 0);
if(VirtualDisk_major){
result = register_chrdev_region(devno, 1, “module”);
}else{
result = alloc_chrdev_region(&devno, 0, 1, “module”);
VirtualDisk_major = MAJOR(devno);
}
if(result < 0 ){
return result;
}
VirtualDiskp = kmalloc(sizeof(struct VirtualDisk), GFP_KERNEL);
if(!VirtualDiskp){
result = -ENOMEM;
goto fail_malloc;
}
memset(VirtualDiskp, 0, sizeof(struct VirtualDisk));
VirtualDisk_setup_cdev(VirtualDiskp, 0);
module_class = class_create(THIS_MODULE, “module_drv”);
if (IS_ERR(module_class))
return PTR_ERR(module_class);
module_class_dev = class_device_create(module_class, NULL, MKDEV(VirtualDisk_major, 0), NULL, “module”); /* /dev/xyz */
if (IS_ERR(module_class_dev))
return PTR_ERR(module_class_dev);
return 0;
fail_malloc:
unregister_chrdev_region(devno, 1);
return result;
}
static void module_drv_exit(void)
{
cdev_del(&VirtualDiskp->cdev);
kfree(VirtualDiskp);
unregister_chrdev_region(MKDEV(VirtualDisk_major, 0), 1);
class_device_unregister(module_class_dev);
class_destroy(module_class);
}
module_init(module_drv_init);
module_exit(module_drv_exit);
MODULE_L%&&&&&%ENSE(“GPL”);
实例三)b)与驱动对应的运用程序部分
#include
#include
#include
#include
#include
#include
int main(){
int fd;
char *start;
//char buf[100];
char *buf;
char *end;
char key_vals[20];
/*翻开文件*/
printf(“mypid is %d”,getpid());
fd = open(“/dev/module”,O_RDWR);
buf = (char *)malloc(100);
memset(buf, 0, 100);
start=mmap(NULL,10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
end = mmap(NULL, 20,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//addr为NULL,由体系分配
/* 读出数据 */
strcpy(buf,start);
sleep (1);
printf(“buf 1 = %s\n”,buf);
/* 写入数据 */
strcpy(start,”Buf Is Not Null!rgrgrfgfgfdg”);
memset(buf, 0, 100);
strcpy(buf,start);
sleep (1);
printf(“buf 2 = %s\n”,buf);
/* 写入数据 */
strcpy(end,”is it reality! is not sure,are you ok, make sure ,you”);
memset(buf, 0, 100);
strcpy(buf,start);
sleep (1);
printf(“buf 3 = %s\n”,buf);
printf(“buf 3 = %s\n”,end);
read(fd, key_vals, sizeof(key_vals));
printf(“key_vals 3 = %s\n”,key_vals);
// while(1);
munmap(start,10); /*免除映射*/
munmap(end,20); /*免除映射*/
free(buf);
close(fd);
return 0;
}
第三部分:struct stat 效果
1、stat,lstat,fstat1 函数都是获取文件(一般文件,目录,管道,socket,字符,块()的特点。函数原型#include
2、向stat,fstat1、lstat传入文件姓名path、fd、path,获取文件对应特点buf。
int stat(const char *path, struct stat *buf); //文件途径或文件名
int fstat(int fd, struct stat *buf);//文件描绘符
int lstat(const char *path, struct stat *buf);//衔接文件
结构体二)struct stat结构
struct stat {
mode_t st_mode; //文件对应的形式,文件,目录等
ino_t st_ino; //inode节点号
dev_t st_dev; //设备号码
dev_t st_rdev; //特别设备号码
nlink_t st_nlink; //文件的衔接数
uid_t st_uid; //文件所有者
gid_t st_gid; //文件所有者对应的组
off_t st_size; //一般文件,对应的文件字节数(常用)
time_t st_atime; //文件最终被拜访的时刻
time_t st_mtime; //文件内容最终被修正的时刻
time_t st_ctime; //文件状况改动时刻
blksize_t st_blksize; //文件内容对应的块巨细
blkcnt_t st_blocks; //文件内容对应的块数量
};
四、与mmap运用程序中“一般文件虚拟内存映射模板和实例
模板二)1、mmap()运用程序模板
int fd;
/*取得映射区域地址,赋值mapChar*/
fd = open(“/tmp/test.txt”,O_RDWR);
struct stat fileStat;
/* 获取文件的特点 */
if ((fstat(fd, &fileStat)) == -1) {
perror(“fstat”);
}
unsigned int fileBufferSize;
fileBufferSize = fileStat.st_size;/*mmap回写时,字节最大巨细
为fileStat.st_size,所以界说字节大fileStat.st_size*/
mmap(NULL,fileBufferSize,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//取得映射区域地址
munmap(checkChar, fileBufferSize);
实例四)2、完好实例
#include
#include
#include
#include
#include
#include
#include
#include
#include
void printfMapChar(char *nameChar, char *mapChar){
printf(“%s = %s\n\n”, nameChar,mapChar);
}
void printfDivLine(char *desc){
printf(“********%s*******\n”, desc);
}
int main(){
int fd;
char *mapChar;
char *checkChar;//验证是否mapChar是映射地址
struct stat fileStat;
printf(“mypid is %d\n”,getpid());//输出本pid
/*取得映射区域地址,赋值mapChar*/
fd = open(“/tmp/test.txt”,O_RDWR);
/* 获取文件的特点 */
if ((fstat(fd, &fileStat)) == -1) {
perror(“fstat”);
}
unsigned int fileBufferSize;
fileBufferSize = fileStat.st_size;/*mmap回写时,字节最大巨细
为fileStat.st_size,所以界说字节大fileStat.st_size*/
Tip:mmap回写时,回写字节最大巨细为fileStat.st_size,所以界说字节大fileStat.st_size。(这个我没有根据,仅仅试验成果是这样)
mapChar = mmap(NULL,fileBufferSize,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//取得映射区域地址MAP_SHARED更改mapchar后改动fd文件内容
/*****************/
/********打印映射区域内容;和mapChar*********/
printfDivLine(“打印映射区域内容;和mapChar”);
printfMapChar(“mapChar”, mapChar);
/**************/
/*******经过mapChar将数据写入映射区域*******/
strcpy(mapChar, “writeSrc,writeSrc,writeSrc,writeSrc,writeSrc,writeSrc,”);//写入映射区域
printfDivLine(“经过mapChar将数据写入映射区域”);
printfMapChar(“mapChar”, mapChar);
/**********checkChar验证*********/
checkChar = mmap(NULL,fileBufferSize,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//取得映射区域地址
close(fd);//不运用fd时就能够close
printfDivLine(“checkChar验证”);
printfMapChar(“checkChar”, checkChar);
munmap(mapChar, fileBufferSize);//开释mapchar的映射,此刻文件的映射在内存内然存在
munmap(checkChar, fileBufferSize);
return 0;
}