2015年2月27日 星期五

[Quick Note] Android JobScheduler

Android API 21 新加入了 JobScheduler API 以及他的朋友們,目的就如字面上的意思,可以允許開發者排定一個定期工作,取代原有的 Alarm Manager + Broadcast Receiver 方式。

我第一眼看到 API 是覺得還蠻新奇的啦
setPersisted():開機後可以繼續活著
setRequiredNetworkType(), setRequiresCharging(), setRequiresDeviceIdle()喔喔!這個不錯,終於可以設定條件了嗎?!
雖說可以依照電量或機子狀態做選擇,但效能部分呢?會不會只是 Alarm Manager 的再包裝版呢?

先來看看常見的 Alarm Manager + Broadcast Receiver 方法:
  1. App 必須要開一個 Service,持續運行在背景
  2. Service 要註冊一個 Alarm Broadcast Receiver,接收 Alarm Service 的broadcast
  3. 向 Alarm Service 請求定期發送 broadcast
缺點一當然就是彈性不夠,不像 JobScheduler 可以設定條件,但我認為最致命的就是你必須開一個 Service 持續在背景運行。以使用者的角度,有一個東西在背景佔記憶體當然不怎麼高興,以開發者的角度,最糟的情況是如果機子記憶體很小,你的 Service 很容易因為 Low Memory 被殺掉了。

那 JobScheduler 怎麼做呢?Reference 這樣講過:
When the criteria declared are met, the system will execute this job on your application's JobService.
基本上我們邏輯的部份還是要繼承 JobService 呀,至於 JobScheduler 的 schedule(JobInfo) 吃的 JobInfo 事實上只是個加上資訊的包裝罷了,因為我們在調用 JobInfo.Builder(int, ComponentName) 時,還是要傳入 ComponentName 這個需要 JobService 建構的參數呀。
稍微翻了一下原始碼,終於在 android/server/job/JobServiceContext.java(看來他的邏輯還是簡單地劃出一個執行的 context) 找到了實際執行的函式。
142    /**
143     * Give a job to this context for execution. Callers must first check {@link #isAvailable()}
144     * to make sure this is a valid context.
145     * @param job The status of the job that we are going to run.
146     * @return True if the job is valid and is running. False if the job cannot be executed.
147     */
148    boolean executeRunnableJob(JobStatus job) {
149        synchronized (mLock) {
150            if (!mAvailable) {
151                Slog.e(TAG, "Starting new runnable but context is unavailable > Error.");
152                return false;
153            }
154
155            mRunningJob = job;
156            mParams = new JobParameters(this, job.getJobId(), job.getExtras(),
157                    !job.isConstraintsSatisfied());
158            mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
159
160            mVerb = VERB_BINDING;
161            scheduleOpTimeOut();
162            final Intent intent = new Intent().setComponent(job.getServiceComponent());
163            boolean binding = mContext.bindServiceAsUser(intent, this,
164                    Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND,
165                    new UserHandle(job.getUserId()));
166            if (!binding) {
167                if (DEBUG) {
168                    Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable.");
169                }
170                mRunningJob = null;
171                mParams = null;
172                mExecutionStartTimeElapsed = 0L;
173                removeOpTimeOut();
174                return false;
175            }
176            try {
177                mBatteryStats.noteJobStart(job.getName(), job.getUid());
178            } catch (RemoteException e) {
179                // Whatever.
180            }
181            mAvailable = false;
182            return true;
183        }
184    }
第162~165行就是大部份的答案了:JobScheduler 還是會生出一個 Service 的,但這個 Service 很特殊,也就是 JobService,有 onStartJob(), onStopJob(),不會一直在後台運行的,我個人感覺,JobService 比較像定義一個 task,在對的時間點執行,執行完 task 就結束,讓它比傳統需要一直開 Service 然後等 alarm 的方法更加省資源,也比較直觀。
    那這樣每一個工作就要 spawn 一個 process 的做法,代價是不是有點高呀?畢竟創造進程不是一件小事。
    我也有想過說要不要讓開發者可以把輕量級的工作丟到一個進程,在一個進程裡統一執行。
    恩...我是認為由於 Android App 的模型裡,App 進程已經跟 zygote 共用很多 address space 資源了,因為不管是 activity 還是 service 都是從 zygote fork 過來的,所以會比較像創一個 thread 而不是 process,代價比較輕一些。至於統一執行的部分,實作上會用像是 v8 isolate 的概念,在同一個進程裡區分不同工作的 context,但我覺得不會這樣做是因為 android 的 sandBox system 是用獨立出 process + uid / pid 等方式來做隔離,嘛...遵照一下現有的安全模型還是比較統一啊。


