进程间通信目的
数据共享
一个进程需要将他的数据发送给另一个进程资源共享
多个进程之间共享同样的资源- 通知事件
一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程) - 进程控制
有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
进程间通信发展
管道pipe
System V IPC
- 消息队列(数据传输)msg
- 共享内存(数据共享)shm
- 信号量(事件通知)sem
- POSIX IPC
- 消息队列
- 共享内存
- 互斥量
- 条件变量
- 信号量
- 读写锁
管道pipe
- 概念
管道是Unix中最古老的进程间通信的形式。
我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
- 匿名管道
功能:创建一无名管道
原型
#include <unistd.h>
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:
成功返回0,失败返回错误代码
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <string.h>
5
6 int main( void )
7 {
8 int fd[2];
9 if(pipe(fd)==-1)
10 perror("pipe error"),exit(1);
11 pid_t pid=fork();
12 if(pid==-1)
13 perror("fork error"),exit(2);
14 if(pid==0){//子进程,往管道中写数据
15 close(fd[0]);//关闭读端
16
17 sleep(1);
18 write(fd[1],"abs",3);
19 close(fd[1]);
20 exit(0);
21 }else{
22 close(fd[1]);//关闭写端
23 char buf[1024]={0};
24 int ret=read(fd[0],buf,1024);
25 if(ret==0)
26 printf("read EOF\n");
27 else if(ret==-1) perror("read error"),exit(3);
28 else if(ret>0)
29 printf("buf=[%s]\n",buf);
30 close(fd[0]);
31 exit(0);
32 }
33 }
管道特点
- 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
- 管道提供流式服务
- 一般而言,进程退出,管道释放,所以管道的生命周期随进程
- 一般而言,内核会对管道操作进行同步与互斥
- 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
命名管道
- 概念
如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
- 创建
- 命名管道可以从命令行上创建
$ mkfifo filename
- 命名管道也可以从程序里创建
int mkfifo(const char *filename,mode_t mode);
- 打开
int fd=open(name,O_RDONLY);//读
int fd=open(name,O_WRONLY);//写
匿名管道和命名管道的区别
- 匿名管道由pipe函数创建并打开。
- 命名管道由mkfifo函数创建,打开用open
- FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义
使用管道实现Xshell通信
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <string.h>
5 #include <sys/wait.h>
6 #include <ctype.h>
7
8 #define MAXLINE 1024
9 #define ARGV 8
10 #define MAXPIPE 8
11
12 char cmdline[MAXLINE+1];
13 char avline[MAXLINE+1];
14 int pipe_num;
15 int lastpid;
16
17 typedef struct cmd{
18 char *argv[ARGV];
19 int argc;
20 int infd;
21 int outfd;
22 }cmd_t;
23
24 cmd_t command[8];
25
26 void init(void)
27 {
28 int i;
29 memset(cmdline,0x00,sizeof(cmdline));
30 memset(avline,0x00,sizeof(avline));
31 memset(&command,0x00,sizeof(command));
32 for(i=0;i<MAXPIPE;++i){
33 command[i].infd=0;
34 command[i].outfd=1;
35 }
36 pipe_num=0;
37 lastpid=0;
38 }
39 int read_cmd()
40 {
41 printf("Xshell> ");
42 return fgets(cmdline,MAXLINE,stdin)==NULL?0:1;
43 }
44
45 void parse_cmd()
46 {
47 int i;//控制第几条命令
48 int j;//每条命令的第几个参数
49 char *p=cmdline;
50 char *q=avline;
51
52 for(i=0;i<MAXPIPE && *p!='\0';i++){
53
54 for(j=0;*p!='\0';++j){
55 //跳过开头空白符
56 while(*p!='\0' && (*p=='\t' || *p==' ' || *p=='\n'))
57 p++;
58 command[i].argv[j]=q;
59 while(*p!='\0' && *p!='\t' && *p!=' ' && *p!='\n' && *p!='|')
60 *q++=*p++;
61 *q++='\0';
62 if(*p=='|'){
63 command[i].argv[j]=NULL;
64 pipe_num++;
65 p++;
66 break;
67 }
68 if(*p=='\n'){
69 command[i].argv[j+1]=NULL;
70 return;
71 }
72 }
73 }
74 }
75
76 void print_cmd()
77 {
78 int i,j;
79 for(i=0;i<=pipe_num;++i){
80 printf("pipe=%d\n",i);
81 for(j=0;command[i].argv[j]!=NULL;++j)
82 printf("\targv[%d]=%s\n",i,command[i].argv[j]);
83 }
84 }
85
86 void execute_cmd()
87 {
88 int i;
89 int fds[2];
90
91 for(i=0;i<=pipe_num;i++){
92 if(i<pipe_num){
93 pipe(fds);//创建管道
94 command[i].outfd=fds[1];
95 command[i+1].infd=fds[0];
96 }
97 pid_t pid=fork();
98 if(pid==0){
99 if(command[i].outfd!=1){
100 close(1);
101 dup(command[i].outfd);
102 close(command[i].outfd);
103 }
104 if(command[i].infd!=0){
105 close(0);
106 dup(command[i].infd);//引用计数为2
107 close(command[i].infd);//删除源指向
108 }
109 execvp(command[i].argv[0],command[i].argv);
110 exit(1);
111 }else{//父进程
112 if(command[i].outfd!=1){
113 close(command[i].outfd);
114 }
115 if(command[i].infd!=0){
116 close(command[i].infd);//删除源指向
117 }
118 lastpid=pid;
119 }
120 }
121 while(wait(NULL)!=lastpid); ;
122 }
123
124
125 int main( void )
126 {
127 do{
128 init();
129 if(read_cmd()==0)//读命令
130 break;
131 parse_cmd();//解析命令
132 print_cmd();
133 execute_cmd();//执行命令
134 }while(1);
135 }
消息队列
消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法
每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值
消息队列也有管道一样的不足,就是每个消息的最大长度是有上限的(MSGMAX),每个消息队列的总的字节数是有上限的(MSGMNB),系统上消息队列的总数也有一个上限(MSGMNI)
- msgget函数
功能:用来创建和访问一个消息队列
原型:
#include <sys/msg.h>
#include <sys/ipc.h>
int msgget(key_t key, int msgflg);
参数:
key: 某个消息队列的名字
msgflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的。打开:0,创建:IPC_CREAT | 0644
返回值:
成功返回一个非负整数,即该消息队列的标识码;失败返回-1
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <sys/ipc.h>
4 #include <sys/msg.h>
5
6
7 int main( void )
8 {
9 int msgid=msgget(1234,IPC_CREAT | 0644);
10 if(msgid==-1)
11 perror("msgget"),exit(1);
12 else
13 printf("msgget creat ok\n");
14 }
查看IPC对象
ipcs -q
删除IPC对象
ipcrm -Q key
系统中最多能够创建多少个消息队列?
cat /proc/sys/kernel/msgmni
一条消息最多能够装多少个字节?
cat /proc/sys/kernel/msgmax
一个消息队列中所有消息的总字节数是多少?
cat /proc/sys/kernel/msgmnb
- 往消息队列中发送数据 msgsnd
int msgsnd(int *id,//msgget 的返回值
const void *msgp,//要发送的消息在哪里
size_t len,//消息的字节数,不包括channel的大小
int flag);//0
返回值:成功返回0,失败返回-1
消息结构参考形式
struct msgbuf{
long channel;//消息类型(通道号),必须>=1,必须是long类型
char mtext[size] //发送内容,写上自己的消息
};
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <sys/ipc.h>
4 #include <sys/msg.h>
5 #include <string.h>
6
7 struct msgbuf{
8 long mtype;
9 char mtext[1000];
10 };
11
12 int main( int argc,char *argv[] )
13 {
14 // ./msgsnd type
15 if(argc!=2)
16 {
17 fprintf(stderr,"usage:%s type\n",argv[0]);
18 exit(1);
19 }
20 int id=msgget(1234,0);
21 if(id==-1) perror("msgget"),exit(1);
22 struct msgbuf mb;
23 memset(&mb,0x00,sizeof(mb));
24
25 mb.mtype=atoi(argv[1]);
26 printf("msg: ");
27 fgets(mb.mtext,999,stdin);//从标准输入中读数据到mb.text中
28 int r=msgsnd(id,&mb,strlen(mb.mtext),0);//填0表示如果消息队列满了就阻塞
29 if(r==-1) perror("msgsnd"),exit(1);
30 }
- 从消息队列中取数据 msgrcv
size_t msgrcv(int id,//由msgget函数返回的消息队列标识码
void *mssgp,//取出来的消息放在那里
size_t len,//装消息的地方的大小,不包括类型
long mtype,//取哪个类型的消息
int flag); //0
返回值:成功返回实际放到接收缓冲区⾥里去的字符个数,失败返回-1
msgtype=0返回队列第一条信息
msgtype>0返回队列第一条类型等于msgtype的消息
msgtype<0返回队列第一条类型小于等于msgtype绝对值的消息,并且是满足条件的消息类型最小的消息
msgflg=IPC_NOWAIT,队列没有可读消息不等待,返回ENOMSG错误。 msgflg=MSG_NOERROR,消息大小超过msgsz时被截断
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <sys/ipc.h>
4 #include <sys/msg.h>
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <sys/ipc.h>
4 #include <sys/msg.h>
5 #include <string.h>
6
7 struct msgbuf{
8 long mtype;
9 char mtext[1000];
10 };
11
12 int main( int argc,char *argv[] )
13 {
14 // ./msgrcv type
15 if(argc!=2)
16 {
17 fprintf(stderr,"usage:%s type\n",argv[0]);
18 exit(1);
19 }
20 int id=msgget(1234,0);
21 if(id==-1) perror("msgget"),exit(1);
22
23 struct msgbuf mb;
24 memset(&mb,0x00,sizeof(mb));
25
26 if(msgrcv(id ,&mb,1000,atoi(argv[1]),0)==-1)
27 perror("msgrcv"),exit(1);
28
29 printf("%s\n",mb.mtext);
30 }
使用消息队列实现通信
//服务器端代码
1 #include <stdio.h>
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <sys/ipc.h>
4 #include <sys/msg.h>
5 #include <string.h>
6
7 struct msgbuf{
8 long mtype;
9 char mtext[1024];
10 };
11
12 int main( void )
13 {
14 int id=msgget(1234,IPC_CREAT|0644);
15 if(id==-1) perror("msgget error"),exit(1);
16
17 struct msgbuf mb;
18
19 while(1){
20 //读取1号通道消息
21 memset(&mb,0x00,sizeof(mb));
22 msgrcv(id,&mb,1024,1,0);
23 //发送给客户端
24 mb.mtype=*(int *)(mb.mtext);
25 msgsnd(id,&mb,strlen(mb.mtext+sizeof(int))+sizeof(int),0);
26 }
27 }
//客户端代码:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <sys/ipc.h>
4 #include <sys/msg.h>
5 #include <string.h>
6 #include <unistd.h>
7
8 struct msgbuf{
9 long mtype;
10 char mtext[1024];
11 };
12
13 int main( void )
14 {
15 int id=msgget(1234,0);
16 if(id==-1) perror("msgget error"),exit(1);
17
18 struct msgbuf mb;
19 int sz=sizeof(int);
20 while(1){
21 memset(&mb,0x00,sizeof(mb));
22 mb.mtype=1;
23 *(int *)(mb.mtext)=getpid();
24 //读取键盘
25 char *p=fgets(mb.mtext+sz,1024-sz,stdin);
26 if(p==NULL)
27 break;
28 //发送给服务器
29 msgsnd(id,&mb,strlen(mb.mtext+sz)+sz,0);
30 //读取服务器
31 memset(&mb,0x00,sizeof(mb));
32 msgrcv(id,&mb,1024,getpid(),0);
33 //显示到屏幕
34 printf(" : %s\n",mb.mtext+sz);
35 }
36 }
共享内存
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。
- 创建或打开共享内存shmget
原型:
int shmget(key_t key, size_t size, int shmflg);
参数:
key:这个共享内存段名字
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:
成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <sys/ipc.h>
4 #include <sys/shm.h>
5
6 struct stu{
7 int id;
8 char name[20];
9 };
10
11
12 int main( void )
13 {
14 int shmid=shmget(1234,sizeof(struct stu),IPC_CREAT|0644);
15 if(shmid==-1) perror("shmget creat"),exit(1);
16 printf("creat ok\n");
17 }
查看当前共享内存状态信息ipcs -m
- 让共享内存和本进程建立关系shmat
功能:
将共享内存段连接到进程地址空间
原型:
void *shmat(int shmid, const void *shmaddr, int shmflg); 参数:
shmid: 共享内存标识
shmaddr:指定连接的地址,想让操作系统挂到这个地址空间。如果选择NULL,表示让操作系统自己选择
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个字节;失败返回-1
- 卸载掉共享内存shmctl
功能:
将共享内存段与当前进程脱离
原型:
int shmdt(const void *shmaddr);
参数:
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
//写端
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <assert.h>
4 #include <unistd.h>
5 #include <string.h>
6 #include <sys/ipc.h>
7 #include <sys/shm.h>
8
9 struct festival{
10 int data;
11 char buf[20];
12 };
13 int main( void )
14 {
15 int shmid=shmget(1234,sizeof(struct festival),0);
16 if(shmid==-1) perror("shmid"),exit(1);
17
18 struct festival *p=(struct stu *)shmat(shmid,NULL,0);
19 assert(p!=NULL);
20
21 p->data=17;
22 strcpy(p->buf,"七夕快乐");
23 sleep(30);
24 shmdt(p);//断开连接
25 }
//读端
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <assert.h>
4 #include <unistd.h>
5 #include <string.h>
6 #include <sys/ipc.h>
7 #include <sys/shm.h>
8
9 struct festival{
10 int data;
11 char buf[20];
12 };
13 int main( void )
14 {
15 int shmid=shmget(1234,sizeof(struct festival),0);
16 if(shmid==-1) perror("shmid"),exit(1);
17
18 struct festival *p=(struct stu *)shmat(shmid,NULL,0);
19 assert(p!=NULL);
20
21 printf("p->data=%d,p->buf=%s\n",p->data,p->buf);
22 sleep(30);
23 shmdt(p);
24 }:q
- 删除共享内存
功能:用于控制共享内存
原型:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值),IPC_RMID 删除共享内存
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1
1 #include <sys/ipc.h>
2 #include <sys/shm.h>
3 #include <stdio.h>
4
5 int main( void )
6 {
7 int id =shmget(1234,0,0);
8 shmctl(id,IPC_RMID,NULL);
9 }
信号量(集)
信号量主要用于同步和互斥的
进程互斥
- 由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥
- 系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。
- 在进程中涉及到互斥资源的程序段叫临界区
进程互斥
进程同步指的是多个进程需要相互配合共同完成一项任务
经典案例
火车售票系统、司机售票员
- 创建或打开信号量semget
功能:
用来创建和访问一个信号量集
原型:
int semget(key_t key, int nsems, int semflg);
参数:
key: 信号集的名字
nsems:信号集中信号量的个数
semflg: 由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该信号集的标识码;失败返回-1
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <sys/ipc.h>
5 #include <sys/sem.h>
6
7 int main( void )
8 {
9 int id=semget(1234,1,IPC_CREAT|0644);
10 if(id==-1)
11 perror("semget"),exit(1);
12 printf("semget craet ok\n");
13 }
查看信号量状态 ipcs -s
信号量可以看做是系统的环境变量
- 设置信号量初值
功能:
用于控制信号量集
原型:
int semctl(int semid, int semnum, int cmd, ...);
参数:
semid:由semget返回的信号集标识码
semnum:信号集中信号量的序号
cmd:将要采取的动作(有三个可取值),SETVAL表示设置信号量
最后一个参数根据命令不同而不同,表示信号量初值
返回值:成功返回0;失败返回-1
//最后一个参数必须是联合体形式
union semun{
int val; /*value for SETVAL */
}
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <sys/ipc.h>
5 #include <sys/sem.h>
6
7 union semun{
8 int val;
9 };
10
11 int main( void )
12 {
13 int id=semget(1234,0,0);
14 if(id==-1)
15 perror("semget"),exit(1);
16 union semun su;
17 su.val=5;
18 semctl(id,0,SETVAL,su);
19 }
- 查看信号量的值
int semctl(semid,int semnum,//信号量中第几个信号量
int cmd,//GETVAL,表示信号量的值
0);
返回值:当前信号量的值
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <sys/ipc.h>
5 #include <sys/sem.h>
6
7
8 int main( void )
9 {
10 int id=semget(1234,0,0);
11 if(id==-1)
12 perror("semget"),exit(1);
13 int val=semctl(id,0,GETVAL);
14 printf("val=%d\n",val);
15 }
- PV操作
功能:用来创建和访问一个信号量集
原型:
int semop(int semid, struct sembuf *sops, unsigned nsops); 参数:
semid:是该信号量的标识码,也就是semget函数的返回值
sops:是个指向一个结构数值的指针
nsops:信号量的个数
返回值:成功返回0;失败返回-1
//sembuf结构体定义格式:
struct sembuf{
short sem_num;//信号量的下标
short sem_op;//信号量一次PV操作时加减的数值,一般只会用到两个值:一个是“-1”,也就是P操作,等待信号量变得可用;另一个是“+1”,也就是我们的V操作,发出信号量已经变得可用
short sem_flag;//一般填0,两个取值是IPC_NOWAIT或SEM_UNDO
}
//p操作
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <sys/ipc.h>
5 #include <sys/sem.h>
6
7 void p(int id)
8 {
9 struct sembuf sb[1];
10 sb[0].sem_num=0;
11 sb[0].sem_op=-1;//P操作
12 sb[0].sem_flg=0;
13
14 semop(id,sb,1);
15 }
16 int main( void )
17 {
18 int id=semget(1234,0,0);
19 if(id==-1)
20 perror("semget"),exit(1);
21 p(id);
22 }
//v操作
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <sys/ipc.h>
5 #include <sys/sem.h>
6
7 void v(int id)
8 {
9 struct sembuf sb[1];
10 sb[0].sem_num=0;
11 sb[0].sem_op=1;
12 sb[0].sem_flg=0;
13
14 semop(id,sb,1);
15 }
16 int main( void )
17 {
18 int id=semget(1234,0,0);
19 if(id==-1)
20 perror("semget"),exit(1);
21 v(id);
22 }