zoukankan      html  css  js  c++  java
  • 进程间通信(二)

    System V 中的的三种通信机制
    <1>msg_ids消息队列:
    消息队列就是一个消息的链表。可以把消息看作一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向中按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读走消息。消息队列是随内核持续的,记录消息队列的数据结构位于内核中,只有在内核重起或者显示删除一个消息队列时,该消息队列才会真正被删除。
     
     
    //comm.h
    #pragma once
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    #include <string.h>
    #include <errno.h>
     
    #define _PROJ_NAME_ "/home/shihao/Linux/Class8/msg_ids/tmp"
    #define _PROJ_ID_ 0x66
    #define _SIZE_ 1024
     
    #define _SERVER_TYPE_ 1
    #define _CLIENT_TYPE_ 2
     
    typedef struct msgbuf
    {
               long mtype;
               char mtext[_SIZE_];
    }msgbuf;
     
    static int creatFun( int flags);
    int creat_msg_queue();
    int get_msg_queue();
    int destory_msg_queue(int msg_id);
    int recv_msg(int msg_id, int t, char * msg);
    int send_msg(int msg_id, int t, const char * msg);
     
     
     
    //comm.c
    #include "comm.h"
     
    static int creatFun( int flags)
    {
              key_t _key = ftok(_PROJ_NAME_,_PROJ_ID_);
               if (_key == -1)
              {
                       perror( "ftok" );
                        return -1;
              }
               int msg_id =  msgget(_key,flags);
               if (msg_id == -1)
              {
                       perror( "msgget" );
                        return -2;
              }
               return msg_id;
    }
     
    int creat_msg_queue()
    {
               int flags = IPC_CREAT | IPC_EXCL | 0644;
               return creatFun(flags);
    }
     
    int get_msg_queue()
    {
               int flags = IPC_CREAT;
               return creatFun(flags);
    }
     
    int destory_msg_queue(int msg_id)
    {
               if (msgctl(msg_id,IPC_RMID,NULL) == -1)
              {
                       perror( "msgctl" );
                        return -3;
              }
               return 0;
    }
     
    int recv_msg(int msg_id, int t, char * msg)
    {
              msgbuf _msg;
              _msg.mtype = t;
              memset(_msg.mtext, '' ,sizeof (_msg.mtext));
               if (msgrcv(msg_id,&_msg,sizeof (_msg.mtext),t,0) < 0)
              {
                       perror( "msgrcv" );
                        return -1;
              }
               else
              {
                       strcpy(msg,_msg.mtext);
              }
               return 0;
    }
    int send_msg(int msg_id, int t, const char * msg)
    {
     
              msgbuf _msg;
              _msg.mtype = t;
              strncpy(_msg.mtext,msg,strlen(msg)+1);
               if (msgsnd(msg_id,&_msg,sizeof (_msg.mtext),0) == -1)
              {
                       perror( "msgrcv" );
                        return -1;
              }
               return 0;
    }
     
     
     
    //server.c
    #include "comm.h"
     
    int main()
    {
               int msg_id = creat_msg_queue();
               char buf[_SIZE_];
               while (1)
               {
                       memset(buf, '' ,sizeof (buf));
                       recv_msg(msg_id,_CLIENT_TYPE_,buf);
                       printf( "client->server : %s " ,buf);
     
                       printf( "please input " );
                       fflush(stdout);
     
                       memset(buf, '' ,sizeof (buf));
                       read(0,buf, sizeof (buf)-1);
                       send_msg(msg_id,_SERVER_TYPE_,buf);
              }
              destory_msg_queue(msg_id);
               return 0;
    }
     
     
     
     
    //client.c
    #include "comm.h"
    int main()
    {
               int msg_id = get_msg_queue();
               char buf[_SIZE_];
               while (1)
               {
                       printf( "please input " );
                       fflush(stdout);
     
                       memset(buf, '' ,sizeof (buf));
                       read(0,buf, sizeof (buf)-1);
                       send_msg(msg_id,_CLIENT_TYPE_,buf);
     
                       memset(buf, '' ,sizeof (buf));
                       recv_msg(msg_id,_SERVER_TYPE_,buf);
                       printf( "sever->client :" );
                       printf( "%s " ,buf);
               }
               return 0;
    }
     
    <2>sem_ids信号量:
    信号量与其他进程间通信方式不大相同,它主要提供对进程间共享资源访问控制机制。相当于内存中的标志,进程可以根据它判定是否能够访问某些共享资源,同时,进程也可以修改该标志。除了用于访问控制外,还可用于进程同步。信号灯有以下两种类型:
    ①二值信号量:最简单的信号量形式,信号量的值只能取0或1,类似于互斥锁。 注:二值信号量 能够实现互斥锁的功能,但两者的关注内容不同。信号量 强调共享资源,只要共享资源可用,其他进程同样可以修改信号量 的值;互斥锁更强调进程,占用资源的进程使用完资源后,必须由进程本身来解锁。
    ②计算信号量:信号量的值可以取任意非负值(当然受内核本身的约束)。
     
     
    //test.h
    #pragma once
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>
    #include <string.h>
    #include <errno.h>
    #include <unistd.h>
     
    #define _PATH_ "/home/shihao/Linux/Class10/tmp"
    #define _ID_ 0x66
    #define _SIZE_ 100
     
    typedef union semun
    {
               int val;
               struct semid_ds* buf;
               unsigned short * array;
               struct seminfo* _buf;
    }semun;
     
    static int commFun( int nsems, int semflags);
     
    int create_sem_set(int nsems);
     
    int get_sem_set();
     
    int init_sem_set(int sem_id, int semnum, int val);
     
    int destory_sem_set(int sem_id);
     
    static int commOp( int sem_id, int semnum,int op);
     
    int P(int sem_id, int which);
     
    int V(int sem_id, int which);
     
     
     
     
    //test.c
    #include "comm.h"
    static int commFun( int nsems, int semflags)
    {
              key_t _key = ftok(_PATH_,_ID_);    //creat key
               if (_key == -1)
              {
                       perror( "ftok" );
                        return -1;
              }
               int sem_id = semget(_key,nsems,semflags);
               if (sem_id == -1)
              {
                       perror( "semget" );
                        return -1;
              }
               else
              {
                        return sem_id;
              }
    }
    int create_sem_set(int nsems)
    {
               int semflags = IPC_CREAT | IPC_EXCL | 0644;
               return commFun(nsems,semflags);
    }
    int get_sem_set()
    {
               int semflags = IPC_CREAT;
               return commFun(0,semflags);
    }
    int init_sem_set(int sem_id, int semnum, int val)
    {
              semun _un;
              _un.val = val;
               if (semctl(sem_id,semnum,SETVAL,_un) == -1)
              {
                       perror( "semctl" );
                        return -1;
              }
               return 0;
    }
    int destory_sem_set(int sem_id)
    {
               if (semctl(sem_id,0,IPC_RMID) == -1)
              {
                       perror( "semctl" );
                        return -1;
              }
               return 0;
    }
    static int commOp( int sem_id, int semnum,int op)
    {
               struct sembuf _sem;
              _sem.sem_num = semnum;
              _sem.sem_op = op;
              _sem.sem_flg = 0;
               if (semop(sem_id,&_sem,1) == -1)
              {
                       perror( "semop" );
                        return -1;
              }
               return 0;
    }
     
    /*struct sembuf
    {
     
        short semnum; /*信号量集合中的信号量编号,0代表第1个信号量*/
     
        short val;    /*若val>0进行V操作信号量值加val,表示进程释放控制的资源 */
     
                      /*若val<0进行P操作信号量值减val,若(semval-val)<0(semval为该信号量值),则调用进程阻塞,直到资源可用;若设置IPC_NOWAIT不会睡眠,进程直接返回EAGAIN错误*/
     
                     /*若val==0时阻塞等待信号量为0,调用进程进入睡眠状态,直到信号值为0;若设置IPC_NOWAIT,进程不会睡眠,直接返回EAGAIN错误*/
     
        short flag;  /*0 设置信号量的默认操作*/
     
                     /*IPC_NOWAIT设置信号量操作不等待*/
     
                     /*SEM_UNDO 选项会让内核记录一个与调用进程相关的UNDO记录,如果该进程崩溃,则根据这个进程的UNDO记录自动恢复相应信号量的计数值*/
     
      };
    */
    int P(int sem_id, int which)
    {
              commOp(sem_id,which,-1);
               return 0;
    }
    int V(int sem_id, int which)
    {
              commOp(sem_id,which,1);
               return 0;
    }
     
     
    //test.c
    #include "comm.h"
    int main()
    {
               int sem_id = create_sem_set(1);
              init_sem_set(sem_id,0,1);
              pid_t id = fork();
               if (id == 0)
              { //child
                        while (1)
                       {
                                  int child_sem_id = get_sem_set();
                                 P(child_sem_id,0);
                        printf( "A" );
                        fflush(stdout);
                        usleep(rand()%1234567);
                        printf( "A" );
                        fflush(stdout);
                                 V(child_sem_id,0);
                       }
              }
               else if (id > 0)
              { //father
                        while (1)
                       {
                                 P(sem_id,0);
                        printf( "B" );
                        fflush(stdout);
                        usleep(rand()%1111111);
                        printf( "B" );
                        fflush(stdout);
                                 V(sem_id,0);
                       }
              }
    }
     
    <3>shm_ids共享内存区:
    共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以。
    采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据[1]: 一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建 立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回 文件的。因此,采用共享内存的通信方式效率是非常高的。
     
    可以说,共享内存是一种最为高效的进程间通信方式,因为进程可以直接读写内存,不需要任何数据的复制。为了在多个进程间交换信息,内核专门留出了一块内存区,这段内存区可以由需要访问的进程将其映射到自己的私有地址空间。因此,进程就可以直接读写这一内存区而不需要进行数据的复制,从而大大提高了效率。当然,由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等。其原理示意图如图1所示。
    图1  共享内存原理示意图
        共享内存的实现分为两个步骤:第一步是创建共享内存,这里用到的函数是shmget(),也就是从内存中获得一段共享内存区域;第二步是映射共享内存,也就是把这段创建的共享内存映射到具体的进程空间中,这里使用的函数是shmat()。到这里,就可以使用这段共享内存了,也就是可以使用不带缓冲的I/O读写命令对其进行操作。除此之外,还有撤销映射的操作,其函数为shmdt()。
     
    //shm.h
    #pragma once
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <errno.h>
    #include <unistd.h>
     
    #define _PATH_ "/home/shihao/Linux/Class11/shm/1"
    #define _ID_ 0x66
    #define _SIZE_ 4*1024
     
    static int comm_shm( int size, int shmflag);
    int create_shm();
    int get_shm();
    void * at_shm(int shm_id);
    int dt_shm(char * shmaddr);
    int destory_shm(int shm_id);
     
     
    //shm.c
    #include "shm.h"
     
    static int comm_shm( int size, int shmflag)
    {
    //       key_t _key = ftok(_PATH_,_ID_);
    //       if(_key == -1)
    //       {
    //                 perror("ftok");
    //                 return -1;
    //       }
               int shm_id = shmget((key_t)123456,_SIZE_,shmflag);
               if (shm_id == -1)
              {
                       perror( "getshm" );
                        return -1;
              }
               return shm_id;
    }
    int create_shm()
    {
               int shmflag = IPC_CREAT | IPC_EXCL | 0664;
               return comm_shm(_SIZE_,shmflag);
    }
    int get_shm()
    {
               int shmflag = IPC_CREAT;
               return comm_shm(0,shmflag);
    }
    void * at_shm(int shm_id)
    {
               return shmat(shm_id,NULL,0);
    }
    int dt_shm(char * shmaddr)
    {
               return shmdt(shmaddr);
    }
    int destory_shm(int shm_id)
    {
               return shmctl(shm_id,IPC_RMID,NULL);
    }
     
     
    //server.c
    #include "shm.h"
    int main()
    {
               int shm_id = create_shm();
               char * buf = (char *)at_shm(shm_id);
              size_t index = 0;
               while (1)
              {
                       buf[index++] = 'A' ;
                       buf[index] = '' ;
                       sleep(1);
                        if (index == 15)
                                  break ;
              }
              dt_shm(buf);
              printf( "HaHa i am here " );
              destory_shm(shm_id);
    }
     
     
     
     
     
    //client.c
    #include "shm.h"
     
    int main()
    {
               int shm_id = get_shm();
               char * buf = (char *)at_shm(shm_id);
              size_t index = 0;
               while (1)
              {
                       printf( "%s " ,buf);
                       sleep(1);
                        if (index++ == 20)
                                  break ;
              }
              dt_shm(buf);
              printf( "HaHa i am here " );
     
    }
     
     
     
    一、mmap系统调用    
        mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。munmap执行相反的操作,删除特定地址区域的对象映射。
    当使用mmap映射文件到进程后,就可以直接操作这段虚拟地址进行文件的读写等操作,不必再调用read,write等系统调用.但需注意,直接对该段内存写时不会写入超过当前文件大小的内容.
    采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。  
        基于文件的映射,在mmap和munmap执行过程的任何时刻,被映射文件的st_atime可能被更新。如果st_atime字段在前述的情况下没有得到更新,首次对映射区的第一个页索引时会更新该字段的值。用PROT_WRITE 和 MAP_SHARED标志建立起来的文件映射,其st_ctime 和 st_mtime在对映射区写入之后,但在msync()通过MS_SYNC 和 MS_ASYNC两个标志调用之前会被更新。
    用法:
     
    void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
    int munmap(void *start, size_t length);
    返回说明:
    成功执行时,mmap()返回被映射区的指针,munmap()返回0。失败时,mmap()返回MAP_FAILED[其值为(void *)-1],munmap返回-1。
    errno被设为以下的某个值
    EACCES:访问出错
    EAGAIN:文件已被锁定,或者太多的内存已被锁定
    EBADF:fd不是有效的文件描述词
    EINVAL:一个或者多个参数无效
    ENFILE:已达到系统对打开文件的限制
    ENODEV:指定文件所在的文件系统不支持内存映射
    ENOMEM:内存不足,或者进程已超出最大内存映射数量
    EPERM:权能不足,操作不允许
    ETXTBSY:已写的方式打开文件,同时指定MAP_DENYWRITE标志
    SIGSEGV:试着向只读区写入
    SIGBUS:试着访问不属于进程的内存区
    参数:
    start:映射区的开始地址。
     
    length:映射区的长度。
     
    prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起
    PROT_EXEC //页内容可以被执行
    PROT_READ //页内容可以被读取
    PROT_WRITE //页可以被写入
    PROT_NONE //页不可访问
     
    flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体
    MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。
    MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。
    MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。
    MAP_DENYWRITE //这个标志被忽略。
    MAP_EXECUTABLE //同上
    MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。
    MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。
    MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展。
    MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。
    MAP_ANON //MAP_ANONYMOUS的别称,不再被使用。
    MAP_FILE //兼容标志,被忽略。
    MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。
    MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。
    MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。
     
    fd:有效的文件描述词。如果MAP_ANONYMOUS被设定,为了兼容问题,其值应为-1。
     
    offset:被映射对象内容的起点。
     
     
    二. 系统调用mmap()用于共享内存的两种方式: 
    (1)使用普通文件提供的内存映射:适用于任何进程之间;此时,需要打开或创建一个文件,然后再调用mmap();典型调用代码如下: 
     
    fd=open(name, flag, mode);
    if(fd<0)
       ...
    ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0);
    通过mmap()实现共享内存的通信方式有许多特点和要注意的地方
    (2)使用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间;由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。 
    对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可.
     
     
    三. mmap进行内存映射的原理
    1.在用户虚拟地址空间中寻找空闲的满足要求的一段连续的虚拟地址空间,为映射做准备(由内核mmap系统调用完成)
    2.建立虚拟地址空间和文件或设备的物理地址之间的映射(设备驱动完成)
    3. 实际访问新映射的页面(由缺页中断完成)
     
  • 相关阅读:
    C#之Raw Socket实现网络封包监视
    es6Promise及小程序Promise用法
    在微信小程序的JS脚本中使用Promise来优化函数处理
    小程序踩过的一个小坑---解析二维码decodeURIComponent() url解码
    js json转url参数
    微信小程序-实现分享(带参数)
    php中的匿名函数和闭包(closure)
    微信小程序之回调函数
    php AES cbc模式 pkcs7 128位加密解密(微信小程序)
    微信小程序,开发中几个重要的知识点(加密解密,转发,进入场景,session_key)
  • 原文地址:https://www.cnblogs.com/shihaochangeworld/p/5707156.html
Copyright © 2011-2022 走看看