Linux程序设计学习笔记----System V进程通信(共享内存)

转载请注明出处:http://blog.csdn.net/suool/article/details/38515863

共享内存可以被描述成内存一个区域(段)的映射,这个区域可以被更多的进程所共享。这是IPC机制中最快的一种形式,因为它不需要中间环节,而是把信息直接从一个内存段映射到调用进程的地址空间。 一个段可以直接由一个进程创建,随后,可以有任意多的进程对其读和写。但是,一旦内存被共享之后,对共享内存的访问同步需要由其他
IPC 机制,例如信号量来实现。象所有的System V IPC 对象一样,Linux 对共享内存的存取是通过对访问键和访问权限的检查来控制的.

共享空间数据结构

<bits/shm.h>

/* 连接共享内存区的进程数的数据类型 */
typedef unsigned long int shmatt_t;

struct shmid_ds
{
    struct ipc_perm shm_perm;           /* operation permission struct */
    size_t shm_segsz;                   /* 共享存储段的最大字节数 */

    __time_t shm_atime;                 /* time of last shmat() */
    __time_t shm_dtime;                 /* time of last shmdt() */
    __time_t shm_ctime;                 /* time of last change by shmctl() */

    __pid_t shm_cpid;                   /* pid of creator */
    __pid_t shm_lpid;                   /* pid of last shmop */

    shmatt_t shm_nattch;                /* 连接共享内存区的进程数 */

//保留字段
#if __WORDSIZE == 32
    unsigned long int __unused1;
	unsigned long int __unused2;
	unsigned long int __unused3;
#endif
    unsigned long int __unused4;
    unsigned long int __unused5;
};

两个进程在使用此共享空间之前需要在进程地址空间与共享内存空间之间建立联系,即是将共享内存空间挂在到进程中.

同时在使用共享内存进行数据存取的时候,为了避免出现读取交叉等错误,常常需要使用二元信号量来同步两个进程实现对共享内存的读取操作.

同时对于共享内存有一定的系统限制:

#include <bits/shm.h>
struct  shminfo
{
    unsigned long int shmmax;	//一个共享内存区的最大字节数
    unsigned long int shmmin;	//一个共享内存区的最小字节数
    unsigned long int shmmni;	//系统范围内的共享内存区对象的最大个数
    unsigned long int shmseg;	//每个进程连接的最大共享内存区的数目
    unsigned long int shmall;	//系统范围内的共享内存区的最大页数
    unsigned long int __unused1;
    unsigned long int __unused2;
    unsigned long int __unused3;
    unsigned long int __unused4;
};

在Linux 下shmctl中可以指定IPC_INFO来获取上面结构所示的系统范围内的限制。在Linux下,具体的限制值可以通过sysctl来查看,如下:

[[email protected] program]# sysctl -a | grep shm
...
kernel.shmmni = 4096			//系统范围内的共享内存区对象的最大个数
kernel.shmall = 4294967296		//系统范围内的共享内存区的最大页数
kernel.shmmax = 68719476736		//一个共享内存区的最大字节数

一般情况下不需要对System V共享内存区的系统限制进程修改,因为基本可以满足应用需求,如果要在系统范围内对内核限制进行修改,在Linux下面可以通过修改/etc/sysctl.conf 内核参数配置文件,然后配合sysctl命令来对内核参数进行设置。例如下面示例:

[[email protected] program]#echo "kernel.shmmni= 1000" >>/etc/sysctl.conf
[[email protected] program]#sysctl -p
[[email protected] program]#sysctl -a |grep shm
kernel.shmmni = 1000
kernel.shmall = 4294967296
kernel.shmmax = 68719476736

共享内存的处理过程

某个进程第一次访问共享虚拟内存时将产生缺页异常。这时,Linux找出描述该内存的 vm_area_struct 结构,该结构中包含用来处理这种共享虚拟内存段的处理函数地址。共享内存缺页异常处理代码对shmid_ds 的页表项表进行搜索,以便查看是否存在该共享虚拟内存的页表项。如果没有,系统将分配一个物理页并建立页表项,该页表项加入
shmid_ds 结构的同时也添加到进程的页表中。这就意味着当下一个进程试图访问这页内存时出现缺页异常,共享内存的缺页异常处理代码则把新创建的物理页给这个进程。因此说,第一个进程对共享内存的存取引起创建新的物理页面,而其它进程对共享内存的存取引起把那个页加添加到它们的地址空间。

