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 系統有更透徹的了解,進而可以靈活的運用。


2015年1月30日 星期五

Using Binder to Vibrate - Android Binder 實戰 (Client Side, First Part)

    這次要示範的是如何使用 AOSP 裡面的 Binder 相關 API,來跟 system_server 裏的 VibratorService 通訊,使用震動器。

    我不是要解析 Binder 內部原理,解析 Android 原始碼的文章去 CSDN 隨便搜都一大堆(大陸人超喜歡解析原始碼),本文後面附的參考書籍也不錯,我在這邊單純只是要示範如何使用而已。以下講解的 App,已經放上 Github 了:
https://github.com/mshockwave/android-binder-demo-with-vibrator

架構

    我本來的計劃是想直接載入 libhardware(或者 libhardware_legacy) 來操作 vibrator,但是想也知道:這種不用在 Manifest 聲明權限就可以使用硬體的好康,要是現在還存在的話 Google 早就不用玩了,process 本身權限也不夠。當然如果你有 root 權限的話,可以寫一個像是 MediaServer 一樣的 native service 來做前述的事。在不 root 的情況下,似乎就只剩下乖乖跟 system_server 進行通訊來使用 vibrator 了。
    因此這個 App 就是製作一個「Native 版 AIDL 檔」來跟 system_server 裏的 VibratorService 溝通。而後面也會講到,事實上 AIDL 檔只是 Binder 的 Java 綁定的一個包裝罷了。下列文中會用到一些 AOSP 的 header files 或是 shared library,礙於篇幅關係,就先不在這邊說明如何引入了。

(一)Binder 初識

    Binder 乍看之下還頗複雜的,但仔細一看才發現,看起來複雜的地方只是「包裝」而已,它核心的部分也是簡單的訊息傳遞。藉由哪邊傳遞訊息呢?答案是 kernel ,Binder 負責訊息傳遞的組件事實上是一個 kernel module,我剛開始非常好奇為什麼要搞那麼複雜,但後來有稍微理解了一點:kernel 的記憶體空間也是每個 process 共享的,跟 Sys V 的 shared memory IPC 道理一樣是共用記憶體。

    再來我們要有的概念就是 Binder 可以分成「業務」以及「通訊」兩部分。上面剛提到的,Binder 的包裝看起來很複雜,事實上他裡面就是把業務以及通訊融合在一起,傳統的 IPC (像是我們熟悉的 Sys V 系列) 都是要手動將資料做讀寫,而 Binder 則是將物件導向裡的介面以及繼承發揮得淋漓盡致,把資料的讀寫全部藏起來。

(二)業務介面:IInterface

    先來從最簡單的業務介面介紹起。(IMyVibrator.h)
class IMyVibrator : public IInterface {

public:
    DECLARE_META_INTERFACE(MyVibrator);

    virtual bool hasVibrator(void) = 0;

    virtual void vibrate(int32_t uid, 
                         String16& opPkg, 
                         int64_t milliseconds, int32_t usageHint, 
                         sp<IBinder>& token) = 0;

    virtual void vibratePattern(int32_t uid, 
                                String16& opPkg, 
                                int64_t pattern[], int32_t repeat, int32_t usageHint,
                                sp<IBinder>& token) = 0;

    virtual void cancelVibrate(sp<IBinder>& token) = 0;

};
請先忽略函式參數的意思。這邊定義的就是最終客戶端操作的介面,其中最有趣的莫過於 DECLARE_META_INTERFACE 這個 macro 了,他定義於 IInterface 裡,目前 5.0.0 (因為最近幾個版本 AOSP 檔案路徑變動很多) 座落在 frameworks/native/include/binder/IInterface.h。
#define DECLARE_META_INTERFACE(INTERFACE)                               \
    static const android::String16 descriptor;                          \
    static android::sp<I##INTERFACE> asInterface(                       \
            const android::sp<android::IBinder>& obj);                  \
    virtual const android::String16& getInterfaceDescriptor() const;    \
    I##INTERFACE();                                                     \
    virtual ~I##INTERFACE();                                            \

