进程间通讯(二)

System V 中的的三种通讯机制
<1>msg_ids消息队列:
消息队列就是一个消息的链表。能够把消息看做一个记录,具备特定的格式以及特定的优先级。对消息队列有写权限的进程能够向中按照必定的规则添加新消息;对消息队列有读权限的进程则能够从消息队列中读走消息。消息队列是随内核持续的,记录消息队列的数据结构位于内核中,只有在内核重起或者显示删除一个消息队列时,该消息队列才会真正被删除。
与消息队列的相关函数: http://blog.csdn.net/guoping16/article/details/6584024
 
 
//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, '\0' ,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, '\0' ,sizeof (buf));
                   recv_msg(msg_id,_CLIENT_TYPE_,buf);
                   printf( "client->server : %s\n" ,buf);
 
                   printf( "please input\n" );
                   fflush(stdout);
 
                   memset(buf, '\0' ,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\n" );
                   fflush(stdout);
 
                   memset(buf, '\0' ,sizeof (buf));
                   read(0,buf, sizeof (buf)-1);
                   send_msg(msg_id,_CLIENT_TYPE_,buf);
 
                   memset(buf, '\0' ,sizeof (buf));
                   recv_msg(msg_id,_SERVER_TYPE_,buf);
                   printf( "sever->client :" );
                   printf( "%s\n" ,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] = '\0' ;
                   sleep(1);
                    if (index == 15)
                              break ;
          }
          dt_shm(buf);
          printf( "HaHa i am here\n" );
          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\n" ,buf);
                   sleep(1);
                    if (index++ == 20)
                              break ;
          }
          dt_shm(buf);
          printf( "HaHa i am here\n" );
 
}
 
 
 
1、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. 实际访问新映射的页面(由缺页中断完成)
相关文章
相关标签/搜索