0%

Android的编译时间是如何生成的

我们经常查看 Settings -> About phone -> Kernel version 中的编译时间,那么,这个时间到底怎么来的呢?
我们今天基于Android 7.0代码来分析一下。

Settings中用于显示Kernel version的关键代码如下:

Java部分

1
2
3
4
5
6
7
8
// packages/apps/Settings/src/com/android/settings/DeviceInfoSettings.java

public void onCreate(Bundle icicle) {
...
// 设置版本号
findPreference(KEY_KERNEL_VERSION).setSummary(DeviceInfoUtils.getFormattedKernelVersion());
...
}

我们来看一下DeviceInfoUtils.getFormattedKernelVersion()的实现:

1
2
3
4
5
6
7
8
9
10
// frameworks/base/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java

private static final String FILENAME_PROC_VERSION = "/proc/version";

public static String getFormattedKernelVersion() {
...
// 从"/proc/version"文件中读取编译信息,通过解析,组合出想要显示的版本号
return formatKernelVersion(readLine(FILENAME_PROC_VERSION));
...
}

我们来看一下formatKernelVersion()的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// frameworks/base/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java

public static String formatKernelVersion(String rawKernelVersion) {
// Example (see tests for more):
// Linux version 3.0.31-g6fb96c9 ([url=mailto:android-build@xxx.xxx.xxx.xxx.com]android-build@xxx.xxx.xxx.xxx.com[/url]) \
// (gcc version 4.6.x-xxx 20120106 (prerelease) (GCC) ) #1 SMP PREEMPT \
// Thu Jun 28 11:02:39 PDT 2012

final String PROC_VERSION_REGEX =
"Linux version (\\S+) " + /* group 1: "3.0.31-g6fb96c9" */
"\\((\\S+?)\\) " + /* group 2: "x@y.com" (kernel builder) */
"(?:\\(gcc.+? \\)) " + /* ignore: GCC version information */
"(#\\d+) " + /* group 3: "#1" */
"(?:.*?)?" + /* ignore: optional SMP, PREEMPT, and any CONFIG_FLAGS */
"((Sun|Mon|Tue|Wed|Thu|Fri|Sat).+)"; /* group 4: "Thu Jun 28 11:02:39 PDT 2012" */

// 解析版本信息
Matcher m = Pattern.compile(PROC_VERSION_REGEX).matcher(rawKernelVersion);
...

// 组合出想要的版本号
return m.group(1) + "\n" + // 3.0.31-g6fb96c9
m.group(2) + " " + m.group(3) + "\n" + // [url=mailto:x@y.com]x@y.com[/url] #1
m.group(4); // Thu Jun 28 11:02:39 PDT 2012
}

我们可以看到,Kernel version是通过解析”/proc/version”文件得到的,而”/proc/version” 则是被”kernel-3.18/fs/proc/version.c”生成的。

Kernel部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 输出编译信息到"/proc/version" 文件
static int version_proc_show(struct seq_file *m, void *v)
{
// "linux_proc_banner"为format string,定义 kernel-3.18/init/version.c 中
// utsname(),内联函数,定义在 kernel-3.18/include/linux/utsname.h 中
seq_printf(m, linux_proc_banner,
utsname()->sysname,
utsname()->release,
utsname()->version); // version 包括了编译时间
return 0;
}

// 打开"/proc/version" 文件
static int version_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, version_proc_show, NULL);
}

// 操作"/proc/version" 文件的回调函数结构体
static const struct file_operations version_proc_fops = {
.open = version_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};

static int __init proc_version_init(void)
{
// 创建 "/proc/version" 文件, 并调用回调函数
proc_create("version", 0, NULL, &version_proc_fops);
return 0;
}

其中utsname()的定义如下:

1
2
3
4
5
// kernel-3.18/include/linux/utsname.h
static inline struct new_utsname *utsname(void)
{
return &current->nsproxy->uts_ns->name;
}

nsproxy 的定义如下:

1
2
3
4
5
6
7
8
// kernel-3.18/include/linux/nsproxy.h
struct nsproxy { atomic_t count;
struct uts_namespace *uts_ns;
struct ipc_namespace *ipc_ns;
struct mnt_namespace *mnt_ns;
struct pid_namespace *pid_ns;
struct net *net_ns;
};

uts_ns的初始化代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
// kernel-3.18/kernel/nsproxy.c
struct nsproxy init_nsproxy = { .count = ATOMIC_INIT(1),
.uts_ns = &init_uts_ns,
#if defined(CONFIG_POSIX_MQUEUE) || defined(CONFIG_SYSVIPC)
.ipc_ns = &init_ipc_ns,
#endif
.mnt_ns = NULL,
.pid_ns = &init_pid_ns,
#ifdef CONFIG_NET
.net_ns = &init_net,
#endif
};

可以看到uts_ns = &init_uts_ns, init_uts_ns的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// kernel-3.18/init/version.c
struct uts_namespace init_uts_ns = { .kref = {
.refcount = ATOMIC_INIT(2),
},
.name = {
.sysname = UTS_SYSNAME,
.nodename = UTS_NODENAME,
.release = UTS_RELEASE,
.version = UTS_VERSION,
.machine = UTS_MACHINE,
.domainname = UTS_DOMAINNAME,
},
.user_ns = &init_user_ns,
.proc_inum = PROC_UTS_INIT_INO,
};

我们可以看到version = UTS_VERSION,UTS_VERSION这个宏定义在compile.h中,这是一个生成文件,是被 kernel-3.18/scripts/mkcompile_h 这个脚本生成的,生成路径为:

1
./out/target/product/projectname/obj/KERNEL_OBJ/include/generated/compile.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# kernel-3.18/scripts/mkcompile_h

# 通过抓Log,$KBUILD_BUILD_TIMESTAMP 为空
if [ -z "$KBUILD_BUILD_TIMESTAMP" ]; then
# 获取系统当前的日期和时间
TIMESTAMP=`date`
else
TIMESTAMP=$KBUILD_BUILD_TIMESTAMP
fi

# 组合出UTS_VERSION
UTS_VERSION="$UTS_VERSION $CONFIG_FLAGS $TIMESTAMP"

# Generate a temporary compile.h
( echo /\* This file is auto generated, version $VERSION \*/
if [ -n "$CONFIG_FLAGS" ] ; then echo "/* $CONFIG_FLAGS */"; fi

echo \#define UTS_MACHINE \"$ARCH\"

# 输出UTS_VERSION 到compile.h
echo \#define UTS_VERSION \"`echo $UTS_VERSION | $UTS_TRUNCATE`\"

echo \#define LINUX_COMPILE_BY \"`echo $LINUX_COMPILE_BY | $UTS_TRUNCATE`\"
echo \#define LINUX_COMPILE_HOST \"`echo $LINUX_COMPILE_HOST | $UTS_TRUNCATE`\"

echo \#define LINUX_COMPILER \"`$CC -v 2>&1 | tail -n 1`\"
) > .tmpcompile

至此,打完收功!