編輯:關於Android編程
int main(int argc, char **argv)
{
//
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;
} 將main函數分為上述4個部分,對應part1到part4,下面分別做具體說明。 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函數之
接下來
[Android開發系列]IT博客應用
1.關於坑 好吧,在此之前先來說一下,之前開的坑,恩,確實是坑,前面開的兩個android開發教程的坑,對不起,實在是沒什麼動力了,不過源碼都有的,大家可以參照githu
android創建桌面快捷鍵shortcut
有很多人也寫過創建桌面快捷鍵的blog,但是大部分都只講了怎麼用,其實技術使用起來都很簡單,但是你使用後下次還知道嗎? 根本原因還是不清楚原理,今天我就來講講shor
Android ViewPager實現動畫切換效果
概述ViewPager是Android開發中使用場景非常頻繁的控件,單一的動畫效果切換已經越來越不能滿足追求個性化的應用中。而ViewPager自身也帶有一個接口來處理頁
android-多種方式實現主界面的Tab
前言這篇文章主要介紹多種方式實現主界面的tab,包括:(1)使用Fragment實現(2)使用ViewPage實現(3)使用ViewPage+FragmentPageAd