================
linux 内存映射 驱动与引用的实现。实现内存映射 来通信:
在 Linux 系统中,内存映射(Memory Mapping)是一种高效的内存管理技术,它允许将一个文件或设备的内容映射到进程的地址空间中,从而使用户空间的进程可以通过指针直接访问这些内容。对于设备驱动来说,内存映射主要用于以下两个方面:
让用户空间程序直接访问设备内存。
实现用户空间程序和内核空间程序之间的高效通信。
以下是实现内存映射的一般步骤和示例代码:
1. 设备驱动内存映射的实现
(1) 设备驱动的内存映射函数实现
在设备驱动中,实现内存映射通常需要定义 mmap
文件操作函数,并在这个函数中调用 remap_pfn_range
或 vm_iomap_memory
等函数将设备内存区域映射到用户空间。
#include <linux/module.h> #include <linux/fs.h> #include <linux/mm.h> #include <linux/io.h> #include <linux/uaccess.h> #define DEVICE_NAME "mymemdev" #define MEM_BASE 0x10000000 #define MEM_SIZE 0x1000 static int mymemdev_open(struct inode *inode, struct file *file) { return 0; } static int mymemdev_release(struct inode *inode, struct file *file) { return 0; } static int mymemdev_mmap(struct file *file, struct vm_area_struct *vma) { unsigned long pfn = MEM_BASE >> PAGE_SHIFT; unsigned long size = vma->vm_end - vma->vm_start; if (size > MEM_SIZE) { return -EINVAL; } if (remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot)) { return -EAGAIN; } return 0; } static const struct file_operations mymemdev_fops = { .owner = THIS_MODULE, .open = mymemdev_open, .release = mymemdev_release, .mmap = mymemdev_mmap, }; static int __init mymemdev_init(void) { int ret; ret = register_chrdev(0, DEVICE_NAME, &mymemdev_fops); if (ret < 0) { printk(KERN_ERR "Failed to register device\n"); return ret; } printk(KERN_INFO "Device registered with major number %d\n", ret); return 0; } static void __exit mymemdev_exit(void) { unregister_chrdev(0, DEVICE_NAME); printk(KERN_INFO "Device unregistered\n"); } module_init(mymemdev_init); module_exit(mymemdev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("A simple memory mapped device driver");
(2) 编写用户空间程序访问内存映射
用户空间程序使用 mmap
系统调用将设备内存映射到自己的地址空间,然后可以通过指针直接访问这段内存。
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/mman.h> #include <unistd.h> #define DEVICE_PATH "/dev/mymemdev" #define MEM_SIZE 0x1000 int main() { int fd; void *mapped_mem; fd = open(DEVICE_PATH, O_RDWR); if (fd < 0) { perror("Failed to open device"); return EXIT_FAILURE; } mapped_mem = mmap(NULL, MEM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (mapped_mem == MAP_FAILED) { perror("Failed to mmap"); close(fd); return EXIT_FAILURE; }
2. 总结
通过上述步骤,你可以实现设备驱动中的内存映射功能,并让用户空间程序能够直接访问设备内存。这种方法不仅可以提高访问效率,还可以简化内核与用户空间之间的通信。关键在于在驱动中正确实现 mmap
函数,并在用户空间程序中正确使用 mmap
系统调用。
===========================================
go 使用 mmap 内存映射 应用程序 ,与 c 驱动程序 通信:
要在 Go 应用程序中使用 mmap 与 C 设备驱动程序通信,首先需要编写一个 C 设备驱动程序来实现内存映射,然后编写一个 Go 程序来利用 mmap 访问设备内存。以下是完整的实现步骤和代码示例:
1. 设备驱动内存映射的实现
1.1 设备驱动程序代码
以下是一个简单的 C 设备驱动程序,它实现了内存映射功能:
#include <linux/module.h> #include <linux/fs.h> #include <linux/mm.h> #include <linux/io.h> #include <linux/uaccess.h> #define DEVICE_NAME "mymemdev" #define MEM_BASE 0x10000000 #define MEM_SIZE 0x1000 static int mymemdev_open(struct inode *inode, struct file *file) { return 0; } static int mymemdev_release(struct inode *inode, struct file *file) { return 0; } static int mymemdev_mmap(struct file *file, struct vm_area_struct *vma) { unsigned long pfn = MEM_BASE >> PAGE_SHIFT; unsigned long size = vma->vm_end - vma->vm_start; if (size > MEM_SIZE) { return -EINVAL; } if (remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot)) { return -EAGAIN; } return 0; } static const struct file_operations mymemdev_fops = { .owner = THIS_MODULE, .open = mymemdev_open, .release = mymemdev_release, .mmap = mymemdev_mmap, }; static int __init mymemdev_init(void) { int ret; ret = register_chrdev(0, DEVICE_NAME, &mymemdev_fops); if (ret < 0) { printk(KERN_ERR "Failed to register device\n"); return ret; } printk(KERN_INFO "Device registered with major number %d\n", ret); return 0; } static void __exit mymemdev_exit(void) { unregister_chrdev(0, DEVICE_NAME); printk(KERN_INFO "Device unregistered\n"); } module_init(mymemdev_init); module_exit(mymemdev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("A simple memory mapped device driver");
编译并安装这个设备驱动程序后,你会得到一个字符设备 /dev/mymemdev
。
2. Go 应用程序使用 mmap 访问设备内存
在 Go 中,可以使用 golang.org/x/exp/mmap
包来进行内存映射。首先,确保你安装了这个包:
go get -u golang.org/x/exp/mmap
然后编写 Go 程序如下:
package main import ( "fmt" "os" "golang.org/x/exp/mmap" ) const MEM_SIZE = 0x1000 func main() {
3. 总结
通过上述步骤,你可以在 Go 应用程序中使用 mmap 与 C 设备驱动程序通信。设备驱动程序实现了内存映射功能,用户空间的 Go 程序通过 mmap 访问设备内存,从而实现了高效的内核空间和用户空间之间的通信。关键在于在驱动程序中正确实现 mmap
函数,并在 Go 程序中正确使用 mmap 包进行内存映射。
=======================================================================
C 头文件是一些 C 预处理器指令的集合,用于在 C 程序中声明各种变量和函数,以及定义需要使用的各种常量和宏等等。这些头文件通常包含在其他 C 程序代码文件之前,以便在编译时进行预处理。
在使用 mmap 时,需要头文件<sys/mman.h>
,该头文件中包含了 mmap 函数的声明以及与内存映射相关的常量和宏。具体来说,我们需要使用以下常量:
PROT_READ:表示区域可读。
PROT_WRITE:表示区域可写。
PROT_EXEC:表示区域可执行。
MAP_SHARED:表示映射区域可以被其他进程共享。
MAP_PRIVATE:表示映射区域是私有的,对该区域的修改不会影响其他映射到同一区域的进程。
MAP_FAILED:表示 mmap 函数执行失败。
为了使用 mmap 函数,需要先打开文件,使用文件描述符作为第五个参数。可以使用 open 函数或者其他文件操作函数打开文件。下面是一个使用 mmap 将一个文件映射到内存中的示例代码:
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FILE_SIZE 1024
int main () {
int fd = open ("test.txt" , O_RDWR);
char *p = mmap (NULL , FILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 );
if (p == MAP_FAILED) {
perror ("mmap failed" );
exit (EXIT_FAILURE);
}
strcpy (p, "Hello mmap!" );
msync (p, FILE_SIZE, MS_SYNC);
munmap (p, FILE_SIZE);
close (fd);
return 0 ;
}
复制代码
在这个例子中,我们打开一个名为"test.txt"的文件,并使用 mmap 将文件映射到内存中。然后我们修改了映射区域中的内容,并使用 msync 将内存区域中的内容写回文件中。最后,我们在程序结束前解除了映射并关闭了文件描述符。
=======================================================================
来自:https://geektutu.com/post/quick-go-mmap.html
go mmap:
Go Mmap 文件内存映射简明教程
1 mmap 简介
In computing, mmap is a POSIX-compliant Unix system call that maps files or devices into memory. It is a method of memory-mapped file I/O. – mmap - wikipedia.org
简单理解,mmap 是一种将文件/设备映射到内存的方法,实现文件的磁盘地址和进程虚拟地址空间中的一段虚拟地址的一一映射关系。也就是说,可以在某个进程中通过操作这一段映射的内存,实现对文件的读写等操作。修改了这一段内存的内容,文件对应位置的内容也会同步修改,而读取这一段内存的内容,相当于读取文件对应位置的内容。
mmap 另一个非常重要的特性是:减少内存的拷贝次数。在 linux 系统中,文件的读写操作通常通过 read 和 write 这两个系统调用来实现,这个过程会产生频繁的内存拷贝。比如 read 函数就涉及了 2 次内存拷贝:
操作系统读取磁盘文件到页缓存;
从页缓存将数据拷贝到 read 传递的 buf 中(例如进程中创建的byte数组)。
mmap 只需要一次拷贝。即操作系统读取磁盘文件到页缓存,进程内部直接通过指针方式修改映射的内存。因此 mmap 特别适合读写频繁的场景,既减少了内存拷贝次数,提高效率,又简化了操作。KV数据库 bbolt 就使用了这个方法持久化数据。
2 标准库 mmap
Go 语言标准库 golang.org/x/exp/mmap 仅实现了 read 操作,后续能否支持 write 操作未知。使用场景非常有限。看一个简单的例子:
从第4个byte开始,读取 tmp.txt 2个byte的内容。
1234567891011121314
package mainimport ( "fmt" "golang.org/x/exp/mmap")func main() { at, _ := mmap.Open("./tmp.txt") buff := make([]byte, 2) _, _ = at.ReadAt(buff, 4) _ = at.Close() fmt.Println(string(buff))}
123
$ echo "abcdefg" > tmp.txt$ go run .ef
如果使用 os.File
操作,代码几乎是一样的,os.File
还支持写操作 WriteAt
:
12345678910111213141516
package mainimport ( "fmt" "os")func main() { f, _ := os.OpenFile("tmp.txt", os.O_CREATE|os.O_RDWR, 0644) _, _ = f.WriteAt([]byte("abcdefg"), 0) buff := make([]byte, 2) _, _ = f.ReadAt(buff, 4) _ = f.Close() fmt.Println(string(buff))}
3 mmap(linux)
如果要支持 write 操作,那么就需要直接调用 mmap 的系统调用来实现了。Linux 和 Windows 都支持 mmap,但接口有所不同。对于 linux 系统,mmap 方法定义如下:
1
func Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, err error)
每个参数的含义分别是:
1234567891011
- fd:待映射的文件描述符。- offset:映射到内存区域的起始位置,0 表示由内核指定内存地址。- length:要映射的内存区域的大小。- prot:内存保护标志位,可以通过或运算符`|`组合 - PROT_EXEC
首先定义2个常量和数据类型Demo:
1234567891011121314
const defaultMaxFileSize = 1 << 30
内存有换页机制,映射的物理内存可以远小于文件。
Demo结构体由3个字段构成,file 即文件描述符,data 是映射内存的起始地址,dataRef 用于后续取消映射。
定义 mmap, grow, ummap 三个方法:
12345678910111213141516171819
func (demo *Demo) mmap() { b, err := syscall.Mmap(int(demo.file.Fd()), 0, defaultMemMapSize, syscall.PROT_WRITE|syscall.PROT_READ, syscall.MAP_SHARED) _assert(err == nil, "failed to mmap", err) demo.dataRef = b demo.data = (*[defaultMaxFileSize]byte)(unsafe.Pointer(&b[0]))}func (demo *Demo) grow(size int64) { if info, _ := demo.file.Stat(); info.Size() >= size { return } _assert(demo.file.Truncate(size) == nil, "failed to truncate")}func (demo *Demo) munmap() { _assert(syscall.Munmap(demo.dataRef) == nil, "failed to munmap") demo.data = nil demo.dataRef = nil}
mmap 传入的内存保护标志位为 syscall.PROT_WRITE|syscall.PROT_READ
,即可读可写,映射类型为 syscall.MAP_SHARED
,即对内存的修改会同步到文件。
syscall.Mmap
返回的是一个切片对象,需要从该切片中获取到内存的起始地址,并转换为可操作的 byte 数组,byte数组的长度为 defaultMaxFileSize
。
grow 用于修改文件的大小,Linux 不允许操作超过文件大小之外的内存地址。例如文件大小为 4K,可访问的地址是data[0~4095]
,如果访问 data[10000]
会报错。
munmap 用于取消映射。
在文件中写入 hello, geektutu!
12345678910111213141516
func main() { _ = os.Remove("tmp.txt") f, _ := os.OpenFile("tmp.txt", os.O_CREATE|os.O_RDWR, 0644) demo := &Demo{file: f} demo.grow(1) demo.mmap() defer demo.munmap() msg := "hello geektutu!" demo.grow(int64(len(msg) * 2)) for i, v := range msg { demo.data[2*i] = byte(v) demo.data[2*i+1] = byte(' ') }}
在调用 mmap
之前,调用了 grow(1)
,因为在 mmap
中使用 &b[0]
获取到映射内存的起始地址,所以文件大小至少为 1 byte。
接下来,便是通过直接操作 demo.data
,修改文件内容了。
运行:
123
$ go run .$ cat tmp.txth e l l o g e e k t u t u !
4 mmap(Windows)
相对于 Linux,Windows 上 mmap 的使用要复杂一些。
123456789101112131415161718
func (demo *Demo) mmap() { h, err := syscall.CreateFileMapping(syscall.Handle(demo.file.Fd()), nil, syscall.PAGE_READWRITE, 0, defaultMemMapSize, nil) _assert(h != 0, "failed to map", err) addr, err := syscall.MapViewOfFile(h, syscall.FILE_MAP_WRITE, 0, 0, uintptr(defaultMemMapSize)) _assert(addr != 0, "MapViewOfFile failed", err) err = syscall.CloseHandle(syscall.Handle(h)); _assert(err == nil, "CloseHandle failed")
需要 CreateFileMapping
和 MapViewOfFile
两步才能完成内存映射。MapViewOfFile
返回映射成功的内存地址,因此可以直接将该地址转换成 byte 数组。
Windows 对文件的大小没有要求,直接操作内存data
,文件大小会自动发生改变。
使用时无需关注文件的大小。
12345678910111213
func main() { _ = os.Remove("tmp.txt") f, _ := os.OpenFile("tmp.txt", os.O_CREATE|os.O_RDWR, 0644) demo := &Demo{file: f} demo.mmap() defer demo.munmap() msg := "hello geektutu!" for i, v := range msg { demo.data[2*i] = byte(v) demo.data[2*i+1] = byte(' ') }}
123
$ go run .$ cat .\tmp.txth e l l o g e e k t u t u !
附 参考
=======================================================================
参考:
https://xie.infoq.cn/article/5c3a8bcd73a0daba69a8e7c2d
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?