上方只是先單純的做一些宣告,descriptor 顧名思義就是用來辨識這個 Binder 節點,要注意的是不要跟後面講到 ServiceManager 裡 addService/getService 的那個服務名稱搞混,這邊的 descriptor 是 Binder 驅動裡的辨識符,而後者的服務名稱只適用於 ServiceManager。asInterface 則是要等一下才會講到,但如果有用過 AIDL 的朋友就會想起,使用 AIDL 時也有 asInterface 這個函式 ( 例:IMyInterface.Stub.asInterface() ),而我在這邊就先告訴您:他們兩者做的事是一樣的。

(三)客戶端通訊介面:BpInterface

剛剛的業務介面可能會讓你一頭霧水,感覺沒在做什麼,但沒錯,就如字面上的意思:他只是個介面,所以先請耐心地看下去,看看負責通訊的 BpInterface 類。BpInterface 我們在這邊要知道的,首先是他繼承了 BpRefBase
template<typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase
{
public:
                                BpInterface(const sp<IBinder>& remote);

protected:
    virtual IBinder*            onAsBinder();
};
而 BpRefBase 裡有一個成員變數 mRemote 以及跟他相關的 remote() 函式。
class BpRefBase : public virtual RefBase
{
protected:
                            BpRefBase(const sp<IBinder>& o);
    virtual                 ~BpRefBase();
    virtual void            onFirstRef();
    virtual void            onLastStrongRef(const void* id);
    virtual bool            onIncStrongAttempted(uint32_t flags, const void* id);

    inline  IBinder*        remote()                { return mRemote; }
    inline  IBinder*        remote() const          { return mRemote; }

private:
                            BpRefBase(const BpRefBase& o);
    BpRefBase&              operator=(const BpRefBase& o);

    IBinder* const          mRemote;
    RefBase::weakref_type*  mRefs;
    volatile int32_t        mState;
};
我們透過 BpInterface 的建構子來設定 mRemote,這點請稍微留意,到時候則會呼叫 mRemote 的 transact() 函式來傳送處理過後的訊息,而這邊也是本篇文章討論 Binder 部分的最底層,再往下如何跟 Binder 驅動溝通,就留給各位了,您也可以參見本篇文章後面附上的參考書籍,裡面有非常詳盡的解說。

(三)通訊 + 業務: BpXXXX

    到了這步驟,我們當然就要把業務以及通訊部分融合在一起啦。(MyVibratorProxy.cpp)
using namespace android;

//Client proxy interface
class BpMyVibrator : public BpInterface<IMyVibrator> {

public:
    BpMyVibrator(const sp<IBinder>& remote) : BpInterface<IMyVibrator>(remote) {
        ALOGD("BpMyVibrator constructor");
    }

    virtual bool hasVibrator(void) {
        ALOGD("BpMyVibrator::hasVibrator invoked");
        Parcel _data, _reply;
        _data.writeInterfaceToken(IMyVibrator::getInterfaceDescriptor());
        remote() -> transact(TRANSACTION_hasVibrator, _data, &_reply);
        /*
        * Must read the exception before fetching return data!!
        * Since methods in AIDL file will always throw RemoteException
        */
        ALOGD("%s exception code: %d", __func__, _reply.readExceptionCode());
        int retInt32 = _reply.readInt32();
        //ALOGD("Return in int: %d", retInt32);

        return (bool)(0 != retInt32);
    }

    virtual void vibrate(int32_t uid, 
                         String16& opPkg, 
                         int64_t milliseconds, int32_t usageHint, 
                         sp<IBinder>& token) {
        ALOGD("BpMyVibrator::vibrate invoked");
        Parcel _data, _reply;
        _data.writeInterfaceToken(IMyVibrator::getInterfaceDescriptor());
        _data.writeInt32(uid);
        _data.writeString16(opPkg);
        _data.writeInt64(milliseconds);
        _data.writeInt32(usageHint);
        _data.writeStrongBinder(token);

        remote() -> transact(TRANSACTION_vibrate, _data, &_reply);
        //ALOGD("%s exception code: %d", __func__, _reply.readExceptionCode());
    }

