Linux进阶-ipc共享内存

目录

共享内存

shmget():创建或获取共享内存

shmat():映射

shmdt():解除映射

shmctl():获取或设置属性

sem.h文件

sem.c文件

shm.c文件

Makefile文件

执行过程


共享内存

共享内存:将内存进行共享,它允许多个不相关的进程访问同一个逻辑内存,直接将一块裸露的内存放在需要数据传输的进程前,供进程使用。

因此,共享内存是效率最高的一种IPC通信机制,可以在多个进程间共享和传递数据,进程间需要共享的数据被放在共享内存区域所有需要访问该共享内存的进程都要把该共享区域映射到本进程的地址空间中,因此所有进程都可访问共享内存的地址。

但是,共享内存需要进程自己去维护,如同步、互斥等。如进程1在读取共享内存的数据时,进程2却修改了共享内存的数据,这会导致数据混乱。因此共享内存属于临界资源,在某一时刻只能有一个进程对其操作(读写)。共享内存一般不能单独使用,而是配合信号量、互斥锁等协调机制,让各个进程在高效交换数据时,不会发生数据践踏、破坏等行为。

共享内存思想:进程间虚拟内存空间本来相互独立,不能相互访问,但是可以通过某种方式使得相同的一块物理内存多次映射到不同的进程虚拟空间中,相当于多个进程的虚拟内存空间部分重叠在一起。共享内存少了拷贝的操作,减少了系统开销,因此效率极高

shmget():创建或获取共享内存

shmget()函数会创建或获取一个共享内存对象,并返回共享内存标识符。

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
/*
key:共享内存的标识符
    IPC_PRIVATE:创建一块新的共享内存
    0:当shmflg参数设置了IPC_PRIVATE标志,则将创建一块新的共享内存
    大于0的32位整数:根据shmflg参数来确定操作
size:要创建共享内存的大小,所有的内存分配操作都是以页为单位的,所有即使只申请一个字节的内存,内存也会分配一页。
shmflg:表示创建的共享内存的模式标志参数。标志 | mode
    IPC_CREAT:如果内核中不存在关键字与key相等的共享内存,则新建一个共享内存;如果存在则返回此共享内存的标识符。
    IPC_EXCL:如果内核中不存在关键字与key相等的共享内存,则新建一个共享内存;如果存在则报错
    SHM_HUGETLB:使用“大页面”来分配共享内存,所谓的“大页面”指的是内核为了提高程序性能,对内存实行分页管理时,采用比默认尺寸(4KB)更大的分页,以减少缺页中断。 Linux 内核支持以 2MB 作为物理页面分页的基本单位。
    SHM_NORESERVE:不在交换分区中为这块共享内存保留空间。
返回值:共享内存的ID。
*/

当调用shmget()函数失败时将产生错误代码:

        EACCES:指定的消息队列已存在,但调用进程没有权限访问它

        EEXIST:key指定的消息队列已存在,而msgflg中同时指定IPC_CREAT和IPC_EXCL标志

        EINVAL:创建共享内存是参数size小于SHMMIN或大于SHMMAX

        ENFILE:已达到系统范围内打开文件总数的限制

        ENOENT:给定的key不存在任何共享内存,并且未指定IPC_CREAT

        ENOMEM:内存不足,无法为共享内存分配内存

shmat():映射

shmat()函数是把共享内存区域对象映射到调用进程的地址空间。

#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
/*
shmid:共享内存ID
shmaddr:如果不为NULL,则系统会根据shmaddr来选择一个合适的内存区域;如果为NULL,则系统会自动选择一个合适的虚拟内存空间地址去映射共享内存
shmflg:
    SHM_RDONLY:以只读方式映射共享内存
    SHM_REMAP:重新映射,此时shmaddr不能为NULL
    NULLSHM:自动选择比shmaddr小的最大页对齐地址
返回值:共享内存的起始地址。
*/

共享内存的映射需注意:

共享内存只能以只读或可读写方式映射,无法以只写方式映射。

shmaddr参数一般设置为NULL,让系统自动地寻找合适的地址。当不为NULL时,SHMFLG必须设置为SHM_RND,系统将会选择比shmaddr小而又最大的页对齐地址(即为SHMLBA的整数倍)作为共享内存区域的起始地址。如果没有设置SHM_RND,那么shmaddr必须是严格的页对齐地址。

shmdt():解除映射

shmdt()函数是解除进程和共享内存间的映射。

#include <sys/types.h>
#include <sys/shm.h>
void shmdt(const void *shmaddr);
/*
shmaddr:映射的共享内存的起始地址
返回值:
    执行成功:0
    执行失败:-1,并将错误原因存于errno
*/

注意:该函数并不删除所指定的共享内存区,而只是将先前用shmat()函数映射好的共享内存脱离当前进程,共享内存还是存在于物理内存中。

shmctl():获取或设置属性

shmctl()函数用于获取或设置共享内存的相关属性。

#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
/*
shmid:共享内存标识符
cmd:
    IPC_STAT:获取属性信息,放置到 buf 中。
    IPC_SET:设置属性信息为 buf 指向的内容。
    IPC_RMID:删除该共享内存。
    IPC_INFO:获得关于共享内存的系统限制值信息。
    SHM_INFO:获得系统为共享内存消耗的资源信息。
    SHM_STAT:与 IPC_STAT 具有相同的功能,但 shmid 为该 SHM 在内核中记录所有 SHM 信息的数组的下标,因此通过迭代所有的下标可以获得系统中所有 SHM 的相关信息。
    SHM_LOCK:禁止系统将该 SHM 交换至 swap 分区。
    SHM_UNLOCK:允许系统将该 SHM 交换至 swap 分区。
buf:共享内存属性信息结构体指针,设置或者获取信息都通过该结构体
*/

