Linux系统编程之使用存储映射与共享内存实现进程间通信详解以及例程分享

目录

linux进程间通信的主要方式

存储映射

存储映射相关API函数

mmap()

mummap()

匿名映射

存储映射注意事项

存储映射相关例程

例程分析

例程分享

共享内存

共享内存相关API函数

shmget()

shmat()

shmdt()

shmctl()

共享内存相关例程

例程分析

例程分享

存储映射与共享内存的关系


linux进程间通信的主要方式

  1. 管道(Pipe):管道可在具有亲缘关系的进程间搭建通道,用于PROCESS-PROCESS之间的通信。
  2. 信号量(Semaphore):主要作为进程间以及同一进程不同线程之间的一种锁机制,用于进程间的同步。
  3. 消息队列(Message queue):消息队列是由消息的链表,存放在内核中并由消息队列标识符标识的一种通信机制。
  4. 共享内存(Shared memory):映射一段可以被其他进程所共享的内存区域,这段共享内存区域可供多个进程进行读写,从而交换数据。
  5. 套接字(Socket):更为一般的进程间通信机制,可用于不同机器上的进程间的通信。底层可以是TCP或UDP协议。

        此外,Linux也支持其他的一些IPC方式,比如信号(Signal)、文件(File)、命名管道(Named pipe)等。这些IPC机制为进程间通信提供了不同的选择,它们各有特点,使用场景也不尽相同。合理选用可以提高多进程协同工作的效率。

存储映射

        在Linux中,存储映射是一种将文件或设备映射到进程地址空间的机制。这意味着进程可以使用指针(地址)直接访问文件或设备的内容,就好像它是在内存中一样。存储映射通常使用​​mmap​​系统调用来实现。

Linux系统编程之使用存储映射与共享内存实现进程间通信详解以及例程分享_第1张图片

存储映射的一些常见用途包括:

  1. 文件映射:将文件映射到进程的地址空间,可以使进程直接读取或写入文件,而无需使用标准的读写操作。这种方式通常比传统的读写操作更高效。
  2. 共享内存:通过将特定的内存区域映射到不同的进程中,可以实现进程间的共享内存通信。
  3. 匿名映射:可以映射一块未关联具体文件的内存区域,用于匿名共享内存和内存分配等操作。

        使用​​mmap​​系统调用创建存储映射时,需要指定要映射的文件描述符(如果是文件映射),映射的起始地址,映射的长度,映射的权限等参数。​​mmap​​调用返回的是映射区域的起始地址,进程可以通过该地址直接访问映射的内容。

        存储映射提供了一种高效的I/O操作方式,并且可以简化进程间通信的实现。但是,需要注意的是,使用存储映射时需要谨慎处理内存访问权限、同步和互斥等问题,以避免潜在的安全风险和数据一致性问题。

存储映射相关API函数
mmap()
功能
	将文件或设备映射到进程地址空间
头文件
	sys/mman.h
原型
	void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
参数
	addr:指定映射的起始地址,通常设为NULL,表示由系统自动分配。
    length:指定映射的长度,单位是字节。
   prot:指定映射区域的保护权限,包括PROT_READ(可读)、PROT_WRITE(可写)、PROT_EXEC(可执行)和PROT_NONE(无权限)等。
   flags:指定映射的选项,例如MAP_SHARED表示映射区域可以共享,MAP_PRIVATE表示映射区域是私有的。
   fd:指定要映射的文件描述符,如果是匿名映射则设为-1。
   offset:指定文件的偏移量,对于文件映射,表示从文件的哪个位置开始映射;对于匿名映射,通常设为0。
返回值
  成功	映射区域的起始地址
  失败	返回MAP_FAILED宏
  
使用mmap()函数可以实现如下功能:
  将文件映射到内存中,实现高效的文件I/O操作。
  实现共享内存,允许多个进程访问同一块物理内存空间。
  实现匿名映射,用于进程间通信或共享数据。

    需要注意的是,在使用mmap()函数时,需要注意对映射区域的访问权限、同步和互斥的处理,以避免潜在的安全风险和数据一致性问题。同时,在不再需要映射时,应该使用munmap()函数来解除映射。
mummap()
功能
	取消共享映射区