    virtual void vibratePattern(int32_t uid, 
                                String16& opPkg, 
                                int64_t pattern[], int32_t repeat, int32_t usageHint, 
                                sp<IBinder>& token) {
        ALOGD("BpMyVibrator::vibratePattern invoked");
        Parcel _data, _reply;
        _data.writeInterfaceToken(IMyVibrator::getInterfaceDescriptor());
        _data.writeInt32(uid);
        _data.writeString16(opPkg);
        size_t len = (size_t)(sizeof(pattern) / sizeof(pattern[0]));
        _data.write((const void*)pattern, len);
        _data.writeInt32(repeat);
        _data.writeInt32(usageHint);
        _data.writeStrongBinder(token);

        remote() -> transact(TRANSACTION_vibratePattern, _data, &_reply);
        //ALOGD("%s exception code: %d", __func__, _reply.readExceptionCode());
    }

    virtual void cancelVibrate(sp<IBinder>& token) {
        ALOGD("BpMyVibrator::cancelVibrate invoked");
        Parcel _data, _reply;
        _data.writeInterfaceToken(IMyVibrator::getInterfaceDescriptor());
        _data.writeStrongBinder(token);

        remote() -> transact(TRANSACTION_cancelVibrate, _data, &_reply);
        //ALOGD("%s exception code: %d", __func__, _reply.readExceptionCode());
    }
};

IMPLEMENT_META_INTERFACE(MyVibrator, "android.os.IVibratorService");
先從建構子開始看,前面有提到,我們是用建構子來傳遞通訊用的 IBinder 物件,所以一定要呼叫父建構子。接下來每一個函式都是我們在 IMyVibrator 裡宣告的業務函式,看看每一個函式都在做什麼?看起來就是把函式參數稍微處理一下,打包到 Parcel 型態的包裹裡,然後用前面提過的 remote()  與 transact() 函式傳送出去。到這邊您大概就猜的出來 BpInterface 的那個 "p" 是什麼的縮寫了:proxy。

    沒錯,在 BpMyVibrator 裡我們做的工作就像一個 proxy,代理人,當客戶端在使用 IMyVibrator 時,其實他們是呼叫這些代理函式。還有另一個問題就是,Parcel 有提供像是 int32, float 等基本型態的封裝,那麼較複雜的物件呢?答案是你的類別必須實作 Parcelable 介面,把類別可以變成 Parcel 攜帶,這部分 Android 官方文件介紹的很清楚(雖然是 java 的 Parcelable,但大同小異),這邊就不提了。另外 transact() 函式第一個參數是用來辨識傳送給哪一個函式,要注意的是 AIDL 檔案在生成的時候,是照函式的順序由上而下編號的,例如我這次傳送的對象,IVibratorService,AIDL 檔 (位於 /frameworks/base/core/java/android/os/IVibratorService.aidl) 長這樣:
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);
}

所以我在客戶端這邊的編號就要按照他的順序:(IMyVibrator.h)
#define TRANSACTION_hasVibrator (IBinder::FIRST_CALL_TRANSACTION + 0)
#define TRANSACTION_vibrate (IBinder::FIRST_CALL_TRANSACTION + 1)
#define TRANSACTION_vibratePattern  (IBinder::FIRST_CALL_TRANSACTION + 2)
#define TRANSACTION_cancelVibrate  (IBinder::FIRST_CALL_TRANSACTION + 3)
FIRST_CALL_TRANSACTION 預設的數值是 1。

    在代理函式實作步驟裡面,有幾個重點。第一,Parcel 包以什麼順序寫入,在另一端就會以什麼順序解開,Parcel 並沒有像 Bundle 那樣是以 key-value 對儲存。第二,您應該有發現,傳送出去的包,第一個寫入的一定是前面提到的 descriptor,以供 Binder 辨識對象。第三,Parcel 原生支援打包 IBinder 物件,也就是 writeStrongBinder() 函式 (應該說是打包 strong pointer 型態的 IBinder,也就是 sp<IBinder>),因此可以看到 AOSP 裡常常把一個 IBinder 傳送給對方,當作單純的 token,就是不想再多創造一個 Parcelable 的物件,而多利用原生支持的 IBinder 傳送。第四,如上方程式碼註解的,如果對方有丟出 Exception,例如我們這個 App 因為對方是用 java 的 AIDL,一定會丟出 RemoteException,那我們必須先調用 readExceptionCode() 讀取例外,接著才能讀取到正確的返回值 (我那時在這邊卡很久)。

    在 MyVibratorProxy.cpp 最後一行有一個 IMPLEMENT_META_INTERFACE 的 macro,是不是很眼熟呢?沒錯,他就是 DECLARE_META_INTERFACE 的「另一半」,實作那些宣告。直接來看看他的內容:(也是座落於 IInterface.h)
