面试时一再地被问到进程间通信的情况,而我的理解一直停留在概念上和名词上,必须要做点实验加深理解。
一、概述
由于网上资料和各种书籍关于IPC的讲述有少许区别或完整性不够,所以要先选择要查阅的资料,这里我主要基于书《APUE》和《UNIX网络编程第二卷进程间通信》学习IPC。
《UNIX网络编程第二卷进程间通信》中主要叙述4种不同形式的IPC:
1.消息传递(管道、FIFO、消息队列)
2.同步(互斥锁、条件变量、读写锁、文件与记录锁、信号量(semaphore)
3.共享内存区(匿名共享内存区、有名共享内存区)
4.远程过程调用(Solaris门、Sun RPC)
该书讲它不讨论通过计算机通信网络实现的IPC,而主要讨论不涉及网络的IPC,所以,我认为加上
5.Socket(Unix Domain Socket、TCP/IP Socket)就构成完整的IPC.
(面试时最好回答管道、FIFO、信号量等,而不是消息传递、同步等,因为更多书是按照前者分类IPC)
由于每个进程有各自独立的地址空间,进程间通信就要在地址空间外借助于一些其他的东西,图1.1[1]可以形象说明IPC借助什么进行通信。
二、pipes和FIFOs
1. pipes(管道) :使用最广泛,具有亲缘关系的进程间才能使用管道进行IPC。创建的pipes存在于内核中。一般情况下,管道是半双工管道(不是全双工,也不是单工),即在某一时刻从一端进从一端出,不能在一端即能进也能出(补:单工的意思是任何时刻只能一端进一端出)。有些系统提供了全双工管道,不过,使用半双工管道基本就可以解决很多问题了。用两个半双工管道就能实现全双工管道的功能。
shell中使用管道举例:ls -a | grep "jun"
自己的程序中使用管道的方法:父进程创建pipe,然后fork子进程,这时父子进程通过pipe进行通信了。
程序中使用管道例子代码:(为了抓住重点,删除了处理错误的代码)
int main()
{
int fd[2]; //管道是一种文件,有read端和write端,要用到两个file descriptor
pipe(fd); //创建管道
pid = fork();
if (pid > 0) { //父进程
close(fd[0]); //fd[0]是read端,fd[1]是write端,这里关闭read端。close()是File I/O的API。
write(fd[1], ”hello pipe/n", 11); //通过write端,写入数据。write()是File I/O的API。
}
else { //子进程
close(fd[1]); //关闭write端
int n = read(fd[0], buf, MAX_SIZE);
write(1, buf, n); //整数1,在我所知道的操作系统的实现中是标准输出的File descriptor
}
exit(0);
}
程序代码中使用管道的套路一般是:pipe;fork;exec*,标准I/O提供了popen和pclose函数整合有关管道的函数,使得可以在自己的程序中用更少的代码实现管道。
2. FIFOs, FIFOs are sometimes called named pipes(有名管道)。FIFOs突破了只能在具有亲缘关系的进程间使用的限制,这通过创建一个FIFO类型的文件实现。FIFO数据流是半双工。
shell中使用FIFO举例:
mkfifo fifo1 //创建名为fifo1的有名管道,使用ls命令可以看到fifo1文件(文件类型字母是p)
cat < fifo1 & //后台运行cat,重定向fifo1为输入
ls –a > fifo1 //ls -a的结果重定向到fifo1,也就是通过fifo1传递数据给cat命令
在Unix/Linux中提供了命令mkfifo用于创建FIFO和unlink或rm删除所创建的FIFO。
程序中使用FIFO例子代码:(一个很简单的server-client程序,没有出错处理)
server.c |
#define FIFO "fifo" int main() return 0; |
client.c |
#define FIFO "fifo" //FIFO的名字就是文件名 int main() unlink(FIFO); //删除FIFO return 0; |
测试方法,先server &, 然后client。
小结[1]
参考资料
1、《UNIX网络编程第二卷进程间通信》
2、《APUE》