2015年2月16日 星期一

What is hardware cache line ?

When I was learning the SLAB allocator in the Linux kernel, there was always a question in my mind:
WAHT IS "CACHE LINE" ?!
I know CPU has caches, like L1, L2, etc. But why there is a "line" in the name? Moreover, when I was searching for the answer, I encountered more unfamiliar names like "2 way sets cache" or "Fully Associated cache" , and I was confused by those proper nouns, too.

In short, the reason it is called "line" is that a cache needs to be organized in groups, or blocks, and each of them is called "a cache line". What's more important is the way how it's organized. The first method comes out in our mind is to simply divide a memory address(32 bits, for example) into two fields: field at the most-significant-bits side indicates the cache line index, and the other field, the least-significant-bits side, represents the offset within a cache line. Although this sounds pretty straight forward and there do exist a kind of cache organization which is similar to the above description, it still needs to be slightly fixed before putting into practice.

The first kind we are going to introduce, is Fully Associated  cache.
This approach simply split a 32 bits address into 28 + 4 bits. The prior 28 bits field represents the memory block id, so there will be 2^28 memory blocks in this case. As mentioned above, this sort of organization is straight forward, however, it's difficult to implement since every time we want to find a specific memory block among the cache lines, it takes too many comparisons and efforts due to its large tag field, a memory block may locate in any of the cache line.

Before we introduce the solution how most of the modern processors fix the above problem, let's look at the other extreme compared to fully associated cache: Directed Mapped cache
There are some assumptions which are showed at the top-left corner in the picture above. This organization approach is just like its name - it directly maps each memory block to the cache. So the 19 bits field is the id of a memory block and the 9 bits field acts like a "cache line offset within a memory block" which also represents the index of a cache line in the cache. What is the downside of this approach? Well, let's take a look at a vivid example I saw in a lecture web page in Colorado University's ECEE department: If we are going to add two arrays and store the result to the third one which their sizes are all happen to be 16 bytes x 512, since each array perfectly fits the entire cache and each element in the array also perfectly fits a cache line, we might get cache miss on EVERY array access during the calculation!!

Let's look at the usual approach most of the modern processors adopt, thus, the N-Way Sets cache. Here is the 2 way sets:
The N-Ways Sets approach is really similar to the directed mapped approach, that's why I introduced the latter first. If you split the cache into half in the directed mapped cache and "merge" two parts into one by putting them parallel, then you almost get a 2 way sets cache! That is, a cache line now can contain two "slices"(16 bytes, in this case) of memory block which have the same cache line index, and the tag field is charged to identify which memory block this "slice" belongs to.


2015年2月2日 星期一

Using Binder To Vibrate - Android Binder 實戰 (Client Side, Second Part)

    在第一部分,我們幾乎沒有提到 AIDL 的事情,也沒有解釋為什麼 IMyVibrator 要寫成那樣,在這部分就要為大家來揭曉。

    在講 AIDL 之前,先來提一下 Android 的 Java Service 吧,或者更明確的說,bindService() 這個函式與相關的東西。我剛開始學寫 Service 時,常常困惑:假如我要跟 Service 溝通,不就弄一個 public 的變數或函式,讓雙方可以互相存取,為什麼還莫名其妙地跑出一個  onBind() 以及 bindService() 呢?的確,如果是在同一個 package 裏,是可以這麼做的,原因很簡單:在這個情況下,Service 跟主 App 是在同一個 thread。到這邊應該可以大概想出來,常與 bind 相關一起提到的 AIDL 是用在什麼情況了吧?沒錯,就是跨進程的 Service,所以我才在上一篇說到,AIDL 是 Binder 的 Java 綁定版本相關事物。

AIDL 真面目

    事實上 AIDL 檔在 App 的 Build 過程中,會編譯成一個 Java 檔,例如這次例子中的 IVibratorService.aidl ( 位於 frameworks/base/core/java/android/os/ ):
package android.os;