#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME)                       \
    const android::String16 I##INTERFACE::descriptor(NAME);             \
    const android::String16&                                            \
            I##INTERFACE::getInterfaceDescriptor() const {              \
        return I##INTERFACE::descriptor;                                \
    }                                                                   \
    android::sp<I##INTERFACE> I##INTERFACE::asInterface(                \
            const android::sp<android::IBinder>& obj)                   \
    {                                                                   \
        android::sp<I##INTERFACE> intr;                                 \
        if (obj != NULL) {                                              \
            intr = static_cast<I##INTERFACE*>(                          \
                obj->queryLocalInterface(                               \
                        I##INTERFACE::descriptor).get());               \
            if (intr == NULL) {                                         \
                intr = new Bp##INTERFACE(obj);                          \
            }                                                           \
        }                                                               \
        return intr;                                                    \
    }                                                                   \
    I##INTERFACE::I##INTERFACE() { }                                    \
    I##INTERFACE::~I##INTERFACE() { }                                   \
我們最困惑的 asInterface() 函式,終於在這邊獲得解答了。先看看第12行到第14行在做什麼吧,queryLocalInterface 宣告在 IBinder 類別裡,他的註解說:
/**
* Check if this IBinder implements the interface named by
* descriptor. If it does, the base pointer to it is returned,
* which you can safely static_cast<> to the concrete C++ interface.
*/
看來就是說,如果這個「業務 + 通訊 綜合體」有實作 descriptor 對應的業務介面,就把該業務介面回傳。但是 BpInterface 與 BnInterface 這兩者貌似只有 BnInterface 有實作這個函式,因此那三行現在對我們來說是沒用的,會回傳 NULL。再看下去,發現原來 asInterface 在這邊的作用單純只是 new 一個 BpInterface!那新的 BpInterface 怎麼通訊?想起前面說要稍微留意一下 BpInterface 的建構子嗎?看看這邊,obj 這個 IBinder 物件就是通訊用的,也就是會變成 BpInterface 中的 mRemote 值,可以透過 remote()  得到。這下您應該可以理解這小段的標題為什麼有一個 BpXXXX 了吧:一定要把 「業務 + 通訊 綜合體」命名成 BpXXXX 才行。

Summary

    Binder 美的地方就在於他把 IPC 向上提升一個境界,將物件導向的概念融合於自身,Firefox OS(B2G) 也有使用這個觀念,他們使用 local socket,但是用一些 IDL(介面描述語言) 加以包裝,讓開發者與使用者不用再直接處理通訊的事情。
    Second Part 將會分析比較 AIDL 與 此篇的原生 Binder 的實作,希望能夠更融會貫通,不管是 Native 對 Native 的通訊,還是如本 App 一樣 Native 對 Java 的通訊,都可以得心應手。

Book

《 深入理解 Android,卷 I 》(簡),鄧凡平,機械工業出版社,2011。
Note: 作者是 Android Media 領域的專家,所以有關 MediaServer 等等的部分書裡都介紹的不錯,唯一可惜的是 HAL 層提到的比較少,然後作者有時候用的比喻台灣人可能比較難以理解(笑。

   

2015年1月29日 星期四

How To Use Android Internal APIs

    When I was working on this demo(exercise, actually), I want to use the ServiceManager java class(which is hidden in Android SDK) since the demo is aim to build a simple native service which has a role like the system_server ( accessing some crucial system parts like hardware )except that it isn't live in the system_server process. So neither the bindService routine, which is designed for "App Service", nor the getService method, which is created for the services live in system_server, will work.

    Here are the steps:
  1. We need to build the AOSP, at least some specific framework parts, which located mostly in framework/base. You can go there and invoked the mma or mm command to build the module instead of the whole Android system.
       
  2. Assume your output dir is out, then go to out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/, copy the classes.jar to ${YOUR_SDK_DIR}/platforms/android-${YOUR_API_LEVEL}/ .
       
  3. In the dir ${YOUR_SDK_DIR}/platforms/android-${YOUR_API_LEVEL}, unpack both the classes.jar we've just copy and the android.jar since a jar file is simply an archive file.
       
  4. This step depends on your need. Since I only wants the ServiceManager class, I only have to copy content in the android folder in the output of the classes.jar, which holds the android.* classes we normally seen in the java import statements, into the corresponding android folder in the output of the android.jar. By doing this, you can still keep some libraries like dalvik.* in the origin SDK but not exist in the AOSP framework part.
       
  5. Change dir to the modified android.jar output dir. Repack them using the command:
    jar -cf ./your_new_android.jar *
    Remember to cascade your new android.jar file path RIGHT AFTER the -f option.
       
  6. Then you will got the new android.jar which can used in the App development. I also kept the original android.jar and wrote a simple script to switch between the origin one and the "Hacked" one:
    #!/bin/bash
    
    USAGE="Usage: ${0} <aosp|sdk>"
    
    case "$1" in 
    "aosp") ln -sf android_aosp.jar android.jar
     ;;
    
    "sdk") ln -sf android_sdk_origin.jar android.jar
     ;;
    *) echo $USAGE
     ;;
    
    esac
    
    exit 0
    
    In the old Eclipse era, we can add the new android.jar to the build path and raise the priority among the dependency libraries. However, since Android Studio is highly integrated with Android SDK, we can't modify the actual SDK files in the IDE.

