进程间通信机制有哪些?

进程间的通信方式:

   1.管道(pipe)及有名管道(named pipe):

    管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

    有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

    2.信号(signal):

     信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

   3.消息队列(message queue):

    消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息。       

   其基本思想是:根据”生产者-消费者”原理,利用内存中公用消息缓冲区实现进程之间的信息交换.  

   内存中开辟了若干消息缓冲区,用以存放消息.每当一个进程向另一个进程发送消息时,便申请一个消息缓冲区,并把已准备好的消息送到缓冲区,然后把该消息缓冲区插入到接收进程的消息队列中,最后通知接收进程.接收进程收到发送里程发来的通知后,从本进程的消息队列中摘下一消息缓冲区,取出所需的信息,然后把消息缓冲区不定期给系统.系统负责管理公用消息缓冲区以及消息的传递.  
   一个进程可以给若干个进程发送消息,反之,一个进程可以接收不同进程发来的消息.显然,进程中关于消息队列的操作是临界区.当发送进程正往接收进程的消息队列中添加一条消息时,接收进程不能同时从该消息队列中到出消息:反之也一样.  

   4.共享内存(shared memory):

     可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。

    这种通信模式需要解决两个问题:第一个问题是怎样提供共享内存;第二个是公共内存的互斥关系则是程序开发人员的责任。   

   5.信号量(semaphore):

     主要作为进程之间及同一种进程的不同线程之间得同步和互斥手段。

   6.套接字(socket);

     套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。

