zoukankan      html  css  js  c++  java
  • Linux进程间通信 共享内存+信号量+简单样例

    每个进程都有着自己独立的地址空间,比方程序之前申请了一块内存。当调用fork函数之后。父进程和子进程所使用的是不同的内存。

    因此进程间的通信,不像线程间通信那么简单。可是共享内存编程接口能够让一个进程使用一个公共的内存区段,这样我们便能轻易的实现进程间的通信了(当然对于此内存区段的訪问还是要控制好的)。

    共享内存实现进程通信的长处:

    共享内存是进程通信方式中最高速的方式之中的一个,它的高速体如今,为数据共享而进行的复制很少。这里举例来说。使用消息队列时。一个进程向消息队列写入消息时。这里有一次数据的复制,从用户空间到内核空间。而还有一个进程读取消息的时候。又有一次数据的复制。从内核空间到用户空间,这无疑是耗费时间与资源的。然而共享内存的存在则无需这两次复制,进程是从它们各自的地址空间直接訪问共享的内存区段的。

    缺点:

    还是拿消息队列来对照,消息队列已经实现了对自身读写的保护,然而共享内存须要我们开发人员自己来实现,这无疑添加了开发的难度


    以下我们还是沿袭之前的风格,先简要介绍GNU/LINUX中提供哪些关于共享内存的编程接口,再以一个简单的小样例收尾。

    #include<sys/shm,h>


    int   shmget(key_t  key, size_t size,int flag)

    该函数用于创建一个新的共享内存区段,或者是获取一个已经存在的共享内存区段

    返回一个id(描写叙述符),该id唯一表示系统中创建的这段内存

    key能够是一个非0的值,也能够指定为IPC_PRIVATE,跟信号量一样。IPC_PRIVATE指明创建的是一个私有的内存区段。这种话其他进程无法找到它。通常在仅须要一个进程组

    的内部进行訪问时,会使用这样的方法。

    size是创建的内存区段的大小,由于内存区段是创建在内存页面上的。它的容量上限通常为4MB,视实际环境决定。

    flag。该參数一般是由两部分组成,一部分是訪问权限。一部分是指令。

    指定具有三种情况。一种是创建一个共享内存区段,那么设置为IPC_CREAT就可以,假设该内存区段以存在时我们须要返回一个错误的话,那么传入IPC_CREAT|IPC_EXCL,在已经存在的情况下。会返回一个错误,而且将errno置为EEXIST.第三种是获取已经存在的内存区段,那么传入0就可以。关于权限,普通情况下我们设置0666或者0600就可以了,它的详细值和意义例如以下所看到的:

    0400   用户拥有读取权限

    0200   用户拥有写入权限

    0040   用户组拥有读取权限

    0020   用户组拥有写入权限


    0004   其它用户拥有读取权限

    0002   其它用户拥有写入权限


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

    操作成功返回0。否则返回错误值

    shmid,要操作的共享内存区段的描写叙述符

    cmd,要进行操作的指令

    shmid_ds 用于获取共享内存数据结构体,该结构体里存储关于该段内存的全部信息。这里不细说了。大家能够百度了解一下。

    该函数一般是用于完毕三个功能。一是cmd为IPC_STAT,读取当前的共享内存数据结构体,二是cmd为IPC_SET,用于写入共享内存结构体。三是传入IPC_RMID,用于移除该段内存

    void* shmat(int shmid,const void* shmaddr,int flag)

    返回值是共享内存地址映射在该进程内存地址的起始地址。为-1时,挂接失败。

    shmid,共享内存的描写叙述符

    shmaddr,指定共享内存地址映射在进程内存地址的什么位置,置为NULL时。让内核自己决定。

    flag,假设为SHM_READONLY,那么在调用进程中将以仅仅读方式挂接内存区段。传入0时(不指定时)将以可读写方式挂接。

    int shmdt(void* shmaddr)

    该函数作用是脱离内存区段,取消从共享内存区段想进程的局部空间的映射,从而也释放了为了挂接区段而占用的局部空间地址。成功返回0,失败返回-1.

    shmaddr。是调用shmat时返回的进程空间地址(也就是共享内存地址映射在进程空间的地址)


    共享内存的基本操作介绍完了。在文章开头提到过,共享内存须要我们进行读写控制,这里我採用信号量。(假设对于信号量不清楚的,能够去看我之前写的一篇文章用信号量和Posix线程操作来实现双线程快速下载)以下提供一个小样例:

    1  父进程负责将数据写入到共享内存中,假设共享内存数据量满了则等待

    2  子进程负责从共享内存中将数据读出来,假设没有数据,则等待

    有点任务队列驱动的进程池的意思,以下是源码:

    #include <sys/shm.h>
    #include <sys/sem.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <wait.h>
    #define MAX_SIZE 100
    #define SHM_KEY_T 9494
    #define SEM_KEY_T 8989

    struct SHM_BLOCK{
        int semid;
        int dataCount;
        int beginPos;
        int curPos;
        char data[MAX_SIZE];
    };

    void childProcess();
    void parentProcess();

    SHM_BLOCK* block; //以下要用到的指向共享内存的指针
    struct sembuf buf; //操作信号量的结构体
    int  pid;//用于存储子进程的pid
    int shmid;//共享内存的id
    int main()
    {
        shmid=shmget(SHM_KEY_T,sizeof(SHM_BLOCK),0600|IPC_CREAT);
        block=(SHM_BLOCK*)shmat(shmid,(const void*)0,0);
        block->semid=semget(SEM_KEY_T,1,0600|IPC_CREAT);
        block->dataCount=0;
        block->beginPos=0;
        block->curPos=0;
        semctl(block->semid,0,SETVAL,1); //初始化信号量。赋值为1
        pid=fork();
        if(pid==0)
            childProcess();
        else
            parentProcess();
        return 0;
    }
    void childProcess(){
        printf("Im child  Process pid is %d lets begin do work ",getpid());
        getchar();
        int taskCount=0;
        char task;
        block=(SHM_BLOCK*)shmat(shmid,(const void*)0,0);
        buf.sem_flg=0;
        buf.sem_num=0;
        while(taskCount<200){
        buf.sem_op=-1;
        semop(block->semid,&buf,1);
        buf.sem_op=1;
        if(block->dataCount==0){
            semop(block->semid,&buf,1);
            continue;
        }
        block->dataCount--;
        task=block->data[block->beginPos++];
        block->beginPos%=MAX_SIZE;
        semop(block->semid,&buf,1);
        taskCount++;
        printf("Cur Task is %d   the data is %c ",taskCount,task);
        }
        shmdt((const void*)block);
        return ;
    }

    void parentProcess(){
        printf("Im parent Process pid is %d lets begin add work ",getpid());
        getchar();
        buf.sem_flg=0;
        buf.sem_num=0;
        int taskCount=0;
        while(taskCount<200){
            buf.sem_op=-1;
            semop(block->semid,&buf,1);
            buf.sem_op=1;
            if(block->dataCount>=MAX_SIZE)
            {
                semop(block->semid,&buf,1);
                continue;
            }
            block->dataCount++;
            block->data[block->curPos++]='a'+taskCount%26;
            block->curPos%=MAX_SIZE;
            semop(block->semid,&buf,1);
            taskCount++;
        }
        //回收进程资源
        waitpid(pid,NULL,0);
        //释放调信号量和共享内存
        semctl(block->semid,0,IPC_RMID);
        shmdt((const void*)block);
        shmctl(shmid,IPC_RMID,0);
        return ;
    }

    执行截图就不贴了。父进程加入200个任务到任务队列中。子进程不断从任务队列中获取任务打印它的值。注意父进程要记得回收子进程资源,否则这里子进程可能成为僵尸进程。最后,释放掉共享内存和信号量,它们是不会随着进程退出就自己主动释放掉的,而是作为内核资源一直存在,须要我们手动释放。


    若有错误欢迎指出。




  • 相关阅读:
    document.getElementById(), getElementsByname(),getElementsByClassName(),getElementsByTagName()方法表示什么以及其意义
    Go -10 Go Web 简单实现
    Go -09 Go 函数和方法区别
    Go -08 Go win 环境搭建
    Go-07 Go 规范代码风格
    Go-06 Go 语言注释(comment)
    Go-05 Go 转义字符
    Go-04 Go 语法要求和注意事项
    Go-03 Go 快速入门
    Go-02 搭建 Go 开发环境(mac系统)
  • 原文地址:https://www.cnblogs.com/tlnshuju/p/7089173.html
Copyright © 2011-2022 走看看