2014年8月31日 星期日

Embedded Linux on Cubieboard2 (II): Kernel

本篇Outline


  1. Kernel 開機所需檔案
  2. Building Kernel

Kernel 開機所需檔案

    前一篇最後講完了 U-boot 的事,但如果要讓 cb2 開機,還需要一些設定檔。第一個要講的是 script.bin。
 
    script.bin 基本上是 cb2 自己的硬體設定檔,供設定的參數非常的多種,它也是個 binary 檔,原來的文本不用自己寫,官方已經有寫好了,可從 linux sunxi 的 github 抓取。
git clone https://github.com/linux-sunxi/sunxi-boards.git
script.bin 原來的文本副檔名是 .fex,而 cb2 的 fex 檔位於 sunxi-boards/sys_config/a20/cubieboard2.fex。所有可設定的硬體參數都在這個官方文件裡,在這邊我要提起的一個設定是 MAC address 問題。cb2 本身有 EMAC 的功能,簡單來說就是動態獲取 MAC address,當然你也可以把它關掉使用靜態 address,但必須要注意的是 fex 檔裡絕對不能鬧雙包,也就是同時使用 EMAC 及靜態 address,鬧雙包的話他不會噴錯,但核心網路界面就會莫名其妙的不見了。如果你要使用靜態 MAC (預設是 EMAC ),就在 fex 檔裡加入下列文字。
[dynamic]
MAC="你的address"
接著找到檔案裡的 emac_para 區段(也就是找到"[emac_para]"),把第一行 "emac_used=1" 改成 "emac_used=0"。
接下來要把 fex 檔轉換成 binary 檔,需要 linux sunxi 的工具。
git clone https://github.com/linux-sunxi/sunxi-tools.git
切換到 sunxi-tools 目錄之後,建置 fex2bin。
make fex2bin
轉換 fex 檔。
fex2bin cubieboard2.fex ./script.bin

    接著要講boot.scr。boot.scr 是 U-boot 開機時會讀取的 binary 設定檔,它原來的文本( boot.cmd )是一個腳本,可以在裡面寫你要執行的 U-boot 指令。boot.scr 並非必要,沒有它 U-boot 還是有自己的預設值,雖然如此,我還是親手來寫一遍比較有感覺吧。我在 boot.cmd 裡第一條要寫的是設定 kernel argument。cb2 完整的 kernel arguments 在這個網站,但在這邊,我只要設定最重要的幾個。
