# 一、 进程间通信机制说明

linux 下多个进程间的通信机制叫做 IPC,它是多个进程之间相互沟通的一种方法。在 linux 下有多种进程间通信的方法:半双工管道、命名管道、消息队列、信号、信号量、共享内存、内存映射文件,套接字等等。

# 二、 进程间通信方法说明

# 2.1 半双工管道方法说明

(1)管道实际是用于进程间通信的一段共享内存,创建管道的进程称为管道服务器,连接到一个管道的进程为管道客户机。一个进程在向管道写入数据后,另一进程就可以从管道的另一端将其读取出来。管道只能在具有公共祖先的两个进程之间使用。通常,一个管道由一个进程创建,在进程调用 fork 后,这个管道就能在父子进程间使用了。

(2)管道通过 pipe (int fd [2]) 函数创建,参数 fd [0] 为读而打开,fd [1] 为写而打开。fd [1] 的输出是 fd [0] 的输入。简单来说,当父进程打开 fd [1] 关闭 fd [0], 就可以向管道写入文件,而子进程打开 fd [0] 关闭 fd [1], 可以从管道中读取文件。

# 2.2 命名管道方法说明

命名管道又称 FIFO, 它是一种特殊类型的文件,它在系统中以文件形式存在。未命名的管道只能在两个相关的进程之间使用,而且这两个相关的进程还要有一个共同的创建了它们的祖先进程。但是,通过 FIFO, 不相关的进程也能交换数据。我们可以使用 mkfifo 或者 mkfifoat 创建 FIFO, 然后用 opne 来打开它。如果命名管道 FIFO 打开时设置了读权限,则读进程将一直阻塞,一直到其他进程打开该 FIFO 并向管道写入数据。

# 2.3 消息队列方法说明

消息队列是消息的链接表,储存在内核中,由消息队列标识符标识。用户进程可以向消息队列添加消息,也可以向消息队列读取消息。 msgget 创建一个新队列或打开一个存在的队列;msgsnd 向队列末端添加一条新消息;msgrcv 从队列中取消息, 取消息是不一定遵循先进先出的, 也可以按消息的类型字段取消息。

# 2.4 共享储存方法说明

共享内存允许两个或多个进程共享一个给定的存储区,这一段存储区可以被两个或两个以上的进程映射至自身的地址空间中,一个进程写入共享内存的信息,可以被其他使用这个共享内存的进程,通过内存读读出其中的消息内容,从而实现了进程间的通信。通过 shmget 创建一个共享内存段,并获得一个进程标识符。然后通过 shmat 将其连接到它的地址空间,shmat 的返回值是该段所连接的实际地址。可以通过指针指向该地址对其中内容进行操作。

# 三、 同步互斥方法实现

# 3.1 半双工管道方法实现

# 3.1.1 半双工管道示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include<sys/types.h>
#include<stdio.h>
#include<string.h>
#include<unistd.h>

#define BUFFER_SIZE 25
#define READ_END 0
#define WRITE_END 1

int main()
{
char write_msg[BUFFER_SIZE]="Greetings\n";//要写入的信息
char read_msg[BUFFER_SIZE];//存放读取文件
int fd[2];
pid_t pid;
if(pipe(fd)==-1)
{
fprintf(stderr,"Pipe failed");
return 1;
}
pid=fork();
if(pid<0)
{
fprintf(stderr,"Fork failed");
return 1;
}
if(pid>0)//父进程,向管道写入信息
{
close(fd[READ_END]);//关闭读出端
/*从write_msg中写入数据到fd[WRUTE_END]中,并指定写入数据长度*/
write(fd[WRITE_END],write_msg,strlen(write_msg)+1);
close(fd[WRITE_END]);
}
else//子进程,向管道读取信息
{
close(fd[WRITE_END]); //关闭写入端
/*从fd[READ_END]中读取数据到read_msg中*/
read(fd[READ_END],read_msg,BUFFER_SIZE);
printf("read:%s",read_msg);
close(fd[READ_END]);
}
return 0;
}

编译及运行结果:

# 3.1.2 半双工管道关键代码说明

(1)父进程关闭读出端,通过 write 函数从 write_msg 中写入数据到 fd [WRUTE_END] 中