当某个进程不再共享其虚拟内存时,利用系统调用将共享段从自己的虚拟地址区域中移去,并更新进程页表。当最后一个进程释放了共享段之后,系统将释放给共享段所分配的物理页。

当共享的虚拟内存没有被锁定到物理内存时,共享内存也可能会被交换到交换区中。

System V共享内存区的创建和打开

下面是创建共享内存的shmget函数的接口以及说明:

#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
                     //成功返回共享内存标识符,失败返回-1

shmget函数用于创建或打开一个共享内存区对象,shmget成功调用会返回一个共享内存区的标识符,供其它的共享内存区操作函数使用。

key:用于创建共享内存区的键值,这个在前面其他System IPC创建的时候已经讨论过了,System IPC都有一个key,作为IPC的外部标识符,创建成功后返回的描述符作为IPC的内部标识符使用。key的主要目的就是使不同进程在同一IPC汇合。key具体说可以有三种方式生成:

  • 不同的进程约定好的一个值;
  • 通过相同的路径名和项目ID,调用ftok()函数,生成一个键;
  • 还可以设置为IPC_PRIVATE,这样就会创建一个新的,唯一的IPC对象;然后将返回的描述符通过某种方式传递给其他进程;

size:指定创建共享内存区的大小,单位是字节。如果实际操作为创建一个共享内存区时,必须指定一个非0值,如果实际操作是访问一个已存在的共享内存区,那么size应为0。

shmflg:指定创建或打开消息队列的标志和读写权限(ipc_perm中的mode成员)。我们知道System V IPC定义了自己的操作标志和权限设置标志,而且都是通过该参数传递,这和open函数存在差别,open函数第三个参数mode用于传递文件的权限标志。System V IPC的操作标志包含:IPC_CREAT,IPC_EXCL。读写权限如下图:

图1 System V共享内存区的读写权限标志

System V共享内存区在创建后,该size大小的内存区会被初始化为0,这和POSIX共享内存不同,POSIX标准并没有规定新创建的POSIX共享内存区的初始内容

System V共享内存区的连接和断接

通过shmctl创建或打开共享内存区对象后,并没有将该共享内存区映射到调用进程的地址空间中,所以无法访问该共享内存区,需要通过shmat函数,将该共享内存区连接到调用进程的地址空间中,才能进行访问,当该进程完成对该共享内存区的访问后,可以调用shmdt断接这个共享内存区,当然进程结束后会自动断接所有连接的共享内存区。下面是shmat和shmdt函数的接口以及说明:

#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
                        //成功返回映射区的起始地址,失败返回-1
int shmdt(const void *shmaddr);
                        //成功返回0,失败返回-1

shmat用于将一个共享内存区连接到调用进程的地址空间中。

shmid:打开的System V共享内存对象的标识符;

shmaddrshmflg参数共同决定了共享内存区连接到调用进程的具体地址,规则如下:

  • shmaddr为空指针:连接的地址由系统内核决定,这是推荐的方法,具有可移植性
  • shmaddr非空:此时还要根据shmflg参数是否指定SHM_RND标志进行判断:
    • 没有指定SHM_RND:共享内存区连接到调用进程的shmaddr指定的地址;
    • 指定SHM_RND:共享内存区连接到shmaddr指定的地址向下舍入SHMLBA的位置。

shmflg:除了上面说的SHM_RND外,还有可以指定SHM_RDONLY标志,限定只读访问。一般该标志置为0。

shmdt用于将一个共享内存区从该进程内断接,当一个进程终止时,它连接的所有共享内存区会自动断接。

System V共享内存区的控制操作

shmctl函数可以对共享内存区进行多种控制操作,下面是shmctl函数的接口以及说明:

