多进程通信(IPC)--共享内存

1、共享内存介绍

    共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以。采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。因此,采用共享内存的通信方式效率是非常高的。

2、应用场景

①进程间通讯-生产者消费者模式

    生产者进程和消费者进程通讯常使用共享内存,比如一个网络服务器,接入进程收到数据包后,直接写到共享内存中,并唤醒处理进程,处理进程从共享内存中读数据包,进行处理。当然,这里要解决互斥的问题。

②父子进程间通讯

    由于fork产生的子进程和父进程不共享内存区,所以父子进程间的通讯也可以使用共享内存,以POSIX共享内存为例,父进程启动后使用MAP_SHARED建立内存映射,并返回指针ptr。fork结束后,子进程也会有指针ptr的拷贝,并指向同一个文件映射。这样父、子进程便共享了ptr指向的内存区。

 ③进程间共享-只读模式

    业务经常碰到一种场景,进程需要加载一份配置文件,可能这个文件有100K大,那如果这台机器上多个进程都要加载这份配置文件时,比如有200个进程,那内存开销合计为20M,但如果文件更多或者进程数更多时,这种对内存的消耗就是一种严重的浪费。比较好的解决办法是,由一个进程负责把配置文件加载到共享内存中,然后所有需要这份配置的进程只要使用这个共享内存即可。

3、共享内存相关函数详解

①ftok()函数

    获得一个ID号.
应用说明:
    在IPC中,我们经常用用key_t的值来创建或者打开信号量,共享内存和消息队列。
函数原型:
    key_t ftok(const char *pathname, int proj_id);
Keys:
    1)pathname一定要在系统中存在并且进程能够访问的
    2)proj_id是一个1-255之间的一个整数值,典型的值是一个ASCII值。
当成功执行的时候,一个key_t值将会被返回,否则-1被返回。我们可以使用strerror(errno)来确定具体的错误信息。
考虑到应用系统可能在不同的主机上应用,可以直接定义一个key,而不用ftok获得:

#define IPCKEY 0x344378

②shmget()函数    

    用来开辟/指向一块共享内存的函数
应用说明:
    shmget()用来获得共享内存区域的ID,如果不存在指定的共享区域就创建相应的区域。
函数原型:
    int shmget(key_t key, size_t size, int shmflg);
@key_t key 是这块共享内存的标识符。如果是父子关系的进程间通信的话,这个标识符用IPC_PRIVATE来代替。如果两个进程没有任何关系,所以就用ftok()算出来一个标         识符(或者自己定义一个)使用了。
@int size 是这块内存的大小.
@int flag 是这块内存的模式(mode)以及权限标识。
模式可取如下值:        
    IPC_CREAT 新建(如果已创建则返回目前共享内存的id)
    IPC_EXCL   与IPC_CREAT结合使用,如果已创建则则返回错误
然后将“模式” 和“权限标识”进行“或”运算,做为第三个参数。
如:    IPC_CREAT | IPC_EXCL | 0640   
例子中的0666为权限标识,4/2/1 分别表示读/写/执行3种权限,第一个0是UID,第一个6(4+2)表示拥有者的权限,第二个4表示同组权限,第3个0表示他人的权限。

@这个函数成功时返回共享内存的ID,失败时返回-1。

关于这个函数,要多说两句
    创建共享内存时,shmflg参数至少需要 IPC_CREAT | 权限标识,如果只有IPC_CREAT 则申请的地址都是k=0xffffffff,不能使用;
获取已创建的共享内存时,shmflg不要用IPC_CREAT(只能用创建共享内存时的权限标识,如0640),否则在某些情况下,比如用ipcrm删除共享内存后,用该函数并用IPC_CREAT参数获取一次共享内存(当然,获取失败),则即使再次创建共享内存也不能成功,此时必须更改key来重建共享内存。

③shmat()函数

   将这个内存区映射到本进程的虚拟地址空间,用来允许本进程访问一块共享内存的函数。

函数原型:

    void *shmat( int shmid , char *shmaddr , int shmflag );

@shmid是那块共享内存的ID。

@shmaddr是共享内存的起始地址,如果shmaddr为0,内核会把共享内存映像到调用进程的地址空间中选定位置;如果shmaddr不为0,内核会把共享内存映像到shmaddr指定的位  置。所以一般把shmaddr设为0。
@shmflag是本进程对该内存的操作模式。如果是SHM_RDONLY的话,就是只读模式。其它的是读写模式
@成功时,这个函数返回共享内存的起始地址。失败时返回-1。