/** {@hide} */
interface IVibratorService
{
    boolean hasVibrator();
    void vibrate(int uid, String opPkg, long milliseconds, int usageHint, IBinder token);
    void vibratePattern(int uid, String opPkg, in long[] pattern, int repeat, int usageHint, IBinder token);
    void cancelVibrate(IBinder token);
}
我們把內容複製下來,改一下 package,貼到一個一樣名字的 AIDL 檔,並且執行有關 AIDL 的 gradle 任務,如下圖所示:
接下來會在 app/build/generated/source/aidl 底下看到生成的 Java 檔。
(綠色的那個 IVibratorService)
把它打開來看,以下是已經排版過的。(盡量不要修改該檔案)
package com.test.vibratorbinder;
// Declare any non-default types here with import statements

public interface IVibratorService extends android.os.IInterface {
 
 /** Local-side IPC implementation stub class. */
 public static abstract class Stub extends android.os.Binder implements com.test.vibratorbinder.IVibratorService {
  private static final java.lang.String DESCRIPTOR = "com.test.vibratorbinder.IVibratorService";
  
  /** Construct the stub at attach it to the interface. */
  public Stub() {
   this.attachInterface(this, DESCRIPTOR);
  }
  
  /**
   * Cast an IBinder object into an com.test.vibratorbinder.IVibratorService interface,
   * generating a proxy if needed.
   */
  public static com.test.vibratorbinder.IVibratorService asInterface(android.os.IBinder obj) {
   if ((obj == null)) {
    return null;
   }
   android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
   if (((iin != null) && (iin instanceof com.test.vibratorbinder.IVibratorService))) {
    return ((com.test.vibratorbinder.IVibratorService) iin);
   }
   return new com.test.vibratorbinder.IVibratorService.Stub.Proxy(obj);
  }

  @Override
  public android.os.IBinder asBinder() {
   return this;
  }

  @Override 
  public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
  throws android.os.RemoteException {
   switch (code) {
    case INTERFACE_TRANSACTION:
     {
      reply.writeString(DESCRIPTOR);
      return true;
     }
    case TRANSACTION_hasVibrator:
     {
      data.enforceInterface(DESCRIPTOR);
      boolean _result = this.hasVibrator();
      reply.writeNoException();
      reply.writeInt(((_result) ? (1) : (0)));
      return true;
     }
    case TRANSACTION_vibrate:
     {
      data.enforceInterface(DESCRIPTOR);
      int _arg0;
      _arg0 = data.readInt();
      java.lang.String _arg1;
      _arg1 = data.readString();
      long _arg2;
      _arg2 = data.readLong();
      int _arg3;
      _arg3 = data.readInt();
      android.os.IBinder _arg4;
      _arg4 = data.readStrongBinder();
      this.vibrate(_arg0, _arg1, _arg2, _arg3, _arg4);
      reply.writeNoException();
      return true;
     }
    case TRANSACTION_vibratePattern:
     {
      data.enforceInterface(DESCRIPTOR);
      int _arg0;
      _arg0 = data.readInt();
      java.lang.String _arg1;
      _arg1 = data.readString();
      long[] _arg2;
      _arg2 = data.createLongArray();
      int _arg3;
      _arg3 = data.readInt();
      int _arg4;
      _arg4 = data.readInt();
      android.os.IBinder _arg5;
      _arg5 = data.readStrongBinder();
      this.vibratePattern(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
      reply.writeNoException();
      return true;
     }
    case TRANSACTION_cancelVibrate:
     {
      data.enforceInterface(DESCRIPTOR);
      android.os.IBinder _arg0;
      _arg0 = data.readStrongBinder();
      this.cancelVibrate(_arg0);
      reply.writeNoException();
      return true;
     }
   }
   return super.onTransact(code, data, reply, flags);
  }

  private static class Proxy implements com.test.vibratorbinder.IVibratorService {
   private android.os.IBinder mRemote;
   
   Proxy(android.os.IBinder remote) {
    mRemote = remote;
   }
   
   @Override
   public android.os.IBinder asBinder() {
    return mRemote;
   }

   public java.lang.String getInterfaceDescriptor() {
    return DESCRIPTOR;
   }

   @Override
   public boolean hasVibrator() throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    boolean _result;
    try {
     _data.writeInterfaceToken(DESCRIPTOR);
     mRemote.transact(Stub.TRANSACTION_hasVibrator, _data, _reply, 0);
     _reply.readException();
     _result = (0 != _reply.readInt());
    } finally {
     _reply.recycle();
     _data.recycle();
    }
    return _result;
   }

   @Override
   public void vibrate(int uid, java.lang.String opPkg, long milliseconds, int usageHint, android.os.IBinder token) throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    try {
     _data.writeInterfaceToken(DESCRIPTOR);
     _data.writeInt(uid);
     _data.writeString(opPkg);
     _data.writeLong(milliseconds);
     _data.writeInt(usageHint);
     _data.writeStrongBinder(token);
     mRemote.transact(Stub.TRANSACTION_vibrate, _data, _reply, 0);
     _reply.readException();
    } finally {
     _reply.recycle();
     _data.recycle();
    }
   }