#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
                     //成功返回0,失败返回-1

shmid:共享内存区的标识符。

cmd:对共享内存区控制操作的命令,Open Group 的SUS定义了一下三个操作命令:

  • IPC_SET:按第三个参数buf所指定的内容,设置共享内存区的操作权限字段的:shm_perm.uid,shm_perm.gid和shm_perm.mode。此命令只能由以下进程执行:有效用户ID等于shm_perm.uid或shm_perm.cuid,以及有超级用户权限的进程。
  • IPC_STAT:获取共享内存区的shmid_ds结构,存放到传入的第三个参数中。
  • IPC_RMID:从系统内核中删除该共享内存区。因为每个共享内存区有一个连接数据的计数,除非连接该共享内存区的最后一个进程断接或终止,否则该共享内存区不会被实际删除。这和其他的System V IPC,例如System V消息队列的删除差别很大,倒是和POSIX IPC的xxx_unlink删除操作很相识。调用该命令后,该共享内存区标识符不能再继续被连接。此命令也只能由以下进程执行:有效用户ID等于shm_perm.uid或shm_perm.cuid,以及有超级用户权限的进程。

在Linux中还定义了其他的控制命令,如:IPC_INFO,SHM_INFO,SHM_LOCK,SHM_UNLOCK等,具体可以参考Linux手册。

下面是创建System V共享内存区和查看其属性的测试代码:

#include <iostream>
#include <cstring>
#include <errno.h>

#include <unistd.h>
#include <fcntl.h>
#include <sys/shm.h>

using namespace std;

#define PATH_NAME "/tmp/shm"

int main()
{
    int fd;

    if ((fd = open(PATH_NAME, O_CREAT, 0666)) < 0)
    {
        cout<<"open file "<<PATH_NAME<<"failed.";
        cout<<strerror(errno)<<endl;
        return -1;
    }

    close(fd);

    key_t key = ftok(PATH_NAME, 0);

    int shmID;

    if ((shmID = shmget(key, sizeof(int), IPC_CREAT | 0666)) < 0)
    {
        cout<<"shmget failed..."<<strerror(errno)<<endl;
        return -1;
    }

    shmid_ds shmInfo;
    shmctl(shmID, IPC_STAT, &shmInfo);

    cout<<"shm key:0x"<<hex<<key<<dec<<endl;
    cout<<"shm id:"<<shmID<<endl;
    cout<<"shm_segsz:"<<shmInfo.shm_segsz<<endl;
    cout<<"shm_nattch:"<<shmInfo.shm_nattch<<endl;

    return 0;
}

共享内存示例应用

此示例程序实现没有亲缘关系的两个进程之间通过共享内存通信,同时使用信号量保证两个进程的读写同步:即是发送方在写共享内存的时候,接收端不能读内存区,反之亦同 .

在代码中,使用共享内存传递数据,使用信号量同步读写端基本思路如下:

  • 首先设置信号量初始为0,表示没有写入数据,不可读.
  • 发送端在信号量位0的时候写数据到共享内存,并阻塞读进程,写入完成后,设置信号量位1,此时可以读数据,不能写数据.
  • 接收端在信号量位1的时候读出内存区的数据并阻塞写入端,读出完成后,设置信号量的值位0,表示独处完成,可以再次写数据.

下面是编译和运行结果:

发送端:

接收端

代码如下:

发送:

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

