第九章 进程间通信
进程通信有如下一些目的:
A、数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几 M 字节之间
B、共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程,应该立刻看到。
C、通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
D、资源共享:多个进程之间共享同样的资源。为了作到这一点,需要内核提供锁和同步机制。
E、进程控制:有些进程希望完全控制另一个进程的执行(如 Debug 进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
现在 linux 使用的进程间通信方式:
1)管道(pipe)和有名管道(FIFO)
2)信号(signal)
3)消息队列
4)共享内存
5)信号量
6)套接字(socket)
9.1 管道
管道是 Linux 支持的最初 Unix IPC 形式之一,具有以下特点:
(1)管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;
(2)只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);
(3)单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的
文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。
(4)数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添
加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。
c
#include <unistd.h>
int pipe(int fd[2])
返回的 fd[0]用于读,fd[1]用于写。因此,一个进程在由 pipe()创建管道后,一般再 fork 一个子进程,然后通过管道实现父子进程间的通信(因此也不难推出,只要两个进程中存在亲缘关系,这里的亲缘关系指的是具有共同的祖先,都可以采用管道方式来进行通信)。
管道的局限:
- 只支持单向数据流;
- 只能用于具有亲缘关系的进程之间;
- 没有名字;
- 管道的缓冲区是有限的(管道制存在于内存中,在管道创建时,为缓冲区分配一个页面大小);
- 管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式。
9.1.1 单项管道
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
void read_from_pipe(int fd)
{
char message[100];
read(fd,message,100);
printf("The messiage from parent is %s\n",message);
}
void write_to_pipe(int fd)
{
char *message = "hello world\n";
write(fd,message,strlen(message)+1);
}
int main()
{
int fd[2];
pid_t pid;
int stat_val;
if(pipe(fd))
{
printf("error");
exit(1);
}
pid=fork();
switch(pid)
{
case -1:
printf("fork error");
exit(1);
case 0:
printf("The child process is:%d\n",getpid());
close(fd[1]);
read_from_pipe(fd[0]);
exit(0);
default:
printf("The father process is:%d\n",getpid());
close(fd[0]);
write_to_pipe(fd[1]);
wait(&stat_val);
exit(0);
}
return 0;
}
9.1.2 全双工通讯,两个通道
这里用pipe1 父进程发送子线程 , ,pipe2 子线程发送父父线程
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
void child_rw_pipe(int readfd,int writefd)
{
char *message1="from child process\n";
write(writefd,message1,strlen(message1)+1);
char message2[100];
read(readfd,message2,100);
printf("child read is: %s\n",message2);
}
void parent_rw_pipe(int readfd,int writefd)
{
char *message1 = "from parent process\n";
write(writefd,message1,strlen(message1)+1);
char message2[100];
read(readfd,message2,100);
printf("parent read is: %s \n",message2);
}
int main()
{
int pipe1[2],pipe2[2];
pid_t pid;
int stat_val;
if(pipe(pipe1))
{
printf("error");
exit(1);
}
if(pipe(pipe2))
{
printf("error");
exit(1);
}
pid=fork();
switch(pid)
{
case -1:
printf("fork error");
exit(1);
case 0:
printf("The child process is:%d\n",getpid());
close(pipe1[1]);
close(pipe2[0]);
child_rw_pipe(pipe1[0],pipe2[1]);
exit(0);
default:
printf("Thefather process is:%d\n",getpid());
close(pipe1[0]);
close(pipe2[1]);
parent_rw_pipe(pipe2[0],pipe1[1]);
wait(&stat_val);
exit(0);
}
return 0;
}
9.2 有名管道
FIFO 不同于管道之处在于它提供一个路径名与之关联,以 FIFO 的文件形式存在于文件系统中。这样,即使与 FIFO 的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过 FIFO 相互通信(能够访问该路径的进程以及 FIFO 的创建进程之间),因此,通过 FIFO 不相关的进程也能交换数据。
c
include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int mkfifo(const char * pathname, mode_t mode)
mkfifo()会依参数 pathname 建立特殊的 FIFO 文件,该文件必须不存在,而参数 mode 为该文件的权限(mode%~umask) ,因此 umask 值也会影响到 FIFO 文件的权限。Mkfifo()建立的 FIFO 文件其他进程都可以用读写一般文件的方式存取。当使用 open()来打开 FIFO 文件时,O_NONBLOCK 旗标会有影响
1、当使用 O_NONBLOCK 旗标时,打开 FIFO 文件来读取的操作会立刻返回,但是若还没有其他进
程打开 FIFO 文件来读取,则写入的操作会返回 ENXIO 错误代码。
2、没有使用 O_NONBLOCK 旗标时,打开 FIFO 来读取的操作会等到其他进程打开 FIFO 文件来写入才正常返回。同样地,打开 FIFO 文件来写入的操作会等到其他进程打开 FIFO 文件来读取后才正常返回。
下面用server 发送,client 接收实现全双工通讯。
server.c
c
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#define FIFO_READ "readfifo"
#define FIFO_WRITE "writefifo"
#define BUF_SIZE 1024
int main(void)
{
int wfd,rfd;
char buf[BUF_SIZE];
int len;
umask(0);
if(mkfifo(FIFO_WRITE,S_IFIFO|0666)){
printf("can't create FIFO %s beause %s",FIFO_WRITE,strerror(errno));
exit(1);
}
umask(0);
wfd = open(FIFO_WRITE,O_WRONLY);
if(wfd == -1){
printf("open FIFO %s error :%s",FIFO_WRITE,strerror(errno));
exit(1);
}
while((rfd = open(FIFO_READ,O_RDONLY)) == -1){
sleep(1);
}
while(1) {
printf("Server:");
fgets(buf,BUF_SIZE,stdin);
buf[strlen(buf)-1] = '\0';
if(strncmp(buf,"quit",4) == 0) {
close(wfd);
unlink(FIFO_WRITE);
close(rfd);
exit(0);
}
write(wfd,buf,strlen(buf));
len = read(rfd,buf,BUF_SIZE);
if(len > 0) {
buf[len] = '\0';
printf("Client:%s\n",buf);
}
}
}
linux中的 umask 函数主要用于:
在创建新文件或目录时,屏蔽掉新文件或目录不应有的访问允许权限。文件的访问允许权限共有9种,分别是:r w x r w x r w x(它们分别代表:用户读 用户写 用户执行 组读 组写 组执行 其它读 其它写 其它执行)。
其实这个函数的作用,就是设置允许当前进程创建文件或者目录最大可操作的权限,比如这里设置为0,它的意思就是0取反再创建文件时权限相与,也就是:(~0) & mode 等于八进制的值0666 & mode了,这样就是给后面的代码调用函数mkfifo给出最大的权限,避免了创建目录或文件的权限不确定性。
S_IFIFO|0666"指明创建一个命名管道且存取权限为0666,即创建者、与创建者同组的用户、其他用户对该命名管道的访问权限都是可读可写(这里要注意umask对生成的管道文件权限的影响)。
client.c
c
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#define FIFO_READ "writefifo"
#define FIFO_WRITE "readfifo"
#define BUF_SIZE 1024
int main(void)
{
int wfd,rfd;
char buf[BUF_SIZE];
int len;
umask(0);
if(mkfifo(FIFO_WRITE,S_IFIFO|0666)){
printf("can't create FIFO %s beause %s",FIFO_WRITE,strerror(errno));
exit(1);
}
while((rfd = open(FIFO_READ,O_RDONLY)) == -1) {
sleep(1);
}
Wfd = open(FIFO_WRITE,O_WRONLY);
if(wfd == -1){
printf("open FIFO %s error :%s",FIFO_WRITE,strerror(errno));
exit(1);
}
while(1){
len = read(rfd,buf,BUF_SIZE);
if(len > 0) {
buf[len] = '\0';
printf("Server:%s\n",buf);
}
printf("Client:");
fgets(buf,BUF_SIZE,stdin);
buf[strlen(buf)-1] = '\0';
if(strncmp(buf,"quit",4) == 0) {
close(wfd);
unlink(FIFO_WRITE);
close(rfd);
exit(0);
}
write(wfd,buf,strlen(buf));
}
return 0;
}