   @Override
   public void vibratePattern(int uid, java.lang.String opPkg, long[] pattern, int repeat, int usageHint, android.os.IBinder token)
   throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    try {
     _data.writeInterfaceToken(DESCRIPTOR);
     _data.writeInt(uid);
     _data.writeString(opPkg);
     _data.writeLongArray(pattern);
     _data.writeInt(repeat);
     _data.writeInt(usageHint);
     _data.writeStrongBinder(token);
     mRemote.transact(Stub.TRANSACTION_vibratePattern, _data, _reply, 0);
     _reply.readException();
    } finally {
     _reply.recycle();
     _data.recycle();
    }
   }

   @Override
   public void cancelVibrate(android.os.IBinder token) throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    try {
     _data.writeInterfaceToken(DESCRIPTOR);
     _data.writeStrongBinder(token);
     mRemote.transact(Stub.TRANSACTION_cancelVibrate, _data, _reply, 0);
     _reply.readException();
    } finally {
     _reply.recycle();
     _data.recycle();
    }
   }
  }

  static final int TRANSACTION_hasVibrator = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
  static final int TRANSACTION_vibrate = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
  static final int TRANSACTION_vibratePattern = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
  static final int TRANSACTION_cancelVibrate = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
 }
 
 public boolean hasVibrator() throws android.os.RemoteException;
 public void vibrate(int uid, java.lang.String opPkg, long milliseconds, int usageHint, android.os.IBinder token) throws android.os.RemoteException;
 public void vibratePattern(int uid, java.lang.String opPkg, long[] pattern, int repeat, int usageHint, android.os.IBinder token) throws android.os.RemoteException;
 public void cancelVibrate(android.os.IBinder token) throws android.os.RemoteException;
}
    看來本體是一個 interface,我們先從 Proxy 類開始分析吧。會從這個類開始講不是沒有原因的,往裡面一看:每一個函式的內容,不就是 BpMyVibrator 裡面函式在做的事嗎,像一個代理人一樣,把傳進來的函式參數打包進 Parcel,調用 IBinder 的 transact() 傳送出去,瞧,連名字大部份都一樣呢!之前說 AIDL 一定會丟出例外,所以若要讀取返回值的話,要先把 exceptionCode 讀出來,在這邊也可以得到應證。

    再來看看 Stub 類吧。裡面最大的一部分,onTransact(),雖然前一篇沒有介紹,但您應該看得出來他就是接收端的處理函式,而我們在這邊要看的,是 asInterface()。韓式裡面似乎也很好懂,原理甚至命名都跟 IMPLEMENT_META_INTERFACE() 幾乎一模一樣:先看看自己有沒有實作對應的業務的介面,如果沒有的話,就 new 一個 Proxy,角色等同於前一篇的 BpMyVibrator,解讀上完全沒有障礙。

    透過剛剛的解析,就可以了解為什麼前一篇的 IMyVibrator 的那些 virtual 函式要長成那樣,然後 descriptor 要設定成那個值了。因為雖然我們要溝通的對方,VibratorService,是活在 system_server 裏的 Java Service,但多虧了 Binder,我們只要業務介面、descriptor 是一樣的,就可以跟他通訊。

Summary(謎之音:本篇重點似乎只是展示編譯完、排版好的 AIDL(笑))

    這篇文章希望透過將 Java 的 Binder API 與 Native 的 Binder API 互相呼應對照,讓您能夠對這個在 AOSP 中佔有重要地位的 IPC 系統有更透徹的了解,進而可以靈活的運用。