#include <string.h>
int main(int argc,char *argv[])
{
    int running=1;
    int shid;
    int semid;
    int value;
    char *sharem=NULL;

    struct sembuf  sem_b;
    sem_b.sem_num = 0;
    sem_b.sem_flg = SEM_UNDO;

    if((semid=semget((key_t)123456,1,0666|IPC_CREAT))==-1)  // 创建信号量
    {
        perror("semget");
        exit(EXIT_FAILURE);
    }

    if (semctl(semid, 0, SETVAL, 0) == -1)         // 设置初始值位0
    {
        printf("sem init error");
        if(semctl(semid,0,IPC_RMID,0)!=0)
        {
            perror("semctl");
            exit(EXIT_FAILURE);
        }
        exit(EXIT_FAILURE);
    }
    shid=shmget((key_t)654321,(size_t)2048,0600|IPC_CREAT);    // 创建共享内存(读取id)
    if(shid==-1)
    {
        perror("shmget");
        exit(EXIT_FAILURE);
    }

    sharem=shmat(shid,NULL,0);           // 挂载共享内存到进程
    if(sharem==NULL)
    {
        perror("shmat");
        exit(EXIT_FAILURE);
    }

    while(running)
    {
        if((value=semctl( semid, 0, GETVAL ))==0)     // 读取为0时
        {
            printf("write data operate\n");       // 可写
            printf("please input something:");
            scanf("%s",sharem);                   // 读取到共享内存
            sem_b.sem_op = 1;                     // 设置信号量加一操作
            if (semop(semid, &sem_b, 1) == -1)    // 执行加一操作
            {
                fprintf(stderr, "semaphore_p failed\n");
                exit(EXIT_FAILURE);
            }
        }
        if(strcmp(sharem,"end")==0)             // 输入end时结束
        running--;
    }
shmdt(sharem);        // 卸载内存
return 0;
}

接受:

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

int main(int argc,char *argv[])
{
    int running=1;
    char *shm_p=NULL;
    int shmid;
    int semid;
    int value;

    struct sembuf  sem_b;
    sem_b.sem_num = 0;
    sem_b.sem_flg = SEM_UNDO;

    if((semid=semget((key_t)123456,1,0666|IPC_CREAT))==-1)
    {
        perror("semget");
        exit(EXIT_FAILURE);
    }
    shmid=shmget((key_t)654321,(size_t)2048,0600|IPC_CREAT);
    if(shmid==-1)
    {
        perror("shmget");
        exit(EXIT_FAILURE);
    }
    shm_p=shmat(shmid,NULL,0);            // 挂接
    if(shm_p==NULL)
    {
        perror("shmat");
        exit(EXIT_FAILURE);
    }

    while(running)
    {
        if((value=semctl( semid, 0, GETVAL ))==1)
        {
            printf("read data operate\n");
            sem_b.sem_op = -1;                      // 减一
            if (semop(semid, &sem_b, 1) == -1)
            {
                fprintf(stderr, "semaphore_p failed\n");
                exit(EXIT_FAILURE);
            }
            printf("%s\n",shm_p);
        }
        if(strcmp(shm_p,"end")==0)
        running--;
    }
    shmdt(shm_p);                       // 卸载
    if(shmctl(shmid,IPC_RMID,0)!=0)    // 删除共享内存
    {
        perror("shmctl");
        exit(EXIT_FAILURE);
    }

    if(semctl(semid,0,IPC_RMID,0)!=0)     //  删除信号量
    {
        perror("semctl");
        exit(EXIT_FAILURE);
    }
    return 0;
}

转载请注明出处:http://blog.csdn.net/suool/article/details/38515863

Linux程序设计学习笔记----System V进程通信(共享内存)

时间: 2024-10-20 01:05:01

Linux程序设计学习笔记----System V进程通信(共享内存)的相关文章

Linux程序设计学习笔记----System V进程通信之消息队列

一个或多个进程可向消息队列写入消息,而一个或多个进程可从消息队列中读取消息,这种进程间通讯机制通常使用在客户/服务器模型中,客户向服务器发送请求消息,服务器读取消息并执行相应请求.在许多微内核结构的操作系统中,内核和各组件之间的基本通讯方式就是消息队列.例如,在 MINIX 操作系统中,内核.I/O 任务.服务器进程和用户进程之间就是通过消息队列实现通讯的. Linux中的消息可以被描述成在内核地址空间的一个内部链表,每一个消息队列由一个IPC的标识号唯一的标识.Linux 为系统中所有的消息队

Linux程序设计学习笔记----System V进程间通信(信号量)

