Linux应用编程---8.共享内存

Linux应用编程—8.共享内存

​ 共享内存是进程之间通讯的方式。大概原理是先申请一块共享内存,然后通过“映射”,映射到进程中。进程中读写这块被映射过来的内存,共享内存也会随之改变,同理其它进程也能做相同的操作。所以,两个不同的进程通过共享内存实现了通讯。

8.1 创建共享内存

​ 创建共享内存,使用到的库函数是:shmget(),是:share memory get的缩写。在Linux编程手册中查看这个函数。

NAME
       shmget - allocates a System V shared memory segment

SYNOPSIS
       #include <sys/ipc.h>
       #include <sys/shm.h>

       int shmget(key_t key, size_t size, int shmflg);

​ shmge()函数,用来分配一个“System v”的共享内存段。使用时需要包含头文件:sys/ipc.h与sys/shm.h。函数原型是:int shmget(key_t key, size_t size, int shmflg);需要传入3个参数,返回值是int类型的。

DESCRIPTION
shmget()  returns  the identifier of the System V shared memory segment associated with the value of the argument key.  It may be  used  either to  obtain the identifier of a previously created shared memory segment(when shmflg is zero and key does not have the value  IPC_PRIVATE), or to create a new set.

​ shmget()函数返回与参数key的值相关联的System v共享内存段的标识符。它即可用于获取先前创建的共享内存段的标识符,当shmflg为0且key没有值IPC_PRIVATE时,也可以用于创建一个新的共享内存段。

A  new  shared  memory  segment,  with  size equal to the value of size rounded up to a multiple of PAGE_SIZE, is created if key has the  value IPC_PRIVATE  or  key isn't IPC_PRIVATE, no shared memory segment corre‐sponding to key exists, and IPC_CREAT is specified in shmflg.

​ 如果key的值为IPC_PRIVATE或key不是IPC_PRIVATE,不存在与key对应的共享内存段,并且在shmflg中指定了IPC_CREAT,则创建一个新的共享内存段,其大小等于size的值的四舍五入到PAGE_SIZE的整数倍。

If shmflg specifies both IPC_CREAT and IPC_EXCL  and  a  shared  memory segment  already  exists for key, then shmget() fails with errno set to EEXIST.  (This is analogous to the effect of the combination O_CREAT | O_EXCL for open(2).)

​ 如果shmflg同时指定了IPC_CREAT和IPC_EXCL,并且key的共享内存段已经存在,那么shmget()将失败,errno设置为EEXIST。(这类似于open(2)的组合O_CREAT | O_EXCL的效果。)

​ shmflg的取值可以是以下几种:

IPC_CREAT   Create  a  new  segment.   If  this  flag is not used, then shmget() will find the  segment  associated  with  key  and check  to see if the user has permission to access the segment.
IPC_EXCL    This flag is used with IPC_CREAT to ensure that  this  call creates  the  segment.   If the segment already exists, the call fails.
SHM_HUGETLB  (since Linux 2.6) Allocate the segment using "huge  pages."   See  the  Linux kernel  source  file  Documentation/admin-guide/mm/hugetlbpage.rst for further information.

​ 创建一个新的段。如果没有使用此标志,那么shmget()将查找与key相关联的段并检查用户是否有访问该段的权限。此标志与IPC_CREAT一起使用,以确保该调用创建了段。如果该段已经存在,则调用失败。使用“大页面”分配段。参见Linux内核源文件Documentation/admin-guide/mm/hugetlbpage。RST查询进一步信息。

​ shmget()函数返回值:

RETURN VALUE
On success, a valid shared memory identifier is returned.  On error, -1 is returned, and errno is set to indicate the error.

​ 函数调用成功,返回一个有效的共享内存标识符,失败则返回-1。

8.2 shmat()、shmdt()函数详情

NAME
       shmat, shmdt - System V shared memory operations
SYNOPSIS
       #include <sys/types.h>
       #include <sys/shm.h>

       void *shmat(int shmid, const void *shmaddr, int shmflg);

       int shmdt(const void *shmaddr);
