?通信是Android開發(fā)必不可少的一部分胚迫,不管是我們做應用App開發(fā)薯定,還是Android系統(tǒng),都使用了大量的通信。通信又分為進程間通信和進程內通信垢揩,在這篇文章玖绿,我主要深入講解Android系統(tǒng)所涉及到的所有進程間通信方式。
Android系統(tǒng)中有大量IPC(進程間通信)的場景叁巨,比如我們想要創(chuàng)建一個新的進程斑匪,需要通過Socket這種IPC方式去讓Zygote Fork新進程;如果我們要殺掉一個進程锋勺,需要通過信號這種IPC方式去將SIGNAL_KILL信號傳遞到系統(tǒng)內核蚀瘸;如果我們想要喚醒主線程處于休眠中的Looper,需要管道這種IPC方式來喚醒庶橱;我們想要在應用開發(fā)中使用AIDL贮勃,廣播或者Messager等方式來進行跨進程通信,其實底層都是使用了Binder這種IPC方式苏章。
那么寂嘉,Android到底有多少種進程間通信的方式呢?什么樣的場景要選擇什么樣的通信方式呢枫绅?這些IPC通信方式怎么使用呢泉孩?這些IPC通信的底層原理又是什么呢?看完這篇文章撑瞧,你就能回答這幾個問題了棵譬。
我們先通過下面一覽Android系統(tǒng)所具有的IPC通信方式
可以看到,Android所擁有的IPC總共有這些:
- 基于Unix系統(tǒng)的IPC的管道预伺,F(xiàn)IFO订咸,信號
- 基于SystemV和Posix系統(tǒng)的IPC的消息隊列,信號量酬诀,共享內存
- 基于Socket的IPC
- Linux的內存映射函數(shù)mmap()
- Linux 2.6.22版本后才有的eventfd
- Android系統(tǒng)獨有的Binder和匿名共享內存Ashmen
下面脏嚷,我會詳細介紹這些IPC的通信機制。
管道
PIPE和FIFO的使用及原理
PIPE和FIFO都是指管道瞒御,只是PIPE獨指匿名管道父叙,F(xiàn)IFO獨指有名管道,我們先看一下管道的數(shù)據(jù)結構以及他們的使用方式:
//匿名管道(PIPE)
#include <unistd.h>
int pipe (int fd[2]); //創(chuàng)建pipe
ssize_t write(int fd, const void *buf, size_t count); //寫數(shù)據(jù)
ssize_t read(int fd, void *buf, size_t count); //讀數(shù)據(jù)
?
//有名管道(FIFO)
#include<sys/stat.h>
#include <unistd.h>
int mkfifo(const char *path, mode_t mode); //創(chuàng)建fifo文件
int open(const char *pathname, int flags); //打開fifo文件
ssize_t write(int fd, const void *buf, size_t count); //寫數(shù)據(jù)
ssize_t read(int fd, void *buf, size_t count); //讀數(shù)據(jù)
可以看到肴裙,匿名管道通過pipe函數(shù)創(chuàng)建趾唱,pipe函數(shù)通過在內核的虛擬文件系統(tǒng)中創(chuàng)建兩個pipefs虛擬文件(不清楚虛擬文件的可以去了解下Linux的虛擬文件系統(tǒng)VFS),并返回這兩個虛擬文件描述符蜻懦,有了這兩個文件描述甜癞,我們就能進行跨進程通信了。
匿名管道是單向半雙工的通信方式宛乃,單向即意味著只能一端讀另一端寫悠咱,半雙工意味著不能同時讀和寫蒸辆,其中文件描述符fd[1]只能用來寫,文件描述符f[0]只能用來讀析既,pipe創(chuàng)建好后躬贡,我們就可以用Linux標準的文件讀取函數(shù)read和寫入函數(shù)write來對pipe進行讀寫了。
為什么pipe是匿名管道呢眼坏?因為pipefs文件是特殊的虛擬文件系統(tǒng)拂玻,并不會顯示在VFS的目錄中,所以用戶不可見空骚,既然用戶不可見纺讲,那么又怎么能進行進程間通信呢?因為pipe是匿名的囤屹,所以它只支持父子和兄弟進程之間的通信熬甚。通過fork創(chuàng)建父子進程或者通過clone創(chuàng)建兄弟進程的時候,會共享內存拷貝肋坚,這時乡括,子進程或兄弟進程就能在共享拷貝中拿到pipe的文件描述符進行通信了。我們通過下圖看一下通信的流程智厌。
接著說有名管道FIFO诲泌,F(xiàn)IFO是半雙工雙向通信,所以通過FIFO創(chuàng)建的管道既能讀也能寫铣鹏,但是不能同時讀和寫敷扫,F(xiàn)IFO本質上是一個先進先出的隊列數(shù)據(jù)結構,最早放入的數(shù)據(jù)被最先讀出來诚卸,這樣的數(shù)據(jù)結構能保證信息交流的順序葵第。
FIFO使用也很簡單,通過mkfifo函數(shù)創(chuàng)建管道合溺,它同樣會在內核的虛擬文件系統(tǒng)中創(chuàng)建一個FIFO虛擬文件卒密,F(xiàn)IFO文件在在VFS目錄中可見,所以他是有名管道棠赛,F(xiàn)IFO創(chuàng)建后哮奇,我們需要調用open函數(shù)打開這個fifo文件,然后才能通過write和read函數(shù)進行讀寫
我們來總結一下pipe和fifo的異同點
相同點
- IPC的本質都是通過在內核創(chuàng)建虛擬文件睛约,并且調用文件讀寫函數(shù)來進行數(shù)據(jù)通信
- 都只能接收字節(jié)流數(shù)據(jù)
- 都是半雙工通信
不同點
- pipe是單向通信鼎俘,fifo可以雙向通信
- pipe只能在父子,兄弟進程間通信辩涝,fifo沒有這個限制
那么管道的使用場景是什么呢而芥?匿名管道只能用在親屬進程之間通信,而且傳輸?shù)臄?shù)量小膀值,一般只支持4K棍丐,不適合大數(shù)據(jù)的交換數(shù)據(jù)和不同進程間的通信,但是使用簡單方便沧踏,因為是單向通信歌逢,所以不存在并發(fā)問題。雖然FIFO能在任意兩個進程間進行通信翘狱,但是因為FIFO是可以雙向通信的秘案,這樣也不可避免的帶來了并發(fā)的問題,我們需要花費比較大的精力用來控制并發(fā)問題潦匈。
管道在Android系統(tǒng)中的使用場景
下面說一下Android系統(tǒng)中具體使用到管道的場景:Looper阱高。Looper不就是一個消息隊列嗎?怎么還使用到了管道呢茬缩?其實在Android 6.0 以下版本中赤惊,主線程Looper的喚醒就使用到了管道。
//文件-->/system/core/libutils/Looper.cpp
Looper::Looper(bool allowNonCallbacks) :
mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
int wakeFds[2];
int result = pipe(wakeFds); //創(chuàng)建pipe
?
mWakeReadPipeFd = wakeFds[0];
mWakeWritePipeFd = wakeFds[1];
?
result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking. errno=%d",
errno);
?
result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking. errno=%d",
errno);
?
mIdling = false;
?
// Allocate the epoll instance and register the wake pipe.
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance. errno=%d", errno);
?
struct epoll_event eventItem;
memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeReadPipeFd;
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance. errno=%d",
errno);
}
從第上面代碼可以看到凰锡,native層的Looper的構造函數(shù)就使用了pipe來創(chuàng)建管道未舟,通過mWakeReadPipeFd,mWakeWritePipeFd這兩個文件描述符的命名也看出掂为,它是用來做喚醒的裕膀,我們就來看一下具體的喚醒的實現(xiàn)吧。
//文件-->/system/core/libutils/Looper.cpp
void Looper::wake() {
ssize_t nWrite;
do {
nWrite = write(mWakeWritePipeFd, "W", 1);
} while (nWrite == -1 && errno == EINTR);
if (nWrite != 1) {
if (errno != EAGAIN) {
ALOGW("Could not write wake signal, errno=%d", errno);
}
}
}
可以看到勇哗,喚醒函數(shù)其實就是往管道m(xù)WakeWritePipeFd里寫入一個字母“W”昼扛,mWakeReadPipeFd接收到數(shù)據(jù)后,就會喚醒Looper欲诺。
信號
信號的使用及原理
信號實質上是一種軟中斷抄谐,既然是一種中斷,就說明信號是異步的瞧栗,信號接收函數(shù)不需要一直阻塞等待信號的到達斯稳。當信號發(fā)出后,如果有地方注冊了這個信號迹恐,就會執(zhí)行響應函數(shù)挣惰,如果沒有地方注冊這個信號,該信號就會被忽略殴边。我們來看一下信號的使用方法憎茂。
#include <signal.h>
sighandler_t signal(int signum, sighandler_t handler); //信號注冊函數(shù)
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); //信號注冊函數(shù)
struct sigaction {
void (*sa_handler)(int); //信號處理程序,不接受額外數(shù)據(jù)锤岸,SIG_IGN 為忽略竖幔,SIG_DFL 為默認動作
void (*sa_sigaction)(int, siginfo_t *, void *); //信號處理程序,能夠接受額外數(shù)據(jù)和sigqueue配合使用
sigset_t sa_mask;//阻塞關鍵字的信號集是偷,可以再調用捕捉函數(shù)之前拳氢,把信號添加到信號阻塞字募逞,信號捕捉函數(shù)返回之前恢復為原先的值。
int sa_flags;//影響信號的行為SA_SIGINFO表示能夠接受數(shù)據(jù)
};
?
int kill(pid_t pid, int sig); //信號發(fā)送函數(shù)
int sigqueue(pid_t pid, int sig, const union sigval value); //信號發(fā)送函數(shù)
//……
注冊信號有兩個方法
- signal()函數(shù):signal不支持傳遞信息馋评,signum入?yún)樾盘柫糠沤樱琱andler入?yún)樾盘柼幚砗瘮?shù)
- sigaction()函數(shù):sigaction支持傳遞信息,信息放在sigaction數(shù)據(jù)結構中
信號發(fā)送函數(shù)比較多留特,這里我列舉一下纠脾。
- kill():用于向進程或進程組發(fā)送信號;
- sigqueue():只能向一個進程發(fā)送信號蜕青,不能向進程組發(fā)送信號苟蹈;
- alarm():用于調用進程指定時間后發(fā)出SIGALARM信號;
- setitimer():設置定時器右核,計時達到后給進程發(fā)送SIGALRM信號慧脱,功能比alarm更強大;
- abort():向進程發(fā)送SIGABORT信號蒙兰,默認進程會異常退出磷瘤。
- raise():用于向進程自身發(fā)送信號;
通過kill -l指令可以查看Android手機支持的信號搜变,從下圖可以看到采缚,總共有64個,前31個信號是普通信號挠他,后33個信號是實時信號扳抽,實時信號支持隊列,可以保證信號不會丟失殖侵。
我列舉一下前幾個信號的作用贸呢,其他的就不講解了
1 | SIGHUP | 掛起 |
---|---|---|
2 | SIGINT | 中斷 |
3 | SIGQUIT | 中斷 |
3 | SIGQUIT | 退出 |
4 | SIGILL | 非法指令 |
5 | SIGTRAP | 斷點或陷阱指令 |
6 | SIGABRT | abort發(fā)出的信號 |
7 | SIGBUS | 非法內存訪問 |
8 | SIGFPE | 浮點異常 |
9 | SIGKILL | 殺進程信息 |
當我們調用信號發(fā)送函數(shù)后,信號是怎么傳到注冊的方法調用中去的呢拢军?這里以kill()這個信號發(fā)送函數(shù)講解一下這個流程楞陷。
kill()函數(shù)會經(jīng)過系統(tǒng)調用方法sys_tkill()進入內核,sys_tkill是SYSCALL_DEFINE2這個方法來實現(xiàn)茉唉,這個方法的實現(xiàn)是一個宏定義固蛾。我會從這個方法一路往底追蹤,這里我會忽略細節(jié)實現(xiàn)度陆,只看關鍵部分的代碼艾凯。
//文件-->syscalls.h
asmlinkage long sys_kill(int pid, int sig);
?
//文件-->kernel/signal.c
SYSCALL_DEFINE2(kill, pid_t, pid, int, sig)
{
struct kernel_siginfo info;
?
clear_siginfo(&info);
info.si_signo = sig;
info.si_errno = 0;
info.si_code = SI_USER;
info.si_pid = task_tgid_vnr(current);
info.si_uid = from_kuid_munged(current_user_ns(), current_uid());
?
return kill_something_info(sig, &info, pid);
}
?
static int kill_something_info(int sig, struct kernel_siginfo *info, pid_t pid)
{
int ret;
?
if (pid > 0) {
ret = kill_pid_info(sig, info, find_vpid(pid));
return ret;
}
……
}
?
int kill_pid_info(int sig, struct kernel_siginfo *info, struct pid *pid)
{
……
for (;;) {
error = group_send_sig_info(sig, info, p, PIDTYPE_TGID);
……
}
}
?
int group_send_sig_info(int sig, struct kernel_siginfo *info,
struct task_struct *p, enum pid_type type)
{
……
ret = do_send_sig_info(sig, info, p, type);
return ret;
}
?
int do_send_sig_info(int sig, struct kernel_siginfo *info, struct task_struct *p,
enum pid_type type)
{
……
ret = send_signal(sig, info, p, type);
return ret;
}
?
static int send_signal(int sig, struct kernel_siginfo *info, struct task_struct *t,
enum pid_type type)
{
return __send_signal(sig, info, t, type, from_ancestor_ns);
}
我們從sys_kill函數(shù)一路追蹤,最終調用了__send_signal函數(shù),我們接著看這個函數(shù)的實現(xiàn)懂傀。
//文件-->kernel/signal.c
static int __send_signal(int sig, struct kernel_siginfo *info, struct task_struct *t,
enum pid_type type, int from_ancestor_ns)
{
……
out_set:
signalfd_notify(t, sig); //將信號發(fā)送給監(jiān)聽的fd
sigaddset(&pending->signal, sig);
complete_signal(sig, t, type); //完成信號發(fā)送
ret:
trace_signal_generate(sig, info, t, type != PIDTYPE_PID, result);
return ret;
}
?
static void complete_signal(int sig, struct task_struct *p, enum pid_type type)
{
struct signal_struct *signal = p->signal;
struct task_struct *t;
?
//尋找處理信號的線程
if (wants_signal(sig, p))
t = p;
else if ((type == PIDTYPE_PID) || thread_group_empty(p))
return;
else {
t = signal->curr_target;
while (!wants_signal(sig, t)) {
t = next_thread(t);
if (t == signal->curr_target)
return;
}
signal->curr_target = t;
}
?
//如果是SIGKILL信號趾诗,則殺掉線程組
if (sig_fatal(p, sig) &&
!(signal->flags & SIGNAL_GROUP_EXIT) &&
!sigismember(&t->real_blocked, sig) &&
(sig == SIGKILL || !p->ptrace)) {
/*
* This signal will be fatal to the whole group.
*/
if (!sig_kernel_coredump(sig)) {
/*
* Start a group exit and wake everybody up.
* This way we don't have other threads
* running and doing things after a slower
* thread has the fatal signal pending.
*/
signal->flags = SIGNAL_GROUP_EXIT;
signal->group_exit_code = sig;
signal->group_stop_count = 0;
t = p;
do {
task_clear_jobctl_pending(t, JOBCTL_PENDING_MASK);
sigaddset(&t->pending.signal, SIGKILL);
signal_wake_up(t, 1);
} while_each_thread(p, t);
return;
}
}
/*
* The signal is already in the shared-pending queue.
* Tell the chosen thread to wake up and dequeue it.
*/
signal_wake_up(t, sig == SIGKILL);
return;
}
可以看到,信號最終被分發(fā)到了監(jiān)聽的fd中蹬蚁,交給了我們注冊的函數(shù)處理恃泪,從最后部分也可以看到郑兴,如果是SIGKILL信號,內核會專門處理去殺進程悟泵。這里我詳細講了發(fā)送信號的底層實現(xiàn)杈笔,關于注冊信號的底層實現(xiàn),就不再這里詳細講了糕非,有興趣的可以自己去研究。
信號在Android中的使用場景
我們已經(jīng)知道如何使用信號以及它的原理球榆,那么我們在來看一個Android系統(tǒng)中使用信號的場景:殺進程朽肥。從上面部分可以看到,SIGKILL信號是由內核捕獲并處理的持钉,我們看一下Android是怎么調用殺進程的信號的吧衡招。
//文件-->Process.java
public static final void killProcess(int pid) {
sendSignal(pid, SIGNAL_KILL);
}
?
//文件-->android_util_Process.cpp
void android_os_Process_sendSignal(JNIEnv* env, jobject clazz, jint pid, jint sig) {
if (pid > 0) {
//打印Signal信息
ALOGI("Sending signal. PID: %" PRId32 " SIG: %" PRId32, pid, sig);
kill(pid, sig);
}
}
可以看到,當我們調用Process的killProcess函數(shù)殺掉某個進程時每强,最終會調用到native方法kill()始腾,入?yún)ig信號量就是SIGKILL,這個kill()方法空执,就是我在上面講的信號量發(fā)送函數(shù)浪箭,最終內核會響應我們的SIGKILL,殺掉進程辨绊。
我們已經(jīng)了解了基于Unix的三種通信方式以及他們在Android系統(tǒng)上的應用奶栖,我們接著來看看另外三種IPC通信方式,消息隊列门坷,信號量和共享內存宣鄙。這三種IPC方式有基于SystemV和基于Posix的兩個版本,由于有些Linux系統(tǒng)并沒有實現(xiàn)基于POSIX的IPC默蚌,所以這兒就只說SystemV的IPC了冻晤,他們在本質上其實都是相似的。
消息隊列
消息隊列的使用和原理
我們首先看看消息隊列的創(chuàng)建及其如何使用
#include <sys/ipc.h>
#include <sys/msg.h>
?
int msgget(key_t, key, int msgflg); //創(chuàng)建和訪問消息隊列
int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg); //發(fā)送消息
int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg); //獲取消息
我們可以通過msgget()函數(shù)來創(chuàng)建消息隊列绸吸,它會在內核空間創(chuàng)建一個消息鏈表鼻弧,msgsend()函數(shù)往消息隊列發(fā)送消息,msgrcv()函數(shù)獲取消息隊列里的數(shù)據(jù)惯裕。
通過消息發(fā)送和接收函數(shù)可以看到温数,消息隊列的每個消息消息都有msgid,msgtype蜻势,msgflg字段撑刺,msgid是消息的隊列標識符,msgtype是消息的類型握玛,發(fā)送函數(shù)中放在msg_ptr這個結構體里够傍,msgflg用來控制讀取消息時隊列已滿或者隊列為空時的操作甫菠。
我在這里介紹一下消息隊列的數(shù)據(jù)結構,它是一個消息的鏈表,存放在內核中并由消息隊列標識符標識冕屯,也就是上面提到的msgid寂诱,標識符標識用大于0的整數(shù)表示,并且每中標識符的消息隊列都有自己的鏈表安聘。它的表現(xiàn)結構如下圖:
消息隊列有哪些優(yōu)點呢痰洒?它克服了Linux早期IPC機制的很多缺點,比如消息隊列具有異步能力浴韭,又克服了具有同樣能力的信號承載信息量少的問題丘喻;具有數(shù)據(jù)傳輸能力,又克服了管道只能承載無格式字節(jié)流以及緩沖區(qū)大小受限的問題念颈。但是缺點是消息隊列比信號和管道都要更加重量泉粉,在內核中會使用更多內存,并且消息隊列能傳輸?shù)臄?shù)據(jù)也有限制榴芳,一般上限都是16kb嗡靡。
消息隊列在Android中的使用場景
受限于性能,數(shù)據(jù)量等問題的限制窟感,Android系統(tǒng)沒有直接使用Linux消息隊列來進行IPC的場景讨彼,但是有大量的場景都利用了消息隊列的特性來設計通信方案,比如我們最頻繁使用的Handler肌括,就是一個消息隊列点骑,由于Handler只是進程內的通信方式,所以它的實現(xiàn)不在這兒討論谍夭,消息隊列的架構模型被非常多的場景使用黑滴,主要有下面幾個有點原因。
- 解耦:消息隊列可以實現(xiàn)兩個模塊之間的解耦紧索,兩個需要通信的模塊不需要之間對接袁辈,發(fā)送方只需要將消息丟到隊列,接收方只需要從隊列里面取消息珠漂。
- 異步:我們可以將多條消息并行的發(fā)送給消息隊列晚缩,然后不同的模塊去并行的處理,在這種方案下媳危,我們不需要串行的處理任務荞彼。
- 緩沖:消息隊列的數(shù)據(jù)結構就是一個緩沖池,可以幫助我們減輕流量過大的壓力待笑。
關于消息隊列的介紹就講到這兒了鸣皂,它并不是一個常用的Linux IPC通信方式导俘,我們接著信號量句各。
信號量
信號量的使用和原理
信號量和信號是不同的IPC通信機制杏瞻,信號量是在進程之間傳遞是一個整數(shù)值蛹批,信號量只有三種操作可以進行:初始化,P操作荆陆,V操作滩届,我們看一下具體的使用函數(shù)和數(shù)據(jù)結構。
#include<sys/sem.h>
?
int semget(key_t key, int num_sems, int sem_flags);//創(chuàng)建新信號量或獲取已有的信號量
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);//改變信號量的值
?
struct sembuf{
short sem_num;
short sem_op;//通常是兩個數(shù)被啼,一個是-1帜消,即P操作,一個是+1趟据,即V操作券犁。
short sem_flg;//跟蹤信號
};
我們通過semget()函數(shù)獲取或者創(chuàng)建一個信號量,并返回一個信號量id汹碱,有了這個id,我們就可以通過semop()函數(shù)進行V和P的操作荞估。V就是將這個信號量加1咳促,P就是將信號量減1。這三種操作都是原子操作勘伺,我們通常用信號量來進行并發(fā)和同步的控制跪腹。
講到了信號量,不免要提一下互斥鎖Mutex飞醉,信號量可以是非負整數(shù)冲茸,互斥鎖只能是0和1兩個值,我們可以將Mutex理解為特殊的信號量缅帘。在大部分情況下轴术,用互斥鎖來做并發(fā)的控制會比信號量更方便。關于Android或者Java的線程并發(fā)钦无,我會專門寫一篇文章來講逗栽,也就不在這兒再繼續(xù)深入講了。
共享內存
共享內存的使用和原理
還是先看共享內存的使用方法失暂,我主要介紹兩個函數(shù):
#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); //把共享內存映射到進程的地址空間
通過shmget()函數(shù)申請共享內存,它的入?yún)⑷缦?/p>
- key:用來唯一確定這片內存的標識,彼宠。
- size:就是我們申請內存的大小
- shmflg:讀寫權限
- 返回值:這個操作會返回一個id, 我們一般稱為 shmid
通過shmat()函數(shù)將我們申請到的共享內存映射到自己的用戶空間,映射成功會返回地址弟塞,有了這個地址凭峡,我們就可以隨意的讀寫數(shù)據(jù)了,我們繼續(xù)看一下這個函數(shù)的入?yún)?/p>
- shmid :我們申請內存時, 返回的shmid
- shmaddr:共享內存在進程的內存地址决记,傳NULL讓內核自己決定一個合適的地址位置.
- shmflg:讀寫權限
- 返回值:映射后進程內的地址指針, 代表內存的頭部地址
共享內存的原理是在內存中單獨開辟的一段內存空間摧冀,這段內存空間其實就是一個tempfs(臨時虛擬文件),tempfs是VFS的一種文件系統(tǒng),掛載在/dev/shm上按价,前面提到的管道pipefs也是VFS的一種文件系統(tǒng)惭适。
由于共享的內存空間對使用和接收進程來講,完全無感知楼镐,就像是在自己的內存上讀寫數(shù)據(jù)一樣癞志,所以也是效率最高的一種IPC方式,上面提到的IPC的方式都是在內核空間中開辟內存來存儲數(shù)據(jù)框产,寫數(shù)據(jù)時凄杯,需要將數(shù)據(jù)從用戶空間拷貝到內核空間,讀數(shù)據(jù)時秉宿,需要從內核空間拷貝到自己的用戶空間戒突,而共享內存就只需要一次拷貝,而且共享內存不是在內核開辟空間描睦,所以可以傳輸?shù)臄?shù)據(jù)量大膊存。
但是共享內存最大的缺點就是沒有并發(fā)的控制,我們一般通過信號量配合共享內存使用忱叭,進行同步和并發(fā)的控制隔崎。
Android中共享內存的使用場景
共享內存在Android系統(tǒng)中主要的使用場景是用來傳輸大數(shù)據(jù),并且Android并沒有直接使用Linux原生的共享內存方式韵丑,而是設計了Ashmem匿名共享內存爵卒。之前說到有名管道和匿名管道的區(qū)別在于有名管道可以在vfs目錄樹中查看到這個管道的文件,但是匿名管道不行撵彻,所以匿名共享內存同樣也是無法在vfs目錄中查看到的钓株,Android之所以要設計匿名共享內存,我覺得主要是為了安全性的考慮吧陌僵。
我們來看看共享內存的一個使用場景轴合,在Android中,如果我們想要將當前的界面顯示出來拾弃,需要將當前界面的圖元數(shù)據(jù)傳遞Surfaceflinger去做圖層混合值桩,圖層混合之后的數(shù)據(jù)會直接送入幀緩存,送入幀緩存后豪椿,顯卡就會直接取出幀緩存里的圖元數(shù)據(jù)顯示了奔坟。那么我們如何將應用的Activity的圖元數(shù)據(jù)傳遞給SurfaceFlinger呢?想要將圖像數(shù)據(jù)這樣比較大的數(shù)據(jù)跨進程傳輸搭盾,靠binder是不行的咳秉,所以這兒便用到匿名共享內存。
從谷歌官方提供的架構圖可以看到鸯隅,圖元數(shù)據(jù)是通過BufferQueue傳遞到SurfaceFlinger去的澜建,當我們想要繪制圖像的時候向挖,需要從BufferQueue中申請一個Buffer,Buffer會調用Gralloc模塊來分配共享內存當作圖元緩沖區(qū)存放我們的圖元數(shù)據(jù)炕舵。我們看一下代碼的實現(xiàn)何之。
//文件-->hardware/libhardware/modules/gralloc/gralloc.cpp
static int gralloc_alloc_buffer(alloc_device_t* dev,
size_t size, int usage, buffer_handle_t* pHandle)
{
int err = 0;
int fd = -1;
size = roundUpToPageSize(size);
// 創(chuàng)建共享內存,并且設定名字跟size
fd = ashmem_create_region("gralloc-buffer", size);
if (err == 0) {
private_handle_t* hnd = new private_handle_t(fd, size, 0);
gralloc_module_t* module = reinterpret_cast<gralloc_module_t*>(
dev->common.module);
// 執(zhí)行mmap咽筋,將內存映射到自己的進程
err = mapBuffer(module, hnd);
if (err == 0) {
*pHandle = hnd;
}
}
?
return err;
}
?
int mapBuffer(gralloc_module_t const* module,
private_handle_t* hnd)
{
void* vaddr;
return gralloc_map(module, hnd, &vaddr);
}
?
static int gralloc_map(gralloc_module_t const* module,
buffer_handle_t handle,
void** vaddr)
{
private_handle_t* hnd = (private_handle_t*)handle;
if (!(hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER)) {
size_t size = hnd->size;
//映射創(chuàng)建的匿名共享內存
void* mappedAddress = mmap(0, size,
PROT_READ|PROT_WRITE, MAP_SHARED, hnd->fd, 0);
if (mappedAddress == MAP_FAILED) {
return -errno;
}
hnd->base = intptr_t(mappedAddress) + hnd->offset;
}
*vaddr = (void*)hnd->base;
return 0;
}
可以看到Android的匿名共享內存是通過ashmem_create_region() 函數(shù)來申請共享內存的溶推,它會在/dev/ashmem下創(chuàng)建一個虛擬文件,Linux原生共享內存是通過shmget()函數(shù)奸攻,并會在/dev/shm下創(chuàng)建虛擬文件蒜危。
匿名共享內存是通過mmap()函數(shù)將申請到的內存映射到自己的進程空間,而Linux是通過*shmat()函數(shù)睹耐。雖然函數(shù)不一樣辐赞,但是Android的匿名共享內存和Linux的共享內存在本質上是大同小異的。
Socket
Socket的使用和原理
socket套接字本來是設計給基于TCP/IP協(xié)議的網(wǎng)絡通信使用的硝训,但由于它是一種C/S架構模型响委,即客戶端服務器端架構,這種模型能帶來很大的安全性以及快速的響應能力窖梁,所以也常常用在進程之間的通信上晃酒。Socket的使用方式比上面前面提到的其他IPC都要復雜很多,我們先通過下圖了解它的使用流程窄绒。
我們在看看具體的函數(shù)
#include <sys/socket.h>
#include <unistd.h>
#include <unistd.h>
int socket(int protofamily, int type, int protocol);//創(chuàng)建socket
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);//綁定socket
int listen(int sockfd, int backlog);//監(jiān)聽端口號
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);//客戶端請求建立連接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);//服務端接收連接請求
ssize_t send(int sockfd, const void *buf, size_t len, int flags); //IO寫函數(shù)
ssize_t recv(int sockfd, void *buf, size_t len, int flags);//IO讀函數(shù)
int close(int fd); //關閉函數(shù)
Linux系統(tǒng)中萬物皆文件,所以Socket也是一個虛擬文件崔兴,socket文件的數(shù)據(jù)結構中包含了當前主機的ip地址彰导,當前主機進程的端口號,發(fā)送端主機的ip地址等信息敲茄,通過這些信息位谋,我們可以在虛擬文件系統(tǒng)中唯一定位到一個Socket文件,通過對這個文件的讀寫達到通信的目的堰燎。
Socket在Android系統(tǒng)中的使用場景
當我們使用socket來進行進程間的通信時掏父,實際是通過將IP設置為127.0.0.1這個本地IP來實現(xiàn)的,Android系統(tǒng)為我們提供了LocalSocket來進行進程間的通信秆剪,LocalSocket的實質也是對Socket的封裝赊淑,通過直接使用LocalSocket,我們省掉了設置本機IP等一系列繁瑣的操作仅讽。
我們看一個LocalSocket的使用場景:當我們啟動一個App應用時陶缺,如果當前的應用的進程不存在,AMS會通過Socket通知Zygote去Fork新進程洁灵。它的代碼實現(xiàn)如下饱岸。
//文件-->frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
// 服務端
public static void main(String argv[]) {
ZygoteServer zygoteServer = new ZygoteServer();
……
zygoteServer.registerServerSocket(socketName);
……
Log.i(TAG, "Accepting command socket connections");
zygoteServer.runSelectLoop(abiList);
zygoteServer.closeServerSocket();
……
}
ZygoteInit啟動時,會創(chuàng)建一個ZygoteServer,然后fork生成System Server進程苫费,接著啟動整個Framwork的Server汤锨,最終執(zhí)行zygoteServer的runSelectLoop函數(shù),始終等待其他進程發(fā)送過來的fork進程的消息百框。在上面的代碼中闲礼,我只展示了ZygoteServer相關的代碼,啟動System Server等其他流程的代碼我全部省略了琅翻,有興趣的可以去看看這一塊的源碼位仁。我們接著看registerServerSocket函數(shù)和runSelectLoop函數(shù)的實現(xiàn)
//文件-->/frameworks/base/core/java/com/android/internal/os/ZygoteServer.java
void registerServerSocket(String socketName) {
if (mServerSocket == null) {
int fileDesc;
final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
try {
String env = System.getenv(fullSocketName);
fileDesc = Integer.parseInt(env);
} catch (RuntimeException ex) {
throw new RuntimeException(fullSocketName + " unset or invalid", ex);
}
try {
FileDescriptor fd = new FileDescriptor();
fd.setInt$(fileDesc);
mServerSocket = new LocalServerSocket(fd);
} catch (IOException ex) {
throw new RuntimeException(
"Error binding to local socket '" + fileDesc + "'", ex);
}
}
}
?
void runSelectLoop(String abiList) throws Zygote.MethodAndArgsCaller {
ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
?
fds.add(mServerSocket.getFileDescriptor());
peers.add(null);
?
while (true) {
StructPollfd[] pollFds = new StructPollfd[fds.size()];
for (int i = 0; i < pollFds.length; ++i) {
pollFds[i] = new StructPollfd();
pollFds[i].fd = fds.get(i);
pollFds[i].events = (short) POLLIN;
}
try {
Os.poll(pollFds, -1);
} catch (ErrnoException ex) {
throw new RuntimeException("poll failed", ex);
}
for (int i = pollFds.length - 1; i >= 0; --i) {
if ((pollFds[i].revents & POLLIN) == 0) {
continue;
}
if (i == 0) {
ZygoteConnection newPeer = acceptCommandPeer(abiList);
peers.add(newPeer);
fds.add(newPeer.getFileDesciptor());
} else {
boolean done = peers.get(i).runOnce(this);
if (done) {
peers.remove(i);
fds.remove(i);
}
}
}
}
}
可以看到registerServerSocket函數(shù)實際是創(chuàng)建了LocalServerSocket,這個LocalServerSocket的名字就叫“zygote”方椎,runSelectLoop函數(shù)將ServerSocket加入多路復用模型里聂抢,當收到消息時便調用runOnce方法去fork進程。
我們已經(jīng)了解了Server端了棠众,我們接著看一下Client端
//文件-->/frameworks/base/core/java/android/os/Process.java
//客戶端
public static final ProcessStartResult start(final String processClass,
final String niceName,
int uid, int gid, int[] gids,
int debugFlags, int mountExternal,
int targetSdkVersion,
String seInfo,
String abi,
String instructionSet,
String appDataDir,
String invokeWith,
String[] zygoteArgs) {
return zygoteProcess.start(processClass, niceName, uid, gid, gids,
debugFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, zygoteArgs);
}
?
//文件-->/frameworks/base/core/java/android/os/ZygoteProcess.jav
public final Process.ProcessStartResult start(final String processClass,
final String niceName,
int uid, int gid, int[] gids,
int debugFlags, int mountExternal,
int targetSdkVersion,
String seInfo,
String abi,
String instructionSet,
String appDataDir,
String invokeWith,
String[] zygoteArgs) {
try {
return startViaZygote(processClass, niceName, uid, gid, gids,
debugFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, zygoteArgs);
} catch (ZygoteStartFailedEx ex) {
Log.e(LOG_TAG,
"Starting VM process through Zygote failed");
throw new RuntimeException(
"Starting VM process through Zygote failed", ex);
}
}
?
private Process.ProcessStartResult startViaZygote(final String processClass,
final String niceName,
final int uid, final int gid,
final int[] gids,
int debugFlags, int mountExternal,
int targetSdkVersion,
String seInfo,
String abi,
String instructionSet,
String appDataDir,
String invokeWith,
String[] extraArgs)
throws ZygoteStartFailedEx {
……
synchronized(mLock) {
//連接服務端socket琳疏,并發(fā)送數(shù)據(jù)
return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
}
}
?
private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx {
if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
try {
primaryZygoteState = ZygoteState.connect(mSocket);
} catch (IOException ioe) {
throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe);
}
}
……
}
?
public static ZygoteState connect(String socketAddress) throws IOException {
DataInputStream zygoteInputStream = null;
BufferedWriter zygoteWriter = null;
final LocalSocket zygoteSocket = new LocalSocket();
zygoteSocket.connect(new LocalSocketAddress(socketAddress,
LocalSocketAddress.Namespace.RESERVED));
?
zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream());
?
zygoteWriter = new BufferedWriter(new OutputStreamWriter(
zygoteSocket.getOutputStream()), 256);
?
return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter,
Arrays.asList(abiListString.split(",")));
}
?
private static Process.ProcessStartResult zygoteSendArgsAndGetResult(
ZygoteState zygoteState, ArrayList<String> args)
throws ZygoteStartFailedEx {
int sz = args.size();
for (int i = 0; i < sz; i++) {
if (args.get(i).indexOf('\n') >= 0) {
throw new ZygoteStartFailedEx("embedded newlines not allowed");
}
}
final BufferedWriter writer = zygoteState.writer;
final DataInputStream inputStream = zygoteState.inputStream;
?
writer.write(Integer.toString(args.size()));
writer.newLine();
?
for (int i = 0; i < sz; i++) {
String arg = args.get(i);
writer.write(arg);
writer.newLine();
}
?
writer.flush();
Process.ProcessStartResult result = new Process.ProcessStartResult();
result.pid = inputStream.readInt();
result.usingWrapper = inputStream.readBoolean();
?
if (result.pid < 0) {
throw new ZygoteStartFailedEx("fork() failed");
}
return result;
}
從上面的代碼實現(xiàn)可以看到,當AMS調用Process的start()函數(shù)時闸拿,最終執(zhí)行到了ZygoteProcess類中的openZygoteSocketIfNeeded() 函數(shù)空盼,連接socket,然后調用zygoteSendArgsAndGetResult() 函數(shù)通過LocalSocket 往LocalServerSocket發(fā)送消息 新荤。
為什么Android fork進程要用Socket揽趾,而不用Binder呢?這個問題留給大家去思考苛骨。
mmap函數(shù)
mmap是一個很重要的函數(shù)篱瞎,它可以實現(xiàn)共享內存,但并不像SystemV和Posix的共享內存存粹的只用于共享內存痒芝,mmap()的設計俐筋,主要是用來做文件的映射的,它提供了我們一種新的訪問文件的方案严衬。
mmap函數(shù)的使用非常簡單澄者,我們來看一下
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
- addr:用于指定映射到進程空間的起始地址,為了應用程序的可移植性请琳,一般設置為NULL粱挡,讓內核來選擇一個合適的地址
- length:表示映射到進程地址空間的大小
- prot:用于設置內核映射區(qū)域的讀寫屬性等。
- flags:用于設置內存映射的屬性单起,例如共享映射抱怔、私有映射等。
- fd:表示這個是一個文件映射嘀倒,fd是打開文件的句柄屈留。
- offset:在文件映射時局冰,表示文件的偏移量。
常規(guī)文件操作為了提高讀寫效率和保護磁盤灌危,使用了頁緩存機制康二,這種機制會造成讀文件時需要先將文件頁從磁盤拷貝到頁緩存中,由于頁緩存處在內核空間勇蝙,不能被用戶進程直接尋址沫勿,所以還需要將頁緩存中數(shù)據(jù)頁再次拷貝到內存對應的用戶空間中。這樣味混,通過了兩次數(shù)據(jù)拷貝過程产雹,才能完成進程對文件內容的獲取任務。寫操作也是一樣翁锡,待寫入的buffer在內核空間不能直接訪問蔓挖,必須要先拷貝至內核空間對應的主存,再寫回磁盤中(延遲寫回)馆衔,也是需要兩次數(shù)據(jù)拷貝瘟判。
而使用mmap操作文件中,由于不需要經(jīng)過內核空間的數(shù)據(jù)緩存角溃,只使用一次數(shù)據(jù)拷貝拷获,就從磁盤中將數(shù)據(jù)傳入內存的用戶空間中,供進程使用减细。
mmap的關鍵點是實現(xiàn)了用戶空間和內核空間的數(shù)據(jù)直接交互而省去了空間不同數(shù)據(jù)不通的繁瑣過程匆瓜,因此mmap效率很高。
Android中mmap()的使用場景
mmap()使用非常頻繁未蝌,看過Android系統(tǒng)源碼的人陕壹,肯定看到過大量的地方使用mmap()函數(shù),比如上面提到的匿名共享內存的使用就使用到了mmap來映射/dev/ashmem里的文件树埠。
這里我再介紹一種mmap()在Android系統(tǒng)上的使用場景,mmap的設計目的就是為了讓文件的訪問更有效率嘶伟,所以當APK進行安裝時怎憋,為了更高效的讀取APK包里面的文件,同樣也用到了mmap函數(shù)九昧。Dalvik在安裝應用時绊袋,需要加載dex文件,然后進行odex優(yōu)化處理铸鹰,優(yōu)化函數(shù)為dvmContinueOptimization癌别,我們看一下他的大致實現(xiàn)。
bool dvmContinueOptimization(int fd, off_t dexOffset, long dexLength,
const char* fileName, u4 modWhen, u4 crc, bool isBootstrap)
{
……
//通過mmap映射dex文件
mapAddr = mmap(NULL, dexOffset + dexLength, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, 0);
if (mapAddr == MAP_FAILED) {
ALOGE("unable to mmap DEX cache: %s", strerror(errno));
goto bail;
}
?
……
//驗證和優(yōu)化dex文件
success = rewriteDex(((u1*) mapAddr) + dexOffset, dexLength,
doVerify, doOpt, &pClassLookup, NULL);
?
……
//取消文件映射
if (munmap(mapAddr, dexOffset + dexLength) != 0) {
ALOGE("munmap failed: %s", strerror(errno));
goto bail;
}
……
bail:
dvmFreeRegisterMapBuilder(pRegMapBuilder);
free(pClassLookup);
return result;
}
可以看到蹋笼,dvmContinueOptimization函數(shù)中對dex文件的加載便用了mmap內存映射函數(shù)展姐。
eventfd
eventfd 是 Linux 2.6.22后才開始支持的一種IPC通信方式躁垛,它的作用主要時用來做事件通知,并且完全可以替代pipe圾笨,對于內核來說教馆,eventfd的開銷更低,eventfd只需要創(chuàng)建一個虛擬文件擂达,而pipe需要創(chuàng)建兩個土铺,并且可用于select或epoll等多路復用模型中,來實現(xiàn)異步的信號通知功能板鬓。所以eventfd 是很好用的一種IPC方式悲敷,而且它的使用也簡單。
#include<sys/eventfd.h>
#include <unistd.h>
int eventfd(unsigned int initval,int flags);//創(chuàng)建eventfd
ssize_t write(int fd, const void *buf, size_t count); //寫數(shù)據(jù)
ssize_t read(int fd, void *buf, size_t count); //讀數(shù)據(jù)
eventfd在內核里的核心是一個計數(shù)器counter俭令,它是一個uint64_t的整形變量counter后德,初始值為initval。
當調用read() 函數(shù)讀取eventfd時唤蔗,會根據(jù)counter值執(zhí)行下列操作:
- 如果當前counter > 0探遵,那么read返回counter值,并重置counter為0妓柜;
- 如果當前counter等于0箱季,那么read 函數(shù)阻塞直到counter大于0,如果設置了NONBLOCK棍掐,那么返回-1藏雏。
當調用write() 往eventfd寫數(shù)據(jù)時,我們只能寫入一個64bit的整數(shù)value
Eventfd在Android中的使用場景
正是因為eventfd比管道更簡單高效作煌,所以在Android6.0之后掘殴,Looper的喚醒就換成了eventfd。
Looper::Looper(bool allowNonCallbacks) :
mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
mWakeEventFd = eventfd(0, EFD_NONBLOCK);
LOG_ALWAYS_FATAL_IF(mWakeEventFd < 0, "Could not make wake event fd. errno=%d", errno);
?
AutoMutex _l(mLock);
rebuildEpollLocked();
}
?
void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ wake", this);
#endif
uint64_t inc = 1;
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
if (nWrite != sizeof(uint64_t)) {
if (errno != EAGAIN) {
ALOGW("Could not write wake signal, errno=%d", errno);
}
}
}
可以看到粟誓,Looper的構造函數(shù)中mWakeEventFd已經(jīng)由之前提到的pipe換成了evnentfd奏寨,wake()函數(shù)也不是之前的寫入一個“w”字符,而是寫入了一個64位整數(shù)1鹰服。
Binder
終于講到Android的最后一種IPC的通信機制Binder了病瞳,有人會疑問,為什么AIDL悲酷,BroadCast套菜,Content Provider這些不是Android的IPC機制呢?這些方式其實也是Android的IPC方式设易,但是他們的底層都是基于Binder實現(xiàn)的逗柴。
Binder的機制比較復雜,由于這篇文章只是為了橫向介紹Android的IPC機制顿肺,所以不會對Binder有太過深入的講解戏溺,也不會在這兒介紹基于binder實現(xiàn)的BroadCast渣蜗,Content Provider等IPC方式,這些我之后會寫文專門講解于购。我們主要了解一下Binder的架構及其設計思想袍睡。
Linux已經(jīng)有了前面提到的這么多的IPC方式了,為什么還要設計Binder呢肋僧?我覺得主要有三個原因的考慮斑胜。
- 通信效率
- 安全問題
- 并發(fā)問題
在上面提到的所有的IPC中,只有共享內存效率是最高嫌吠,但是直接使用共享內存會有并發(fā)問題和安全問題
我們先來解決安全問題止潘。如果想要解決安全問題,我們可以采用C/S架構或者匿名的IPC通信機制辫诅,匿名的IPC通信機制無疑會影響進程間通信的方便性凭戴,比如匿名管道,就只能在親屬進程間通信炕矮。所以我們需要采用C/S的架構么夫,在C/S架構下,Server端可以對Client端的請求做校驗來保證安全性肤视。
接著我們需要解決并發(fā)問題档痪,解決并發(fā)問題我們可以采用消息隊列,或者通過鎖來控制并發(fā)情況邢滑,或者采用C/S架構腐螟。
在這三者的考慮下,我們發(fā)現(xiàn)只有采用C/S架構的共享內存才是最高效的困后。所以我們的Binder本質上就是C/S架構的共享內存的IPC機制乐纸。
從架構圖可以看到,Binder其實是掛載在/dev/binder下的一個虛擬文件摇予。我們可能猜測汽绢,Clinet端和Server端通過前面提到的mmap()文件映射函數(shù),將/dev/binder下的文件映射到自己的用戶空間中侧戴,這樣Client端直接往這塊內容寫數(shù)據(jù)庶喜,Server也能同時讀取文件了。
但實際不是這樣的救鲤。Binder的機制其實是通過將/dev/binder下的文件同時映射到Server端的用戶空間和內核空間,在這種情況下秩冈,Server端想要讀寫這一塊內容時本缠,就不需要執(zhí)行將數(shù)據(jù)從用戶空間拷貝內核空間,或者將數(shù)據(jù)從內核空間拷貝到用戶空間的操作了入问。我們的Client端只需要通過將數(shù)據(jù)寫入內核空間丹锹,Server端的用戶空間便能直接讀取這塊數(shù)據(jù)了稀颁。
為什么binder的設計不采用上面這種方案,而采用下面的方案呢楣黍?因為上面的方案其實就是和共享內存的方案是一模一樣了匾灶。采用下面的方案,Client寫數(shù)據(jù)時租漂,依然會陷入內核阶女,內核函數(shù)此時可以充當Server的角色。
總結
自此哩治,Android的IPC通信機制全部講完了秃踩,受限于篇幅問題,有很多地方?jīng)]有深入展開业筏,比如Binder憔杨,如果深入展開又需要寫非常長了。寫這篇文章的目的蒜胖,主要是想通過對Android IPC機制廣度的認識消别,來達到更加深入思考的目的。比如為什么Android要設計Binder台谢,Binder的優(yōu)缺點是什么寻狂,如果讓我們自己設計IPC,需要怎么設計对碌?在我看來荆虱,我覺得Binder還是有一些缺點的,比如相比于Linux自身的IPC通信朽们,它的內存占用過多怀读,使用太過復雜,而且數(shù)據(jù)傳輸量有限制骑脱。又比如菜枷,Linux系統(tǒng)自帶的IPC機制優(yōu)缺點又是什么?它會往什么樣的方向發(fā)展叁丧?2.6.22內核中為什么要新出eventfd這種IPC啤誊,接下來的內核中,又可能出現(xiàn)哪些IPC機制呢拥娄?Linux的圖形操作系統(tǒng)中蚊锹,如Ubuntu等系統(tǒng)的應用程序進行IPC時是采用的哪種IPC呢?
有很多問題稚瘾,我自己也不清楚牡昆。只能說技術這條路,路漫漫其修遠兮摊欠,吾將上下而求索丢烘。