close(fd[READ_END]);

write(fd[WRITE_END],write_msg,strlen(write_msg)+1);

(2)子进程关闭写入端,通过 read 函数从 fd [READ_END] 中读取数据到 read_msg 中,然后输出到标标准输出。

close(fd[WRITE_END]);

read(fd[READ_END],read_msg,BUFFER_SIZE);

# 3.2 命名管道方法实现

# 3.2.1 命名管道示例代码

# 服务端代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<linux/stat.h>

#define FIFO_FILE "MYFIFO"

int main()
{
FILE *fp;//文件指针
char readbuf[80];
/*创建命名管道MYFIFO*/
mkfifo("MYFIFO",S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH);
while(1)
{
/*打开命名管道*/
if((fp=fopen(FIFO_FILE,"r"))==NULL)
{
printf("open fifo failed\n");
exit(1);
}
/*从命名管道中读取数据*/
if(fgets(readbuf,80,fp)!=NULL)
{
printf("Received string : %s\n",readbuf);
fclose(fp);
}
else
{
if(ferror(fp))
{
printf("read fifo failed\n");
exit(1);
}
}
}
return 0;
}
# 客户端代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include<stdio.h>
#include<stdlib.h>

#define FIFO_FILE "MYFIFO"

int main(int argc,char *argv[])
{
FILE *fp;
int i;
if(argc<=1)
{
printf("usage : %s <pathname>\n",argv[0]);
exit(1);
}
if((fp=fopen(FIFO_FILE,"w"))==NULL)
{
printf("open fifo failed\n");
exit(1);
}
for(i=1;i<argc;i++)//读取命名行参数作为输入数据
{
if(fputs(argv[i],fp)==EOF)
{
printf("write fifo error\n");

exit(1);
}
if(fputs(" ",fp)==EOF)
{
printf("write fifo error\n");
exit(1);
}
}
fclose(fp);
return 0;
}
  • 首先启动客户端,进入阻塞状态等待客户端的输入。

  • 然后启动客户端,写入数据到管道中。

  • 服务端从中读取数据,显示到屏幕。

# 3.2.2 命名管道关键代码说明

(1) 创建一个命名管道:

mkfifo(“MYFIFO”,S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH);

参数 S_IWUSR:用户写权限

​ S_IRUSR:用户读权限

​ S_IRGRP:用户组读权限

​ S_IROTH:其他组读权限

(2)服务端:

fp 指向以可读形式打开的管道文件,并从中读取数据到 readbuf 中,然后显示到屏幕。

fp=fopen(FIFO_FILE,“r”)

fgets(readbuf,80,fp)

(3)客户端:

fp 指向以可写形式打开的管道文件,并将命令行参数写入到管道文件中,以便服务端读取。

fp=fopen(FIFO_FILE,“w”)

for(i=1;i<argc;i++)

