进程间通信

进程是一个独立的资源分配单元,不同进程(用户进程)之间的资源是独立的,没有关联的,不能在一个进程中直接访问另一个进程中的资源。

但是进程不是孤立的,不同的进程需要进行信息的交互和状态的传递,因此需要进程间通信(IPC(inter processes communication)).

进程间通信的目的

数据传输。一个进程需要将他的数据传输给另一个进程。

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

资源共享。多个进程之间共享同样的资源(需要内核提供互斥和同步机制)。

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

linux进程间的通信方式

匿名管道

管道也叫做无名(匿名)管道,他是 unix 系统 IPC 最古老形式,所有的 unix 系统都支持这种通信方式。

例如,统计一个目录中文件的数目命令:ls | wc -l,为了执行这个命令,shell创建了两个进程来分别执行 ls 和 wc 命令

管道的特点

管道其实是一个在内核中维护的缓冲器,这个缓冲期的存储能力是有限的,不同的操作系统下大小不一定相同。

管道拥有文件读写操作的特性,可以按照操作文件的方式来操作管道。匿名管道没有文件实体,有名管道有文件实体,但是不存储数据。

管道是一个字节流,使用管道时不存在消息或者消息边界的概念,从管道中读取数据的进程可以读取任意大小,而不管写入数据的进程写入了多少数据。

通过管道传输的数据是顺序的,从管道读出数据的顺序和写入管道中数据的顺序一致。

管道中数据单向传输,从一端写入从另一端读取,是半双工的。

管道中的数据只能被读取一次,读完之后被抛弃以便写入新的数据,管道中不能使用lseek来随机的读取数据。

匿名管道只能用在同一个公共祖先的管道之间(父子、兄弟等具有亲缘关系的管道之间)。

管道的数据结构

管道的数据结构是一个环形队列。

匿名管道的使用

创建与使用管道

#include

int pipe(int pipefd[2]);

作用: 创建一个匿名管道用于进程间通信

参数: 一个传入值传出结果的数组参数,pipefd[0]对应的是管道的读端,pipefd[1]对应的是管道的写端。

返回: 成功0,失败-1

管道是默认阻塞的: 如果管道中没有数据,read阻塞。如果管道满了,write阻塞。

注意: 匿名管道只能用于有公共祖先的进程之间通信(父子进程,兄弟进程等)。

查看管道缓冲大小的shell命令:

ulimit -a

查看管道缓冲大小的函数:

#include

long fpathconf(int fd, int name);

子进程发送数据给父进程,父进程读取数据的例子:

#include

#include

#include

#include

#include

int main()

{

// 需要在 fork 之前创建管道

int aiPipeFd[2];

int iRet = pipe(aiPipeFd);

if (-1 == iRet) {

perror("pipe create failed!\n");

exit(0);

}

// 创建子进程

pid_t tPid = fork();

if (tPid > 0) {

printf("I am parent, pid:%d\n", getpid());

// 关闭管道的写端

close(aiPipeFd[1]);

char acBuf[1024] = {0};

while(1) {

int iLen = read(aiPipeFd[0], acBuf, sizeof(acBuf));

printf("parent pid:%d, recv:%s\n", getpid(), acBuf);

bzero(acBuf, 1024);

}

}

else if (tPid == 0) {

printf("I am child, pid:%d\n", getpid());

// 关闭管道的读端

close(aiPipeFd[0]);

char* pStr = "Hello, I am child!";

while(1) {

write(aiPipeFd[1], pStr, strlen(pStr));

sleep(1);

}

}

return 0;

}

输出:

获取管道的大小,其中 name 参数可以从 man fpathconf 中查询

#include

#include

#include

#include

#include

int main()

{

int aiPipeFd[2];

int iRet = pipe(aiPipeFd);

if (-1 == iRet) {

perror("pipe created failed!\n");

exit(0);

}

// 获取管道的大小

long lSize = fpathconf(aiPipeFd[0], _PC_PIPE_BUF);

printf("pipe size:%ld\n", lSize);

return 0;

}

管道的读写特点

使用管道时需要注意以下几种特殊情况(假设都是阻塞I/O操作):

如果所有的指向管道写端的文件描述符都被关闭了(管道写端引用计数为0),当有进程从管道读端读数据,管道中剩余的数据被读完之后,再次 read 会返回0,就像读到文件结尾一样。

如果指向管道写端的文件描述符没有被关闭(管道写端引用计数大于0),且持有管道写端描述符的进程没有向管道中写数据。此时如果有进程从管道中读数据,当管道中的数据被读完之后,再次 read 将会阻塞,直到管道中被写入了新数据才会读到数据并返回。

如果所有指向管道读端的文件描述符都被关闭了(管道读端引用计数为0),当有进程向管道中写数据,那么该进程会收到一个 SIGPIPE 信号,通常会导致进程异常终止。

如果指向管道读端的文件描述符没有被关闭(管道读端引用计数大于0),且持有管道读端描述符的进程没有从管道中读数据。此时如果有进程向管道中写数据,当管道被写满之后,再次 write 将会阻塞,直到管道中有空位置才会写入数据并返回。

总结:

读管道:

管道中有数据,read返回实际读到的字节数。

管道中无数据:

写端被全部关闭,read返回0(相当于读到文件末尾)

写端没有完全关闭,read阻塞等待。

写管道:

管道读端全部被关闭,进程异常终止(进程收到 SIGPIPE 信号)

管道读端没有全部被关闭

管道已满,write阻塞

管道没有满,write写入数据并返回实际写入的字节数