关于System V Unix System V,是Unix操作系统众多版本中的一支.它最初由AT&T开发,在1983年第一次发布,因此也被称为AT&T System V.一共发行了4个System V的主要版本:版本1.2.3和4.System V Release 4,或者称为SVR4,是最成功的版本,成为一些UNIX共同特性的源头,例如"SysV 初始化脚本"(/etc/init.d),用来控制系统启动和关闭,System V Interface Definitio

Linux 程序设计学习笔记----进程管理与程序开发(下)

转载请注明出处:http://blog.csdn.net/suool/article/details/38419983,谢谢! 进程管理及其控制 创建进程 fork()函数 函数说明具体参见:http://pubs.opengroup.org/onlinepubs/009695399/functions/fork.html 返回值:Upon successful completion, fork() shall return 0 to the child process and shall re

Linux 程序设计学习笔记----进程管理与程序开发(上)

转载请注明出处,http://blog.csdn.net/suool/article/details/38406211,谢谢! Linux进程存储结构和进程结构 可执行文件结构 如下图: 可以看出,此ELF可执行文件存储时(没有调入内存)分为代码区.数据区和未出花数据区三部分. 代码区:存放cpu的执行的机器指令. 数据区:包含程序中的已经初始化的静态变量,以及已经初始化的全局变量. 未初始化数据区:存入的是未初始化的全局变量和未初始化的静态变量. 现在在上面的程序代码中增加一个int的静态变量

Linux程序设计学习笔记----进程间通信——管道

转载请注明出处: http://blog.csdn.net/suool/article/details/38444149, 谢谢! 进程通信概述 在Linux系统中,进程是一个独立的资源管理单元,但是独立而不孤立,他们需要之间的通信,因此便需要一个进程间数据传递.异步.同步的机制,这个机制显然需要由OS来完成管理和维护.如下: 1.同一主机进程间数据交互机制:无名管道(PIPE),有名管道(FIFO),消息队列(Message Queue)和共享内存(Share Memory).无名管道多用于亲

Linux 程序设计学习笔记----终端及串口编程及实例应用

转载请注明出处,http://blog.csdn.net/suool/article/details/38385355. 部分内容类源于网络. 终端属性详解及设置 属性 为了控制终端正常工作,终端的属性包括输入属性.输出属性.控制属性.本地属性.线路规程属性以及控制字符. 其在系统源代码的termios.h中定义(具体的说明文档http://pubs.opengroup.org/onlinepubs/7908799/xsh/termios.h.html),其结构体成员主要是 Thetermios

Linux程序设计学习笔记----网络通信编程API及其示例应用

转载请注明出处, http://blog.csdn.net/suool/article/details/38702855. BSD Socket 网络通信编程 BSD TCP 通信编程流程 图为面向连接的Socket通信的双方执行函数流程.使用TCP协议的通信双方实现数据通信的基本流程如下 建立连接的步骤 1.首先服务器端需要以下工作: (1)调用socket()函数,建立Socket对象,指定通信协议. (2)调用bind()函数,将创建的Socket对象与当前主机的某一个IP地址和TCP端口

Linux 程序设计学习笔记----命令行参数处理

转载请注明出处.http://blog.csdn.net/suool/article/details/38089001 问题引入----命令行参数及解析 在使用linux时,与windows最大的不同应该就是经常使用命令行来解决大多数问题.比如下面这样的: 而显然我们知道C语言程序的入口是mian函数,即是从main函数开始执行,而main函数的原型是: int main( int argc, char *argv[] ); int main( int argc, char **argv );

Linux程序设计学习笔记----Socket网络编程基础之TCP/IP协议簇

转载请注明出处: ,谢谢! 内容提要 本节主要学习网络通信基础,主要涉及的内容是: TCP/IP协议簇基础:两个模型 IPv4协议基础:IP地址分类与表示,子网掩码等 IP地址转换:点分十进制\二进制 TCP/IP协议簇基础 OSI模型 我们知道计算机网络之中,有各种各样的设备,那么如何实现这些设备的通信呢? 显然是通过标准的通讯协议,但是,整个网络连接的过程相当复杂,包括硬件.软件数据封包与应用程序的互相链接等等,如果想要写一支将联网全部功能都串连在一块的程序,那么当某个小环节出现问题时,整只