fputs(argv[i],fp;

# 3.3 消息队列方法实现

# 3.3.1 消息队列示例代码

# 发送消息代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>

struct my_msg
{
long int my_msg_type;
char text[BUFSIZ];
}msgbuf;


int main()
{
int running=1;
int msgid;
long int msg_to_receive=0;
msgid=msgget((key_t)1028,0666|IPC_CREAT);//创建引用标识符为1028的消息队列
if(msgid==-1)
{
printf("magget failed\n");
exit(1);
}
while(running)
{
printf("请输入你想写的信息:");
fflush(stdin);//过滤掉最后的enter(换行符)
fgets(msgbuf.text,BUFSIZ,stdin);
msgbuf.my_msg_type=1;
/*发送消息;第二个参数是一个指向长整数的指针,其后紧接着的是消息数据*/
if(msgsnd(msgid,(void*)&msgbuf,BUFSIZ,0)==-1)
{
printf("magsnd failed\n");
exit(1);
}
if(strncmp(msgbuf.text,"end",3)==0)
running=0;
}
return 0;
}
# 接收消息代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>

struct my_msg
{
long int my_msg_type;
char text[BUFSIZ];
}msgbuf;

int main()
{
int running=1;
int msgid;
long int msg_to_receive=0;
msgid=msgget((key_t)1028,0666|IPC_CREAT);//创建我们想打开的的消息队列
if(msgid==-1)
{
printf("magget failed\n");
exit(1);
}
while(running)
{
/*接受消息,将它赋值到结构体的地址空间*/
if(msgrcv(msgid,(void*)&msgbuf,BUFSIZ,msg_to_receive,0)==-1)
{
printf("magrcv failed\n");
exit(1);
}
printf("你写入信息是:%s",msgbuf.text);
/*比较两个字符串,指定长度为3; 即输入end就结束*/
if(strncmp(msgbuf.text,"end",3)==0)
running=0;
}
/*msgctl删除队列(cmd参数指定为IPC_RMID)*/
if(msgctl(msgid,IPC_RMID,0)==-1)
{
printf("msgct(IPC_RMID) failed\n");
exit(1);
}
return 0;
}

运行结果:发送方发送消息(不需要先运行接受方程序)

接收方接收消息:

# 3.3.2 消息队列关键代码说明

(1)创建消息队列:msgid=msgget ((key_t) 1028,0666|IPC_CREAT)

(2) 用户从键盘输入数据,并将数据放到消息队列中:

fgets(msgbuf.text,BUFSIZ,stdin);

msgsnd(msgid,(void*)&msgbuf,BUFSIZ,0)

(3)从队列中取用消息,将它赋值到结构体的地址空间:

msgrcv(msgid,(void*)&msgbuf,BUFSIZ,msg_to_receive,0)

第四个参数 type 即 msg_to_receive 指定想要哪一种消息。

type==0 返回队列中的第一个消息

type>0 返回队列中的消息类型为 type 的第一个消息

tyoe<0 返回队列中消息类型值小于等于绝对值的消息,如果消息有若干个,则取类型值最小的消息

# 3.4 共享储存方法实现

# 3.4.1 共享储存示例代码

# 服务进程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{
int shmid;
char c;
char *shmptr,*s;
/*创建一个名为"1234"的共享内存,返回值为共享内存的进程标识符*/
if((shmid=shmget(1234,256,IPC_CREAT|0666))<0){
printf("shmget failed\n");
exit(1);
}
/*将共享内存附加到自己的地址空间上*/
if((shmptr=shmat(shmid,0,0))==(void*)-1)
{
/*指定参数IPC_RMID,用于删除共享内存*/
shmctl(shmid,IPC_RMID,(void*)shmptr);
printf("shmat failed\n");
exit(1);
}
s=shmptr;
for(c='a';c<='z';c++)
{
*s++=c;
}
*s=NULL;
while(*shmptr!='*')//等待客户端读完数据
sleep(1);
shmctl(shmid,IPC_RMID,(void*)shmptr);
return 0;
}
# 客户进程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

int main()
{
int shmid;
char c;
char *shmptr,*s;
if((shmid=shmget(1234,256,0666))<0)
{
printf("shmget failed\n");
exit(1);
}
if((shmptr=shmat(shmid,0,0))==(void*)-1)
{
shmctl(shmid,IPC_RMID,(void*)shmptr);
printf("shmat failed\n");
exit(2);
}
for(s=shmptr;*s!=NULL;s++)
putchar(*s);
printf("\n");
*shmptr='*';
return 0;
}

服务进程开启等待客户读取完数据:

客户读取数据:

客户读取完数据之后服务器进程关闭:

# 3.4.2 共享储存关键代码说明

服务端进程:创建标识符为 1234 的共享内存,并用 shmprt 指向它返回的实际地址空间

1
2
3
shmid=shmget(1234,256,IPC_CREAT|0666)

shmptr=shmat(shmid,0,0)

向其中写入数据后等待客户读取完数据并删除该共享内存段

1
2
3
4
5
while(*shmptr!='*')//等待客户端读完数据

sleep(1);

shmctl(shmid,IPC_RMID,(void*)shmptr);

客户端进程:打开该共享内存,并用 shmprt 指向它返回的实际地址空间

1
2
3
shmid=shmget(1234,256,0666)

shmptr=shmat(shmid,0,0)

然后读取共享内存段里面的内容,并将共享内存里面的内容设置为’*’, 告诉服务进程已经读取完毕

1
2
3
4
5
for(s=shmptr;*s!=NULL;s++)

putchar(*s);

\*shmptr='\*';