天天看點

第二章 Android核心和驅動程式(轉)

第二章 Android核心和驅動程式(轉)

2010年07月30日

  這一章我們來介紹Android系統的核心部分-Android核心。我們說Android系統是基于Linux系統的,最根本的原因就在于Android采用了Linux核心。 Android核心是基于Linux 2.6核心的(目前最新開發版本是2.6.31),它是一個增強核心版本,除了修改部分Bug外,它提供了用于支援Android平台的裝置驅動,主要包括: 基于OpenBinder架構的一個驅動,用于提供Android平台的程序間通訊(IPC,inter-process communication)。 源代碼位于 drivers/staging/android/binder.c 一個基于标準Linux電源管理系統的輕量級的Android電源管理驅動,針對嵌入式裝置做了很多優化。 源代碼位于 kernel/power/earlysuspend.c

  kernel/power/consoleearlysuspend.c

  kernel/power/fbearlysuspend.c

  kernel/power/wakelock.c

  kernel/power/userwakelock.c 相對于Linux标準OOM(Out Of Memory)機制更加靈活,它可以根據需要殺死程序來釋放需要的記憶體。 源代碼位于 drivers/staging/android/lowmemorykiller.c 為程序間提供大塊共享記憶體,同時為核心提供回收和管理這個記憶體的機制。 源代碼位于 mm/ashmem.c PMEM用于向使用者空間提供連續的實體記憶體區域,DSP和某些裝置隻能工作在連續的實體記憶體上, 源代碼位于 drivers/misc/pmem.c 一個輕量級的日志裝置,用于抓取Android系統的各種日志 源代碼位于drivers/staging/android/logger.c 提供了一個定時器用于把裝置從睡眠狀态喚醒,同時它也提供了一個即使在裝置睡眠時也會運作的時鐘基準, 源代碼位于 drivers/rtc/alarm.c 一個基于标準Linux USB gadget驅動架構的裝置驅動,Android的USB驅動是基于gaeget架構的, 源代碼位于drivers/usb/gadget/ 為了提供調試功能,Android允許将調試日志資訊寫入一個被稱為RAM Console的裝置裡,它是一個基于RAM的Buffer。 源代碼位于 drivers/staging/android/ram_console.c。 提供了對裝置進行定時控制功能,目前支援vibrator和LED裝置。 源代碼位于drivers/staging/android/timed_output.c(timed_gpio. c)。 Android采用Yaffs2作為MTD nand flash檔案系統 源代碼位于fs/yaffs2/目錄下。 Yaffs2是一個快速穩定的應用于NAND和NOR Flash的跨平台的嵌入式裝置檔案系統,同其他Flash檔案系統相比,Yaffs2使用更小的記憶體來儲存他的運作狀态,是以它占用記憶體小;Yaffs2的垃圾回收非常簡單而且快速,是以能達到更好的性能;Yaffs2在大容量的NAND Flash上性能表現尤為明顯,非常适合大容量的Flash存儲。 下面的清單是Android平台核心源檔案: drivers/misc/kernel_debugger.c drivers/misc/pmem.c drivers/misc/qemutrace/qemu_trace_sysfs.c drivers/misc/qemutrace/qemu_trace.c drivers/misc/qemutrace/qemu_trace.h drivers/misc/uid_stat.c drivers/staging/android/lowmemorykiller.c drivers/staging/android/logger.c drivers/staging/android/timed_output.h drivers/staging/android/ram_console.c drivers/staging/android/timed_gpio.c drivers/staging/android/logger.h drivers/staging/android/binder.h drivers/staging/android/binder.c drivers/staging/android/timed_output.c drivers/staging/android/timed_gpio.h drivers/rtc/alarm.c drivers/rtc/rtc-goldfish.c drivers/net/pppolac.c drivers/net/ppp_mppe.c drivers/net/pppopns.c drivers/video/goldfishfb.c drivers/switch/switch_class.c drivers/switch/switch_gpio.c drivers/char/dcc_tty.c drivers/char/goldfish_tty.c drivers/watchdog/i6300esb.c drivers/input/misc/gpio_event.c drivers/input/misc/gpio_input.c drivers/input/misc/gpio_output.c drivers/input/misc/keychord.c drivers/input/misc/gpio_axis.c drivers/input/misc/gpio_matrix.c drivers/input/keyreset.c drivers/input/keyboard/goldfish_events.c drivers/input/touchscreen/synaptics_i2c_rmi.c drivers/usb/gadget/android.c drivers/usb/gadget/f_adb.h drivers/usb/gadget/f_mass_storage.h drivers/usb/gadget/f_adb.c drivers/usb/gadget/f_mass_storage.c drivers/mmc/host/goldfish.c drivers/power/goldfish_battery.c drivers/leds/ledtrig-sleep.c drivers/mtd/devices/goldfish_nand_reg.h drivers/mtd/devices/goldfish_nand.c kernel/power/earlysuspend.c kernel/power/consoleearlysuspend.c kernel/power/fbearlysuspend.c kernel/power/wakelock.c kernel/power/userwakelock.c kernel/cpuset.c kernel/cgroup_debug.c kernel/cgroup.c mm/ashmem.c include/linux/ashmem.h include/linux/switch.h include/linux/keychord.h include/linux/earlysuspend.h include/linux/android_aid.h include/linux/uid_stat.h include/linux/if_pppolac.h include/linux/usb/android.h include/linux/wifi_tiwlan.h include/linux/android_alarm.h include/linux/keyreset.h include/linux/synaptics_i2c_rmi.h include/linux/android_pmem.h include/linux/kernel_debugger.h include/linux/gpio_event.h include/linux/wakelock.h include/linux/if_pppopns.h net/ipv4/sysfs_net_ipv4.c net/ipv4/af_inet.c net/ipv6/af_inet6.c net/bluetooth/af_bluetooth.c security/commoncap.c fs/proc/base.c Android是基于Linux的,對于一個新的裝置,我們首先要編譯一個支援Android的Kernel,那麼如何使你的Kernel Android化呢?除了要移植前面提到的驅動之外,就是如何配置你的Kernel來支援Android平台,這可以參考Goldfish的核心配置檔案- arch/arm/configs/goldfish_defconfig。

  一般來說,我們會基于一個平台标準核心配置選項來配置Android核心,你可以根據具體的硬體平台來選擇Android核心的配置選項,可以參考下面的Android核心配置清單:

  表2-1:Android核心配置選項(Required Enabled) ANDROID_PARANOID_NETWORK

  ASHMEM

  CONFIG_FB_MODE_HELPERS

  CONFIG_FONT_8x16

  CONFIG_FONT_8x8

  CONFIG_YAFFS_SHORT_NAMES_IN_RAM

  DAB

  EARLYSUSPEND

  FB

  FB_CFB_COPYAREA

  FB_CFB_FILLRECT

  FB_CFB_IMAGEBLIT

  FB_DEFERRED_IO

  FB_TILEBLITTING

  HIGH_RES_TIMERS

  INOTIFY

  INOTIFY_USER

  INPUT_EVDEV

  INPUT_GPIO

  INPUT_MISC

  LEDS_CLASS

  LEDS_GPIO

  LOCK_KERNEL

  LkOGGER

  LOW_MEMORY_KILLER

  MISC_DEVICES

  NEW_LEDS

  NO_HZ

  POWER_SUPPLY

  PREEMPT

  RAMFS

  RTC_CLASS

  RTC_LIB

  SWITCH

  SWITCH_GPIO

  TMPFS

  UID_STAT

  UID16

  USB_FUNCTION

  USB_FUNCTION_ADB

  USER_WAKELOCK

  VIDEO_OUTPUT_CONTROL

  WAKELOCK

  YAFFS_AUTO_YAFFS2

  YAFFS_FS

  YAFFS_YAFFS1

  YAFFS_YAFFS2

  表2-2:Android核心配置選項(Required Disabled) 表2-3:Android核心配置選項(Recommended Enabled) 配置好後,就可以用Toolchain來編譯核心了。編譯核心比較簡單,以Emulator的kernel為例: - git clone git://android.kernel.org/kernel/common.git kernel-emulator

  - cd kernel-emulator

  - git checkout original/android-goldfish-2.6.29 -b android-goldfish-2.6.29

  - export ARCH=arm

  - export CROSS_COMPILE=arm-eabi-

  - export PATH=:$PATH

  - make goldfish_defconfig

  - make 編譯完成後就得到了Android Emulator (Goldfish)的核心 arch/arm/boot/zImage 通常來說,Binder是Android系統中的内部程序通訊(IPC)之一。在Android系統中共有三種IPC機制,分别是:

  -标準Linux Kernel IPC接口

  -标準D-BUS接口

  -Binder接口

  盡管Google宣稱Binder具有更加簡潔、快速,消耗更小記憶體資源的優點,但并沒有證據表明D-BUS就很差。實際上D-BUS可能會更合适些,或許隻是當時Google并沒有注意到它吧,或者Google不想使用GPL協定的D-BUS庫。我們不去探究具體的原因了,你隻要清楚Android系統中支援了多個IPC接口,而且大部分程式使用的是我們并不熟悉的Binder接口。

  Binder是OpenBinder的Google精簡實作,它包括一個Binder驅動程式、一個Binder伺服器及Binder用戶端(?)。這裡我們隻要介紹核心中的Binder驅動的實作。

  對于Android Binder,它也可以稱為是Android系統的一種RPC(遠端過程調用)機制,因為Binder實作的功能就是在本地"執行"其他服務程序的功能的函數調用。不管是IPC也好,還是RPC也好,我們所要知道的就是Android Binder的功能是如何實作的。 Openbinder介紹 2.1.1 Android Binder協定 Android 的Binder機制是基于OpenBinder (http://www.angryredplanet.com/~hackbod/openbinder/ docs/html/BinderIPCMechanism.html)來實作的,是一個OpenBinder的Linux實作。

  Android Binder的協定定義在binder.h頭檔案中,Android的通訊就是基于這樣的一個協定的。 (描述binder type的功能) Android定義了五個(三大類)Binder類型,如下: enum {

  BINDER_TYPE_BINDER = B_PACK_CHARS('s', 'b', '*', B_TYPE_LARGE),

  BINDER_TYPE_WEAK_BINDER = B_PACK_CHARS('w', 'b', '*', B_TYPE_LARGE),

  BINDER_TYPE_HANDLE = B_PACK_CHARS('s', 'h', '*', B_TYPE_LARGE),

  BINDER_TYPE_WEAK_HANDLE= B_PACK_CHARS('w', 'h', '*', B_TYPE_LARGE),

  BINDER_TYPE_FD = B_PACK_CHARS('f', 'd', '*', B_TYPE_LARGE),

  }; 程序間傳輸的資料被稱為Binder對象(Binder Object),它是一個flat_binder_object,定義如下: struct flat_binder_object {

  

  unsigned long type;

  unsigned long flags;

  

  union {

  void *binder;

  signed long handle;

  };

  

  void *cookie;

  }; 其中,類型字段描述了Binder 對象的類型,flags描述了傳輸方式,比如同步、異步等。 enum transaction_flags {

  TF_ONE_WAY = 0x01,

  TF_ROOT_OBJECT = 0x04,

  TF_STATUS_CODE = 0x08,

  TF_ACCEPT_FDS = 0x10,

  }; 傳輸的資料是一個複用資料聯合體,對于BINDER類型,資料就是一個binder本地對象,如果是HANDLE類型,這資料就是一個遠端的handle對象。該如何了解本地binder對象和遠端handle對象呢?其實它們都指向同一個對象,不過是從不同的角度來說。舉例來說,假如A有個對象X,對于A來說,X就是一個本地的binder對象;如果B想通路A的X對象,這對于B來說,X就是一個handle。是以,從根本上來說handle和binder都指向X。本地對象還可以帶有額外的資料,儲存在cookie中。

  Binder對象的傳遞是通過binder_transaction_data來實作的,即Binder對象實際是封裝在binder_transaction_data結構體中。 這個資料結構才是真正要傳輸的資料。它的定義如下: struct binder_transaction_data {

  

  union {

  size_t handle;

  void *ptr;

  } target;

  void *cookie;

  unsigned int code;

  unsigned int flags;

  pid_t sender_pid;

  uid_t sender_euid;

  size_t data_size;

  size_t offsets_size;

  union {

  struct {

  

  const void *buffer;

  

  const void *offsets;

  } ptr;

  uint8_t buf[8];

  } data;

  }; 結構體中的資料成員target是一個複合聯合體對象,請參考前面的關于binder本地對象及handle遠端對象的描述。code是一個指令,描述了請求Binder對象執行的操作。 Binder中的一個重要概念就是對象的映射和索引。就是要把對象從一個程序映射到另一個程序中,以實作線程遷移的概念。前面描述過Binder的一個重要概念是程序/線程遷移,即當一個程序需要同另一個程序通信時,它可以"遷移"遠端的程序/線程到本地來執行。對于調用程序來說,看起來就像是在本地執行一樣。這是Binder與其他IPC機制的不同點或者說是優點。當然遷移的工作是由Binder驅動來完成的,而實作的基礎和核心就是對象的映射和索引。

  Binder中有兩種索引,一是本地程序位址空間的一個位址,另一個是一個抽象的32位句柄(HANDLE),它們之間是互斥的:所有的程序本地對象的索引都是本地程序的一個位址(address, ptr, binder),所有的遠端程序的對象的索引都是一個句柄(handle)。對于發送者程序來說,索引就是一個遠端對象的一個句柄,當Binder對象資料被發送到遠端接收程序時,遠端接受程序則會認為索引是一個本地對象位址,是以從第三方的角度來說,盡管名稱不同,對于一次Binder調用,兩種索引指的是同一個對象,Binder驅動則負責兩種索引的映射,這樣才能把資料發送給正确的程序。 Binder驅動的指令協定(BC_指令),定義了Binder驅動支援的指令格式及資料定義(協定)。不同的指令所帶有的資料是不同的。Binder的指令由binder_write_read資料結構描述,它是ioctl指令(BINDER_WRITE_READ)的參數。 struct binder_write_read {

  signed long write_size;

  signed long write_consumed;

  unsigned long write_buffer;

  signed long read_size;

  signed long read_consumed;

  unsigned long read_buffer;

  }; 對于寫操作,write_buffer包含了一系列請求線程執行的Binder指令;對于讀(傳回)操作,read_buffer包含了一系列線程執行後填充的傳回值。

  Binder指令(BC_)用于BINDER_WRITE_READ的write操作。

  Binder的BC的指令格式是:| CMD | Data... | Binder Commands

  CMD Data Format Notes

  BC_TRANSACTION

  BC_REPLY binder_transaction_data

  BC_ACQUIRE_RESULT

  BC_ATTEMPT_ACQUIRE Not implement

  BC_FREE_BUFFER data_ptr ptr to transaction data received on a read

  BC_INCREFS

  BC_ACQUIRE

  BC_RELEASE

  BC_DECREFS int target descriptor

  BC_INCREFS_DONE

  BC_ACQUIRE_DONE node_ptr | cookie

  BC_REGISTER_LOOPER

  BC_ENTER_LOOPER

  BC_EXIT_LOOPER No parameters

  BC_REQUEST_DEATH_NOTIFICATION target_ptr | cookie

  BC_DEAD_BINDER_DONE cookie

  Binder驅動的響應(傳回,BR_)協定,定義了Binder指令的資料傳回格式。同Binder指令協定一樣,Binder驅動傳回協定也是通過BINDER_WRITE_READ ioctl指令實作的,不同的是它是read操作。

  Binder BR的指令格式是:| CMD | Data... | Binder BR 指令

  CMDS Data Format Notes

  BR_ERROR int Error code

  BR_OK

  BR_NOOP

  BR_SPAWN_LOOPER No parameters

  BR_TRANSACTION

  BR_REPLY binder_transaction_data the received command

  BR_ACQUIRE_RESULT

  BR_ATTEMPT_ACQUIRE

  BR_FINISHED Not implement

  BR_DEAD_REPLY The target of the last transaction is no longer with us.

  bcTRANSACTION or bcATTEMPT_ACQUIRE

  BR_TRANSACTION_COMPLETE No parameters...

  Always refers to the last transaction requested (including replies).

  Note that this will be sent even for asynchronous transactions

  BR_INCREFS

  BR_ACQUIRE

  BR_RELEASE

  BR_DECREFS target_ptr | cookie

  BR_DEAD_BINDER

  BR_CLEAR_DEATH_NOTIFICATION_DONE cookie

  BR_FAILED_REPLY The the last transaction

  (either a bcTRANSACTION or a bcATTEMPT_ACQUIRE) failed

  (e.g. out of memory).

  (描述Android的程序模型) 2.1.2 驅動接口

  Android Binder裝置驅動接口函數是 device_initcall(binder_init); 我們知道一般來說裝置驅動的接口函數是module_init和module_exit,這麼做是為了同時相容支援靜态編譯的驅動子產品(buildin)和動态編譯的驅動子產品(module)。但是Android的Binder驅動顯然不想支援動态編譯的驅動,如果你需要将Binder驅動修改為動态的核心子產品,可以直接将device_initcall修改為module_init,但不要忘了增加module_exit的驅動解除安裝接口函數。 初始化函數首先使用proc_mkdir建立了一個Binder的proc檔案系統的根節點(binder_proc_dir_entry_root,/proc/binder),并為binder建立了binder proc節點(binder_proc_dir_entry_proc,/proc/binder/proc),注意不要混淆Linux Proc和Binder Proc。

  然後Binder驅動使用misc_register把自己注冊為一個Misc裝置(/dev/misc/binder)。

  最後,如果驅動成功的建立了/proc/binder根節點,就調用create_proc_read_entry建立隻讀proc檔案:/proc/binder/state,/proc/binder/stats,/proc/binder/transactions,/proc/binder/transaction_log,/proc/binder/failed_transaction_log。

  這個初始化函數有個小小的問題,它沒有判斷Misc裝置是否注冊成功了,如果注冊失敗了,那麼Binder就不能正常工作了,是以這裡應該有個錯誤處理流程。 2.1.3 Binder核心資料 在進一步介紹Binder驅動之前,我們有必要了解一下Binder的核心資料。 struct binder_proc {

  struct hlist_node proc_node;

  struct rb_root threads;

  struct rb_root nodes;

  struct rb_root refs_by_desc;

  struct rb_root refs_by_node;

  int pid;

  struct vm_area_struct *vma;

  struct task_struct *tsk;

  struct files_struct *files;

  struct hlist_node deferred_work_node;

  int deferred_work;

  void *buffer;

  ptrdiff_t user_buffer_offset;

  struct list_head buffers;

  struct rb_root free_buffers;

  struct rb_root allocated_buffers;

  size_t free_async_space;

  struct page **pages;

  size_t buffer_size;

  uint32_t buffer_free;

  struct list_head todo;

  wait_queue_head_t wait;

  struct binder_stats stats;

  struct list_head delivered_death;

  int max_threads;

  int requested_threads;

  int requested_threads_started;

  int ready_threads;

  long default_priority;

  }; binder_proc用于儲存調用binder的各個程序或線程資訊,比如線程id、程序id、binder狀态資訊等。

  (各個資料的意義?) struct binder_node {

  int debug_id;

  struct binder_work work;

  union {

  struct rb_node rb_node;

  struct hlist_node dead_node;

  };

  struct binder_proc *proc;

  struct hlist_head refs;

  int internal_strong_refs;

  int local_weak_refs;

  int local_strong_refs;

  void __user *ptr;

  void __user *cookie;

  unsigned has_strong_ref : 1;

  unsigned pending_strong_ref : 1;

  unsigned has_weak_ref : 1;

  unsigned pending_weak_ref : 1;

  unsigned has_async_transaction : 1;

  unsigned accept_fds : 1;

  int min_priority : 8;

  struct list_head async_todo;

  }; 2.1.4 使用者接口 驅動程式的一個主要同能就是向使用者空間的程式提供操作接口,這個接口是标準的,對于Android Binder驅動,包含的接口有:

  -Proc接口(/proc/binder)

  . /proc/binder/state

  . /proc/binder/stats

  . /proc/binder/transactions

  . /proc/binder/transaction_log

  . /proc/binder/failed_transaction_log

  . /proc/binder/proc/ - 裝置接口(/dev/binder)

  . binder_open

  . binder_release

  . binder_flush

  . binder_mmap

  . binder_poll

  . binder_ioctl 這些核心接口函數是在驅動程式的初始化函數(binder_init)中初始化的,感興趣的讀者可以閱讀相關函數的實作代碼,很簡單明了,一共才十幾行的代碼。 通常來說,驅動程式的open函數是使用者調用驅動接口來使用驅動功能的第一個函數,稱為入口函數。同其他驅動一樣,對于Android驅動,任何一個程序及其内的所有線程都可以打開一個binder裝置。首先來看看Binder驅動是如何打開裝置的。

  首先,binder驅動配置設定記憶體以儲存binder_proc資料結構。

  然後,binder填充binder_proc資料(初始化),增加目前線程/程序的引用計數并指派給tsk get_task_struct(current);

  proc->tsk = current; 初始化binder_proc的隊列及預設優先級 INIT_LIST_HEAD(&proc->todo);

  init_waitqueue_head(&proc->wait);

  proc->default_priority = task_nice(current); 增加BINDER_STAT_PROC的對象計數,并把建立的binder_proc對象添加到全局的binder_proc哈希清單中,這樣任何一個程序就都可以通路到其他程序的binder_proc對象了。 binder_stats.obj_created[BINDER_STAT_PROC]++;

  hlist_add_head(&proc->proc_node, &binder_procs); 把目前程序/線程的線程組的pid(pid指向線程id)指派給proc的pid字段,可以了解為一個程序id(thread_group指向線程組中的第一個線程的task_struct結構)。同時把binder_proc對象指針指派給filp的private_data對象儲存起來。 proc->pid = current->group_leader->pid;

  INIT_LIST_HEAD(&proc->delivered_death);

  filp->private_data = proc; 最後,在bindr proc目錄中建立隻讀檔案(/proc/binder/proc/$pid)用來輸出目前binder proc對象的狀态。

  這裡要注意的是,proc->pid字段,按字面了解它應該是儲存目前程序/線程的id,但實際上它儲存的是線程組的pid,也就是線程組中的第一個線程的pid(等于tgid,程序id)。

  這樣當一個程序或線程打開一個binder裝置時,驅動就會在核心中為其建立binder_proc結構來儲存打開此裝置的程序/線程資訊。 binder_release是一個驅動的出口函數,當程序退出(exit)時,程序需要顯示或隐式的調用release函數來關閉打開的檔案。Release函數一般來清理程序的核心資料,釋放申請的記憶體。

  Binder驅動的release函數相對比較簡單,它删除open是建立的binder proc檔案,然後排程一個workqueue來釋放這個程序/線程的binder_proc對象(BINDER_DEFERRED_RELEASE)。這裡要提的一點就是使用workqueue(deffered)可以提高系統的響應速度和性能,因為Android Binder的release及flush等操作是一個複雜費事的任務,而且也沒有必要在系統調用裡完成它,是以最好的方法是延遲執行這個費時的任務。其實在中斷處理函數中我們經常要把一些耗時的操作放到底半部中處理(bottom half),這是一樣的道理。

  當然真正的釋放工作是在binder_deferred_release函數裡完成的,後面在做詳盡的介紹。 workqueue是Linux系統中延遲執行任務的一種機制 flush操作在關閉一個裝置檔案描述符拷貝時被調用,Binder的flush函數十分簡單,它隻是簡單的排程一個workqueue執行BINDER_DEFERRED_FLUSH操作。 mmap(memory map)用于把裝置記憶體映射到使用者程序位址空間中,這樣就可以像操作使用者記憶體那樣操作裝置記憶體。(還有一種說法,mmap用于把使用者程序位址空間映射到裝置記憶體上,盡管說法不同,但是說的是同一個事情)

  Binder裝置對記憶體映射是有些限制的,比如binder裝置最大能映射4M的記憶體區域;binder不能映射具有寫權限的記憶體區域。

  不同于一般的裝置驅動,大多的裝置映射的裝置記憶體是裝置本身具有的,或者在驅動初始化時由vmalloc或kmalloc等核心記憶體函數配置設定的,Binder的裝置記憶體是在mmap操作時配置設定的,配置設定的方法是先在核心虛拟映射表上擷取一個可以使用的區域,然後配置設定實體頁,并把實體頁映射到擷取的虛拟空間上。由于裝置記憶體是在mmap操作中實作的,是以每個程序/線程隻能做映射操作一次,其後的操作都會傳回錯誤。

  具體來說,binder_mmap首先檢查mmap調用是否合法,即是否滿足binder記憶體映射的條件,主要檢查映射記憶體的大小、flags,是否是第一次調用。 if ((vma->vm_end - vma->vm_start) > SZ_4M)

  vma->vm_end = vma->vm_start + SZ_4M;

  if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {

  …

  }

  vma->vm_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE;

  if (proc->buffer) {

  …

  } 然後,binder驅動從系統申請可用的虛拟記憶體空間(注意不是實體記憶體空間),這是通過get_vm_area核心函數實作的:(get_vm_area是一個核心,功能是在核心中申請并保留一塊連續的核心虛拟記憶體空間區域) area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);

  proc->buffer = area->addr;

  proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer; 然後根據請求映射的記憶體空間大小,配置設定binder核心資料結構binder_proc的pages成員,它主要用來儲存指向申請的實體頁的指針。 proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);

  proc->buffer_size = vma->vm_end - vma->vm_start; 一切都準備就緒了,現在開始配置設定實體記憶體(page)了。這是通過binder驅動的幫助函數binder_update_page_range來實作的。盡管名字上稱為update page,但實際上它是在配置設定實體頁并映射到剛才保留的虛拟記憶體空間上。在這裡,Binder使用alloc_page配置設定頁面,使用map_vm_area為配置設定的記憶體做映射關系,使用vm_insert_page把配置設定的實體頁插入到使用者vma區域。 *page = alloc_page(GFP_KERNEL | __GFP_ZERO);

  tmp_area.addr = page_addr;

  tmp_area.size = PAGE_SIZE + PAGE_SIZE ;

  page_array_ptr = page;

  ret = map_vm_area(&tmp_area, PAGE_KERNEL, &page_array_ptr);

  user_page_addr = (uintptr_t)page_addr + proc->user_buffer_offset;

  ret = vm_insert_page(vma, user_page_addr, page[0]); 注:alloc_page, map_vm_area和vm_insert_page都是Linux核心中記憶體相關函數。 當然,這裡會使用到的核心資料結構binder_proc,用到的主要域是

  buffer 記錄binder_proc的核心虛拟位址的首位址

  buffer_size 記錄binder_proc的虛拟位址的大小

  user_buffer_offset 記錄binder_proc的使用者位址偏移,即使用者程序vma位址與binder申請的vma位址的偏差

  pages 記錄指向binder_proc實體頁(struct page)的指針(二維指針)

  free

  free_async_space

  files 記錄程序的struct file_struct 結構

  vma 記錄使用者程序的vma結構

  顯然,這些都同記憶體相關。 poll函數是非阻塞型IO(select,poll調用)的核心驅動實作,所有支援非阻塞IO操作的裝置驅動都需要實作poll函數。

  Binder的poll函數僅支援裝置是否可非阻塞的讀(POLLIN),這裡有兩種等待任務,一種是一種是proc_work,另一種是thread_work。同其他驅動的poll實作一樣,這裡也是通過調用poll_wait函數來實作的,這裡就不多做叙述了。 poll_wait(filp, &thread->wait, wait); 這裡需要明确提的一點就是這裡調用了下面的函數來取得目前程序/線程的thread對象: thread = binder_get_thread(proc); 特别要指出的,也是很重要的一點,就是這個函數實際上在為服務程序的線程池建立對應的thread對象。後面還會詳細講解這個函數。 這個函數是Binder的最核心部分,Binder的功能就是通過ioctl指令來實作的。Binder的ioctl指令共有7個,定義在ioctl.h頭檔案中: #define BINDER_WRITE_READ _IOWR('b', 1, struct binder_write_read)

  #define BINDER_SET_IDLE_TIMEOUT _IOW('b', 3, int64_t)

  #define BINDER_SET_MAX_THREADS _IOW('b', 5, size_t)

  #define BINDER_SET_IDLE_PRIORITY _IOW('b', 6, int)

  #define BINDER_SET_CONTEXT_MGR _IOW('b', 7, int)

  #define BINDER_THREAD_EXIT _IOW('b', 8, int)

  #define BINDER_VERSION _IOWR('b', 9, struct binder_version) 首先要說明的是BINDER_SET_IDLE_TIMEOUT 和 BINDER_SET_IDLE_PRIORITY 在目前的Binder驅動中沒有實作。 這裡最重要的就是BINDER_WRITE_READ指令,它是Binder驅動核心的核心,Binder IPC機制主要是通過這個指令來實作的。下面我們首先來介紹簡單的用于設定Binder驅動參數的幾個ioctl指令,最後着重講述BINDER_WRITE_READ指令。 1. BINDER_SET_MAX_THREADS 這個ioctl指令用于設定程序的Biner對象所支援的最大線程數。設定的值儲存在binder_proc結構的max_threads成員裡。

  2. BINDER_SET_CONTEXT_MGR

  在這裡會引入Binder的另一個核心資料binder_node。從功能上看,隻有一個程序/線程能成功設定binder_context_mgr_node對象,這個程序被稱為Context Manager(context_mgr)。當然,也隻有建立binder_context_mgr_node對象的Binder上下文管理程序/線程才有權限重新設定這個對象。程序的權限(cred->euid)儲存在binder_context_mgr_uid對象裡。 binder_context_mgr_uid = current->cred->euid; binder_proc成員node是binder_node的根節點,這是一棵紅黑樹(一種平衡二叉樹)。

  現在來看看binder_new_node函數,它首先根據規則找到第一個頁節點作為新插入的節點的父節點(規則就是binder_node的指針對象,後面還會遇到)。 找到節點了,那麼調用核心函數建立并插入節點吧。 注:rb_link_node和rb_insert_color都是核心紅黑樹函數,rb_link_node是一個内聯函數,它把新節點插入到紅黑樹中的指定父節點下。rb_insert_color節把已經插入到紅黑樹中的節點調整并融合到紅黑樹中(參考根據紅黑樹規則)。 插入完成後,做最後的初始化工作,這裡着重說明兩點,一是把binder_proc對象指針儲存在binder_node對象裡,二是初始化node對象的連結清單頭指針。 描述Context Manager的作用是什麼呢? 通過調用binder_free_thread終止并釋放binder_thread對象及其binder_transaction事務。

  (需要更新,較長的描述函數功能)

  biner_thread會在後面說明,功能是什麼? 讀取目前Binder驅動支援的協定版本号。

  5. BINDER_WRITE_READ

  前面提到,這個ioctl指令才是Binder最核心的部分,Android Binder的IPC機制就是通過這個接口來實作的。我們在這裡來介紹binder_thread對象,其實在前面已經見到過了,但是因為它與這個接口更加緊密,是以我們把它拿到這裡來介紹。

  每一個程序的binder_proc對象都有一個binder_thread對象(proc->threads.rb_node節點),程序/線程的id(pid)就儲存在binder_thread結構的pid成員裡。Binder_thread對象是在binder_get_thread函數中建立的,ioctl函數在入口處會調用它來取得或建立binder_thread對象 如果找到了binder_thread對象,就直接傳回該對象。如果沒有找到,就說明目前程序/線程的binder_thread對象還沒有建立,建立一個新的binder_thread節點并插入到紅黑樹中,傳回這個新建立的binder_thread對象。當然,這裡還要對binder_thread對象最一個初始化工作 thread = kzalloc(sizeof(*thread), GFP_KERNEL);

  binder_stats.obj_created[BINDER_STAT_THREAD]++;

  thread->proc = proc;

  thread->pid = current->pid;

  init_waitqueue_head(&thread->wait);

  INIT_LIST_HEAD(&thread->todo);

  rb_link_node(&thread->rb_node, parent, p);

  rb_insert_color(&thread->rb_node, &proc->threads);

  thread->looper |= BINDER_LOOPER_STATE_NEED_RETURN;

  thread->return_error = BR_OK;

  thread->return_error2 = BR_OK;

繼續閱讀