Linux下进程间的通信

版权声明:原创文章,转载请声名。 https://blog.csdn.net/qq_40707451/article/details/84930855

进程间通信就是在不同进程之间传播或交换信息。但是,我们知道进程是具有独立性的,因此,进程之间不可能直接进行通信。人们之间交流需要空气作为介质,进程之间“交流”也需要一个媒介,那么进程之间存在着什么双方都可以访问的介质呢?

  • 进程的用户空间是互相独立的,一般而言是不能互相访问的,唯一的例外是共享内存区。
  • 系统空间是“公共场所”,各进程均可以访问,所以内核也可以提供这样的条件。
  • 还有双方都可以访问的外设。两个进程当然也可以通过磁盘上的普通文件交换信息,或者通过“注册表”或其它数据库中的某些表项和记录交换信息。广义上这也是进程间通信的手段,但是一般都不把这算作“进程间通信”。

进程间通信官方的说法:

进程间通信(IPC,Interprocess communication)是一组编程接口,让程序员能够协调不同的进程,使之能在一个操作系统里同时运行,并相互传递、交换信息。这使得一个程序能够在同一时间里处理许多用户的要求。因为即使只有一个用户发出要求,也可能导致一个操作系统中多个进程的运行,进程之间必须互相通话。IPC接口就提供了这种可能性。每个IPC方法均有它自己的优点和局限性,一般,对于单个程序而言使用所有的IPC方法是不常见的。

简单来讲程间通信就是不同进程之间进行数据的交换。

进程间通信主要包括管道, 系统IPC(包括消息队列,信号,共享存储), 套接字(SOCKET).

管道分为匿名管道和命名管道,匿名管道只能用于具有亲缘关系(父子、兄弟)进程之间的通信,而命名管道则可用于任意进程之间。

系统IPC的三种方式类同,都是使用了内核里的标识符来识别。

管道与文件描述符,文件指针的关系?

 其实管道的使用方法与文件类似,都能使用read,write,open等普通IO函数. 管道描述符来类似于文件描述符. 事实上, 管道使用的描述符,文件指针和文件描述符最终都会转化成系统中SOCKET描述符. 都受到系统内核中SOCKET描述符的限制. 本质上LINUX内核源码中管道是通过空文件来实现.

  • IPC目的

1)数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几兆字节之间。

2)共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。

3)通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。

4)资源共享:多个进程之间共享同样的资源。为了做到这一点,需要内核提供锁和同步机制。

5)进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

进程通过与内核及其它进程之间的互相通信来协调它们的行为。Linux支持多种进程间通信(IPC)机制,信号和管道是其中的两种。除此之外,Linux还支持System V 的IPC机制(用首次出现的Unix版本命名)。

从IPC的角度看,管道提供了从一个进程向另一个进程传输数据的有效方法。但是,管道有一些固有的局限性:

因为读数据的同时也将数据从管道移去,因此,管道不能用来对多个接收者广播数据。

管道中的数据被当作字节流,因此无法识别信息的边界。

如果一个管道有多个读进程,那么写进程不能发送数据到指定的读进程。同样,如果有多个写进程,那么没有办法判断是它们中那一个发送的数据。

  • 管道特性

  1.     半双工单向通信
  2.     读写特性
  3.     管道自带同步与互斥保护操作
  4.     提供字节流服务:数据传输比较灵活,但有可能会造成数据粘连
  5.     生命周期随进程
  • 管道简单理解:内核的一块缓冲区用于在进程间传输数据资源,操作系统为了接口统一,对于管道这块缓冲区的操作,和io操作使用同一套接口。

匿名管道基本使用CODE:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>


