14 #include <linux/cdev.h>
15 #include <linux/string.h>
16 #include <linux/list.h>
17 #include <linux/pci.h>
18 #include <linux/gpio.h>
19
20
21 #define DEVICE_NAME "mymap"
22
23
24 static unsigned char array[10]={0,1,2,3,4,5,6,7,8,9};
25 static unsigned char *buffer;
26
27
28 static int my_open(struct inode *inode, struct file *file)
29 {
30 return 0;
31 }
32
33
34 static int my_map(struct file *filp, struct vm_area_struct *vma)
35 {
36 unsigned long page;
37 unsigned char i;
38 unsigned long start = (unsigned long)vma->vm_start;
39 //unsigned long end = (unsigned long)vma->vm_end;
40 unsigned long size = (unsigned long)(vma->vm_end - vma->vm_start);
41
42 //得到物理地址
43 page = virt_to_phys(buffer);
44 //将用户空间的一个vma虚拟内存区映射到以page开始的一段连续物理页面上
45 if(remap_pfn_range(vma,start,page>>PAGE_SHIFT,size,PAGE_SHARED))//第三个参数是页帧号,由物理地址右移PAGE_SHIFT得到
46 return -1; www.2cto.com
47
48 //往该内存写10字节数据
49 for(i=0;i<10;i++)
50 buffer[i] = array[i];
51
52 return 0;
53 }
54
55
56 static struct file_operations dev_fops = {
57 .owner = THIS_MODULE,
58 .open = my_open,
59 .mmap = my_map,
60 };
61
62 static struct miscdevice misc = {
63 .minor = MISC_DYNAMIC_MINOR,
64 .name = DEVICE_NAME,
65 .fops = &dev_fops,
66 };
67
68
69 static int __init dev_init(void)
70 {
71 int ret;
72
73 //注册混杂设备
74 ret = misc_register(&misc);
75 //内存分配 www.2cto.com
76 buffer = (unsigned char *)kmalloc(PAGE_SIZE,GFP_KERNEL);
77 //将该段内存设置为保留
78 SetPageReserved(virt_to_page(buffer));
79
80 return ret;
81 }
82
83
84 static void __exit dev_exit(void)
85 {
86 //注销设备
87 misc_deregister(&misc);
88 //清除保留
89 ClearPageReserved(virt_to_page(buffer));
90 //释放内存
91 kfree(buffer);
92 }
93
94
95 module_init(dev_init);
96 module_exit(dev_exit);
97 MODULE_LICENSE("GPL");
98 MODULE_AUTHOR("LKN@SCUT");
应用程序:
1 #include <unistd.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <fcntl.h>
6 #include <linux/fb.h>
7 #include <sys/mman.h>
8 #include <sys/ioctl.h>
9 www.2cto.com
10 #define PAGE_SIZE 4096
11
12
13 int main(int argc , char *argv[])
14 {
15 int fd;
16 int i;
17 unsigned char *p_map;
18
19 //打开设备
20 fd = open("/dev/mymap",O_RDWR);
21 if(fd < 0)
22 {
23 printf("open fail\n");
24 exit(1);
25 }
26
27 //内存映射
28 p_map = (unsigned char *)mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,fd, 0);
29 if(p_map == MAP_FAILED)
30 {
31 printf("mmap fail\n");
32 goto here;
33 } www.2cto.com
34
35 //打印映射后的内存中的前10个字节内容
36 for(i=0;i<10;i++)
37 printf("%d\n",p_map[i]);
38
39
40 here:
41 munmap(p_map, PAGE_SIZE);
42 return 0;
43 }
实际上,内存映射机制并不是完全为了共享内存的目的而设计的,它本身提供了不同于一般普通文件的访问方式,进程可以像访问内存一样对普通文件进程操作.而POSIX或System V共享内存IPC则纯粹是用于共享内存的目的.当然内存映射实现共享内存,也是内存映射的应用之一;
内存映射机制的用途:A、以访问内存的方式读写文件; B、实现共享内存;
、mmap()系统调用:
mmap()系统调用使得进程之间通过映射同一个普通文件而实现共享内存的目的.普通文件被映射到进程的地址空间之后,进程就可以像访问普通内存一样对文件进行访问,不必再调用read()、write()等系统调用操作.
mmap()系统调用介绍:
void* mmap(void* addr, size_t len, int prot, int flags, int fd, off_t offset);
该函数在进程的地址空间与文件对象或共享内存对象之间建立一种映射关系;
addr :该参数指定文件应该被映射到进程地址空间的起始地址,一般被指定为一个空指针,此时,程序把选择起始地址的任务留给内核来完成了.这个地址是进程地址空间中需要映射到文件中的内存区域的首地址;也就是说,在进程地址空间中用于文件映射的内存区域的首地址;
len :文件被映射到调用进程的地址空间中的字节数,它从被映射文件开头offset个字节处开始算起,取len个字节,把文件中的这len个字节的文件空间映射到进程的地址空间中;
port :指定文件被映射到内存中之后的访问权限.可取的值有:PORT_READ(可读)、PORT_WRITE(可写)、PORT_EXEC(可执行)、PORT_NONE(不可访问);
flags :映射标记;取值如下:MAP_SHARED、MAP_PRIVATE、MAP_FIXED,其中,MAP_SHARED和MAP_PRIVATE必选其一,而MAP_FIXED则不推荐使用;
fd :即将被映射到进程地址空间中的文件的描述符.一般由系统调用open()返回;同时,fd可以指定为-1,此时,必须指定flags参数中的MAP_ANON,表明进程的是匿名映射(不涉及具体的文件名,避免了文件的创建及打开,很显然,只能用于具有亲属关系的进程之间的通信).
offset:从文件开头计算offset个字节处开始映射;也就是,文件中需要被映射的文件内容的起始地址,这个起始地址的计算是以文件开头为参照的;这个参数一般取值为0,表示从文件开头处开始映射;
返回值:文件最终映射到进程地址空间中的起始地址;进程可直接以该地址为有效的起始地址进行操作;也就是文件中开始映射的起始字节点到进程中对应映射内存区的起始地址点处的一个映射;换句话就是说,在进程地址空间中用于文件映射的内存区域的首地址;
A、使用普通文件提供的内存映射/共享内存:适用于任何进程之间;此时,需要使用系统调用open()事先打开或创建一个文件,然后再调用mmap():
fd = open(filename, flag, mode);
......
ptr = mmap(NULL, len, PORT_READ|PORT_WRITE, MAP_SHARED, fd, 0);
使用特殊文件提供匿名内存映射:适用于具有亲属关系的进程之间;由于父子进程之间的这种特殊的父子关系,在父进程中先调用mmap(),然后调用fork(),那么,在调用fork()之后,子进程继承了父进程的所有资源,当然也包括匿名映射后的地址空间和mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了;
注意:这里不是一般的继承关系.一般来说,子进程单独维护从父进程继承下来的一些变量,而mmap()返回的地址却是由父子进程共同维护的;对于具有亲属关系的进程之间实现共享内存的最好方式应该是采用匿名映射的方式.此时,不必指定具体的条件,只要设置相应的标志即可.
当进程间通信结束时,需要解除文件页面空间到进程地址空间之间的映射关系;也就说,进程通信结束时,需要把挂载到进程地址空间上的文件卸载下来;这个任务由系统调用munmap();
int munmap(void* addr, size_t len);
该系统调用用于在进程地址空间中结束映射关系;
addr:是调用mmap()返回的进程地址空间中用于文件映射的内存区域的首地址;
len :进程地址空间中映射区域的大小,单位:字节;
当映射关系解除之后,对原来映射地址的访问将导致段错误发生;
返回值: -1:失败; 0:成功;
一般来说,进程在映射空间中对共享内容的修改并不会直接写回到磁盘文件中,往往在调用munmap()之后才会同步输出到磁盘文件中.那么,在程序运行过程中,在调用munmap()之前,可以通过调用msync()来实现磁盘上文件内容与共享内存区中的内容与一致;或者是把对共享内存区的修改同步输出到磁盘文件中;
注意:
1、最终被映射文件内容的长度不会超过文件本身的初始大小,即:内存映射操作不能改变文件的大小;
2、可以用于进程间通信的得有效地址空间大小大体上受限于被映射文件的大小,但是并不完全受限于文件大小.
在Linux中,内存的保护机制是以内存页为单位的,即使被映射的文件只有一个字节的大小,内核也会为这个文件的映射分配一个页面大小的内存空间.当被映射文件的大小小于一个页面大小时,进程可以对mmap()返回地址开始的一个页面大小进行访问,而不会出错;但是,如果对一个页面之外的地址空间进行访问,则导致错误发生.因此,可用于进程间通信的有效地址空间的大小不会超过被映射文件大小与一个页面大小的和;
3、文件一旦被映射之后,调用mmap()的进程对返回地址空间的访问就是对某一内存区域进行访问,暂时脱离了磁盘上文件的影响.所有对mmap()返回地址空间的操作只在内存范围内有意义,只有在调用了munmap()或msync()之后,才会把映射内存中的相应内容写回到磁盘文件中,所写内容的大小仍然不会超过被映射文件的大小;
Linux采用的是页式管理机制.对于用mmap()映射普通文件来说,进程会在自己的地址空间中新增加一块空间,空间的大小由mmap()的len参数指定,注意:进程并不一定能够对新增加的全部空间都进行有效的访问.进程能够访问的有效地址空间的大小取决于文件中被映射部分的大小.简单地说,能够容纳文件中被映射部分大小的最少页面个数决定了进程从mmap()返回的地址开始,能够访问的有效地址空间大小.超过这个空间大小,内核会根据超过的严重程度返回发送不同的信号给进程.
注意:决定进程能够访问的有效地址空间大小的因素是文件中被映射的部分,而不是整个文件;另外,如果指定了文件的偏移部分,一定要注意为页面大小的整数倍;
实际上,内存映射机制并不是完全为了共享内存的目的而设计的,它本身提供了不同于一般普通文件的访问方式,进程可以像访问内存一样对普通文件进程操作.而POSIX或System V共享内存IPC则纯粹是用于共享内存的目的.当然内存映射实现共享内存,也是内存映射的应用之一;
内存映射机制的用途:A、以访问内存的方式读写文件; B、实现共享内存;
、mmap()系统调用:
mmap()系统调用使得进程之间通过映射同一个普通文件而实现共享内存的目的.普通文件被映射到进程的地址空间之后,进程就可以像访问普通内存一样对文件进行访问,不必再调用read()、write()等系统调用操作.
mmap()系统调用介绍:
void* mmap(void* addr, size_t len, int prot, int flags, int fd, off_t offset);
该函数在进程的地址空间与文件对象或共享内存对象之间建立一种映射关系;
addr :该参数指定文件应该被映射到进程地址空间的起始地址,一般被指定为一个空指针,此时,程序把选择起始地址的任务留给内核来完成了.这个地址是进程地址空间中需要映射到文件中的内存区域的首地址;也就是说,在进程地址空间中用于文件映射的内存区域的首地址;
len :文件被映射到调用进程的地址空间中的字节数,它从被映射文件开头offset个字节处开始算起,取len个字节,把文件中的这len个字节的文件空间映射到进程的地址空间中;
port :指定文件被映射到内存中之后的访问权限.可取的值有:PORT_READ(可读)、PORT_WRITE(可写)、PORT_EXEC(可执行)、PORT_NONE(不可访问);
flags :映射标记;取值如下:MAP_SHARED、MAP_PRIVATE、MAP_FIXED,其中,MAP_SHARED和MAP_PRIVATE必选其一,而MAP_FIXED则不推荐使用;
fd :即将被映射到进程地址空间中的文件的描述符.一般由系统调用open()返回;同时,fd可以指定为-1,此时,必须指定flags参数中的MAP_ANON,表明进程的是匿名映射(不涉及具体的文件名,避免了文件的创建及打开,很显然,只能用于具有亲属关系的进程之间的通信).
offset:从文件开头计算offset个字节处开始映射;也就是,文件中需要被映射的文件内容的起始地址,这个起始地址的计算是以文件开头为参照的;这个参数一般取值为0,表示从文件开头处开始映射;
返回值:文件最终映射到进程地址空间中的起始地址;进程可直接以该地址为有效的起始地址进行操作;也就是文件中开始映射的起始字节点到进程中对应映射内存区的起始地址点处的一个映射;换句话就是说,在进程地址空间中用于文件映射的内存区域的首地址;
A、使用普通文件提供的内存映射/共享内存:适用于任何进程之间;此时,需要使用系统调用open()事先打开或创建一个文件,然后再调用mmap():
fd = open(filename, flag, mode);
......
ptr = mmap(NULL, len, PORT_READ|PORT_WRITE, MAP_SHARED, fd, 0);
使用特殊文件提供匿名内存映射:适用于具有亲属关系的进程之间;由于父子进程之间的这种特殊的父子关系,在父进程中先调用mmap(),然后调用fork(),那么,在调用fork()之后,子进程继承了父进程的所有资源,当然也包括匿名映射后的地址空间和mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了;
注意:这里不是一般的继承关系.一般来说,子进程单独维护从父进程继承下来的一些变量,而mmap()返回的地址却是由父子进程共同维护的;对于具有亲属关系的进程之间实现共享内存的最好方式应该是采用匿名映射的方式.此时,不必指定具体的条件,只要设置相应的标志即可.
当进程间通信结束时,需要解除文件页面空间到进程地址空间之间的映射关系;也就说,进程通信结束时,需要把挂载到进程地址空间上的文件卸载下来;这个任务由系统调用munmap();
int munmap(void* addr, size_t len);
该系统调用用于在进程地址空间中结束映射关系;
addr:是调用mmap()返回的进程地址空间中用于文件映射的内存区域的首地址;
len :进程地址空间中映射区域的大小,单位:字节;
当映射关系解除之后,对原来映射地址的访问将导致段错误发生;
返回值: -1:失败; 0:成功;
一般来说,进程在映射空间中对共享内容的修改并不会直接写回到磁盘文件中,往往在调用munmap()之后才会同步输出到磁盘文件中.那么,在程序运行过程中,在调用munmap()之前,可以通过调用msync()来实现磁盘上文件内容与共享内存区中的内容与一致;或者是把对共享内存区的修改同步输出到磁盘文件中;
注意:
1、最终被映射文件内容的长度不会超过文件本身的初始大小,即:内存映射操作不能改变文件的大小;
2、可以用于进程间通信的得有效地址空间大小大体上受限于被映射文件的大小,但是并不完全受限于文件大小.
在Linux中,内存的保护机制是以内存页为单位的,即使被映射的文件只有一个字节的大小,内核也会为这个文件的映射分配一个页面大小的内存空间.当被映射文件的大小小于一个页面大小时,进程可以对mmap()返回地址开始的一个页面大小进行访问,而不会出错;但是,如果对一个页面之外的地址空间进行访问,则导致错误发生.因此,可用于进程间通信的有效地址空间的大小不会超过被映射文件大小与一个页面大小的和;
3、文件一旦被映射之后,调用mmap()的进程对返回地址空间的访问就是对某一内存区域进行访问,暂时脱离了磁盘上文件的影响.所有对mmap()返回地址空间的操作只在内存范围内有意义,只有在调用了munmap()或msync()之后,才会把映射内存中的相应内容写回到磁盘文件中,所写内容的大小仍然不会超过被映射文件的大小;
Linux采用的是页式管理机制.对于用mmap()映射普通文件来说,进程会在自己的地址空间中新增加一块空间,空间的大小由mmap()的len参数指定,注意:进程并不一定能够对新增加的全部空间都进行有效的访问.进程能够访问的有效地址空间的大小取决于文件中被映射部分的大小.简单地说,能够容纳文件中被映射部分大小的最少页面个数决定了进程从mmap()返回的地址开始,能够访问的有效地址空间大小.超过这个空间大小,内核会根据超过的严重程度返回发送不同的信号给进程.
注意:决定进程能够访问的有效地址空间大小的因素是文件中被映射的部分,而不是整个文件;另外,如果指定了文件的偏移部分,一定要注意为页面大小的整数倍;
总之:采用内存映射机制mmap()来实现进程间通信是很方便的,在应用层上,调用接口非常简单,内部实现机制涉及到了Linux的存储管理以及文件系统等方面的内用;