进程是程序运行的实例;每个进程可以并发运行多个线程。进程内(线程间)的数据可以共享,但是进程之间并不能互相访问,因为每个进程都维护着自己的text、stack、heap等等,它们的地址对其他进程不可见。
除了使用文件,还有其他几种方法可以实现进程间的数据共享。它们各自有各自的特点,适用于不同的应用场景。
(* 这里不涉及到网络套接字。)
Pipes
================================
管道通信,是Unix系统里非常古老的通信形式。它有两个限制:
* 半双工(某些系统提供全双工);
* 通信的进程必须有共同的ancester。
管道的应用非常常见,比如当我们在终端输入管道命令时,shell会为每个命令建立一个进程,并且在前一个命令的输出和后一个命令的输入之间建立管道通信。
每个进程有两个管道口,一个用作输入,一个用作输出。如果这个进程fork子进程,那么子进程同样具有两个管道口。父进程关掉自己的输入口,子进程关掉自己的输出口,父进程的输出就可以到达子进程的输入。当然,进程内部也可以使用管道通信(虽然它没什么作用^_^)。
pile函数的原型如下:
#include
int pipe(int fd[2]);
其中,fd[0]为管道输入,fd[1]为管道输出。
假设在一个种植月季的智能温室里,控制中心的电脑上运行着这样一个应用程序,名为RoseGarden,它启动多个进程,分别执行不同的任务,比如温度调节、光线调节、水分调节、肥料施用……
程序如下:
#include <iostream>
#include <unistd.h>
#include <memory.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
using namespace std;
int main() {
cout << "****************************" << endl;
cout << "******** RoseGarden ********" << endl;
cout << "****************************" << endl;
int fd[2];
if (pipe(fd) < 0) {
cerr << "Error: pipe." << endl;
return -1;
}
pid_t pid;
if ((pid = fork()) < 0) {
cerr << "Error: fork." << endl;
return -1;
} else if (pid == 0) {
close(fd[1]);
cout << " Start." << endl;
char data[64];
string str;
while (1) {
memset(data, 0, sizeof(data));
read(fd[0], data, sizeof(data));
str = " R: " + string(data);
cout << str << endl;
}
} else {
close(fd[0]);
int n = 0;
string str;
while (1) {
str = "RoseGarden " + to_string(n++) + " piping.";
write(fd[1], str.data(), str.size());
sleep(1);
}
}
return 0;
}
执行结果:
$ ./RoseGarden
****************************
******** RoseGarden ********
****************************
Start.
R: RoseGarden 0 piping.
R: RoseGarden 1 piping.
R: RoseGarden 2 piping.
R: RoseGarden 3 piping.
R: RoseGarden 4 piping.
R: RoseGarden 5 piping.
这说明父进程RoseGarden的数据通过管道,到达了子进程Water中。
如果将pipe的文件描述符duplicate到标准输入/输出上,会发生非常有趣的事情。在文件IO中,dup函数用来复制文件描述符,函数原型如下:
#include
int dup(int fd);
int dup2(int fd, int fd2);
使用dup函数,那么其返回值和fd会指向内核中的同一个文件table,因此不论是操作其返回值还是fd,修改的是同一个文件。使用dup2函数,那么fd2会关闭自己(如果它已打开),然后指向fd的文件table。
将代码修改如下:
if ((pid = fork()) < 0) {
cerr << "Error: fork." << endl;
return -1;
} else if (pid == 0) {
close(fd[1]);
cout << " Start." << endl;
if (fd[0] != STDIN_FILENO) {
if (dup2(fd[0], STDIN_FILENO) != STDIN_FILENO) {
cerr << " Error: dup2." << endl;
exit(1);
}
close(fd[0]);
}
if (execlp("more", "more", (char *)0) < 0) {
cerr << " Error: execlp." << endl;
exit(1);
}
} else {
close(fd[0]);
int n = 0;
string str;
while (1) {
str = "RoseGarden " + to_string(n++) + " piping.\r\n";
for (int i = 0; i < 10; i++)
str += to_string(i) + " ++++++++++++\r\n";
write(fd[1], str.data(), str.size());
sleep(1);
}
}
程序执行的结果:
$ ./RoseGarden
****************************
******** RoseGarden ********
****************************
Start.
RoseGarden 0 piping.
0 ++++++++++++
1 ++++++++++++
2 ++++++++++++
3 ++++++++++++
4 ++++++++++++
5 ++++++++++++
6 ++++++++++++
7 ++++++++++++
8 ++++++++++++
9 ++++++++++++
RoseGarden 1 piping.
0 ++++++++++++
1 ++++++++++++
2 ++++++++++++
3 ++++++++++++
4 ++++++++++++
5 ++++++++++++
6 ++++++++++++
--More--
这说明,父进程往pipe里面写的数据,由pipe的read端接收。此时more命令读取的STDIN_FILENO的数据,实际上就是读取管道的数据。此时more的命令,比如s、d、q……有效。
另外,虽然管道是单向的,我们可以建立两条管道,实现双向通信功能。
其实,大部分我们要实现的功能是类似的,比如创建一条管道、fork一个子进程、关闭不用的管道ends、exec一个命令、等待命令结束……我们可以使用popen函数,处理这些类似的功能。它的函数原型为:
#include
FILE *popen(const char *cmdstring, const char *type);
int pclose(FILE *fp);
因此之前的程序可以大大的被简化,如下所示:
#include <iostream>
#include <unistd.h>
#include <memory.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
using namespace std;
int main() {
cout << "****************************" << endl;
cout << "******** RoseGarden ********" << endl;
cout << "****************************" << endl;
FILE *fp;
if ((fp = popen("more", "w")) == NULL) {
cerr << "Error: popen." << endl;
exit(1);
}
int n = 0;
string str;
cout << "Start writing pipe: " << endl;
while (1) {
str = "RoseGarden " + to_string(n++) + " piping.\r\n";
for (int i = 0; i < 10; i++)
str += to_string(i) + " ++++++++++++\r\n";
fprintf(fp, str.data());
fflush(fp);
sleep(1);
}
return 0;
}
ps,这里的fp是文件stream,因此别忘了fflush。
FIFOs
================================
FIFOs也被称为named pipes。
Unnamed pipes只能用在有相同ancestor的进程之间,就是说创建这个pipe的进程必须是同一个。
FIFOs也是文件。
FIFO的实验在终端下进行,首先在终端1号上执行:
$ mkfifo fifo1
$ cat fifo1
然后在终端2号上执行:
$ echo "hello world" >> fifo1
就会发现终端1号上打印:
$ cat fifo1
hello world
这说明终端2号往fifo1里面写入的数据,被终端1号读取到了。
FIFOs在终端里的作用非常大,它和之前的pipe一起,可以实现各种命令的组合。
XSI IPC: Message Queues
================================
消息队列是消息组成的链表,它存储在内核中,由一个identifier引用。
* 《Unix环境高级编程》的作者,是有多不喜欢消息队列?以至于隔一会儿就把它批一顿,而且完全没有给出它的应用example,另外在最末尾还建议“we come to the conclusion that we shouldn't use them for new applications”。
* 既然这样,就不做练习了!直接进入下一节吧。
XSI IPC: Semaphores
================================
信号量其实不是用于进程间通信的,它是一个counter,用来保护进程间的共享数据。
* 同上。需要使用信号量的时候,再考虑考虑mutex和record locking。
* 不做练习!直接进入下一节。
XSI IPC: Shared Memory
================================
共享内存是最快速的IPC方法。
假设有这样一个应用场景:RoseGarden每隔一小时拍摄一张照片,放置在共享内存中,供另外一个进程PictureDraw使用。由于照片的体积很大,因此拷贝照片耗费的时间太太,共享内存是非常合适的解决方法。
代码如下:
#include <iostream>
#include <unistd.h>
#include <memory.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
using namespace std;
#define SHM_SIZE 4000000
int main() {
cout << "****************************" << endl;
cout << "******** RoseGarden ********" << endl;
cout << "****************************" << endl;
pid_t pid;
if ((pid = fork()) < 0) {
cerr << "Error: fork." << endl;
exit(1);
} else if (pid == 0) {
cout << " Start." << endl;
int shmid;
void *shmptr;
if ((shmid = shmget(IPC_PRIVATE, SHM_SIZE, 0600)) < 0) {
cerr << "Error: shgmget." << endl;
exit(1);
}
if ((shmptr = shmat(shmid, 0, 0)) == (void *)-1) {
cerr << "Error: shmptr." << endl;
exit(2);
}
cout << " Shared-Memory start at " << shmptr << endl;
cout << " shmid = " << shmid << endl;
if (shmctl(shmid, IPC_RMID, 0) < 0) {
cerr << "Error: shmctl." << endl;
exit(1);
}
} else {
}
return 0;
}
执行结果:
$ ./RoseGarden
****************************
******** RoseGarden ********
****************************
Start.
Shared-Memory start at 0x7fd5d7bb3000
shmid = 11075601
关闭
站长推荐
/2
DiracFatCat 2016-6-6 13:32
用户1291786 2016-6-6 09:48
用户1678053 2016-6-1 08:40