1,常用进程间通信
2,android binder进程间通信
3,binder进程间通信原理
我希望大家重点看看下面这段话,这是我理解的各种进程间通信的共通支出,毫不夸张我们甚至可以基于此,自己设计一套进程间通信的方式(实现通信就行,当然如果考虑效率,以及各种安全和细节因素,我还是比较菜的)。比如在内核自己注册一个字符设备(在读写等操作函数里面实现好同步),然后两个应用程序,一个读,一个写这样就实现了最简单的进程间通信。
- android特色的进程间通信binder
首先我们提到的各种进程间通信方式,主要还是以linux系统为主。大家都知道基于linux的系统,当我们去了解进程间通信的时候会发现一个特点,内核空间是共用的,上面的应用层,每个进程都有独立的地址空间,但是他们共享内核地址空间。
说着有点抽象,做过driver的人都知道,你在内核里面注册的各种节点以及操作函数,在应用层操作的时候操作的是同一个资源,所以在driver里面一定要注意做好同步与互斥。说的在简单一点就是你在内核里面定义的变量对于应用程序的进程是一样的,所以我们才要做好同步互斥,以免意想不到的错误。当我们了解linux的各种进程间通信方式的时候会发现一个特点,那就是他们基本上都利用了内核共享的这个特点。android的binder也不例外(在内核里面有binder驱动就像一个中转站一样,应用程序共享),基于此才实现了进程间通信。我们会发现各种进程间通信的方式都很类似于操作文件一样,一个写,一个读,这个"类似文件"就是我们在内核中维护的共享的,这是由于共享内核地址空间,所以这样就实现了进程间通信。这些策略就是维护在内核中,不同的进程间通信方式,在内核中实现的策略不一样,因此各种就是间通信的使用方式也不一样,不过都会有类似于文件操作的部分。
- 以上这段话就是我理解的进程间通信的本质
虽然各种各样的进程间通信方式各有特点,但是本质都是基于内核空间共享。
1,常用进程间通信
- 无名管道(pipe)
- 有名管道(FIFO)
- 共享内存
- 信号
- 信号量
- 套接字
- 消息对列
下面我们就依次介绍这几种进程间通信方式,我们每一种都会有一个最简单的例子,因为我认为例子是帮助理解的最好放肆
- 无名管道(pipe)
例一:pipe,pipe只能用于父子进程或者兄弟进程
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
int data_count;
int fd_pipes[2];
char some_data[32];
char buffer[BUFSIZ];
pid_t fork_result;
int counter = 30;
printf("Buffer_SIZE=%d\n",BUFSIZ);
memset(buffer, 0, sizeof(buffer));
if (pipe(fd_pipes) == 0)
{
fork_result = fork();
//fork failed
if (fork_result == -1)
{
fprintf(stderr, "Fork Failure");
exit(1);
}
if (fork_result == 0)
{
// child process
close(fd_pipes[1]);//close the write fd
while (counter--)
{
data_count = read(fd_pipes[0], buffer, BUFSIZ);//read the data from parent process
if (data_count == 0)
break;
buffer[data_count] = '\0';
printf("%d - Read %d bytes: %s\n", counter, data_count, buffer);
}
close(fd_pipes[0]);
printf("reader done\n");
}
else
{
// parent process
close(fd_pipes[0]);
while (counter--)
{
sprintf(some_data, "[%d] ", counter);
data_count = write(fd_pipes[1], some_data, strlen(some_data));//write data to pipe for child process read
if (data_count == 0)
break;
printf("%d - Wrote %d bytes\n", counter, data_count);
}
close(fd_pipes[1]);
printf("writer done\n");
}
exit(0);
}
exit(1);
}
这个例子只是简单地实现父进程向pipe中写数据,子进程读数据实现进程间通信,并没有根据读到的数据做实际的操作,实际的应用汇根据读到的数据做相应的操作。不知道大家记不记得之前的一篇博客我有讲过android消息循环里面有提到过pipe(连接:android消息循环),但是那是我们只是用pipe用来做同步,实现的是线程间同步,如果我们上面的这个例子没有调用fork,而是后面pthread_creat()创建线程,那么我们也就能用于线程之间的同步,android的消息循环就是利用了这一点,如果pipe里面没有数据可读,那么当前的线程就会休眠,等待,这样就实现了同步,由此可见android其实也是利用linux原本的机制,不过他已经应用的炉火纯青。
- 有名管道(FIFO)
FIFO:可以用于没有血缘关系的进程间通信。
例子:
//filename:writer.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FIFO_NAME "/tmp/my_fifo"
#define BUFFER_SIZE PIPE_BUF
#define TEN_MEG (1024 * 1024 *10 )
int main()
{
int fifo_fd;
int res;
int open_mode = O_WRONLY;
int bytes_sent = 0;
char buffer[]="hello world!";
if( -1 == access(FIFO_NAME, F_OK) )
{
res = mkfifo( FIFO_NAME, 0777 );
if( 0 != res )
{
fprintf( stderr, "Could not create fifo %s\n", FIFO_NAME );
exit(1);
}
}
fifo_fd = open( FIFO_NAME, open_mode );
if( -1 != fifo_fd )
{
res = write( fifo_fd, buffer, sizeof(buffer) );
if( -1 == res )
{
fprintf( stderr, "Write error on fifo\n");
exit(1);
}
printf("write data:%s\n",buffer);
close( fifo_fd );
}
else
exit(1);
exit(0);
}
读进程
//filename:reader.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FIFO_NAME "/tmp/my_fifo"
#define BUFFER_SIZE PIPE_BUF
int main()
{
int fifo_fd;
int res;
int open_mode = O_RDONLY;
char buffer[BUFFER_SIZE + 1];
int bytes_read = 0;
memset( buffer, '\0', sizeof(buffer) );
fifo_fd = open( FIFO_NAME, open_mode );
if( -1 != fifo_fd )
{
res = read( fifo_fd, buffer, BUFFER_SIZE );
printf("read data:%s\n",buffer);
close( fifo_fd );
}
else
exit(1);
exit(0);
}
上面这个例子是FIFO进程间通信最简单的例子。一个写,一个读,两个程序配合实现通信。
哈哈,写到这里突然觉得后面几个例子暂时不写了,比如套接字通信,可以当成是广义的网络编程,就很像两个电脑之间客户端与服务器通信,只不过客户端与服务器,运行在同一台机器上面,并且建立套接字的参数不同,至少在应用层面是只能看到这些区别。
总之记住一点这几种都很类似与文件读写操作,一个写一个读,由于读写都要经过内核,进而实现进程间通信。下面着重讲解linux的binder进程间通信。
2,android binder进程间通信
下面是一副binder进程间通信的结构图,我着重按照这个图上面标注的流程讲解binder进程间通信。如图:
上面这幅图是我理解的binde进程间通信的原理图,请注意看箭头的标号
如图上面所示主要有四部分,binder驱动,ServiceManager,我们的Service和Client。
binder驱动:注册/dev/binder节点,应用程序通过操作文件似的打开这个节点,请求各种服务或者注册自己。
ServiceManager:这个是一个特殊的service,这个进程即使一个service又是作为service的管理者配合binder驱动一起协调进程间通信
Service:这个主要是我们自己实现的service或者启动过程中的systemservice
Client:这个是我们的服务请求者
下面讲解这四大部分是如何交互的
标号1:我们知道在android系统启动的时候会启动一个很重要的进程这个ServiceManager(代码在/frameworks/native/cmds/servicemanager/service_manager.c)
代码就不贴出来了,在android源码里面自己看。这个进程会打开/dev/binder,将自己注册为service的管理者
标号2:还是在ServiceManager这个进程,当ServiceManager将自己注册为管理者之后,会进入一个IO模式的死循环,不断的读取/dev/binder(当然会休眠)看是否有来自Service端的注册/给CLient的回应或者来自CLient端的服务请求。
标号3:是我们的Service他会打开/dev/binder然后请求注册自己。
标号4:binder驱动将Service的请求注册通过标号2唤醒ServiceManager完成注册,然后Service也会进入死循环(类似于ServiceManager)等待来自Client的请求
标号5:Client请求服务,这是也会打开/dev/binder向binder驱动请求,binder驱动这个时候会唤醒标号2处循环的ServiceManager找到注册的Service,找到service之后,如果Client有通信的请求(说白了就是让Service做事)还是类似标号5的方式告诉binder驱动,然后binder驱动通过标号2告诉ServiceManager,ServiceManager通过标号4告诉Service(前面说过我们的service在循环等待请求),Service根据请求做相应的处理,然后将处理结果按照相反的路线告诉Client,这时就通信成功。
3,binder进程间通信原理
看了上面讲解的binder进程间通信,下面我们总结一下原理。我们会发现不管是我们的Service还是Client都不能直接通信都会经过内核的binder驱动,这就是我前面说的内核空间是共享的,但是应用程序有各自的地址空间,因此想要通信必须经过内核(这里就是我们的binder驱动)。
然后当我们实际应用的时候追源码会发现无论是ServiceManager,Service还是Client都会打开/dev/binder这个节点,这个节点是binder驱动程序注册的,通过这个节点就将Service和Client联系起来了。
最后我给出几个链接,可以加深理解,都是老罗的android之旅,老罗的博客,他讲解的源码比较详细,所以我这里主要通过更加形象的方式讲解,可以通过老罗的博客加深理解。链接如下:
1.老罗binder简要介绍和学习计划
2.浅谈Service Manager成为Android进程间通信(IPC)机制Binder守护进程
3.浅谈Android系统进程间通信(IPC)机制Binder中的Server和Client获得Service Manager接口之路