***************************************************************************************************************************
作者:EasyWave 時間:2012.08.01
類別:Android系統源碼分析 聲明:轉載,請保留連結
注意:如有錯誤,歡迎指正。這些是我學習的日誌文章......
***************************************************************************************************************************
在我的博文基於goldfish和android2.3.5學習之:開天闢地Android啟動機制[二]中,部分的介紹了uevent事件機制,這篇博文將更深入的詳細分析uevent的機制以及如何android是如何透過核心傳遞過來的資料通過uevent機制檢測裝置的hotplug事件。同時也分析到了andorid_ids,還是先把get_android_id()函數拿出來分析一下吧,代碼如下:
static int get_android_id(const char *id){ unsigned int i; for (i = 0; i < ARRAY_SIZE(android_ids); i++) //尋找android_ids表格 if (!strcmp(id, android_ids[i].name)) //比較android_ids中的name return android_ids[i].aid; //返回andorid_ids表格中的aid return 0;}
而andorid_ids是在android_filesystem_config.h檔案中,具體的位置在andorid2.3.5/system/core/include/private中。具體的代碼如下:
struct android_id_info { const char *name; unsigned aid;};static const struct android_id_info android_ids[] = { { "root", AID_ROOT, }, { "system", AID_SYSTEM, }, { "radio", AID_RADIO, }, { "bluetooth", AID_BLUETOOTH, }, { "graphics", AID_GRAPHICS, }, { "input", AID_INPUT, }, { "audio", AID_AUDIO, }, { "camera", AID_CAMERA, }, { "log", AID_LOG, }, { "compass", AID_COMPASS, }, { "mount", AID_MOUNT, }, { "wifi", AID_WIFI, }, { "dhcp", AID_DHCP, }, { "adb", AID_ADB, }, { "install", AID_INSTALL, }, { "media", AID_MEDIA, }, { "nfc", AID_NFC, }, { "shell", AID_SHELL, }, { "cache", AID_CACHE, }, { "diag", AID_DIAG, }, { "net_bt_admin", AID_NET_BT_ADMIN, }, { "net_bt", AID_NET_BT, }, { "sdcard_rw", AID_SDCARD_RW, }, { "vpn", AID_VPN, }, { "keystore", AID_KEYSTORE, }, { "usb", AID_USB, }, { "gps", AID_GPS, }, { "inet", AID_INET, }, { "net_raw", AID_NET_RAW, }, { "net_admin", AID_NET_ADMIN, }, { "misc", AID_MISC, }, { "nobody", AID_NOBODY, },};#define android_id_count \ (sizeof(android_ids) / sizeof(android_ids[0]))
宏定義如下代碼:
/* This is the master Users and Groups config for the platform.** DO NOT EVER RENUMBER.*/#define AID_ROOT 0 /* traditional unix root user */#define AID_SYSTEM 1000 /* system server */#define AID_RADIO 1001 /* telephony subsystem, RIL */#define AID_BLUETOOTH 1002 /* bluetooth subsystem */#define AID_GRAPHICS 1003 /* graphics devices */#define AID_INPUT 1004 /* input devices */#define AID_AUDIO 1005 /* audio devices */#define AID_CAMERA 1006 /* camera devices */#define AID_LOG 1007 /* log devices */#define AID_COMPASS 1008 /* compass device */#define AID_MOUNT 1009 /* mountd socket */#define AID_WIFI 1010 /* wifi subsystem */#define AID_ADB 1011 /* android debug bridge (adbd) */#define AID_INSTALL 1012 /* group for installing packages */#define AID_MEDIA 1013 /* mediaserver process */#define AID_DHCP 1014 /* dhcp client */#define AID_SDCARD_RW 1015 /* external storage write access */#define AID_VPN 1016 /* vpn system */#define AID_KEYSTORE 1017 /* keystore subsystem */#define AID_USB 1018 /* USB devices */#define AID_GPS 1021 /* GPS daemon */#define AID_UNUSED1 1022 /* deprecated, DO NOT USE */#define AID_RFU1 1023 /* RFU */#define AID_RFU2 1024 /* RFU */#define AID_NFC 1025 /* nfc subsystem */#define AID_SHELL 2000 /* adb and debug shell user */#define AID_CACHE 2001 /* cache access */#define AID_DIAG 2002 /* access to diagnostic resources *//* The 3000 series are intended for use as supplemental group id's only. * They indicate special Android capabilities that the kernel is aware of. */#define AID_NET_BT_ADMIN 3001 /* bluetooth: create any socket */#define AID_NET_BT 3002 /* bluetooth: create sco, rfcomm or l2cap sockets */#define AID_INET 3003 /* can create AF_INET and AF_INET6 sockets */#define AID_NET_RAW 3004 /* can create raw INET sockets */#define AID_NET_ADMIN 3005 /* can configure interfaces and routing tables. */#define AID_MISC 9998 /* access to misc storage */#define AID_NOBODY 9999#define AID_APP 10000 /* first app user */
上面的都是部分摘錄,如果要想看明白這些代碼,你手邊必須要有android2.3.5的源碼,否則,這些你還是無法看明白的。還記得這個函數嗎?void set_device_permission(int nargs, char **args),看我的上一篇博文。這裡只摘錄跟上面有關部分的代碼,如下:
ret = get_android_id(args[2]); //得到android_id定義的name,具體見:android_ids表格。 if (ret < 0) { ERROR("invalid uid '%s'\n", args[2]); free(tmp); return; } uid = ret; //設定使用者id ret = get_android_id(args[3]); //得到android_id定義的id,具體見:android_ids表格, if (ret < 0) { ERROR("invalid gid '%s'\n", args[3]); free(tmp); return; } gid = ret; //設定group的id add_dev_perms(name, attr, perm, uid, gid, prefix); //添加到鏈表中。
以/dev/null 0666 root root來分析吧,如何得到uid
ret = get_android_id(args[2]);這個是抓取前面/dev/null 中的第三項,也就是紅色加粗部分,透過root匹配之後,讀取到了AID_ROOT。
以/dev/null 0666 root root來分析吧,如何得到gid
ret = get_android_id(args[3]);這個是抓取前面/dev/null 中的第四項,也就是藍色加粗部分,透過root匹配之後,讀取到了AID_ROOT。
現在再來詳細介紹ueventd_main()沒有分析完的代碼,源碼分析繼續中。當然先說明源碼的位置:在Andorid2.3.5源碼system/core/init.c中。詳細的代碼如下:
int ueventd_main(int argc, char **argv){ struct pollfd ufd; int nr; char tmp[32]; open_devnull_stdio(); log_init(); INFO("starting ueventd\n"); get_hardware_name(hardware, &revision); ueventd_parse_config_file("/ueventd.rc"); snprintf(tmp, sizeof(tmp), "/ueventd.%s.rc", hardware); ueventd_parse_config_file(tmp); ////解析ueventd.xxxxxx.rc檔案,比如:goldfish為ueventd.goldfish.rc檔案,具體的分析可以參考ueventd.rc device_init(); //初始化uevent socket,用於從Linux核心動態抓取裝置的狀態變化,同時處理冷開機事件。 ufd.events = POLLIN; //POLLIN的意思,當核心有資料可讀時,無阻塞的返回 ufd.fd = get_device_fd(); //得到相應的從核心傳過來的有資料具體檔案描述符fd. while(1) { //迴圈操作 ufd.revents = 0; nr = poll(&ufd, 1, -1); if (nr <= 0) continue; if (ufd.revents == POLLIN) handle_device_fd(); //處理具體的從核心傳遞過來的裝置變化事件,像null、usb、ttyS0等等,很多吧。這是我的理解,如有不對之處還望見諒 } //至於核心的ueventd事件,就需要去理解linux裝置驅動模型了,這個會在以後專門在linux核心欄目中詳細的介紹這個,因為這個驅動模型很複雜。}
應用程式如果需要檢測裝置的熱插拔事件,一般會用到這個特殊的socket,在linux中就是這個NETLINK,我們可以透過這個socket中的NETLINK_KOBJ_UEVEN來檢測裝置的熱插拔的動作。其實android的檔案系統採用和busybox以及udev類似的方式。在busybox中採用的mdev來自動的抓取linux所有的裝置驅動,一般是從sys目錄中去抓取。自動根據驅動的名稱來分配相應裝置的主裝置號和次裝置號,以及是字元裝置,還是塊裝置和網路裝置。。如果我們能夠用這個方式來理解andorid的檔案系統,這樣對於分析android的檔案系統有很大的協助。
說了太多的廢話了,還是先來具體看看device_init()函數吧,函數的具體位置在system/core/init/devices.c中,代碼如下:
void device_init(void){ suseconds_t t0, t1; struct stat info; int fd; device_fd = open_uevent_socket(); //建立socket,並且將進程ID和socket建立起串連 if(device_fd < 0) return; fcntl(device_fd, F_SETFD, FD_CLOEXEC);//這兩句詩設定裝置檔案的屬性,同時強制加鎖 fcntl(device_fd, F_SETFL, O_NONBLOCK); //因為android系統冷啟動時,會在dev/下產生一個.coldboot_done檔案。所以需要檢查這個檔案。 if (stat(coldboot_done, &info) < 0) { //查詢coldboot_done的狀態,並且存入info中,如果不成功的話,則倒計時進入冷啟動。 t0 = get_usecs(); //得到當前的時間 coldboot("/sys/class"); coldboot("/sys/block"); coldboot("/sys/devices"); t1 = get_usecs(); fd = open(coldboot_done, O_WRONLY|O_CREAT, 0000); close(fd); log_event_print("coldboot %ld uS\n", ((long) (t1 - t0))); } else { log_event_print("skipping coldboot, already done\n"); //冷啟動完成 }}
具體的分析下open_uevent_socket()函數,這個需要重點關注下,這個可是即時的監視著核心中裝置驅動的變化。因此詳細的分析一下。
static int open_uevent_socket(void){ struct sockaddr_nl addr; int sz = 64*1024; // XXX larger? udev uses 16MB! int on = 1; int s; memset(&addr, 0, sizeof(addr)); addr.nl_family = AF_NETLINK; //透過AF_NETLINK與核心進行通訊 addr.nl_pid = getpid(); //得到進程PID addr.nl_groups = 0xffffffff; //定義socket描述符,這是一個非同步通訊機制,SOCK_DGRAM的意思是無串連不可靠的串連 s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); //具體的事件為NETLINK_KOBJECT_UEVENT,對於這個需要對linux的裝置驅動模型需要有一定的瞭解 if(s < 0) //如果失敗,直接返回-1,也就是0xFFFFFF。 return -1; setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz)); //SO_RCVBUFFORCE是一個特殊的接收緩衝區,意思是不受接收緩衝區大小的上限限制 setsockopt(s, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)); //SO_PASSCRED的意思是:允許接收進程輔助資訊發送的信用證明。 if(bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) { //將進程和socket聯絡起來。 close(s); return -1; //失敗的話,返回-1. } return s; //成功返回}
分析到了這一步之後,基本上ueventd機制基本上也差不多了。還是接上device_init()函數下部分的代碼來回顧一下,部分代碼如下:
ufd.events = POLLIN; ufd.fd = get_device_fd(); ///得到相應的從核心傳過來的有資料具體檔案描述符fd. while(1) { ufd.revents = 0; //先將實際動作的事件清零 nr = poll(&ufd, 1, -1); //監視發生的事件 if (nr <= 0) //如果無事件繼續 continue; if (ufd.revents == POLLIN) //如果監視到發生的事件,則進入具體的事件處理中,比如字元裝置,塊裝置,還是網路裝置。 handle_device_fd(); //具體的處理事件在這個函數中。 }
對於ueventd的理解也許是比較困難一點,這個是因為需要對linux核心的裝置驅動模型要有一定的理解,並且需要知道核心的裝置驅動模式的運行機制有一定的理解。簡單的講一下,在linux核心中,只要是載入裝置驅動,就會產生uevent事件。如果可以將核心的調試資訊列印出來,會看到諸如此類的add@xxxxxxx 以及移除裝置驅動的時候會出現remove@xxxxxxx等等資訊。android核心是如何啟動ueventd的呢?在init.rc中有很詳細的指示。init.rc的部分代碼如下:
on early-init start ueventdon initsysclktz 0loglevel 3# setup the global environment export PATH /sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin export LD_LIBRARY_PATH /vendor/lib:/system/lib export ANDROID_BOOTLOGO 1 export ANDROID_ROOT /system export ANDROID_ASSETS /system/app export ANDROID_DATA /data export EXTERNAL_STORAGE /mnt/sdcard export ASEC_MOUNTPOINT /mnt/asec export LOOP_MOUNTPOINT /mnt/obb
看到了 on early-init 下的start uventd的嗎?對就是這個。因為init是個守護進程,也是核心啟動之後,第一個要調用啟動並執行程式。而init進程會首先去抓取init.rc檔案。至於核心啟動之後,為什麼說init是第一個啟動並執行程式呢。這個會在以後會專門開闢一篇博文來詳細講述這個問題。這裡就不詳細說了,好了關於ueventd部分。已經基本上分析完了,其實ueventd就是初始化核心中所有的裝置驅動,然後透過ueventd分配好所有的裝置。當然,這個是需要根據核心的具體已有的裝置驅動。
未完,Andriod源碼的分析還請繼續關注《基於android2.3.5學習之:開天闢地Android啟動機制[四]》 。。。