8.1.6 常用进程间通信的方法

8.1.6  常用进程间通信的方法

在支持多进程的操作系统里,用户可以创建多个进程,分别处理不同的功能。多进程机制为处理不同的数据带来好处。但是,在实际处理过程中,经常需要在不同的进程之间传递数据。如有两个进程,一个读取不同用户的配置文件并且解析配置文件,另一个进程需要把每个用户的配置发送到远程的服务器,这样的两个进程需要数据的传递,这个时候就会用到进程间通信。

Linux提供了多种进程间通信的方法,常见的包括管道、FIFO、消息队列、信号量、共享存储以及通过socket也可以实现不同进程间的通信。本节将简述管道和共享内存这两种进程间的通信方法。

1.管道

管道是最常用的进程间的通信方法,也是最古老的一种进程间的通信方法。所有的UNIX系统都支持管道。管道的概念比较好理解,如图8-2所示,管道就好像日常的水管一样,在两个进程之间,用来传送数据。与日常生活中的水管不同的是,进程间的管道有两个限制:一个是管道是半双工的,也就是说,一个管道只能在一个方向上传送数据;另一个是管道只能在有共同父进程的进程间使用。通常,管道由一个进程创建,之后进程调用fork()创建新的进程,父进程和子进程之间就可以使用管道通信了。

8-2  进程间通信机制——管道示意图

从图8-2中可以看出,两个进程间通过管道传递数据的时候是单向的,在同一时刻,进程1只能向进程2写入数据,或者从进程2读出数据。

使用管道的方法很简单,需要通过pipe()函数创建一个管道就可以使用了。pipe()函数的定义如下:

 

#include <unistd.h>

int pipe(int filedes[2]);

 

参数fieldes返回两个文件描述符,filedes[0]为读打开,filedes[1]为写打开。当创建管道成功时pipe()返回0,如果创建失败会返回–1。下面的实例演示创建管道后父进程和子进程之间通过管道传递数据。

实例8-5  进程间通过管道通信

 

1 #include <sys/types.h>

2 #include <unistd.h>

3 #include <stdio.h>

4 #include <stdlib.h>

5 #include <string.h>

6

7 int main()

8 {

9   int fd[2];

10   pid_t pid;

11   char buf[64] = "I'm parent process!/n"; // 父进程要写入管道的信息

12   char line[64];

13

14   if (0!=pipe(fd)) {                         // 创建管道并检查结果

15     fprintf(stderr, "Fail to create pipe!/n");

16     return 0;

17   }

18

19   pid = fork();                              // 创建进程

20   if (pid<0) {

21     fprintf(stderr, "Fail to create process!/n");

22     return 0;

23   } else if (0==pid) {                       // 父进程

24     close(fd[0]);                 // 关闭读管道,使得父进程只能向管道写入数据

25     write(fd[1], buf, strlen(buf));           // 写数据到管道

26     close(fd[1]);                             // 关闭写管道

27   } else {                                   // 子进程

28     close(fd[1]);         // 关闭写管道,使得子进程只能从管道读取数据

29     read(fd[0], line, 64);                    // 从管道读取数据

30     printf("DATA From Parent: %s", line);

31     close(fd[0]);                             // 关闭读管道

32   }

33

34   return 0;

35 }

36

 

程序给出了一个操作管道的例子,由于管道的单向特性,在同一时刻,管道的数据只能单方向流动,所以在程序的第24行,父进程关闭了读管道;程序的第28行,子进程关闭了写管道,这样,管道变成了一个从父进程到子进程单向传递数据的通道。程序的运行结果验证了这个特性如下:

 

DATA From Parent: I'm parent process!

 

子进程打印出了父进程通过管道发送来的字符串。管道的操作比较简单,虽然有一定的局限性,但是对于父子进程间传递数据还是很方便的,因此有广泛的应用。还有一种称做有名管道的通信机制,突破了传统管道的限制,可以在不同的进程间传递数据,有兴趣的读者可以通过其他资料或者Linux的在线手册man了解一下。

2.共享内存

共享内存是在内存中开辟一段空间,供不同的进程访问。与管道相比,共享内存不仅能在多个不同进程(非父子进程)间共享数据,而且可以比管道传送更大量的数据。图8-3展示了共享内存的结构。

8-3  共享内存示意图

从图8-3可以看出,进程1和进程2可以访问一块共同的内存区域,并且在同一时刻可以读写共享的内存区域。

共享内存在使用之前需要先创建,之后获得共享内存的入口地址就可以对共享内存操作了。如果不需要使用共享内存的时候,还可以在程序中分离共享内存。Linux为操作共享内存提供了几个函数如下:

 

#include <sys/ipc.h>

#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);

void *shmat(int shmid, const void *shmaddr, int shmflg);

int shmdt(const void *shmaddr);

 

