进程间通信

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

进程间通信主要包括管道, 系统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_descriptors[2];

/*定义子进程号 */

pid_t pid;

char buf[BUFFER_LEN];

int returned_count;

/*创建无名管道*/

pipe(file_descriptors);

/*创建子进程*/

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

{

printf("Error in fork

");

exit(1);

}

/*执行子进程*/

if (pid == 0)

{

printf("in the spawned (child) process...

");

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

close(file_descriptors[INPUT]);

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

exit(0);

}

else

{

/*执行父进程*/

printf("in the spawning (parent) process...

");

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

close(file_descriptors[OUTPUT]);

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

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

",

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.

");

exit(1);

}

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

printf("received from pipe: %s

", 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

");

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

", 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

", 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.

");

exit(1);

}

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

printf("value of semaphore at index 0 is %d

", i);

/*清除信号量*/

semctl(id, 0, IPC_RMID, 0);

}

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

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

类型

无连接

可靠

流控制

记录

消息类型优先级

Copyright© 1999-2026 C114 All Rights Reserved | 联系我们 | 沪ICP备12002291号-4