我们知道docker容器与HOST端有一定的隔离性,但同时也共享着一些资源,比如内存资源。今天我们就看一下Docker容器通过/dev/mem设备节点与HOST共享一段物理内存。
要达到这个目的需进行如下操作:1) 划分一段用于映射到/dev/mem设备文件的保留物理内存;2) 准备一段在容器和HOST端可运行的映射和读写物理内存的代码;3) 启动一个带有--privileged参数的容器; 4) 容器和HOST端分别通过程序映射和读写内存。
一 保留物理内存
可以参考inux通过内核启动参数预留系统内存 这篇介绍在内核启动时,通过启动参数"memmap="来为系统保留一段内存。内核不会使用到这段保留出来的内存,这样我们就可以放心使用这段内存而不用担心冲突。
二 通过/dev/mem映射读写物理内存
有了保留内存,第二步就是写一个小代码,通过将这段物理内存映射到/dev/mem以进行读写访问。下面是代码dev_mem_rw.c:
#define _LARGEFILE64_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdbool.h>
#include <string.h>
#if __LP64__
#define strtoptr strtoull
#else
#define strtoptr strtoul
#endif
static inline size_t minsize(size_t input1, size_t input2)
{
return input1>input2 ? input2:input1;
}
static int usage()
{
#ifdef READ
fprintf(stderr,"dev_mem_read <address>\n");
#elif defined WRITE
fprintf(stderr,"dev_mem_write <address> <value>\n");
#endif
return -1;
}
int main(int argc, char *argv[])
{
int fd = -1, default_map_size = 32;
uintptr_t addr = 0, endaddr = 0;
off64_t mmap_start = 0;
size_t mmap_size = 0;
char *buff = NULL, *end = NULL;
void* pointer = NULL;
size_t page_size = getpagesize();
#ifdef READ
if(argc < 2)
#elif defined WRITE
if(argc < 3)
#endif
return usage();
addr = strtoptr(argv[1], 0, 16);
endaddr = 0;
end = strchr(argv[1], '-');
if (end)
endaddr = strtoptr(end + 1, 0, 16);
if (!endaddr)
endaddr = addr + default_map_size - 1;
if (endaddr <= addr) {
fprintf(stderr, "end address <= start address\n");
return -1;
}
fd = open("/dev/mem", O_RDWR | O_SYNC);
if(fd < 0) {
fprintf(stderr,"cannot open /dev/mem\n");
return -1;
}
mmap_start = addr & ~(page_size - 1);
mmap_size = endaddr - mmap_start + 1;
mmap_size = (mmap_size + page_size - 1) & ~(page_size - 1);
pointer = mmap64(0, mmap_size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, mmap_start);
if(pointer == MAP_FAILED) {
fprintf(stderr,"cannot mmap region\n");
return -1;
}
#ifdef WRITE
bzero(pointer, mmap_size);
#endif
buff = pointer;
#ifdef READ
printf("buff=%s\n", buff);
#elif defined WRITE
strncpy(buff, argv[2], minsize(mmap_size, strlen(argv[2])));
#endif
return 0;
}
接下来进行编译:
1) 编译生成内存读程序dev_mm_r:
gcc dev_mm_rw.c -static -DREAD -o dev_mm_r
2) 编译生成内存写程序dev_mm_w: ;
gcc dev_mem_rw.c -static -DWRITE -o dev_mm_w
三 创建测试容器
创建并启动一个容器tst_dev_mem:
docker create --name tst_dev_mem --privileged -it debian /bin/bash
docker start tst_dev_mem
四 进行测试验证
1 host端写Docker读
假设在第一步中我们预留物理内存的起始地址为0x10DBFFFFF,则操作如下:
./dev_mem_w 0x10DBFFFFF hello_world /* 将hello_world写入物理内存0x10DBFFFFF */
docker cp dev_mem_r tst_dev_mem:/ /* 将读内存程序拷贝到容器 */
docker exec tst_dev_mem /dev_mem_r 0x10DBFFFFF /* 容器读这段内存 */
2 host端读Docker写
docker cp dev_mem_w tst_dev_mem:/ /* 将写内存程序拷贝到容器 */
docker exec tst_dev_mem /dev_mem_w 0x10DBFFFFF hello_world /* 将hello_world写入物理内存0x10DBFFFFF */
./dev_mem_r 0x10DBFFFFF /* 容器读这段内存 */