setenv bootargs console=ttyS0,115200 root=/dev/mmcblk0p2 rootwait panic=10
" setenv bootargs" 是 U-boot 的指令,後面接的那些才是真正的 kernel arguments,上例是用於把 boot 分割區獨立畫出來的情況,所以 root 參數,也就是根檔案系統的位置,是在 p2,如果是沒分開來的情況就要寫 root=/dev/mmcblk0p1。接下來要寫的就是載入核心以及 script.bin,這裡採用的方法是先把他們載到一個記憶體位址再用 bootm 指令開機。
fatload mmc 0 0x43000000 script.bin
fatload mmc 0 0x48000000 uImage
fatload 顧名思義就是從 FAT 型態檔案系統裡載入所需檔案,因此,如果你沒有把 boot 分割區特別畫出來,核心等檔案放在 boot 資料夾且檔案系統是用 ext2 的話,就要這樣寫。
ext2load mmc 0 0x43000000 boot/script.bin
ext2load mmc 0 0x48000000 uImage boot/uImage
接著就是開機的指令了。
bootm 0x48000000
上面講的就是要寫進 boot.cmd 的指令。最後一步是把文本轉成 binary 檔,這裡需要 mkimage 工具,沒有意外的話是包含在 U-boot 原碼裡,跟 U-boot 主體一起編譯好了,位於 U-boot 目錄底下的 tools 資料夾。
mkimage -C none -A arm -T script -d ./boot.cmd boot.scr
boot.scr 也是放在 boot 分割區(或是 boot 資料夾)裡,U-boot 開機時自己會去找。如果你之後開機時出現 machine id 不被 kernel 接受的錯誤,就先記下接受的 machine id,接著用下方指令在 boot.cmd 中設定。(假設 kernel 接受的 id 是 0xf0)
setenv machid 0xf0

Building Kernel

    編譯 kernel 這個步驟相較於前面,顯的非常簡單。首先把官方改過得 kernel 抓下來。
git clone https://github.com/cubieboard2/linux-sunxi.git -b sunxi-3.4-cb2
要注意的是不要用 cb2 的 3.3 kernel,不僅問題很多(我連開機都開不起來),貌似也沒有繼續維護了。

接著設定環境。
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
套用 cb2 預設 config。
make cubieboard2_defconfig
祈禱不會出錯吧
make all modules
建置 uImage。要注意建置 uImage 需要 mkimage 工具喔
make uImage
基本的 kernel 建置就這麼簡單,下一篇會開始講 user space 的事。

2014年8月5日 星期二

Embedded Linux on Cubieboard2 (I): Toolchain 與 Bootloader

系列前言

    Cubieborad2 是深圳 Cubie Tech 公司 Cubieborad 系列ARM架構開發版的第二代。搭載了Allwinner A20(ARM Cortex-A7)@1GHz Dual Core處理器,1GB DDR3 記憶體,其他外部配備有 3.4GB NAND Flash,最高支援到 64GB 的 micro SD 插槽,HDMI,10/100 Ethernet,2 x USB 2.0 HOST,1 x mini USB OTG,SATA3 。
    Cubieboard 最吸引我的是它完全開源的軟硬體與廣大社群,除了頁面整理亂到不行+不定期死機的Wiki以外
    本系列文章是紀錄我個人在移植一個基本且盡量輕小的 Linux 系統到 Cubieboard2 (簡稱cb2)時的一點紀錄。
    作者本身才剛踏入嵌入式系統這個領域,文章裡如有哪邊需要修正請不用客氣的在留言中提出。

本篇Outline

  1. Cross compile toolchain
  2. 製作 Bootloader

