进程间通信

来源:互联网转载 in 百科 2024-03-29 13:25:54

进程间通信就是在不同进程之间传播或交换信息,那么不同进程之间存在着什么双方都可以访问的介质呢?进程的用户空间是互相独立的,一般而言是不能互相访问的,唯一的例外是共享内存区。但是,系统空间却是“公共场所”,所以内核显然可以提供这样的条件。除此以外,那就是双方都可以访问的外设了。在这个意义上,两个进程当然也可以通过磁盘上的普通文件交换信息,或者通过“注册表”或其它数据库中的某些表项和记录交换信息。广义上这也是进程间通信的手段,但是一般都不把这算作“进程间通信”。因为那些通信手段的效率太低了,而人们对进程间通信的要求是要有一定的实时性。

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

管道包括三种:1)普通管道PIPE, 通常有种限制,一是半双工,只能单向传输;二是只能在父子进程间使用. 2)流管道s_pipe: 去除了第一种限制,可以双向传输. 3)命名管道:name_pipe, 去除了第二种限制,可以在许多并不相关的进程之间进行通讯.

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


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

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



FAQ2: 管道的使用方法?

答: 主要有下面几种方法: 1)pipe, 创建一个管道,返回2个管道描述符.通常用于父子进程之间通讯. 2)popen, pclose: 这种方式只返回一个管道描述符,常用于通信另一方是stdin or stdout; 3)mkpipe: 命名管道, 在许多进程之间进行交互.



FAQ3: 管道与系统IPC之间的优劣比较?

答: 管道: 优点是所有的UNIX实现都支持, 并且在最后一个访问管道的进程终止后,管道就被完全删除;缺陷是管道只允许单向传输或者用于父子进程之间.

系统IPC: 优点是功能强大,能在毫不相关进程之间进行通讯; 缺陷是关键字KEY_T使用了内核标识,占用了内核资源,而且只能被显式删除,而且不能使用SOCKET的一些机制,例如select,epoll等.



FAQ4: WINDOS进程间通信与LINUX进程间通信的关系?

答: 事实上,WINDOS的进程通信大部分移植于UNIX, WINDOS的剪贴板,文件映射等都可从UNIX进程通信的共享存储中找到影子.



FAQ5: 进程间通信与线程间通信之间的关系?

答: 因为WINDOWS运行的实体是线程, 狭义上的进程间通信其实是指分属于不同进程的线程之间的通讯.而单个进程之间的线程同步问题可归并为一种特殊的进程通信.它要用到内核支持的系统调用来保持线程之间同步. 通常用到的一些线程同步方法包括:Event, Mutex, 信号量Semaphore, 临界区资源等.



  Linux的进程间通信(IPC,InterProcess Communication)通信方法有管道、消息队列、信号量、共享内存、套接口等。



  管道分为有名管道和无名管道,无名管道只能用于亲属进程之间的通信,而有名管道则可用于无亲属关系的进程之间。



#define INPUT 0

#define OUTPUT 1

void main()

{

 int file_des criptors[2];

 /*定义子进程号 */

 pid_t pid;

 char buf[BUFFER_LEN];

 int returned_count;

 /*创建无名管道*/

 pipe(file_des criptors);

 /*创建子进程*/

 if ((pid = fork()) == - 1)

 {

  printf("Error in fork\n");

  exit(1);

 }

 /*执行子进程*/

 if (pid == 0)

 {

  printf("in the spawned (child) process...\n");

  /*子进程向父进程写数据,关闭管道的读端*/

  close(file_des criptors[INPUT]);

  write(file_des criptors[OUTPUT], "test data", strlen("test data"));

  exit(0);

 }

 else

 {

  /*执行父进程*/

  printf("in the spawning (parent) process...\n");

  /*父进程从管道读取子进程写的数据,关闭管道的写端*/

  close(file_des criptors[OUTPUT]);

  returned_count = read(file_des criptors[INPUT], buf, sizeof(buf));

  printf("%d bytes of data received from spawned process: %s\n",

  returned_count, buf);

 }

}



  上述程序中,无名管道以int pipe(int filedis[2]);方式定义,参数filedis返回两个文件描述符filedes[0]为读而打开,filedes[1]为写而打开,filedes[1]的输出是filedes[0]的输入;



  在Linux系统下,有名管道可由两种方式创建(假设创建一个名为“fifoexample”的有名管道):



  (1)mkfifo("fifoexample","rw");



  (2)mknod fifoexample p



  mkfifo是一个函数,mknod是一个系统调用,即我们可以在shell下输出上述命令。



  有名管道创建后,我们可以像读写文件一样读写之:



/* 进程一:读有名管道*/

void main()

{

 FILE *in_file;

 int count = 1;

 char buf[BUFFER_LEN];

 in_file = fopen("pipeexample", "r");

 if (in_file == NULL)

 {

  printf("Error in fdopen.\n");

  exit(1);

 }

 while ((count = fread(buf, 1, BUFFER_LEN, in_file)) > 0)

  printf("received from pipe: %s\n", buf);

  fclose(in_file);

}



/* 进程二:写有名管道*/

void main()

{

 FILE *out_file;

 int count = 1;

 char buf[BUFFER_LEN];

 out_file = fopen("pipeexample", "w");

 if (out_file == NULL)

 {

  printf("Error opening pipe.");

  exit(1);

 }

 sprintf(buf, "this is test data for the named pipe example\n");

 fwrite(buf, 1, BUFFER_LEN, out_file);

 fclose(out_file);

}



  消息队列用于运行于同一台机器上的进程间通信,与管道相似;



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



int shmget(key_t key, int size, int flag); /* 获得一个共享存储标识符 */



  该函数使得系统分配size大小的内存用作共享内存;



void *shmat(int shmid, void *addr, int flag); /* 将共享内存连接到自身地址空间中*/



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



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



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



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



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



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



  下面是一个使用信号量的例子,该程序创建一个特定的IPC结构的关键字和一个信号量,建立此信号量的索引,修改索引指向的信号量的值,最后清除信号量:



#include <stdio.h>

#include <sys/types.h>

#include <sys/sem.h>

#include <sys/ipc.h>

void main()

{

 key_t unique_key; /* 定义一个IPC关键字*/

 int id;

 struct sembuf lock_it;

 union semun options;

 int i;



 unique_key = ftok(".", ''''a''''); /* 生成关键字,字符''''a''''是一个随机种子*/

 /* 创建一个新的信号量集合*/

 id = semget(unique_key, 1, IPC_CREAT | IPC_EXCL | 0666);

 printf("semaphore id=%d\n", id);

 options.val = 1; /*设置变量值*/

 semctl(id, 0, SETVAL, options); /*设置索引0的信号量*/



 /*打印出信号量的值*/

 i = semctl(id, 0, GETVAL, 0);

 printf("value of semaphore at index 0 is %d\n", i);



 /*下面重新设置信号量*/

 lock_it.sem_num = 0; /*设置哪个信号量*/

 lock_it.sem_op = - 1; /*定义操作*/

 lock_it.sem_flg = IPC_NOWAIT; /*操作方式*/

 if (semop(id, &lock_it, 1) == - 1)

 {

  printf("can not lock semaphore.\n");

  exit(1);

 }



 i = semctl(id, 0, GETVAL, 0);

 printf("value of semaphore at index 0 is %d\n", i);



 /*清除信号量*/

 semctl(id, 0, IPC_RMID, 0);

}



  套接字通信并不为Linux所专有,在所有提供了TCP/IP协议栈的操作系统中几乎都提供了socket,而所有这样操作系统,对套接字的编程方法几乎是完全一样的。



进程间通信各种方式效率比较

类型

无连接

可靠

流控制

记录

消息类型优先级

免责声明:本站文字信息和图片素材来源于互联网,仅用于学习参考,如内容侵权与违规,请联系我们进行删除,我们将在三个工作日内处理。联系邮箱:chuangshanghai#qq.com(把#换成@)

-- End --