shmat()
       shmat()  attaches the System V shared memory segment identified by shmid to
       the address space of the calling process.  The attaching address is  speci‐
       fied by shmaddr with one of the following criteria:

       *  If  shmaddr is NULL, the system chooses a suitable (unused) page-aligned
          address to attach the segment.

       *  If shmaddr isn't NULL and SHM_RND is specified in shmflg, the attach oc‐
          curs  at the address equal to shmaddr rounded down to the nearest multi‐
          ple of SHMLBA.

       *  Otherwise, shmaddr must be a page-aligned address at  which  the  attach
          occurs.

       In  addition to SHM_RND, the following flags may be specified in the shmflg
       bit-mask argument:

​ shmat()函数与shmdt()函数是对System v下的共享内存的操作。

​ shmat()函数:

shmat()  attaches the System V shared memory segment identified by shmid to the address space of the calling process.  The attaching address is  specified by shmaddr with one of the following criteria:
If  shmaddr is NULL, the system chooses a suitable (unused) page-aligned address to attach the segment.If shmaddr isn't NULL and SHM_RND is specified in shmflg, the attach occurs  at the address equal to shmaddr rounded down to the nearest multiple of SHMLBA. Otherwise, shmaddr must be a page-aligned address at  which  the  attachoccurs.

​ 该函数通过shmid标识的System v下的共享内存段附加到调用进程的地址空间。附加地址由shmadr指定,使用以下条件之一:如果shmaddr地址为空,系统选择一个合适的页对其地址附加到段。如果地址不为空并且shmflg标志位被指定,则在等于shmaddr四舍五入到SHMLBA最接近倍数的地址处进行附加。否则,shmaddr地址必须是一个与页面对其的地址,在该地址上执行附加操作。

​ shmat()函数返回值:

On success, shmat() returns the address of the attached shared memory  segment;  on  error, (void *) -1 is returned, and errno is set to indicate the cause of the error.

​ 函数调用成功,返回附加共享内存段的首地址,失败,则返回-1.

​ shmdt()函数:

shmdt() detaches the shared memory segment located at the address specified
       by shmaddr from the address space of the calling  process.   The  to-be-de‐
       tached  segment  must be currently attached with shmaddr equal to the value
       returned by the attaching shmat() call.

       On a successful shmdt()  call,  the  system  updates  the  members  of  the
       shmid_ds structure associated with the shared memory segment as follows:

              shm_dtime is set to the current time.

              shm_lpid is set to the process-ID of the calling process.

              shm_nattch  is  decremented by one.  If it becomes 0 and the segment
              is marked for deletion, the segment is deleted.

​ shmdt()用来断开当前进程与共享内存的连接。分Shmdt()离位于指定地址的共享内存段通过shmaddr从调用进程的地址空间。限于检测所连接的段当前必须附加shmadr等于该值由附加的shmat()调用返回。在成功调用shmdt()时,系统更新Shmid_ds结构与共享内存段关联如下:Shm_dtime设置为当前时间。shm_lpid设置为调用进程的进程id。Shm_nattch减1。如果它变成0,这个线段标记为删除,则删除该段。

​ shmdt()函数返回值:

On success, shmdt() returns 0; on error -1 is returned, and errno is set to
       indicate the cause of the error.

​ 函数调用成功,返回0,失败,则返回-1.

8.2 使用共享内存在父子进程之间进行通讯

​ 父进程往共享内存映射到自己进程中的内存写入一串字符串,子进程访问共享内存映射到自己进程中的内存,将该字符串打印出来。

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

char msg[] = "I love VAE!";

int main(void)
{
        pid_t pid;
        int shmid;

        shmid = shmget(IPC_PRIVATE, 1024, IPC_CREAT);
        if(-1 == shmid)
                perror("shmget");

        pid = fork();

        if(pid > 0)			// Parent process
        {
                char * p_addr;

                p_addr = shmat(shmid, NULL, 0);

                memset(p_addr, '\0', sizeof(msg));
                memcpy(p_addr, msg, sizeof(msg));

                shmdt(p_addr);

                waitpid(pid, NULL, 0);
        }
        else if(pid == 0)
        {
                char * c_addr;

                c_addr = shmat(shmid, NULL, 0);

                printf("Waits 3 seconds:\n");

                sleep(3);

                printf("Data from parent process by shm is %s.\n", c_addr);
        }
        else
        {
                perror("fork.");
        }

        return 0;
}