头文件
	sys/mman.h
原型
	int munmap(void *addr, size_t length)
参数
	addr:指定要解除映射的起始地址,通常是mmap()函数返回的映射区域的起始地址。
	length:指定要解除映射的长度,单位是字节。
返回值
	成功	0
    失败	-1
  
在解除映射时,应该确保不再有任何指针指向该映射区域,以免产生悬空指针的问题。
匿名映射

        通过使用我们发现,使用映射区来完成文件读写非常方便,父子间进程通信也比较容易,但缺陷时每次创建映射区一定要依赖一个文件才能实现,通常要建立映射区要open一个temp文件,创建好了再unlink,close掉,比较麻烦,可以直接使用匿名映射俩代替,起始Linux系统给我们提供了创建匿名映射区的方法,无需依赖一个文件即可创建映射区,同样需要借助标志位参数flags来指定。

使用 MAP_ANONYMOUS(或MAP_ANON)如:

int *p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0)
  其中 size 表示大小,可依实际需求填写

需要注意的是,MAP_ANONUMOUS和MAP_ANON都是Linux操作系统特有的宏,再类Unix系统中如果没有该宏定义,可使用以下两步完成宏定义

fd = open("/dev/zero", O_RDWR);
p = mmap(NULL, size, PROT_WRITE, MMAP_SHARED, FD, 0);
存储映射注意事项
  1. 用于创建映射区的文件大小为 0,实际指定非0大小创建映射区,出 “总线错误”。
  2. 用于创建映射区的文件大小为 0,实际制定0大小创建映射区, 出 “无效参数”。
  3. 用于创建映射区的文件读写属性为,只读。映射区属性为 读、写。 出 “无效参数”。
  4. 创建映射区,需要read权限。当访问权限指定为 “共享”MAP_SHARED是, mmap的读写权限,应该 <=文件的open权限。 只写不行。
  5. 文件描述符fd,在mmap创建映射区完成即可关闭。后续访问文件,用 地址访问。
  6. offset 必须是 4096的整数倍。(MMU 映射的最小单位 4k )
  7. 对申请的映射区内存,不能越界访问。
  8. munmap用于释放的 地址,必须是mmap申请返回的地址。
  9. 映射区访问权限为 “私有”MAP_PRIVATE, 对内存所做的所有修改,只在内存有效,不会反应到物理磁盘上。
  10. 映射区访问权限为 “私有”MAP_PRIVATE, 只需要open文件时,有读权限,用于创建映射区即可。
存储映射相关例程
例程分析

        这个例程展示了使用​​mmap()​​​函数进行内存映射的方式进行进程间通信。其中,进程1将一个​​struct student​​类型的数据写入到共享内存中,而进程2则从共享内存中读取并打印出这个数据。

mmap_w.c:

  1. 首先定义了一个​​struct student​​类型的变量​​stu​​,并初始化其成员。
  2. 打开一个文件描述符​​fd​​,用于后续的内存映射。
  3. 使用​​ftruncate()​​函数设置文件大小为​​sizeof(stu)​​,以确保共享内存的大小与数据结构的大小一致。
  4. 使用​​mmap()​​函数将共享内存映射到进程的地址空间中,指定了​​PROT_READ|PROT_WRITE​​权限和​​MAP_SHARED​​标志,以允许读写共享内存。
  5. 关闭文件描述符​​fd​​。
  6. 进入一个循环,在每次循环中,使用​​memcpy()​​函数将​​stu​​复制到共享内存中,然后修改​​stu​​的​​id​​并等待2秒。

mmap_r.c:

  1. 定义了一个​​struct student​​类型的变量​​stu​​。
  2. 打开一个文件描述符​​fd​​,用于后续的内存映射。
  3. 使用​​mmap()​​函数将共享内存映射到进程的地址空间中,指定了​​PROT_READ​​权限和​​MAP_SHARED​​标志,以允许读取共享内存。
  4. 关闭文件描述符​​fd​​。
  5. 进入一个循环,在每次循环中,从共享内存中读取数据并打印出来,然后等待10毫秒。
例程分享
/*mmap_w.c*/
#include 
#include 
#include 
#include 
#include 
#include 
#include 

