相关介绍
mmap函数把一个文件或者一个Posix共享内存区对象映射到调用进程的地址空间。使用该函数,可以达到三个目的。
1.使用普通文件以提供内存映射I/O。
2.使用特殊文件以提供匿名内存映射。
3.使用shm_open以提供无亲缘关系进程间的Posix共享内存区(本章不介绍,有兴趣可以了解)
相关系统调用
#include<sys/mman.h>
//若成功则返回被映射区的起始地址,若出错则为MAP_FAILED
void* mmap(void* addr, size_t len, int prot, int flags, int fd, off_t offset);
addr:可以制定描述符fd应被映射到进程内空间的起始地址。它通常被指定为一个空指针,这样告诉内核自己去选择起始地址。无论哪种情况下,该函数的返回值都是描述符fd所映射到内存区的起始地址。
len:映射到调用进程地址空间中的字节数,它从被映射文件开头起第offset个字节开始算。offset通常设置为0。
prot:内存映射区的保护由port参数指定。
prot | 说明 |
PROT_READ | 数据可读 |
PROT_WRITE | 数据可写 |
PROT_EXEC | 数据可执行 |
PROT_NONE | 数据不可访问 |
flags使用下图指定的值。MAP_SHARED或者MAP_PRIVATE这两-个标志必须指定一个,并可有选择地或上MAP_FIXED。
1,如果指定了MAP_PRIVATE,那么调用进程对被映射数据所作的修改只对该进程可见,而不改变其底层支撑对象(或者一个文件对象,或者一个共享内存区对象)。
2,如果指定了MAP_SHARED,那么调用进程对映射数据所做的修改对于共享该对象的所有进程可见,而且确实改变了其底层支撑对象。
Flags | 说明 |
MAP_SHARED | 变动是共享的 |
MAP_PRIVATE | 变动是自私的 |
MAP_ANON | 这个映射不基于任何文件 |
MAP_FIXED | 准确地解释addr参数 |
3 , 从移植性上考虑,MAP_FIXED不应该去指定。如果没有指定该标志,但是addr不是一个空指针,那么如何处置取决于实现。不为空的addr通常被当作有关该内存区应如何具体定位的线索。
4 , mmap返回后,fd参数可以关闭,该操作对于由mmap建立的映射关系没有影响。
#include <sys/mman.h>
int munmap(void *addr, size_t len);
其中addr参数是由mmap返回的地址,len是映射区的大小。再次访问这些地址将导致向调用进程产生一个SIGSEGV信号(当然这里假设以后的mmap调用并不重用这部分地址空间)。
相关结构
ngx_shmem.h
typedef struct {
u_char *addr;
size_t size;
ngx_str_t name;
ngx_log_t *log;
ngx_uint_t exists; /* unsigned exists:1; */
} ngx_shm_t;
相关代码
在NGX_HAVE_MAP_ANON宏开启的条件下,将采用MAP_ANON的方式去mmap和munmap。这是4.4BSD提供的匿名内存映射,它彻底避免了文件的创建和打开。
#if (NGX_HAVE_MAP_ANON)
//使用mmap进行alloc
ngx_int_t
ngx_shm_alloc(ngx_shm_t *shm)
{
//指定共享区的数据可读也可以写,并且是shared共享模式的。
shm->addr = (u_char *) mmap(NULL, shm->size,
PROT_READ|PROT_WRITE,
//对于MAP_ANON,将不基于//任何文件,所以文件fd为-1,offset为0,而且内容将被初始化为0.
MAP_ANON|MAP_SHARED, -1, 0);
//出错将返回MAP_FAILED。
if (shm->addr == MAP_FAILED) {
ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,"mmap(MAP_ANON|MAP_SHARED, %uz) failed", shm->size);
return NGX_ERROR;
}
return NGX_OK;
}
//使用munmap进行free
void
ngx_shm_free(ngx_shm_t *shm)
{
//里面的addr是mmap里面返回的addr,size也是mmap里面分配的size
if (munmap((void *) shm->addr, shm->size) == -1){
ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,"munmap(%p, %uz) failed", shm->addr, shm->size);
}
}
#elif (NGX_HAVE_MAP_DEVZERO)
细心的读者在最后可以看到#elif宏,也就是在NGX_HAVE_MAP_ANON没有开启的时候,将采取映射/dev/zero的方式去共享内存
#elif (NGX_HAVE_MAP_DEVZERO)
ngx_int_t
ngx_shm_alloc(ngx_shm_t *shm)
{
ngx_fd_t fd;
//open一个文件,将其作为映射,这是一个特殊文件
fd = open("/dev/zero", O_RDWR);
if (fd == -1) {
ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,"open(\"/dev/zero\") failed");
return NGX_ERROR;
}
//调用mmap和上面的例子一样,不过多填进去fd和少1个MAP_ANON,因为该种方式采用特殊文件,不是匿名方式了。
shm->addr = (u_char *) mmap(NULL, shm->size, PROT_READ|PROT_WRITE,MAP_SHARED, fd, 0);
if (shm->addr == MAP_FAILED) {
ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,"mmap(/dev/zero, MAP_SHARED, %uz) failed", shm->size);
}
//关掉fd文件描述符,前面已经提到,这并没什么卵用,可以直接关掉。
if (close(fd) == -1) {
ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,"close(\"/dev/zero\") failed");
}
return (shm->addr == MAP_FAILED) ? NGX_ERROR : NGX_OK;
}
上述是SVR4提供/dev/zero设备文件,我们open它之后的描述符可以再mmap中使用,从设备读取返回的字节全为0,写往该设备的任何字节则被丢弃。而ngx_shm_free和匿名方式一样,我就不在赘述。而共享内存还使用了第三种方式去共享,那是在这两种都没有的情况下,我们将留到下一章赘述。