shmget()函数用来创建共享内存,参数key是由ftok()函数生成的一个系统唯一的关键字,用来在系统中标示一块内存,size参数制定需要的共享内存字节数,shmflg参数是内存的操作方式,有读或者写两种。如果成功创建共享内存,函数会返回一个共享内存的ID

shmat()函数是获得一个共享内存ID对应的内存起始地址;参数shmid是共享内存IDshmaddr参数指定了共享内存的地址,如果参数值为0,表示需要让系统决定共享内存地址,如果获取内存地址成功,则函数返回对应的共享内存地址。

shmdt()函数从程序中分离一块共享内存。参数shmaddr标识了要分离的共享内存地址。

实例8-6给出两个文件:shm_write.c文件的代码创建共享内存,之后向共享内存写入一个字符串;shm_read.c文件的代码获得已经创建的共享内存,打印共享内存中的数据。

实例8-6  共享内存操作实例

写共享内存操作代码如下:

 

1 // shm_write.c --> gcc -o w shm_write.c

2 #include <sys/ipc.h>

3 #include <sys/shm.h>

4 #include <sys/types.h>

5 #include <unistd.h>

6

7 int main()

8 {

9   int shmid;                          // 定义共享内存ID

10   char *ptr;

11   char *shm_str = "string in a share memory";

12

13   shmid = shmget(0x90, 1024, SHM_W|SHM_R|IPC_CREAT|IPC_EXCL);

                                            // 创建共享内存

14   if (-1==shmid)

15     perror("create share memory");

16

17   ptr = (char*)shmat(shmid, 0, 0);        // 通过共享内存ID获得共享内存地址

18   if ((void*)-1==ptr)

19     perror("get share memory");

20

21   strcpy(ptr, shm_str);                  // 把字符串写入共享内存

22   shmdt(ptr);

23

24   return 0;

25 }

26

 

读共享内存操作代码如下:

 

1 // shm_read.c --> gcc -o r shm_read.c

2 #include <sys/ipc.h>

3 #include <sys/shm.h>

4 #include <sys/types.h>

5 #include <unistd.h>

6

7 int main()

8 {

9   int shmid;                              // 定义共享内存ID

10   char *ptr;

11

12   shmid = shmget(0x90, 1024, SHM_W|SHM_R|IPC_EXCL);

                                            // 根据key获得共享内存ID

13   if (-1==shmid)

14     perror("create share memory");

15

16   ptr = shmat(shmid, 0, 0);              // 通过共享内存ID获得共享内存地址

17   if ((void*)-1==ptr)

18     perror("get share memory");

19

20   printf("string in share memory: %s/n", ptr);

                                            // 打印共享内存中的内容

21

22   shmdt(ptr);

23   return 0;

24 }

25

 

实例中代码没有使用ftok()函数创建共享内存使用的key,因为只要保证共享内存的key是系统中唯一的即可,如果系统没有很多的共享内存程序,指定一个key即可,需要注意的是两个程序需要使用相同的key才能访问到相同的共享内存。

shm_write.c的第13行是创建一块1024字节大小的共享内存,指定了共享内存可以读写。第17行获得共享内存的地址,之后检查共享内存地址是否合法,shmat()函数返回的地址–1表示地址不合法,而不是NULL。第21行写入一个字符串到共享内存,随后用shmdt()函数断开和共享内存的连接。

shm_read.c12行通过key值得到已经创建好的共享内存地址,和shm_write.c13行不同是的,没有指定IPC_CREAT属性,因为这里只是得到共享内存ID。第16行获取共享内存地址,如果地址是合法的,则在第20行打印共享内存的内容,最后调用shmdt()函数断开共享内存连接。

两个文件按照注释中的方法编译,运行w程序,如果创建共享内存成功,则没有任何提示。执行r程序,获得共享内存,打印共享内存内容,程序运行结果如下:

 

string in share memory: string in a share memory

 

程序打印出了共享内存的内容。细心的读者可能会发现,当再一次运行程序w的时候,会提示:

 

create share memory: File exists

get share memory: Invalid argument

段错误

 

出错的原因是已经有相同key值的共享内存了,实例中的两个程序退出的时候都没有删除共享,导致出错,使用命令ipcs可以查看目前的共享资源情况:

 

------ Shared Memory Segments --------

key        shmid      owner      perms      bytes      nattch     status     

0x00000090 360450   gonglei   600        1024       0                      

 

------ Semaphore Arrays --------

key        semid      owner      perms      nsems    

 

------ Message Queues --------

key        msqid      owner      perms      used-bytes   messages   

 

命令输出结果有3部分,分别打印出了共享内存、信号量和消息队列的使用情况。在本例中,系统只创建了一个共享内存,并且没有释放,使用命令ipcrm可以释放指定的共享内存:

 

ipcrm -m 360450

 

程序没有提示表示删除共享内存成功,再次运行ipcs命令查看,共享内存已经被删除。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值