Cross Compile Toolchain

    我 toolchain 用的是大名鼎鼎的 linaro toolchain。基本上把 binary 載下來就好了,但是由於不明原因,編譯 cb2 的 kernel 時請用 gcc 4.7 的,至於其他像是 bootloader 就可以用 gcc 4.8。以本篇為例,我 cross compiler 的 gcc 名字就是 arm-linux-gnueabihf-gcc,值得一提的是 gnueaibhf 的 "hf" 代表的是 hard float,簡單來說就是用硬體運算浮點數,以前比較舊的處理器運算浮點數的時候是用整數的指令來做運算,後來才有專門是作浮點數運算的硬體指令出現,速度自然是比較快的。

製作 Bootloader

    Cubieboard2 使用的是 U-boot 當作他的 bootloader,我沒那麼厲害可以從零開始移植 U-boot 到這塊板子,所以我就先下載官方移植好的 U-boot 自己做編譯和安裝。基本上像 bootloader 這麼底層的東西,文件和原始碼都在 Allwinner 的 wiki 和 github 裡。
首先,下載 U-boot 原始碼。
git clone https://github.com/linux-sunxi/u-boot-sunxi.git
sunxi 是 Allwinner 底下 ARM SoC 的代稱。U-boot 設定 cross compiler 的方式非常方便,跟眾多大型專案一樣,把環境變數 CROSS_COMPILE 設定成 toolchain 名字的 prefix 就可以了。
export CROSS_COMPILE=arm-linux-gnueabihf-
注意最後面有一個"-",因為在 Makefile 裡它是這樣展開(示意):
CC = $(CROSS_COMPILE)gcc
 接下來設定 U-boot:
make Cubieboard2_config
至於全部裝置的列表是在 boards.cfg 裡,所以如果是 cb1 的 U-boot 的話就是 make Cubieboard_config。 config 完之後就大力的輸入 make 吧。

接著,你會在 U-boot 的目錄下看到一個 u-boot-sunxi-with-spl.bin 的檔案,那就是我們要燒進去的 bootloader。事實上,上面的檔案是由兩個東西組成的:U-boot 和 SPL,SPL 就是 Second Program Loader,簡單來說就是負責載入第三階段的 U-boot,描述了許多載入時所需要的硬體資訊,如果要分開燒的話 SPL 在底下的 spl 資料夾的 u-boot-spl.bin。

    下圖是 cb2 SD 卡開機區段的 layout。


SD 卡的分區我是把 boot 與主系統分開來,這樣的優點是主系統可以使用 ext4 檔案系統,因為這個官方提供的 U-boot 在讀取kernel等開機所需檔案時,目前只能支援 fat32, ext2 等比較老的檔案系統(mainline 的 U-boot 已經支援 ext4 囉),所以像是另外一種沒有把 boot 分開來的切割方式,就只能使用有支援的檔案系統。切割工具的部份,我個人試過很多像是 fdisk, sfdisk 等,最後還是覺得:用 GParted 就可以了,又快又方便。

boot 分割區我是把它格式化成 fat32(下次再來試試 ext2 看有什麼差別)外加 LBA,在 fdisk 裡面 fat32(LBA) 的型態代碼是 0x0C,但奇怪的是我用最新版的 fdisk 怎麼這種型態不見了只剩下普通 fat32,至於在 GParted 裡,LBA 是一個旗標,跟 boot 旗標一樣,要按右鍵管理旗標的那個地方。要注意的是,依照上表,磁碟前 1MB 是要保留的,雖然現在的工具都會保留 1MB,在 fdisk 裡,通常就是從第 2048 sector 開始割(通常 1 sector 是 512 bytes,0.5 KB),最後記得 boot 分割區要加 boot 旗標。我的磁碟分割就如下圖。


(畫紅底線的就是磁碟旗標)

在燒錄前,我們先要依 layout 把前 1MB先清乾淨,依本人慘痛的經驗,如果把 partition table 也清掉的話將會是一場災難,雖然 partition table 不一定如 layout 說的有 8KB 那麼大,但我們還是乖乖的保留前 8KB 吧。
dd if=/dev/zero of=/dev/sdX bs=1024 count=1016 seek=8
請把上面的 sdX 改成你自己 SD 卡的裝置檔。接著,把 bootloader 燒進去。
dd if=/path/to/u-boot-sunxi-with-spl.bin of=/dev/sdx bs=1024 seek=8
Bootloader 的準備到這邊基本上就告一個段落了。