进程间通信
部分内容参考《Unix网络编程 卷2》
管道
只能在有公共祖先的进程中使用。
实验:
1 #include <stdio.h>
2 #include "apue.h"
3 #define MAXLINE 1024
4
5 int main()
6 {
7 int fd[2];
8 char buf[MAXLINE]={0};
9 pid_t pid;
10 if(pipe(fd)<0)
11 {
12 perror("pipe error");
13 exit(1);
14 }
15 if((pid = fork())<0)
16 {
17 perror("fork");
18 }
19 else if(pid>0)
20 {
21 close(fd[0]);
22 write(fd[1],"hello",5);
23 }
24 else
25 {
26 close(fd[1]);
27 read(fd[0],buf,5);
28 printf("I read %s\n",buf);
29 }
30 }
管道也可以用来实现父子进程同步(之前使用信号实现的)
函数popen和pclose
popen建立连接另一个进程的管道。
实验:大写字母转小写字母
可以看到,以读的方式popen,则子进程的标准输出会和父进程的管道读端连接起来。
1 #include <stdio.h>
2 #include "apue.h"
3 #include <sys/wait.h>
4 #define MAXLINE 1024
5
6 int main()
7 {
8 FILE * fpin;
9 char line[MAXLINE];
10 fpin = popen("./tolower","r");
11 if(fpin==NULL)
12 {
13 perror("popen");
14 exit(1);
15 }
16 for(;;)
17 {
18 fputs("prompt>",stdout);
19 fflush(stdout);
20 if(fgets(line,MAXLINE,fpin)==NULL)
21 {
22 break;
23 }
24 if(fputs(line,stdout)==EOF)
25 {
26 perror("fputs");
27 exit(2);
28 }
29 }
30 if(pclose(fpin)==-1)
31 {
32 perror("pclose");
33 exit(3);
34 }
35 putchar('\n');
36 exit(0);
37 }
1 #include <stdio.h>
2 #include <ctype.h>
3 #include "apue.h"
4
5 int main()
6 {
7 char c;
8 while((c=getchar())!=EOF)
9 {
10 if(isupper(c))
11 {
12 c = tolower(c);
13 }
14 if(putchar(c)==EOF)
15 {
16 perror("putchar");
17 exit(1);
18 }
19 if(c=='\n')
20 fflush(stdout);
21 }
22 exit(0);
23 }
协同进程
一个过滤程序既产生某个过滤程序的输入,又读取某个过滤程序的输出,就变成了协同进程。
信号SIGPIPE:读进程已经终止,再写管道,会产生这个信号。
子进程:
1 #include <stdio.h>
2 #include "apue.h"
3 #include <string.h>
4 #define MAXLINE 1024
5
6 int main()
7 {
8 int n,int1,int2;
9 char line[MAXLINE];
10 while((n=read(STDIN_FILENO,line,MAXLINE))>0)//必须read,不能fgets(这个会缓冲)
11 {
12 line[n]=0;
13 if(sscanf(line,"%d%d",&int1,&int2)==2)
14 {
15 sprintf(line,"%d\n",int1+int2);
16 n = strlen(line);
17 if(write(STDOUT_FILENO,line,n)!=n)
18 {
19 perror("write");
20 exit(1);
21 }
22 }
23 else
24 {
25 if(write(STDOUT_FILENO,"invalid args\n",13)!=13)
26 {
27 perror("write");
28 exit(2);
29 }
30 }
31 }
32
33 }
父进程:
1 #include <stdio.h>
2 #include <string.h>
3 #include "apue.h"
4 #define MAXLINE 1024
5
6 int main()
7 {
8 //fd1:父进程读,子进程写,fd2:父进程写,子进程读
9 int n;
10 int fd1[2],fd2[2];
11 char line[MAXLINE];
12 pid_t pid;
13 if(pipe(fd1)<0 || pipe(fd2)<0)
14 {
15 perror("pipe");
16 exit(1);
17 }
18 if((pid = fork())<0)
19 {
20 perror("fork");
21 exit(2);
22 }
23 else if(pid>0)
24 {
25 close(fd1[1]);
26 close(fd2[0]);
27 while(fgets(line,MAXLINE,stdin)!=NULL)//从标准输入获得两个数字
28 {
29 n = strlen(line);
30 //通过管道发送数据给子进程
31 if(write(fd2[1],line,n)!=n)
32 {
33 perror("parent write");
34 exit(3);
35 }
36 //读取子进程计算的结果
37 if((n = read(fd1[0],line,MAXLINE))<0)
38 {
39 perror("read");
40 exit(4);
41 }
42 if(n==0)
43 {
44 printf("child close pipe\n");
45 break;
46 }
47 line[n]=0;
48 if(fputs(line,stdout)==EOF)
49 {
50 perror("fputs");
51 exit(5);
52 }
53 }
54 if(ferror(stdin))
55 {
56 perror("fgets error");
57 exit(6);
58 }
59 exit(0);
60
61 }
62 else
63 {
64 close(fd1[0]);
65 close(fd2[1]);
66 //判断是否相等,因为dup2(fd,fd2)的两个参数如果相等,直接返回fd2而不关闭它,3.12节有解释
67 if(fd1[1]!=STDOUT_FILENO)
68 {
69 if(dup2(fd1[1],STDOUT_FILENO)!=STDOUT_FILENO)
70 {
71 perror("dup2 fd1[1]");
72 exit(1);
73 }
74 close(fd1[1]);
75 }
76 if(fd2[0]!=STDIN_FILENO)
77 {
78 if(dup2(fd2[0],STDIN_FILENO)!=STDIN_FILENO)
79 {
80 perror("dup2 fd2[1]");
81 exit(2);
82 }
83 close(fd2[1]);
84 }
85 if(execl("./child","child",(char*)0)<0)
86 {
87 perror("execl");
88 exit(3);
89 }
90 }
91
92 }
运行结果:
1
invalid args
1
invalid args
1 2
3
1 3
4
1 6
7
122 122
244
FIFO
命名管道
不相关的进程之间交换数据。
mkfifo
mkfifoat
管道是线性连接FIFO可以非线性连接。
用途:
1、复制输出流
例如:tee命令会将信息输出到标准输出和某个文件,可以tee fifo(fifo的名字),就不用产生临时文件了
2、客户进程----服务器进程间的通信
XSI IPC
IPC对象:
(1)内部名:非负整数的标识符。
(2)外部名:每个IPC对象与一个键相关联。
key_t ftok(const char *path,int id):根据路径和项目id产生键。
path必须引用现有文件,id只使用低8位。
三个get函数:msgget semget shmget都有两个相似参数:key_t类型和一个整型flag。
实验:IPC键的构造
1 #include <stdio.h>
2 #include <sys/ipc.h>
3 #include <sys/msg.h>
4 #include "apue.h"
5 #include <sys/stat.h>
6
7
8 int main(int argc,char** argv)
9 {
10 struct stat mystat;
11 if(argc!=2)
12 {
13 printf("error\n");
14 exit(1);
15 }
16 if(stat(argv[1],&mystat)<0)
17 {
18 perror("stat");
19 exit(2);
20 }
21 printf("st_dev:%lx,st_ino:%lx,key:%x\n",(unsigned long)mystat.st_dev,(unsigned long)mystat.st_ino,ftok(argv[1],0x57));
22 exit(0);
23 }
结果:
./a.out ./child.c
st_dev:801,st_ino:2b2009d,key:5701009d
./a.out /
st_dev:801,st_ino:2,key:57010002
和《Unix网络编程》卷2的结果不同。测试环境不同(本文用的Linux)。
由结果可知,id的低8位作为key的高8位,接下来是st_dev的低8位和st_ino的低16位。(和FreeBSD是一样的)
权限结构
XSI IPC为每一个IPC关联了一个struct ipc_perm结构。
#include<sys/ipc.h>
struct ipc_perm
{
key_t key;
uid_t uid;
gid_t gid;
uid_t cuid;
gid_t cgid;
unsigned short mode;
unsigned short seq;
};
msgctl semctl 和 shmctl可以修改uid gid和mode字段。(创建者和超级用户有权修改)
ipc不具有可执行权限。
IPC使用“读”和“更改”。
下图来自《Unix网络编程》卷2
结构限制
实验:命令ipcs -l大多数限制可以通过配置内核来改变。
------ Messages Limits --------
系统最大队列数量 = 32000
max size of message (bytes) = 8192
default max size of queue (bytes) = 16384
---------- 同享内存限制 ------------
max number of segments = 4096
max seg size (kbytes) = 18014398509465599
max total shared memory (kbytes) = 18014398442373116
min seg size (bytes) = 1
--------- 信号量限制 -----------
最大数组数量 = 32000
每个数组的最大信号量数目 = 32000
系统最大信号量数 = 1024000000
每次信号量调用最大操作数 = 500
semaphore max value = 32767
生存期:
管道:最后一个引用管道的进程终止,管道被删除。
FIFO:最后一个引用FIFO的进程终止,FIFO名字虽然还在系统中(需要显示删除),但是数据已经被删除。
XSI IPC:发生下列动作的时候删除
(1)调用msgrcv 或 msgctl读取消息或删除消息队列
(2)进程执行ipcrm命令
(3)正在自举的系统删除消息队列
IPC在系统中没有名字、不使用文件描述符
ipc_perm的seq变量
实验:
1 #include <stdio.h>
2 #include "apue.h"
3
4 int main()
5 {
6 int msqid;
7 for(int i=0;i<10;i++)
8 {
9 msqid = msgget(IPC_PRIVATE,IPC_CREAT | 0666);//这个键值是0,也可以用ftok函数返回的键值
10 if(msqid<0)
11 {
12 perror("maqid");
13 exit(1);
14 }
15 printf("msqid = %d\n",msqid);
16 if(msgctl(msqid,IPC_RMID,NULL)<0)
17 {
18 perror("msgctl");
19 exit(2);
20 }
21
22 }
23 return 0;
24 }
运行结果:每删除一个IPC,seq就会增加,确保过早终止的服务器重启后不重用标识符。
防止行为不端的进程从某个应用的消息队列读取消息。
第一次运行:
msqid = 0
msqid = 32768
msqid = 65536
msqid = 98304
msqid = 131072
msqid = 163840
msqid = 196608
msqid = 229376
msqid = 262144
msqid = 294912
第二次
msqid = 327680
msqid = 360448
msqid = 393216
msqid = 425984
msqid = 458752
msqid = 491520
msqid = 524288
msqid = 557056
msqid = 589824
msqid = 622592
--------- 信号量限制 -----------
最大数组数量 = 32000
每个数组的最大信号量数目 = 32000
系统最大信号量数 = 1024000000
每次信号量调用最大操作数 = 500
semaphore max value = 32767
消息队列
消息队列是消息的链接表。由队列标识符(队列ID)标识。
msgget:创建和访问一个消息队列。
msgctl类似于ioctl(垃圾桶函数)
msgsnd:将数据放到消息队列(数据组织成结构体)
msgrcv:从消息队列中取数据(不一定先进先出)
实验:往消息队列写数据
下面的代码都是用g++编译的。
1 #include <stdio.h>
2 #include "apue.h"
3
4
5 struct mytext
6 {
7 int len;
8 char text[1];
9 };
10
11
12 int main()
13 {
14 int msqid;
15 struct msqid_ds info;//消息队列的一些属性
16 struct mytext buf;//消息队列里面的数据
17 msqid = msgget(IPC_PRIVATE,0666|IPC_CREAT);//创建消息队列
18 if(msqid<0)
19 {
20 perror("msgget");
21 exit(1);
22 }
23 buf.len=1;
24 buf.text[0]=1;
25 if(msgsnd(msqid,&buf,1,0)<0)
26 {
27 perror("msgsnd");
28 exit(1);
29 }
30 if(msgctl(msqid,IPC_STAT,&info)<0)
31 {
32 perror("msgctl");
33 exit(3);
34 }
35 printf("qbytes:%lu,qnum:%lu\n",info.msg_qbytes,info.msg_qnum);
36 system("ipcs -q");
37 if(msgctl(msqid,IPC_RMID,NULL)<0)
38 {
39 perror("msgctl");
40 exit(4);
41 }
42 return 0;
43 }
运行结果:0是IPC_PRIVATE键的共同键值。
qbytes:16384,qnum:1
--------- 消息队列 -----------
键 msqid 拥有者 权限 已用字节数 消息
0x00000000 1376256 myhome 666 1 1
实验:消息队列的创建、删除,发送,接收
创建
1 #include <stdio.h>
2 #include "../apue.h"
3
4 int main(int argc,char **argv)
5 {
6 char ch;
7 int flag,msqid;
8 key_t key;
9 flag = 0666 | IPC_CREAT;
10 while((ch = getopt(argc,argv,"e")!=-1))
11 {
12 switch(ch)
13 {
14 case 'e':
15 flag |= IPC_EXCL;
16 break;
17 }
18 }
19 if(optind!=argc-1)
20 {
21 perror("optind");
22 exit(1);
23 }
24 key = ftok(argv[optind],0);
25 if(key<0)
26 {
27 perror("ftok");
28 exit(2);
29 }
30 msqid = msgget(key,flag);
31 if(msqid<0)
32 {
33 perror("msgget");
34 exit(3);
35 }
36 return 0;
37 }
往队列发送消息
1 #include <stdio.h>
2 #include "../apue.h"
3 #include <features.h>
4
5
6 int main(int argc,char ** argv)
7 {
8 if(argc!=4)
9 {
10 printf("error\n");
11 exit(1);
12 }
13 int msqid;
14 struct msgbuf *ptr;
15 long type;
16 size_t len;
17 len = atoi(argv[2]);
18 type = atoi(argv[3]);
19 //后面的相当于写打开,和open的用法很相似
20 msqid = msgget(ftok(argv[1],0),0222);
21 if(msqid<0)
22 {
23 perror("msgget");
24 exit(1);
25 }
26 ptr = (struct msgbuf *)calloc(sizeof(long)+len,sizeof(char));
27 if(ptr==NULL)
28 {
29 printf("calloc error\n");
30 exit(2);
31 }
32 ptr->mtype=type;
33 if(msgsnd(msqid,ptr,len,0)<0)
34 {
35 perror("msgsnd");
36 exit(3);
37 }
38 return 0;
39 }
从队列接收消息
1 #include <stdio.h>
2 #include "../apue.h"
3
4 #define MAXMSG (8192+sizeof(long))
5
6 int main(int argc,char**argv)
7 {
8 struct msgbuf *buf;
9 int n;
10 char ch;
11 int flag;
12 long type;
13 int msqid;
14 //type=0表示返回队列中第一个消息,也就是先进先出
15 type = flag = 0;
16 while((ch = getopt(argc,argv,"nt:"))!=-1)
17 {
18 switch(ch)
19 {
20 case 'n':
21 {
22 flag |= IPC_NOWAIT;
23 break;
24 }
25 case 't':
26 {
27 type = atoi(optarg);
28 break;
29 }
30 case '?':
31 {
32 printf("error\n");
33 exit(1);
34 }
35 }
36 }
37 if(optind!=argc-1)
38 {
39 perror("optind");
40 exit(2);
41 }
42 msqid = msgget(ftok(argv[optind],0),0444);
43 if(msqid<0)
44 {
45 perror("msgget");
46 exit(3);
47 }
48 buf = (struct msgbuf*)malloc(MAXMSG);
49 n = msgrcv(msqid,buf,MAXMSG,type,flag);
50 printf("read %d bytes,type=%ld\n",n,buf->mtype);
51 return 0;
52 }
删除消息队列
1 #include <stdio.h>
2 #include "../apue.h"
3 int main(int argc,char**argv)
4 {
5 int msqid;
6 if(argc!=2)
7 {
8 printf("error\n");
9 exit(1);
10 }
11 msqid = msgget(ftok(argv[1],0),0);
12 if(msqid<0)
13 {
14 perror("msget");
15 exit(2);
16 }
17 msgctl(msqid,IPC_RMID,NULL);
18 exit(0);
19 }
makefile
1 all:
2 g++ ./msg_creat.c -o msg_creat
3 g++ ./msg_snd.c -o msg_snd
4 g++ ./msg_rcv.c -o msg_rec
5 g++ ./msg_rmid.c -o msg_rmid
运行:
创建队列
往队里加入数据
--------- 消息队列 -----------
键 msqid 拥有者 权限 已用字节数 消息
0x00010061 0 wangli 666 6 3
读取数据
./msg_rec -t 200 ./file
read 2 bytes,type=200
./msg_rec -t 300 ./file
read 3 bytes,type=300
./msg_rec -t 100 ./file
read 1 bytes,type=100
信号量
信号量为正,资源可用,使用了资源单位,信号量的值减一。
测试信号量的值与信号量的值减一应该是原子操作。
二元信号量:初始值为1,控制单个资源。
1、每个信号量由一个无名结构体表示。(Unix环境网络编程卷2 是sem结构体)
2、每个信号量集合有一个semid_ds的结构体。(和消息队列类似,这里面放着属性,比如信号量的个数)
3、每个IPC都有ipc_perm结构(里面存放权限有关的信息)。
4、信号量有操作数组,sembuf(消息队列的msgbuf结构体存放消息队列的类型和值)。
5、setctl中需要用到联合体semun(存放值,semid_ds结构体等,根据命令来选择)。
总结:信号量的操作
创建信号量:指出集合里面有几个信号量
设置信号量的值,需要一个联合体semun,里面的array指向的数组就是,每个信号量的值
删除信号量semctl就可以
获取信号量集合的属性,只要拿到senid_ds结构体,通过semctl
实验:
创建:
1 #include <stdio.h>
2 #include "../apue.h"
3 #include <sys/sem.h>
4
5 int main(int argc,char **argv)
6 {
7 int semid,oflag;
8 int nsems;
9 char ch;
10 oflag = 0666 | IPC_CREAT;
11 if(argc<3)
12 {
13 printf("error\n");
14 exit(1);
15 }
16 while((ch = getopt(argc,argv,"e"))!=-1)
17 {
18 switch(ch)
19 {
20 case 'e':
21 oflag |= IPC_EXCL;
22 break;
23 }
24 }
25 if(optind!=argc-2)
26 {
27 perror("error");
28 exit(2);
29 }
30 nsems = atoi(argv[optind]);
31 semid = semget(ftok(argv[optind+1],0),nsems,oflag);
32 if(semid<0)
33 {
34 perror("semget");
35 exit(3);
36 }
37 exit(0);
38 return 0;
39 }
删除:
1 #include <stdio.h>
2 #include "../apue.h"
3 #include <sys/sem.h>
4
5 int main(int argc,char ** argv)
6 {
7 int semid;
8 if(argc!=2)
9 {
10 printf("error\n");
11 exit(1);
12 }
13 semid = semget(ftok(argv[1],0),0,0);
14 if(semid<0)
15 {
16 perror("semget");
17 exit(2);
18 }
19 if(semctl(semid,0,IPC_RMID)<0)
20 {
21 perror("semctl");
22 exit(3);
23 }
24 exit(0);
25 }
设置信号量集合中每个信号量的值
1 #include <stdio.h>
2 #include "../apue.h"
3 #include <sys/sem.h>
4
5 union semun
6 {
7 int val;
8 struct semid_ds *buf;
9 unsigned short *array;
10 };
11
12 int main(int argc,char **argv)
13 {
14 int semid;
15 int nsems;
16 struct semid_ds seminfo;
17 union semun arg;
18 unsigned short *ptr;
19 if(argc<2)
20 {
21 printf("error\n");
22 }
23 semid = semget(ftok(argv[1],0),0,0);
24 arg.buf = &seminfo;
25 semctl(semid,0,IPC_STAT,&seminfo);
26 //get numbers of sem
27 nsems = arg.buf->sem_nsems;
28 //get value of sem from cmdline
29 if(argc != nsems+2)
30 {
31 printf("error\n");
32 exit(1);
33 }
34 ptr = (unsigned short *)calloc(nsems,sizeof(short));
35 for(int i=0;i<nsems;i++)
36 {
37 ptr[i]=atoi(argv[i+2]);
38 }
39 arg.array = ptr;
40 semctl(semid,0,SETALL,arg);
41 exit(0);
42 }
读取每个信号量的值
1 #include <stdio.h>
2 #include "../apue.h"
3 #include <sys/sem.h>
4
5 union semun
6 {
7 int val;
8 struct semid_ds *buf;
9 unsigned short *array;
10 };
11 int main(int argc, char ** argv)
12 {
13 int semid;
14 union semun arg;
15 unsigned short *ptr;
16 int nsems;
17 struct semid_ds seminfo;
18 if(argc!=2)
19 {
20 printf("error\n");
21 exit(1);
22 }
23 semid = semget(ftok(argv[1],0),0,0);
24 semctl(semid,0,IPC_STAT,&seminfo);
25 arg.buf = &seminfo;
26 nsems = arg.buf->sem_nsems;
27 ptr = (unsigned short *)calloc(nsems,sizeof(unsigned short));
28 arg.array = ptr;
29 semctl(semid,0,GETALL,arg);
30 for(int i=0;i<nsems;i++)
31 {
32 printf("%d\n",arg.array[i]);
33 }
34 exit(0);
35 }
makefile
1 all:
2 gcc ./semprint.c -o out.o
3 gcc ./semsetvalues.c -o set.o
4 gcc ./sem_creat.c -o creat.o
5 gcc ./sem_rm.c -o rm.o
6 .PHONY:
7 clean:
8 rm *.o
运行:
./creat.o 5 file
ipcs
--------- 信号量数组 -----------
键 semid 拥有者 权限 nsems
0x00010642 32768 myhome 666 5
./set.o file 1 2 3 4 5
--------- 信号量数组 -----------
键 semid 拥有者 权限 nsems
0x00010642 32768 myhome 666 5
./out.o file
1
2
3
4
5
./rm.o file
--------- 信号量数组 -----------
键 semid 拥有者 权限 nsems
共享存储
多个进程共享一个给定的存储区,是最快的IPC,用信号量同步,或者记录锁,互斥量。
shmget创建共享内存,创建时指定大小。然后用shmat连接到地址空间。
/dev/zero接收任何数据,忽略这些数据。两个进程可以通过映射这个到内存,实现通信。无需创建实际的文件。只在相关进程间起作用(因为相关进程才能共享mmap返回的指针值。)。不相关进程可以用shm共享存储通信。
POSIX信号量
分为命名信号量和未命名信号量。
sem_t * sem_open创建信号量,和open创建文件的方法差不多。返回指向信号量的指针。
sem_close关闭信号量。关闭信号量,信号量的值不受影响。程序退出,内核会自动关闭打开的信号量。
sem_ulink销毁信号量,如果有打开信号量的引用,销毁会延迟到最后一个打开的引用关闭。
sem_wait sem_trywait实现信号量的减一操作。
sem_timewait阻塞一段确定的时间。(带有超时的那些函数,基本都用的绝对时间。)
sem_post使信号量增加1。
未命名信号量,sem_t的指针作为参数可以直接创建。