用法:

    1 管道 

  它包括无名管道有名管道两种,前者用于父进程和子进程间的通信,后者用于运行于同一台机器上的任意两个进程间的通信。 
  1.1 无名管道由pipe()函数创建: 
  

  
  
  1. #include <unistd.h>
  2. int pipe(int filedis[2]);//参数filedis返回两个文件描述符:filedes[0]为读而打开,filedes[1]为写而打开。filedes[1]的输出是filedes[0]的输入。
  3. //下面的例子示范了如何在父进程和子进程间实现通信。 
  4. #define INPUT 0
  5. #define OUTPUT 1
  6. void main() 
  7. {
  8.     int file_descriptors[2];
  9.     /*定义子进程号 */
  10.     pid_t pid;
  11.     char buf[256];
  12.     int returned_count;
  13.     /*创建无名管道*/
  14.     pipe(file_descriptors);
  15.     /*创建子进程*/
  16.     if((pid = fork()) == -1) {
  17.         printf("Error in fork\n");
  18.         exit(1);
  19.     }
  20.     /*执行子进程*/
  21.     if(pid == 0) {
  22.         printf("in the spawned (child) process...\n");
  23.         /*子进程向父进程写数据,关闭管道的读端*/
  24.         close(file_descriptors[INPUT]);
  25.         write(file_descriptors[OUTPUT], "test data", strlen("test data"));
  26.         exit(0);
  27.     }else{
  28.         /*执行父进程*/
  29.         printf("in the spawning (parent) process...\n");
  30.         /*父进程从管道读取子进程写的数据,关闭管道的写端*/
  31.         close(file_descriptors[OUTPUT]);
  32.         returned_count = read(file_descriptors[INPUT], buf, sizeof(buf));
  33.         printf("%d bytes of data received from spawned process: %s\n",
  34.         returned_count, buf);
  35.     }
  36. }

        1.2 有名管道可由两种方式创建

   方式一:mkfifo("myfifo","rw"); 
   方式二:mknod myfifo p 

   生成了有名管道后,就可以使用一般的文件I/O函数如open、close、read、write等来对它进行操作

  
  
  1. /* 进程一:读有名管道*/
  2. #include <stdio.h>
  3. #include <unistd.h>
  4. void main() 
  5. {
  6.     FILE * in_file;
  7.     int count = 1;
  8.     char buf[80];
  9.     in_file = fopen("mypipe", "r");
  10.     if (in_file == NULL) {
  11.         printf("Error in fdopen.\n");
  12.         exit(1);
  13.     }
  14.     while ((count = fread(buf, 1, 80, in_file)) > 0)
  15.         printf("received from pipe: %s\n", buf);
  16.     fclose(in_file);
  17. }
  18. /* 进程二:写有名管道*/
  19. #include <stdio.h>
  20. #include <unistd.h>
  21. void main() 
  22. {
  23.     FILE * out_file;
  24.     int count = 1;
  25.     char buf[80];
  26.     out_file = fopen("mypipe", "w");
  27.     if (out_file == NULL) {
  28.         printf("Error opening pipe.");
  29.         exit(1);
  30.     }
  31.     sprintf(buf,"this is test data for the named pipe example\n");
  32.     fwrite(buf, 1, 80, out_file);
  33.     fclose(out_file);
  34. }
    
     2 消息队列  

   消息队列用于运行于同一台机器上的进程间通信,它和管道很相似,是一个在系统内核中用来保存消息的队列,它在系统内核中是以消息链表的形式出现。消息链表中节点的结构用msg声明。
      事实上,它是一种正逐渐被淘汰的通信方式,我们可以用流管道或者套接口的方式来取代它,所以,我们对此方式也不再解释,也建议读者忽略这种方式。
 


 3 共享内存 
    共享内存是运行在同一台机器上的进程间通信最快的方式,因为数据不需要在不同的进程间复制。通常由一个进程创建一块共享内存区,其余进程对这块内存区进行 读写。

       得到共享内存有两种方式:映射/dev/mem设备内存映像文件前一种方式不给系统带来额外的开销,但在现实中并不常用,因为它控制存取的将是 实际的物理内存

   首先要用的函数是shmget,它获得一个共享存储标识符。 
     #include <sys/types.h> 
     #include <sys/ipc.h> 
     #include <sys/shm.h> 
    

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

    这个函数有点类似大家熟悉的malloc函数,系统按照请求分配size大小的内存用作共享内存  
         当共享内存创建后,其余进程可以调用shmat()将其连接到自身的地址空间中
 

       void *shmat(int shmid, void *addr, int flag); 
     shmid为shmget函数返回的共享存储标识符,addr和flag参数决定了以什么方式来确定连接的地址,函数的返回值即是该进程数据段所连接的实际地址,进程可以对此进程进行读写操作。 

      使用共享存储来实现进程间通信的注意点是对数据存取的同步,必须确保当一个进程去读取数据时,它所想要的数据已经写好了。通常,信号量被要来实现对共享存 储数据存取的同步,另外,可以通过使用shmctl函数设置共享存储内存的某些标志位如SHM_LOCK、SHM_UNLOCK等来实现。 


   4 信号量 
   信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是前一节的共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。一般说来,为了获得共享资源,进程需要执行下列操作: 
   (1) 测试控制该资源的信号量。 
   (2) 若此信号量的值为正,则允许进行使用该资源。进程将信号量减1。 
   (3) 若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转入步骤(1)。 
   (4) 当进程不再使用一个信号量控制的资源时,信号量值加1。如果此时有进程正在睡眠等待此信号量,则唤醒此进程。 

  
  
  1. #include <sys/types.h>
  2. #include <sys/ipc.h>
  3. #include <sys/sem.h>
  4. int semget(key_t key, int nsems, int flag);
  5. struct sem {
  6. short sempid;/* pid of last operaton */
  7. ushort semval;/* current value */
  8. ushort semncnt;/* num procs awaiting increase in semval */
  9. ushort semzcnt;/* num procs awaiting semval = 0 */
  10. }  

       key是前面讲过的IPC结构的关键字,flag将来决定是创建新的信号量集合,还是引用一个现有的信号量集合。nsems是该集合中的信号量数。如果是创建新 集合(一般在服务器中),则必须指定nsems;如果是引用一个现有的信号量集合(一般在客户机中)则将nsems指定为0。 

   semctl函数用来对信号量进行操作。 

   int semctl(int semid, int semnum, int cmd, union semun arg); 
   不同的操作是通过cmd参数来实现的,在头文件sem.h中定义了7种不同的操作,实际编程时可以参照使用。 
   
    
     semop函数自动执行信号量集合上的操作数组。 

   int semop(int semid, struct sembuf semoparray[], size_t nops); 
   semoparray是一个指针,它指向一个信号量操作数组。nops规定该数组中操作的数量


  
  
  1. #include <stdio.h>
  2. #include <sys/types.h>
  3. #include <sys/sem.h>
  4. #include <sys/ipc.h>
  5. void main() 
  6. {
  7.     key_t unique_key; /* 定义一个IPC关键字*/
  8.     int id;
  9.     struct sembuf lock_it;
  10.     union semun options;
  11.     int i;
  12.     unique_key = ftok(".", 'a'); /* 生成关键字,字符'a'是一个随机种子*/
  13.     
  14.     /* 创建一个新的信号量集合*/
  15.     id = semget(unique_key, 1, IPC_CREAT | IPC_EXCL | 0666);
  16.     printf("semaphore id=%d\n", id);
  17.     options.val = 1; /*设置变量值*/
  18.     semctl(id, 0, SETVAL, options); /*设置索引0的信号量*/
  19.     /*打印出信号量的值*/
  20.     i = semctl(id, 0, GETVAL, 0);
  21.     printf("value of semaphore at index 0 is %d\n", i);
  22.     /*下面重新设置信号量*/
  23.     lock_it.sem_num = 0; /*设置哪个信号量*/
  24.     lock_it.sem_op = -1; /*定义操作*/
  25.     lock_it.sem_flg = IPC_NOWAIT; /*操作方式*/
  26.     if (semop(id, &lock_it, 1) == -1) {
  27.         printf("can not lock semaphore.\n");
  28.         exit(1);
  29.     }
  30.     i = semctl(id, 0, GETVAL, 0);
  31.     printf("value of semaphore at index 0 is %d\n", i);
  32.     /*清除信号量*/
  33.     semctl(id, 0, IPC_RMID, 0);
  34. }

         可以使用系统调用semget()创建一个新的信号量集,或者存取一个已经存在的信号量集:

            intsemget(key_t key,int nsems,int semflg);
     下面是一个打开和创建信号量集的程序:
    
    
  1. intopen_semaphore_set(key_t keyval,int numsems)
  2. {
  3.     intsid;
  4.     if(!numsems)
  5.         return(-1);
  6.     if((sid=semget(mykey,numsems,IPC_CREAT|0660))==-1)
  7.     {
  8.         return(-1);
  9.     }
  10.     return(sid);
  11. }

        系统调用:semop();
        调用原型:int semop(int semid,struct sembuf*sops,unsign ednsops);

        返回值:0,如果成功。-1,如果失败:errno=E2BIG(nsops大于最大的ops数目)

系统调用:semctl();

原型:int semctl(int semid,int semnum,int cmd,union semunarg);

返回值:如果成功,则为一个正数。如果失败,则为-1:errno=EACCESS(权限不够)

  • 4
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值