④shmdt()函数

    删除本进程对这块内存的使用,shmdt()与shmat()相反,是用来禁止本进程访问一块共享内存的函数

函数原型:
int shmdt( char *shmaddr );
参数char *shmaddr是那块共享内存的起始地址。
成功时返回0。失败时返回-1。

⑤shmctl() 函数

   控制对这块共享内存的使用

函数原型:
int     shmctl( int shmid , int cmd , struct shmid_ds *buf );
int shmid是共享内存的ID。
int cmd是控制命令,可取值如下:
    IPC_STAT        得到共享内存的状态
    IPC_SET         改变共享内存的状态
    IPC_RMID        删除共享内存
struct shmid_ds *buf是一个结构体指针。IPC_STAT的时候,取得的状态放在这个结构体中。如果要改变共享内存的状态,用这个结构体指定。
返回值:        成功:0
                失败:-1

看代码

#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define MY_SHM_SIZE 1024
int main(int argc, char **argv)
{
    char file[20];
    strcpy(file, argv[1]);
    key_t ket;
    int shm_id;
    char *buf;

    ket = ftok(file, 1);
    shm_id = shmget((key_t)0x12345678, MY_SHM_SIZE, IPC_CREAT|0640);
    if (fork() == 0) {
        shm_id = shmget((key_t)0x12345678, MY_SHM_SIZE, IPC_CREAT|0640);
        if (shm_id == -1) {
            printf("create shm error\n");
            return -1;
        }

        buf = (char *)shmat(shm_id, NULL, 0);
        strcpy(buf, "test shm");
        exit(0);
    } 

    int status;
    wait(&status);
    buf = (char *)shmat(shm_id, NULL, 0);
    printf("get shm buf is :%s\n", buf);

    shmdt(buf);
    return 0;
}

运行结果:

        get shm buf is :test shm

解析代码:

①共享内存在父子进程中公用,直接定义一个key_t值

②由于父子进程的shm_id不能公用,因此为了保证父子进程公用相同的shm_id,调用两次shm_get,第一次创建一块共享内存,第二次直接返回相同的shm_id

③通过指针buf来获取到共享内存的地址,就可以对这块共享内存为所欲为了。


下面再附上一个例子,就是利用获取共享内存的开始地址,然后对内存任意操作,代码我就不解释了

#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define MY_SHM_SIZE 1024
typedef struct info
{
    char name[20]; 
    int no;
} s_info, *p_info;
    
int main(int argc, char **argv)
{   
    char file[20];
    strcpy(file, argv[1]);
    key_t ket;
    int shm_id;
    s_info *s_buf;
    int *ptr;
        
    ket = ftok(file, 1);
    shm_id = shmget((key_t)0x12345678, MY_SHM_SIZE, IPC_CREAT|0640);
    if (fork() == 0) {
        shm_id = shmget((key_t)0x12345678, MY_SHM_SIZE, IPC_CREAT|0640);
        if (shm_id == -1) {
            printf("create shm error\n");
            return -1;
        }
        
        ptr = (int *)shmat(shm_id, NULL, 0);
        *ptr = 3;
        ptr++;
        
        s_buf = (p_info)ptr;
        strcpy(s_buf->name, "honghong");
        s_buf->no = 1;
        ++s_buf;

        strcpy(s_buf->name, "mingming");
        s_buf->no = 2;
        ++s_buf;

        strcpy(s_buf->name, "huahua");
        s_buf->no = 3;

        exit(0);
    }

    int status;
    wait(&status);

    int *get_ptr;
    get_ptr = (int *)shmat(shm_id, NULL, 0);
    printf("counts is %d\n", *get_ptr);

    s_buf = (p_info)++get_ptr;
    while (s_buf != NULL && s_buf->no != 0)
    {
        printf("get no = %d, name = %s \n", s_buf->no, s_buf->name);
        ++s_buf;
    }

    if (shmctl(shm_id, IPC_RMID, NULL) == -1)
    {
        printf("rm shm(%d) error\n", shm_id);
    }
    return 0;
}

运行结果

counts is 3
get no = 1, name = honghong 
get no = 2, name = mingming 
get no = 3, name = huahua 



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值