int main()
{
	  //int pipe(int pipefd[2])
	  //pipefd 用于获取管道的操作描述符
	  //pipefd[0] 用于从管道读取数据
	  //pipefd[1] 用于向管道写入数据
	  //返回值: 成功:0  失败:-1
	  int pipefd[2];
	  //创建匿名管道
	  int ret=pipe(pipefd);
	  if(ret<0)
	  {
		    perror("pipe error!");
		    return -1;
	  }
	  //创建子进程
	  int pid=fork();
	  if(pid<0)
	  {
		    perror("fork error!");
		    return -1;
	  }
	  else if(pid==0)
	  {
		    //子进程用于从管道中读取数据
		    //因为子进程读取在父进程写入之前,这时管道中没有数据,此时子进程挂起等待数据写入
		    close(pipefd[1]);
		    char buff[1024]={0};
		    read(pipefd[0],buff,1023);
		    printf("%s\n",buff);
          }
	  else
	  {
		    //父进程用于向管道中写入数据
		    close(pipefd[0]);
		    char *ptr="leagu of lengend!";
		    write(pipefd[1],ptr,strlen(ptr));
	  }
	  waitpid(pid,NULL,0);
	  close(pipefd[0]);
	  close(pipefd[1]);
	  return 0;
}

命名管道基本使用CODE:

//命名管道写入

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/stat.h>
#include<errno.h>
#include<fcntl.h>
#include<string.h>

int main()
{
	  int ret =mkfifo("./test.fifo",0664);
	  if(ret<0)
	  {
		    if(errno!=EEXIST)
		    {
			      perror("mkfifo error!");
			      return -1;
		    }
	  }
	  int fd =open("./test.fifo",O_WRONLY);
	  if(fd<0)
	  {
		    perror("open error!");
		    return -1;
	  }
	  printf("open fifo success!\n");
	  while(1)
	  {
		    char buff[1024]={0};
		    printf("please input:");
		    fflush(stdout);
		    scanf("%s",buff);
		    write(fd,buff,strlen(buff));
	  }
	  close(fd);
	  return 0;
}
//命名管道读取

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/stat.h>
#include<errno.h>
#include<fcntl.h>


int main()
{
	  int ret =mkfifo("./test.fifo",0664);
	  if(ret<0)
	  {
		    if(errno!=EEXIST)
		    {
			      perror("mkfifo error!");
			      return -1;
		    }
	  }
	  int fd =open("./test.fifo",O_RDONLY);
	  if(fd<0)
	  {
		    perror("open error!");
		    return -1;
	  }
	  printf("open fifo success!\n");
	  while(1)
	  {
		    char buff[1024]={0};
		    read(fd,buff,1023);
		    printf("read buff:%s\n",buff);
	  }
	  close(fd);
	  return 0;
}

Message Queues(消息队列)

消息队列就是消息的一个链表,它允许一个或多个进程向它写消息,一个或多个进程从中读消息。Linux维护了一个消息队列向量表:msgque,来表示系统中所有的消息队列。其定义如下:

struct msqid_ds *msgque[MSGMNI];

该向量表中的每一个元素都是一个指向msqid_ds数据结构的指针,而一个msqid_ds数据结构完整地描述了一个消息队列

MSGMNI的值是128,就是说,系统中同时最多可以有128个消息队列。

当创建消息队列时,一个新的msqid_ds数据结构被从系统内存中分配出来,并被插入到msgque 向量表中。

Linux提供了四个消息队列操作。

1. 创建或获得消息队列(MSGGET)

2. 发送消息   

3. 接收消息

4. 消息控制

共享内存

通常由一个进程创建,其余进程对这块内存区进行读写。得到共享内存有两种方式:映射/dev/mem设备和内存映像文件。前一种方式不给系统带来额外的开销,但在现实中并不常用,因为它控制存取的是实际的物理内存;常用的方式是通过shmXXX函数族来实现共享内存:

对于共享内存,linux本身无法对其做同步,需要程序自己来对共享的内存做出同步计算,而这种同步很多时候就是用信号量实现。

信号量

本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。信号量,分为互斥信号量,和条件信号量。一般说来,为了获得共享资源,进程需要执行下列操作:

(1)测试控制该资源的信号量;

(2)若此信号量的值为正,则允许进行使用该资源,进程将信号量减去所需的资源数;

(3)若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转至步骤(1);

(4)当进程不再使用一个信号量控制的资源时,信号量值加其所占的资源数,如果此时有进程正在睡眠等待此信号量,则唤醒此进程。

 

猜你喜欢

转载自blog.csdn.net/qq_40707451/article/details/84930855