編輯:關於Android編程
int main(int argc, char **argv) { //將main函數分為上述4個部分,對應part1到part4,下面分別做具體說明。if (!strcmp(basename(argv[0]), "ueventd")) return ueventd_main(argc, argv); if (!strcmp(basename(argv[0]), "watchdogd")) return watchdogd_main(argc, argv); // umask(0); mkdir("/dev", 0755); mkdir("/proc", 0755); mkdir("/sys", 0755); mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"); mkdir("/dev/pts", 0755); mkdir("/dev/socket", 0755); mount("devpts", "/dev/pts", "devpts", 0, NULL); mount("proc", "/proc", "proc", 0, NULL); mount("sysfs", "/sys", "sysfs", 0, NULL); .... open_devnull_stdio(); klog_init(); property_init(); .... // INFO("reading config file\n"); init_parse_config_file("/init.rc"); ... action_for_each_trigger("early-init", action_add_queue_tail); .... queue_builtin_action(queue_property_triggers_action, "queue_property_triggers"); // for(;;) { ... execute_one_command(); restart_processes(); .... nr = poll(ufds, fd_count, timeout); if (nr <= 0) continue; for (i = 0; i < fd_count; i++) { if (ufds[i].revents & POLLIN) { if (ufds[i].fd == get_property_set_fd()) handle_property_set_fd(); else if (ufds[i].fd == get_keychord_fd()) handle_keychord(); else if (ufds[i].fd == get_signal_fd()) handle_signal(); } } } return 0; }
open_devnull_stdio(); klog_init(); property_init(); get_hardware_name(hardware, &revision); process_kernel_cmdline(); union selinux_callback cb; cb.func_log = log_callback; selinux_set_callback(SELINUX_CB_LOG, cb); cb.func_audit = audit_callback; selinux_set_callback(SELINUX_CB_AUDIT, cb); selinux_initialize(); /* These directories were necessarily created before initial policy load * and therefore need their security context restored to the proper value. * This must happen before /dev is populatedproperty_init(); by ueventd. */ restorecon("/dev"); restorecon("/dev/socket"); restorecon("/dev/__properties__"); restorecon_recursive("/sys"); is_charger = !strcmp(bootmode, "charger"); INFO("property init\n"); property_load_boot_defaults();
void open_devnull_stdio(void) { int fd; static const char *name = "/dev/__null__"; if (mknod(name, S_IFCHR | 0600, (1 << 8) | 3) == 0) { fd = open(name, O_RDWR); unlink(name); if (fd >= 0) { dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); if (fd > 2) { close(fd); } return; } } exit(1); }該函數中通過mknode函數創建/dev/__null__設備節點文件,隨後打開該文件得到文件描述符fd,然後利用dup2系統調用將文件描述符0、1、2綁定到fd上。這個/dev/__null__看起來很奇怪,Linux系統中的null不是/dev/null麼,這兩者有什麼關系麼?
1 char Memory devices 1 = /dev/mem Physical memory access 2 = /dev/kmem Kernel virtual memory access 3 = /dev/null Null device 4 = /dev/port I/O port access 5 = /dev/zero Null byte source 6 = /dev/core OBSOLETE - replaced by /proc/kcore 7 = /dev/full Returns ENOSPC on write 8 = /dev/random Nondeterministic random number gen. 9 = /dev/urandom Faster, less secure random number gen. 10 = /dev/aio Asynchronous I/O notification interface 11 = /dev/kmsg Writes to this come out as printk's 12 = /dev/oldmem Used by crashdump kernels to access the memory of the kernel that crashed.可見/dev/__null__與/dev/null的設備號完全相同,它就是/dev/null的馬甲。那麼為什麼init進程不直接創建/dev/null呢? 當前我們還無法回答這個問題,要等到分析/sbin/uevnted的原理時才能明白。
void klog_init(void) { static const char *name = "/dev/__kmsg__"; if (klog_fd >= 0) return; /* Already initialized */ if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) { klog_fd = open(name, O_WRONLY); if (klog_fd < 0) return; fcntl(klog_fd, F_SETFD, FD_CLOEXEC); unlink(name); } }klog_init函數首先檢查klog_fd是否已經初始化。首次執行時,調用mknod創建主設備號為1,從設備號為11的設備節點文件/dev/__kmsg__,然後打開該文件將文件描述符保存到變量klog_fd中,接著調用fcntl(klog_fd, F_SETFD, FD_CLOEXEC)句作用是設置當執行execv時,關閉該文件描述符。隨後調用unlink來刪除/dev/__kmsg__文件,這裡比較特殊,具體解釋下。
P.S.根據unlink的mannul,(man 2 unlink),其中寫道: If the name was the last link to a file but any processes still have the file open the file will remain in existence until the last file descriptor referring to it is closed./dev/__kmsg__文件與/dev/kmsg的設備節點完全相同,前者同樣是後者的馬甲。該設備驅動節點是內核日志文件,內核調用printk函數打印的log可以通過該設備節點訪問,向該文件中寫入則等同於執行內核printk。該文件的內容可通Linux系統標准程序dmesg讀取,Android系統也提供了dmesg命令。
static int klog_level = KLOG_DEFAULT_LEVEL; int klog_get_level(void) { return klog_level; } void klog_set_level(int level) { klog_level = level; } #define LOG_BUF_MAX 512 void klog_vwrite(int level, const char *fmt, va_list ap) { char buf[LOG_BUF_MAX]; if (level > klog_level) return; if (klog_fd < 0) klog_init(); if (klog_fd < 0) return; vsnprintf(buf, LOG_BUF_MAX, fmt, ap); buf[LOG_BUF_MAX - 1] = 0; write(klog_fd, buf, strlen(buf)); } void klog_write(int level, const char *fmt, ...) { va_list ap; va_start(ap, fmt); klog_vwrite(level, fmt, ap); va_end(ap); }klog_write調用klog_vwrite函數可用於向/dev/__kmesg__中寫入日志,第一個參數是當前log的級別,如果當前level大於klog_leve則直接返回,即無法將log寫入/dev/__kmesg__中。此外,提供了兩個函數klog_set_level與klog_get_level分別用於設置和讀取當前的klog_level,默認level為KLOG_DEFAULT_LEVEL,在klog.h中定義。
#define KLOG_ERROR_LEVEL 3 #define KLOG_WARNING_LEVEL 4 #define KLOG_NOTICE_LEVEL 5 #define KLOG_INFO_LEVEL 6 #define KLOG_DEBUG_LEVEL 7 #define KLOG_ERROR(tag,x...) klog_write(KLOG_ERROR_LEVEL, "<3>" tag ": " x) #define KLOG_WARNING(tag,x...) klog_write(KLOG_WARNING_LEVEL, "<4>" tag ": " x) #define KLOG_NOTICE(tag,x...) klog_write(KLOG_NOTICE_LEVEL, "<5>" tag ": " x) #define KLOG_INFO(tag,x...) klog_write(KLOG_INFO_LEVEL, "<6>" tag ": " x) #define KLOG_DEBUG(tag,x...) klog_write(KLOG_DEBUG_LEVEL, "<7>" tag ": " x) #define KLOG_DEFAULT_LEVEL 3 /* messages <= this level are logged */可見默認級別為3,即KLOG_ERROR_LEVEL,只有調用KLOG_ERROR才能被輸出到/dev/__kmesg__中。
這一句用來初始化Android的屬性系統,將在init之屬性系統中專門介紹。
get_hardware_name(hardware, &revision)通過讀取/proc/cpuinfo文件獲取硬件信息,以筆者的山寨機為例,該文件內容如下。
shell@android:/ $ cat /proc/cpuinfo Processor : ARMv7 Processor rev 1 (v7l) processor : 0 BogoMIPS : 348.76 processor : 1 BogoMIPS : 348.76 processor : 2 BogoMIPS : 348.76 processor : 3 BogoMIPS : 348.76 Features : swp half thumb fastmult vfp edsp thumbee neon vfpv3 tls vfpv4 CPU implementer : 0x41 CPU architecture: 7 CPU variant : 0x0 CPU part : 0xc05 CPU revision : 1 Hardware : QRD MSM8625Q SKUD Revision : 0000 Serial : 0000000000000000get_hardware_name函數讀取該文件,將Hardware字段的值填入hardware數組中,將Revision字段的值轉換為16進制數字填入revision變量中。
接下來init程序調用函數process_kernel_cmdline解析內核啟動參數。內核通常由bootloader(啟動引導程序)加載啟動,目前廣泛使用的bootloader大都基於u-boot定制。內核允許bootloader啟動自己時傳遞參數。在內核啟動完畢之後,啟動參數可通過/proc/cmdline查看。
例如android4.4模擬器啟動後,查看其內核啟動參數,如下static void process_kernel_cmdline(void) { /* don't expose the raw commandline to nonpriv processes */ chmod("/proc/cmdline", 0440); /* first pass does the common stuff, and finds if we are in qemu. * second pass is only necessary for qemu to export all kernel params * as props. */ import_kernel_cmdline(0, import_kernel_nv); if (qemu[0]) import_kernel_cmdline(1, import_kernel_nv); /* now propogate the info given on command line to internal variables * used by init as well as the current required properties */ export_kernel_boot_props(); }首先修改/proc/cmdline文件權限,0440即表明只有root用戶或root組用戶可以讀寫該文件,其他用戶無法訪問。隨後連續調用import_kernel_cmdline函數,第一個參數標識當前Android設備是否是模擬器,第二個參數一個函數指針。
import_kernel_cmdline函數將/proc/cmdline內容讀入到內部緩沖區中,並將cmdline內容的以空格拆分成小段字符串,依次傳遞給import_kernel_nv函數處理。以前面/proc/cmdline的輸出為例子,該字符串共可以拆分成以下幾段
qemu.gles=0 qemu=1 console=ttyS0 android.qemud=ttyS1 android.checkjni=1 ndns=1因此在import_kernel_nv將會被連續調用6次,依次傳入上述字符串。函數實現如下:
static void import_kernel_nv(char *name, int for_emulator) { char *value = strchr(name, '='); int name_len = strlen(name); if (value == 0) return; *value++ = 0; if (name_len == 0) return; if (for_emulator) { /* in the emulator, export any kernel option with the * ro.kernel. prefix */ char buff[PROP_NAME_MAX]; int len = snprintf( buff, sizeof(buff), "ro.kernel.%s", name ); if (len < (int)sizeof(buff)) property_set( buff, value ); return; } if (!strcmp(name,"qemu")) { strlcpy(qemu, value, sizeof(qemu)); } else if (!strncmp(name, "androidboot.", 12) && name_len > 12) { const char *boot_prop_name = name + 12; char prop[PROP_NAME_MAX]; int cnt; cnt = snprintf(prop, sizeof(prop), "ro.boot.%s", boot_prop_name); if (cnt < PROP_NAME_MAX) property_set(prop, value); } }import_kernel_cmdline第一次執行時,傳入import_kernel_nv的形式參數for_emulator為 0,,因此將匹配name是否為qemu,如果是,將其值保存到qemu全局靜態緩沖區中。對於android模擬器,存在/proc/cmdline中存在“qemu=1”字段。如果for_emulator為1,則將生成ro.kernel.{name}={value}屬性寫入Android的屬性系統中。
此時回到process_kernel_cmdline函數,繼續執行
if (qemu[0]) import_kernel_cmdline(1, import_kernel_nv);當系統為模擬器時,qemu[0]其值為'1',第二次執行import_kernel_cmdline,將再次調用6次import_kernel_nv,並且for_emulator為1,因此將生成6個屬性,現在來確定以下我們的分析。
root@generic:/ # getprop | grep ro.kernel. [ro.kernel.android.checkjni]: [1] [ro.kernel.android.qemud]: [ttyS1] [ro.kernel.console]: [ttyS0] [ro.kernel.ndns]: [1] [ro.kernel.qemu.gles]: [0] [ro.kernel.qemu]: [1]可驗證我們的分析是正確的。
union selinux_callback cb; cb.func_log = log_callback; selinux_set_callback(SELINUX_CB_LOG, cb); cb.func_audit = audit_callback; selinux_set_callback(SELINUX_CB_AUDIT, cb); selinux_initialize(); /* These directories were necessarily created before initial policy load * and therefore need their security context restored to the proper value. * This must happen before /dev is populated by ueventd. */ restorecon("/dev"); restorecon("/dev/socket"); restorecon("/dev/__properties__"); restorecon_recursive("/sys");這部分代碼是在Android4.1之後添加的,隨後伴隨Android系統更新不停迭代。這段代碼主要涉及SELinux初始化。由於SELinux與Android系統啟動關閉不大,暫不分析。
回到init函數
is_charger = !strcmp(bootmode, "charger"); INFO("property init\n"); property_load_boot_defaults();第一句將利用bootmode與字符串"charger"將其保存到is_charger變量中,is_charger非0表明但前Android是以充電模式啟動,否則為正常模式。正常啟動模式與充電模式需要啟動的進程不同的,這兩種模式啟動具體啟動的程序差別將在init.rc解析時介紹。
接下來調用INFO宏打印一條log語句,此宏定義在init/log.h中,其實現如下
#define ERROR(x...) KLOG_ERROR("init", x) #define NOTICE(x...) KLOG_NOTICE("init", x) #define INFO(x...) KLOG_INFO("init", x)顯然這是一條level為KLOG_INFO_LEVEL的log語句。它是否能輸出到/dev/__kmesg__中跟當前klog level的值有關。默認情況下,klog level為3,這條語句將不會輸出到/dev/__kmsg__中。
到這裡init.c main函數之
接下來
1.關於坑 好吧,在此之前先來說一下,之前開的坑,恩,確實是坑,前面開的兩個android開發教程的坑,對不起,實在是沒什麼動力了,不過源碼都有的,大家可以參照githu
有很多人也寫過創建桌面快捷鍵的blog,但是大部分都只講了怎麼用,其實技術使用起來都很簡單,但是你使用後下次還知道嗎? 根本原因還是不清楚原理,今天我就來講講shor
概述ViewPager是Android開發中使用場景非常頻繁的控件,單一的動畫效果切換已經越來越不能滿足追求個性化的應用中。而ViewPager自身也帶有一個接口來處理頁
前言這篇文章主要介紹多種方式實現主界面的tab,包括:(1)使用Fragment實現(2)使用ViewPage實現(3)使用ViewPage+FragmentPageAd