struct student {
    int id;
    char name[256];
    int age;
};

void sys_err(const char *str)
{
	perror(str);
	exit(1);
}

int main(int argc, char *argv[])
{
    struct student stu = {1, "xiaoming", 18};
    struct student *p;
    int fd; 

//    fd = open("test_map", O_RDWR|O_CREAT|O_TRUNC, 0664);
    fd = open("test_map", O_RDWR);
    if (fd == -1)
        sys_err("open error");

    ftruncate(fd, sizeof(stu));

    p = mmap(NULL, sizeof(stu), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (p == MAP_FAILED)
        sys_err("mmap error");

    close(fd);

    while (1) {
        memcpy(p, &stu, sizeof(stu));
        stu.id++;
        sleep(2);
    }
    
    munmap(p, sizeof(stu));

	return 0;
}
/*mmap_r.c*/
#include 
#include 
#include 
#include 
#include 
#include 
#include 

struct student {
    int id;
    char name[256];
    int age;
};

void sys_err(const char *str)
{
	perror(str);
	exit(1);
}

int main(int argc, char *argv[])
{
    struct student stu;
    struct student *p;
    int fd; 

    fd = open("test_map", O_RDONLY);
    if (fd == -1)
        sys_err("open error");

    p = mmap(NULL, sizeof(stu), PROT_READ, MAP_SHARED, fd, 0);
    if (p == MAP_FAILED)
        sys_err("mmap error");

    close(fd);

    while (1) {
        printf("id= %d, name=%s, age=%d\n", p->id, p->name, p->age);
        usleep(10000);
    }
    
    munmap(p, sizeof(stu));

	return 0;
}

        需要注意的是,进程1和进程2都需要使用相同的文件描述符和文件名来进行内存映射,以确保它们使用的是同一个共享内存区域。此外,实际使用时需要注意进程间的同步和互斥问题,以确保数据的正确性。

共享内存

        在Linux中,进程间通信的一种常见方式是通过共享内存进行通信。共享内存允许多个进程访问同一块物理内存空间,从而实现进程间的数据共享。这种通信方式通常比管道或消息队列等方式更快,因为数据直接在内存中传递,而不需要进行内核空间和用户空间之间的数据拷贝。

在Linux中,使用共享内存进行进程间通信通常涉及以下步骤:

  1. 创建共享内存段:首先,需要调用系统调用 shmget 来创建一个新的共享内存段,或者获取一个已经存在的共享内存段的标识符。
  2. 将共享内存段连接到进程的地址空间:接下来,需要使用 shmat 系统调用将共享内存段连接到进程的地址空间中,这样进程就可以访问共享内存中的数据。
  3. 进行数据通信:一旦共享内存段被连接到进程的地址空间,进程就可以像访问普通内存一样访问共享内存中的数据。进程可以在共享内存中写入数据,也可以从中读取数据,从而实现进程间的通信。
  4. 分离共享内存段:当进程不再需要访问共享内存段时,需要使用 shmdt 系统调用将共享内存段从进程的地址空间中分禿。
  5. 删除共享内存段(可选):当所有进程都不再需要访问共享内存段时,可以使用 shmctl 系统调用来删除共享内存段,从而释放相关的系统资源。

        共享内存通信是一种高效的进程间通信方式,但也需要谨慎使用,因为共享内存的数据访问需要进程之间进行同步和互斥,以避免数据的不一致性和竞争条件。

共享内存相关API函数
shmget()
功能
	用于创建或获取一个共享内存标识符
头文件
  #include 
  #include 
原型
	int shmget(key_t key, size_t size, int shmflg)
参数
  key:共享内存的键值,用于标识共享内存对象。
  size:共享内存的大小,单位是字节。
  shmflg:创建或获取共享内存的标志位。
返回值
	成功       返回 int 型共享内存标识符, 用来操作共享内存
    失败        -1
shmat()
功能
	用于将共享内存附加到进程的地址空间中
头文件
	#include 
    #include 
原型
	void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
   shmid:共享内存的标识符。
   shmaddr:指定共享内存的附加地址,通常为NULL,表示由系统选择合适的地址。
   shmflg:共享内存的附加标志位。
返回值
	成功 共享内存的地址
    失败 
shmdt()
功能
	结束映射  不释放共享内存
头文件
	#include 
    #include 
原型
	int shmdt(const void *shmaddr);
参数
	shmaddr:指定要分离的共享内存的起始地址
返回值
	 成功 0
     失败 -1
shmctl()
功能
	修改或删除共享内存,常用作删除共享内存
头文件
	#include 
    #include 
原型
	int shmctl(int shmid, int cmd, struct shmid_ds *buf)
参数
	int shmid    shnget 返回值
    int cmd    宏定义    删除  IPC_RMID 
    struct shmid_ds *buf    结构体指针,修改用
返回值
	成功	0
    失败	-1
共享内存相关例程
例程分析

        在这个例程中,进程1创建了一个共享内存并向其中写入数据,而进程2则获取了这个共享内存并读取其中的数据。这样,两个不同的进程就通过共享内存实现了通信。需要注意的是,实际使用时需要考虑进程间的同步和互斥问题,以确保数据的正确性。

例程分享
/*进程1:写入数据到共享内存*/
#include 
#include 
#include 
#include 
#include 

#define SHM_SIZE 1024

int main() {
    int shmid;
    key_t key;
    char *shmaddr;

    // 生成共享内存的键值
    key = ftok(".", 'S');
    if (key == -1) {
        perror("ftok");
        exit(1);
    }

    // 创建共享内存
    shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
    if (shmid == -1) {
        perror("shmget");
        exit(1);
    }

    // 将共享内存附加到进程的地址空间
    shmaddr = (char *)shmat(shmid, NULL, 0);
    if (shmaddr == (char *)-1) {
        perror("shmat");
        exit(1);
    }

    // 在共享内存中写入数据
    strcpy(shmaddr, "Hello, shared memory from Process 1!");

    // 分离共享内存
    if (shmdt(shmaddr) == -1) {
        perror("shmdt");
        exit(1);
    }

    return 0;
}
/*进程2:读取共享内存中的数据*/
#include 
#include 
#include 
#include 
#include 

#define SHM_SIZE 1024

int main() {
    int shmid;
    key_t key;
    char *shmaddr;

    // 生成共享内存的键值
    key = ftok(".", 'S');
    if (key == -1) {
        perror("ftok");
        exit(1);
    }

    // 获取共享内存
    shmid = shmget(key, SHM_SIZE, 0666);
    if (shmid == -1) {
        perror("shmget");
        exit(1);
    }

    // 将共享内存附加到进程的地址空间
    shmaddr = (char *)shmat(shmid, NULL, 0);
    if (shmaddr == (char *)-1) {
        perror("shmat");
        exit(1);
    }

    // 从共享内存中读取数据并打印
    printf("Message from shared memory in Process 2: %s\n", shmaddr);

    // 分离共享内存
    if (shmdt(shmaddr) == -1) {
        perror("shmdt");
        exit(1);
    }

    return 0;
}

存储映射与共享内存的关系

        共享内存和存储映射是两种不同的概念,但它们可以有一定的关联和联系。

        共享内存用于实现多个进程之间共享数据的方式。它允许多个进程可以访问同一块物理内存空间,从而实现进程间的数据共享。在共享内存中,多个进程可以直接读写同一块内存区域,而无需进行复制或通过其他通信机制。

        存储映射用于将文件或设备映射到进程的地址空间中,从而实现对文件内容或设备内存的直接访问。通过存储映射,进程可以将文件内容或设备内存映射到自己的地址空间中,然后通过对内存的读写操作来实现对文件或设备的访问。

        共享内存和存储映射的关系在于,共享内存可以通过存储映射的方式实现。通过将共享内存区域映射到各个进程的地址空间中,多个进程可以直接访问同一块内存区域,从而实现数据的共享。在这种情况下,使用​​mmap()​​函数可以将共享内存映射到进程的地址空间中,然后各个进程可以通过对映射区域的读写操作来实现共享数据的访问。

        共享内存和存储映射是不同的概念,共享内存更侧重于进程间的数据共享,而存储映射更侧重于对文件或设备的访问。但在某些情况下,可以通过存储映射来实现共享内存的功能。

你可能感兴趣的:(linux,c语言)