linux中将外设物理地址mmap映射到用户空间,通过在用户进程中访问映射的虚拟地址来读取外设的数据,并落盘,落盘时open函数使用O_DIRECT方式,直接write数据会报bad address错误,但如果使用posix_memalign申请一块空间来中转数据,再write就能写进去,这是为什么呢?
因为项目追求速度要快,所以使用O_DIRECT的方式,如果在途中还需要memcpy也会影响效率,有其他方法可以不经过缓存直接落盘吗?
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#define SHR_MEMSIZE 0x800000
#define MEM_CLEAR 0x0
#define MEM_RESET 0x1
#define MEM_DEV_FILENAME "/dev/mychrmem"
int main()
{
int fd;
int file_fd;
ssize_t ret;
char *shm = NULL;
char *rev_buf;
// posix_memalign((void**)&rev_buf, 512, SHR_MEMSIZE);
fd = open(MEM_DEV_FILENAME, O_RDWR);
if (fd < 0) {
printf("open(): %s\n", strerror(errno));
return -1;
}
file_fd = open("/mnt/test",O_WRONLY|O_CREAT|__O_DIRECT,0666);
if (file_fd < 0)
{
perror("open");
return -1;
}
shm = mmap(NULL, SHR_MEMSIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (MAP_FAILED == shm) {
printf("mmap: %s\n", strerror(errno));
}
printf("Before Write, shm = %s\n", shm);
strcpy(shm,"User write to share memory!");
printf("After write, shm = %s\n", shm);
while(1)
{
// memcpy(rev_buf, shm, SHR_MEMSIZE); //中转数据能成功,但影响效率
// ret = write(file_fd, rev_buf, SHR_MEMSIZE);
ret = write(file_fd, shm, SHR_MEMSIZE); //直接写会提示bad address
printf("mem addr is 0x%lx\n",(long)shm);
if(ret==-1)
{
perror("write");
}
else
{
printf("write len 0x%lx\n",ret);
}
}
munmap(shm, SHR_MEMSIZE);
close(file_fd);
close(fd);
return 0;
}
该回答引用ChatGPT
#include
#include
#include
#include
#include
#include
#include
#define PAGE_SIZE 4096
#define FILE_SIZE (PAGE_SIZE * 10)
int main()
{
int fd, file_fd;
char *buf, *aligned_buf;
size_t size = FILE_SIZE;
// 打开文件
file_fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC | O_DIRECT, 0666);
if (file_fd < 0) {
perror("open");
exit(1);
}
// 分配内存
if (posix_memalign((void **)&buf, PAGE_SIZE, size)) {
perror("posix_memalign");
exit(1);
}
// 映射物理地址到用户空间
fd = open("/dev/mem", O_RDWR | O_SYNC);
if (fd < 0) {
perror("open");
exit(1);
}
aligned_buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0x10000000);
if (aligned_buf == MAP_FAILED) {
perror("mmap");
exit(1);
}
// 将物理地址映射到的数据复制到对齐的内存中
memcpy(buf, aligned_buf, size);
// 写入文件
if (write(file_fd, buf, size) < 0) {
perror("write");
exit(1);
}
// 释放资源
munmap(aligned_buf, size);
free(buf);
close(file_fd);
close(fd);
return 0;
}
该回答引用ChatGPT
#include
#include
#include
#include
#include
#include
#include
#define PAGE_SIZE 4096
#define FILE_SIZE (PAGE_SIZE * 10)
int main()
{
int fd, file_fd;
char *buf, *aligned_buf;
ssize_t ret;
fd = open("/dev/mem", O_RDWR | O_SYNC);
if (fd < 0) {
perror("open");
return -1;
}
buf = mmap(NULL, FILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (buf == MAP_FAILED) {
perror("mmap");
return -1;
}
posix_memalign((void **)&aligned_buf, PAGE_SIZE, FILE_SIZE);
memcpy(aligned_buf, buf, FILE_SIZE);
file_fd = open("test.txt", O_WRONLY | O_CREAT | O_DIRECT, 0666);
if (file_fd < 0) {
perror("open");
return -1;
}
ret = write(file_fd, aligned_buf, FILE_SIZE);
if (ret < 0) {
perror("write");
return -1;
}
munmap(buf, FILE_SIZE);
free(aligned_buf);
close(file_fd);
close(fd);
return 0;
}
使用0_DIRECT方式时,操作系统会绕过文件系统缓存,直接将数据写入磁盘,但是需要满足一些硬件和软件的要求。其中一个限制是,数据必须是物理上连续的,而mmap映射的虚拟内存空间可能不是物理上连续的,因此直接使用0_DIRECT方式落盘会出现bad address错误。
使用posix_memalign申请一块空间来中转数据可以解决这个问题,因为posix_memalign可以保证申请到的内存是物理上连续的,这样就可以直接使用0_DIRECT方式将数据写入磁盘了。
另外,还有一种解决方法是将mmap映射的虚拟内存空间转换为物理内存地址,然后再使用0_DIRECT方式将数据写入磁盘。这种方法需要用到Linux内核提供的一些函数,例如virt_to_phys()函数可以将虚拟地址转换为物理地址。不过这种方法相对复杂,使用posix_memalign申请一块中转内存空间更为简单和可靠。
如果使用0_DIRECT方式将数据直接写入磁盘,那么在写入过程中是无法使用memcpy等内存拷贝函数的,因为这些函数需要访问缓存,而0_DIRECT方式是绕过缓存直接将数据写入磁盘的。
如果需要在写入过程中进行内存拷贝,可以考虑使用一块中转内存空间,将外设数据先拷贝到中转内存空间中,然后再使用0_DIRECT方式将中转内存空间中的数据直接写入磁盘。这样可以避免直接访问外设数据和直接访问磁盘数据之间的冲突,同时也可以使用内存拷贝函数提高数据处理的效率。
另外,也可以考虑使用异步IO来实现高效的文件写入。异步IO可以将文件写入操作提交给操作系统后立即返回,操作系统会在后台进行文件写入操作,写入完成后再通知进程。这样可以避免进程在等待文件写入完成时的阻塞,提高文件写入的效率。但是需要注意异步IO的使用需要考虑到线程安全和错误处理等问题,需要仔细设计和实现。
以下答案由GPT-3.5大模型与博主波罗歌共同编写:
在使用O_DIRECT方式写文件时,需要满足以下条件:
1.文件打开时需要指定O_DIRECT标记,才能使用O_DIRECT方式。
2.写入数据时需要满足对齐要求,也就是写入的数据需要和硬盘的扇区大小对齐。
3.写入的数据也需要是页对齐的(通常是4K),所以在使用mmap映射物理空间到用户空间时需要对齐。如果数据没有对齐,会导致bad address错误。
我们来看一下原来的代码:
ret = write(file_fd, shm, SHR_MEMSIZE);
由于我们没有对齐,导致使用O_DIRECT方式写入数据时出现bad address错误。
下面是一种解决方法:
1.将共享内存的地址按照硬盘扇区大小(通常为512字节)对齐,可以通过以下代码进行对齐:
char aligned_buf;
posix_memalign((void*)&aligned_buf, 512, SHR_MEMSIZE);
memcpy(aligned_buf, shm, SHR_MEMSIZE);
2.使用aligned_buf作为写入文件的缓冲区:
ret = write(file_fd, aligned_buf, SHR_MEMSIZE);
完整示例代码如下:(注释中有对齐操作的代码)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#define SHR_MEMSIZE 0x800000
#define MEM_CLEAR 0x0
#define MEM_RESET 0x1
#define MEM_DEV_FILENAME "/dev/mychrmem"已黑化的小白
int main()
{
int fd;
int file_fd;
ssize_t ret;
char *shm = NULL;
char *aligned_buf;
fd = open(MEM_DEV_FILENAME, O_RDWR);
if (fd < 0) {
printf("open(): %s\n", strerror(errno));
return -1;
}
file_fd = open("/mnt/test",O_WRONLY|O_CREAT|__O_DIRECT,0666);
if (file_fd < 0)
{
perror("open");
return -1;
}
shm = mmap(NULL, SHR_MEMSIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (MAP_FAILED == shm) {
printf("mmap: %s\n", strerror(errno));
}
// 对齐操作
posix_memalign((void**)&aligned_buf, 512, SHR_MEMSIZE);
memcpy(aligned_buf, shm, SHR_MEMSIZE);
printf("Before Write, shm = %s\n", shm);
strcpy(shm,"User write to share memory!");
printf("After write, shm = %s\n", shm);
while(1)
{
ret = write(file_fd, aligned_buf, SHR_MEMSIZE); // 使用对齐后的缓冲区
printf("mem addr is 0x%lx\n",(long)shm);
if(ret==-1)
{
perror("write");
}
else
{
printf("write len 0x%lx\n",ret);
}
}
munmap(shm, SHR_MEMSIZE);
close(file_fd);
close(fd);
return 0;
}
如果我的回答解决了您的问题,请采纳!
不知道你这个问题是否已经解决, 如果还没有解决的话:该回答引用ChatGPT:当使用mmap将物理空间映射到用户空间时,为了避免数据在传输过程中被缓存,应该使用O_DIRECT标志。这个标志告诉内核,数据应该直接从应用程序的缓冲区传输到物理设备,而不需要通过中间缓存。
在使用O_DIRECT标志时,要注意以下几点:
1 内存对齐:应用程序缓冲区的起始地址和长度都必须是物理设备块大小的倍数,否则会出现EINVAL错误。
2 文件描述符:使用O_DIRECT标志时,必须使用文件描述符进行IO操作,不能使用标准的文件IO函数(如fread和fwrite)。
3 内存释放:使用O_DIRECT标志时,必须注意在传输完毕后,释放缓冲区的内存。在内核中,缓冲区的大小必须与块设备的大小相同,因此如果释放不完整的缓冲区,可能会造成内存泄漏。
在你的代码中,你使用了O_DIRECT标志来打开文件,但在直接将映射的内存写入文件时,出现了“bad address”错误。这个错误通常是由于内存对齐问题引起的。在使用O_DIRECT标志时,应用程序缓冲区的起始地址和长度必须是块设备大小的倍数。在你的代码中,映射的内存地址可能不是块设备大小的倍数,因此导致了错误。
解决这个问题的方法是,使用posix_memalign函数来分配内存。posix_memalign函数可以保证内存地址和长度是块设备大小的倍数。在你的代码中,你已经使用了posix_memalign来分配内存,但是注释掉了memcpy和使用rev_buf的代码,这是不必要的。你可以使用posix_memalign来分配内存,并直接将映射的内存写入文件,而不需要使用memcpy。
以下是修改后的代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#define SHR_MEMSIZE 0x800000
#define MEM_CLEAR 0x0
#define MEM_RESET 0x1
#define MEM_DEV_FILENAME "/dev/mychrmem"
int main()
{
int fd;
int file_fd;
ssize_t ret;
char *shm = NULL;
char *rev_buf;
posix_memalign((void**)&rev_buf, 512, SHR_MEMSIZE);
fd = open(MEM_DEV_FILENAME, O_RDWR);
if (fd < 0) {
perror("open");
return -1;
}
file_fd = open("/mnt/test", O_WRONLY | O_CREAT | O_DIRECT, 0666);
if (file_fd < 0) {
perror("open");
return -1;
}
shm = mmap(NULL, SHR_MEMSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (MAP_FAILED == shm) {
printf("mmap: %s\n", strerror(errno));
}
printf("Before Write, shm = %s\n", shm);
strcpy(shm, "User write to share memory!");
printf("After write, shm = %s\n", shm);
while(1)
{
ret = write(file_fd, shm, SHR_MEMSIZE);
printf("mem addr is 0x%lx\n",(long)shm);
if(ret==-1)
{
perror("write");
}
else
{
printf("write len 0x%lx\n",ret);
}
}
munmap(shm, SHR_MEMSIZE);
close(file_fd);
close(fd);
return 0;
}
修改后的代码将使用正确的标志打开文件描述符,并在while循环中使用O_DIRECT模式直接将共享内存的内容写入文件,而不需要中转数据到另一块缓冲区。