掌握Android進程間通信機制

?通信是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呢?

有很多問題稚瘾,我自己也不清楚牡昆。只能說技術這條路,路漫漫其修遠兮摊欠,吾將上下而求索丢烘。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末柱宦,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子播瞳,更是在濱河造成了極大的恐慌掸刊,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赢乓,死亡現(xiàn)場離奇詭異忧侧,居然都是意外死亡,警方通過查閱死者的電腦和手機骏全,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進店門苍柏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人姜贡,你說我怎么就攤上這事试吁。” “怎么了楼咳?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵熄捍,是天一觀的道長。 經(jīng)常有香客問我母怜,道長余耽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任苹熏,我火速辦了婚禮碟贾,結果婚禮上,老公的妹妹穿的比我還像新娘轨域。我一直安慰自己袱耽,他們只是感情好,可當我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布干发。 她就那樣靜靜地躺著朱巨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪枉长。 梳的紋絲不亂的頭發(fā)上冀续,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天,我揣著相機與錄音必峰,去河邊找鬼洪唐。 笑死,一個胖子當著我的面吹牛吼蚁,可吹牛的內容都是我干的凭需。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼功炮!你這毒婦竟也來了?” 一聲冷哼從身側響起术唬,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤薪伏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后粗仓,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嫁怀,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年借浊,在試婚紗的時候發(fā)現(xiàn)自己被綠了塘淑。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡蚂斤,死狀恐怖存捺,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情曙蒸,我是刑警寧澤捌治,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站纽窟,受9級特大地震影響肖油,放射性物質發(fā)生泄漏。R本人自食惡果不足惜臂港,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一森枪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧审孽,春花似錦县袱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至搓萧,卻和暖如春杂数,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瘸洛。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工揍移, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人反肋。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓那伐,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子罕邀,可洞房花燭夜當晚...
    茶點故事閱讀 43,514評論 2 348