​ 运行结果:

root@ubuntu:/home/sgk/Documents/Linux_Program/shared_memory# gcc shared_memory_0.c 
root@ubuntu:/home/sgk/Documents/Linux_Program/shared_memory# ./a.out
Waits 3 seconds:
Data from parent process by shm is I love VAE!.

8.3 共享内存用于非亲缘关系进程之间通讯

​ 共享内存创建函数shmget()的原型是:int shmget(key_t key, size_t size, int shmflag),其中参数key的值是相关联的System v共享内存段的标识符。如果已经创建了key值对应的共享内存段它们则对应了起来。非亲缘关系的进程之间通过共享内存通讯,最重要的一点是:这个共享内存段的标识符是相同的 !!!

​ 建立两个.c文件,分别是:write_shm.c与read_shm.c,在write_shm.c中,使用shmget()创建一个指定key值的共享内存段,然后使用函数shmat()与进程建立连接,并得到该进程下被映射内存的首地址,然后写入数据,最后调用shmdt()断开与共享内存的连接。在read_shm.c中,先同样使用shmget()函数创建共享内存,但是此时传入的key值是之前已经创建好的key值,如果该键值对应的共享内存段已经存在,则联系到一起。同样调用shmat()函数将该key值下的共享内存段映射到本进程,读取出里面的数据并打印,最后使用shmdt()断开共享内存连接。

​ write_shm.c:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/ipc.h>

#define KEY_0 0514

char msg[] = "I love vae!";

int main(void)
{
        int shmid;
        char *p1_addr;

        shmid = shmget(KEY_0, 1024, IPC_CREAT);

        if(-1 == shmid)
                perror("shmget.");

        p1_addr = shmat(shmid, NULL, 0);

        memset(p1_addr, '\0', sizeof(msg));
        memcpy(p1_addr, msg, sizeof(msg));

        printf("Wrote data to shared memory done.\n");

        shmdt(p1_addr);

        return 0;
}

​ read_shm.c:

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

#define KEY_0 0514

int main(void)
{
        int shmid;
        char *p2_addr;

        shmid = shmget(KEY_0, 1024, IPC_CREAT);

        if(-1 == shmid)
                perror("shmget.");

        p2_addr = shmat(shmid, NULL, 0);

        printf("Shared memory's data is: %s\n", p2_addr);

        shmdt(p2_addr);

        return 0;
}

​ 分别编译出可执行文件read与write。

​ 运行结果:

image-20221127104824504

图1 运行结果

​ 如果共享内存的标识符KEY_0不相同,将write_shm.c中的KEY_0保持0514,将read_shm.c中的KEY_0改写为0528。运行结果如下:

image-20221127105537131

图2 运行结果

​ 根据结果可知:这两个进程通讯失败。如果共享内存标识符不同,内核分别在两个进程下创建了新的共享内存。

8.4 shmctl()函数详情

	查看Linux编程手册下关于shmctl()函数定义与使用方法。
NAME
       shmctl - System V shared memory control

SYNOPSIS
       #include <sys/ipc.h>
       #include <sys/shm.h>

       int shmctl(int shmid, int cmd, struct shmid_ds *buf);

​ shmctl()控制System v共享内存段。需要包含两个头文件,函数原型是:int shmctl(int shmid, int cmd, struct shmid_ds *buf);

DESCRIPTION
shmctl()  performs  the control operation specified by cmd on the System V shared memory segment whose identifier is given in shmid.

​ shmctl()函数在shmid中给出标识符的System v共享内存段上执行由cmd指定的控制操作。

​ cmd有效参数如下:

Valid values for cmd are:

       IPC_STAT  Copy information from the kernel data  structure  associated
                 with  shmid  into  the shmid_ds structure pointed to by buf.
                 The caller must have read permission on  the  shared  memory
                 segment.

       IPC_SET   Write  the  values of some members of the shmid_ds structure
                 pointed to by buf to the kernel  data  structure  associated
                 with this shared memory segment, updating also its shm_ctime
                 member.  The following fields can be changed:  shm_perm.uid,
                 shm_perm.gid,   and   (the  least  significant  9  bits  of)
                 shm_perm.mode.  The effective UID  of  the  calling  process
                 must    match    the   owner   (shm_perm.uid)   or   creator
                 (shm_perm.cuid) of the shared memory segment, or the  caller
                 must be privileged.

       IPC_RMID  Mark the segment to be destroyed.  The segment will actually
                 be destroyed only after the last process detaches it  (i.e.,
                 when  the  shm_nattch  member  of  the  associated structure
                 shmid_ds is zero).  The caller must be the owner or  creator
                 of  the  segment, or be privileged.  The buf argument is ig‐
                 nored.

                 If a segment has been marked for destruction, then the (non‐
                 standard)  SHM_DEST  flag  of the shm_perm.mode field in the
                 associated data structure retrieved by IPC_STAT will be set.

                 The caller must ensure that  a  segment  is  eventually  de‐
                 stroyed;  otherwise  its pages that were faulted in will re‐
                 main in memory or swap.

                 See also the description of /proc/sys/kernel/shm_rmid_forced
                 in proc(5).

​ cmd的有效值为:

IPC_STAT从相关的内核数据结构中复制信息将shmid放入buf所指向的shmid_ds结构中。调用者必须对共享内存具有读权限段。

写shmid_ds结构的一些成员的值由buf指向相关的内核数据结构使用这个共享内存段,也更新它的shm_ctime成员。以下字段可以更改:shm_perm.uid,shm_perm。Gid,和(最小有效9位)shm_perm.mode。调用进程的有效UID必须匹配所有者(shm_perm.uid)或创建者(shm_perm.cuid)的共享内存段,或调用者必须有特权。

ipc_rmd标识要销毁的段。这部分实际上只有在最后一个进程分离它(即,当关联结构的shm_nattch成员Shmid_ds为零)。调用者必须是所有者或创建者的部分,或享有特权。buf的论点很重要也没有。

如果一段已被标记为要销毁,则(非‐shm_perm的SHM_DEST标志。的模式字段将设置由IPC_STAT检索的关联数据结构。

呼叫者必须确保一个区段最终被解除毁掉了;否则,有错误的页面将重新制作主内存或交换区。

参见/proc/sys/kernel/shm_rmid_forced的描述在proc(5)。

​ 修改write_shm.c代码,在写结束后,使用shmctl()函数,使用IPC_RMID指令销毁,查看效果。

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/ipc.h>

#define KEY_0 0514

char msg[] = "I love vae!";

int main(void)
{
        int shmid;
        char *p1_addr;

        shmid = shmget(KEY_0, 1024, IPC_CREAT);

        if(-1 == shmid)
                perror("shmget.");

        p1_addr = shmat(shmid, NULL, 0);

        memset(p1_addr, '\0', sizeof(msg));
        memcpy(p1_addr, msg, sizeof(msg));

        printf("Wrote data to shared memory done.\n");

        shmdt(p1_addr);

        shmctl(shmid, IPC_RMID, NULL);

        return 0;
}

​ 分别编译出可执行文件:read与write,然后执行。

运行结果:

image-20221127112006397

图3 运行结果

​ 当调用shmctl()销毁共享内存段后,是彻底销毁了。其它进程读取失败。

8.5 总结

​ 共享内存是进程之间通讯的方式之一,建立共享内存大概分为几步:(1)使用shmget()创建共享内存段;(2)使用shmat()将共享内存段与当前进程绑定;(3)使用shmdt()函数将共享内存与进程断开连接;(4)如需彻底销毁共享内存段,调用shmctl(),传入IPC_RMID。
在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值