一个SHM被交换至swap分区后如果被设置了SHM_LOCK,那么任何访问这个SHM的进程都将会遇到页错误。进程可以通过IPC_STAT后得到的mode来检测SHM_LOCKED信息。

struct shmid_ds {
    struct ipc_perm shm_perm;     /* 所有权和权限 */
    size_t shm_segsz;             /* 共享内存尺寸(字节) */
    time_t shm_atime;             /* 最后一次映射时间 */
    time_t shm_dtime;             /* 最后一个解除映射时间 */
    time_t shm_ctime;             /* 最后一次状态修改时间 */
    pid_t shm_cpid;               /* 创建者 PID */
    pid_t shm_lpid;               /* 后一次映射或解除映射者 PID */
    shmatt_t shm_nattch;          /* 映射该 SHM 的进程个数 */
    ...
};

struct ipc_perm {
    key_t __key;           /* 该共享内存的键值 key */
    uid_t uid;             /* 所有者的有效 UID */
    gid_t gid;             /* 所有者的有效 GID */
    uid_t cuid;            /* 创建者的有效 UID */
    gid_t cgid;            /* 创建者的有效 GID */
    unsigned short mode;   /* 读写权限 + SHM_DEST + SHM_LOCKED 标记 */
    unsigned short __seq;  /* 序列号 */
};

sem.h文件

#ifndef __SEM_H
#define __SEM_H
 
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
 
int init_sem(int sem_id, int init_value);
int del_sem(int sem_id);
int sem_p(int sem_id);
int sem_v(int sem_id);
 
#endif

sem.c文件

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
 
union semun
{
        int val;                //供semctl()函数cmd = SETVAL时使用
        struct semid_ds *buf;   //供semctl()汉斯cmd = IPC_STAT|IPC_SET时使用
};
 
/* 初始化信号量 */
int init_sem(int sem_id, int init_value)
{
        union semun sem_union;
 
        sem_union.val = init_value;
 
        /* 信号量ID,信号量编号,命令,联合体 */
        if( semctl( sem_id, 0, SETVAL, sem_union) == -1 )
        {
                printf("Initialize semaphore error!\n");
                return -1;
        }else
        {
                printf("Initialize semaphore!\n");
        }
}
 
/* 删除信号量 */
int del_sem(int sem_id)
{
        union semun sem_union;
 
        /* 信号量ID,信号量编号,命令,联合体 */
        if( semctl( sem_id, 0, IPC_RMID, sem_union) == -1 )
        {
                printf("Delete semaphore error!\n");
                return -1;
        }else
        {
                printf("Delete semaphore!\n");
        }
}
 
/* p操作 */
int sem_p(int sem_id)
{
        struct sembuf sops;
 
        sops.sem_num = 0;               //信号量的编号:0~nsems-1
        sops.sem_op  = -1;              //表示p操作
        sops.sem_flg = SEM_UNDO;        //系统自动释放在系统中残留的信号量
 
        /* 信号量ID,结构体,信号量数量 */
        if( semop( sem_id, &sops, 1) == -1 )
        {
                perror("p operation error!\n");
                return -1;
        }else
        {
                printf("p operation successful!\n");
                return 0;
        }
}
 
/* v操作 */
int sem_v(int sem_id)
{
        struct sembuf sops;
 
        sops.sem_num = 0;               //信号量的编号:0~nsems-1
        sops.sem_op  = 1;               //表示v操作
        sops.sem_flg = SEM_UNDO;        //系统自动释放在系统中残留的信号量
 
        /* 信号量ID,结构体,信号量数量 */
        if( semop( sem_id, &sops, 1) == -1 )
        {
                perror("v operation error!\n");
                return -1;
        }else
        {
                printf("v operation successful!\n");
                return 0;
         }
}

shm.c文件

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "sem.h"

int main(int argc, char** argv)
{
        int sem_id, shm_id;
        char *addr;
        pid_t pid;

        /* 创建信号量:键值,数量,模式 */
        sem_id = semget((key_t)0x1100, 1, IPC_CREAT|0666);
        /* 创建共享内存:键值,共享内存大小,模式 */
        shm_id = shmget((key_t)0x1111, 1024, IPC_CREAT|0666);

        /* 初始化信号量 */
        init_sem(sem_id, 0);

        if( ( pid = fork() ) == -1)
        {
                perror("rork error!\n");
        }else if(pid == 0)      //子进程
        {
                printf("Child process will wait for some seconds...\n");
                sleep(3);

                /* 映射共享内存,映射地址系统自动分配 */
                if( ( addr = shmat(shm_id, NULL, 0) ) == (void *)-1 )
                {
                        printf("child shmat error!\n");
                        exit(-1);
                }

                memcpy(addr, "hello couvrir", 14);
                printf("the child process is running...\n");
                sem_v(sem_id);
        }else                   //父进程
        {
                sem_p(sem_id);
                printf("the father process is running...\n");

                /* 映射共享内存,映射地址系统自动分配 */
                if( ( addr = shmat(shm_id, NULL, 0) ) == (void *)-1 )
                {
                        printf("father shmat error!\n");
                        exit(-1);
                }

                printf("share memory string:%s\n", addr);

                /*解除共享内存映射*/
                shmdt(addr);

                sem_v(sem_id);
                del_sem(sem_id);
        }

        exit(0);
}

Makefile文件

照旧

执行过程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值