标签:printf init 时间 开始 虚拟 get write def path
目录
共享内存是可用IPC机制中最快的,一旦共享内存区映射到共享它的进程地址空间:
对比下面两张图所展示的例子:
mmap函数把一个文件或一个Posix共享内存对象映射到调用进程的地址空间,使用该函数有三个目的:
//成功返回映射内存的起始地址,失败返回MAP_FAILED
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
mmap参数解析:
PROT_READ | PROT_WRITE
MAP_SHARED
和MAP_PRIVATE
这两个标志中选择指定一个,进程间共享内存需要使用MAP_SHAREDprot | 说明 | flags | 说明 |
---|---|---|---|
PROT_READ | 数据可读 | MAP_SHARED | 变动是共享的 |
PROT_WRITE | 数据可写 | MAP_PRIVATE | 变动是私有的 |
PROT_EXEC | 数据可执行 | MAP_FIXED | 准确地解释addr参数 |
PROT_NONE | 数据不可访问 |
mmap成功返回后,可以关闭fd,这对已建立的映射关系没有影响。
注意,不是所有文件都能进行内存映射,例如终端和套接字就不可以。
mmap建立的映射关系通过munmap删除,其中addr是mmap返回的地址,len是映射区的大小,同mmap的参数len。
//成功返回0,失败返回-1
int munmap(void *addr, size_len);
默认情况下,内核采用虚拟内存算法保持内存映射文件与内存映射区的同步,前提是指定了MAP_SHARED标志,但这种同步可能不是立即生效的,而是在随后某个时间进行。
但有时候我们修改完数据并进行下一步操作之前,需要确认数据已经同步完成,这时可调用msync函数。
//成功返回0,失败返回-1
int msync(void *addr, size_t len, int flags);
其中addr和len含义同munmap,flags使用下表中的常值,其中MS_ASYNC
和MS_SYNC
这两个常值中必须选择指定一个。
flags | 说明 |
---|---|
MS_ASYNC | 执行异步写,msync立即返回 |
MS_SYNC | 执行同步写,msync等同步完成才返回 |
MS_INVALIDATE | 使高速缓存的数据失效 |
内存映射IO是父子进程之间共享内存区的一种方法,父进程fork前以MAP_SHARED方式调用mmap,其建立的内存映射关系会被子进程继承。
我们使用这个方法,来实现以下功能:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/mman.h>
#define MMAP_FILE "/home/delphi/mmap_file"
#define SEM_PATH "/sem_mmap"
struct Shared
{
int count;
};
int main(int argvc, char **argv)
{
struct Shared shared;
struct Shared *ptr; //mmap返回指针类型结构和映射文件数据结构需要一致
sem_t *mutex; //因为是进程间同步,不能用互斥锁,因此使用二值信号量模拟互斥锁
int fd;
int i;
fd = open(MMAP_FILE, O_CREAT | O_RDWR, 0666);
memset(&shared, 0, sizeof(shared));
write(fd, &shared, sizeof(shared)); //将映射文件内容初始化为0
ptr = mmap(NULL, sizeof(shared), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
mutex = sem_open(SEM_PATH, O_CREAT, 0666, 1); //二值信号量模拟互斥锁,初始值必须为1
sem_unlink(SEM_PATH);
setbuf(stdout, NULL); //设置标准输出为无缓冲
if (fork() == 0)
{
for (i = 0; i < 10; i++)
{
sem_wait(mutex);
printf("child: %d\n", ptr->count);
ptr->count++;
sem_post(mutex);
}
exit(0);
}
for (i = 0; i < 10; i++)
{
sem_wait(mutex);
printf("parent: %d\n", ptr->count);
ptr->count++;
sem_post(mutex);
}
return 0;
}
该示例代码使用Posix有名信号量来同步父子进程,共享内存区中仅有一个4字节计数器,信号量不在共享内存区中。
将上面这份代码修改一下,改为使用建立在共享内存区中的Posix无名信号量同步父子进程,示意图如下图所示。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/mman.h>
#define MMAP_FILE "/home/delphi/mmap_file"
struct Shared
{
sem_t mutex;
int count;
};
int main(int argvc, char **argv)
{
struct Shared shared;
struct Shared *ptr; //mmap返回指针类型结构和映射文件数据结构需要一致
int fd;
int i;
fd = open(MMAP_FILE, O_CREAT | O_RDWR, 0666);
memset(&shared, 0, sizeof(shared));
write(fd, &shared, sizeof(shared)); //将映射文件内容初始化为0
ptr = mmap(NULL, sizeof(shared), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
sem_init(&ptr->mutex, 1, 1); //Posix无名信号量建立在ptr指向的共享内存中
setbuf(stdout, NULL); //设置标准输出为无缓冲
if (fork() == 0)
{
for (i = 0; i < 10; i++)
{
sem_wait(&ptr->mutex);
printf("child: %d\n", ptr->count);
ptr->count++;
sem_post(&ptr->mutex);
}
exit(0);
}
for (i = 0; i < 10; i++)
{
sem_wait(&ptr->mutex);
printf("parent: %d\n", ptr->count);
ptr->count++;
sem_post(&ptr->mutex);
}
sem_destroy(&ptr->mutex);
return 0;
}
在上面展示的内存映射IO示例代码中,可以看出编码的前两步都是:
如果仅仅是用于父子进程共享内存,可以使用匿名内存映射来避免文件的显式创建打开以及初始化,其办法是:
把基于Posix无名信号量的示例代码改为匿名内存映射,可以看出,fork前的准备工作明显简化了许多。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/mman.h>
struct Shared
{
sem_t mutex;
int count;
};
int main(int argvc, char **argv)
{
struct Shared *ptr;
int i;
ptr = mmap(NULL, sizeof(struct Shared), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
sem_init(&ptr->mutex, 1, 1);
setbuf(stdout, NULL);
if (fork() == 0)
{
for (i = 0; i < 10; i++)
{
sem_wait(&ptr->mutex);
printf("child: %d\n", ptr->count);
ptr->count++;
sem_post(&ptr->mutex);
}
exit(0);
}
for (i = 0; i < 10; i++)
{
sem_wait(&ptr->mutex);
printf("parent: %d\n", ptr->count);
ptr->count++;
sem_post(&ptr->mutex);
}
sem_destroy(&ptr->mutex);
return 0;
}
前面讲的都是在父子进程间使用共享内存的技术,现在把共享内存区的概念扩展到任意进程之间,Posix.1提供了两种在任意进程间共享内存区的方法。
这两种技术都需要调用mmap,区别在于mmap参数fd的获取手段:
shm_open用于创建一个新的Posix共享内存对象或打开一个已存在的Posix共享内存对象。
shm_unlink用于从系统中删除一个Posix共享内存对象。
//成功返回非负描述符,失败返回-1
int shm_open(const char *name, int oflag, mode_t mode);
//成功返回0,失败返回-1
int shun_unlink(const char *name);
shm_open参数说明:
shm_open的返回值是一个描述符,它随后用作mmap的第五个参数fd。
处理mmap的时候,普通文件或Posix共享内存对象的大小都可以通过调用ftruncate设置。
#include <unistd.h>
//成功返回0,失败返回-1
int ftruncate(int fd, off_t length):
我们调用ftruncate来指定新创建的Posix共享内存对象大小,或者修改已存在的Posix共享内存对象大小。
#include <sys/stat.h>
#include <sys/types.h>
//成功返回0,失败返回-1
int fstat(int fd, struct stat *buf);
stat结构有12个或以上的成员,然而当fd指代一个Posix共享内存对象时,只有四个成员含有信息:
struct stat
{
mode_t st_mode; //用户访问权限
uid_t st_uid; //user id of owner
gid_t st_gid; //group id of owner
off_t st_size; //文件大小
};
示例代码包括一个.h文件和两个.c文件:
#ifndef _COMMON_H_
#define _COMMON_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/mman.h>
#define SHM_FILE "/shm_file"
#define SEM_PATH "/sem_mmap"
struct Shared
{
int count;
};
#endif
#include "common.h"
int main()
{
struct Shared *ptr;
sem_t *mutex;
int fd;
shm_unlink(SHM_FILE);
fd = shm_open(SHM_FILE, O_RDWR | O_CREAT, 0666);
ftruncate(fd, sizeof(struct Shared));
ptr = mmap(NULL, sizeof(struct Shared), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
sem_unlink(SEM_PATH);
mutex = sem_open(SEM_PATH, O_CREAT, 0666, 1);
sem_close(mutex);
pause();
return 0;
}
#include "common.h"
int main(int argc, char **argv)
{
struct Shared *ptr;
struct stat buf;
sem_t *mutex;
int fd;
int nloop;
int i;
fd = shm_open(SHM_FILE, O_RDWR, 0);
fstat(fd, &buf);
ptr = mmap(NULL, buf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
mutex = sem_open(SEM_PATH, 0);
nloop = atoi(argv[1]);
for (i = 0; i < nloop; i++)
{
sem_wait(mutex);
printf("pid %d: %d\n", getpid(), ptr->count++);
sem_post(mutex);
}
return 0;
}
编译并启动server,阻塞在pasue()中。
后台同时运行三个client进程。
截取进程切换时的部分输出片段,可以看到切换后计数依然是连续的。
#ifndef _COMMON_H_
#define _COMMON_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/mman.h>
#define SHM_FILE "/shm_file"
struct Shared
{
sem_t mutex;
int count;
};
#endif
#include "common.h"
int main()
{
struct Shared *ptr;
int fd;
shm_unlink(SHM_FILE);
fd = shm_open(SHM_FILE, O_RDWR | O_CREAT, 0666);
ftruncate(fd, sizeof(struct Shared));
ptr = mmap(NULL, sizeof(struct Shared), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
sem_init(&ptr->mutex, 1, 1);
pause();
return 0;
}
#include "common.h"
int main(int argc, char **argv)
{
struct Shared *ptr;
struct stat buf;
int fd;
int nloop;
int i;
fd = shm_open(SHM_FILE, O_RDWR, 0);
fstat(fd, &buf);
ptr = mmap(NULL, buf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
//共享内存中的mutex已由server初始化,client直接使用就可以了,不能重复初始化
nloop = atoi(argv[1]);
for (i = 0; i < nloop; i++)
{
sem_wait(&ptr->mutex);
printf("pid %d: %d\n", getpid(), ptr->count++);
sem_post(&ptr->mutex);
}
return 0;
}
编译并启动server,阻塞在pasue()中。
后台同时运行三个client进程。
截取进程切换时的部分输出片段,可以看到切换后计数依然是连续的。
标签:printf init 时间 开始 虚拟 get write def path
原文地址:https://www.cnblogs.com/songhe364826110/p/11530732.html