如何在把主机及版本信息编译进内核中?

我们知道当Linux系统或者Android系统运行起来之后,可以通过cat /proc/version来获取对应的版本信息.如下所示:

xiehaocheng@xiehaocheng:~$ cat /proc/version
Linux version 3.13.0-19-generic (buildd@akateko) (gcc version 4.8.2 (Ubuntu 4.8.
2-17ubuntu1) ) #40-Ubuntu SMP Mon Mar 24 02:36:06 UTC 2014

那么我们今天就来探讨一下这个信息是怎么编译到内核中的。首先从这个接口来入手,顺藤摸瓜来看清楚具体是如何实现的。

/proc/version接口是在内核中的一个标准接口,它的实现在kernel-4.4/fs/proc/version.c中:

#define BUILDER_INFO_LEN    (24)
static char simu_builder_info[BUILDER_INFO_LEN] = "NONE";
static char simu_linux_proc_banner[] =
    "%s version %s"
    " (%s)"
    " (" LINUX_COMPILER ") %s\n";
static int version_proc_show(struct seq_file *m, void *v)
{
    if (strncmp(simu_builder_info, "NONE", strlen("NONE")) == 0) {
        seq_printf(m, linux_proc_banner,
            utsname()->sysname,
            utsname()->release,
            utsname()->version);
    } else {
        seq_printf(m, simu_linux_proc_banner,
            utsname()->sysname,
            utsname()->release,
            simu_builder_info,
            utsname()->version);
    }
    return 0;
}
static int version_proc_open(struct inode *inode, struct file *file)
{
    return single_open(file, version_proc_show, NULL);
}
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_create("version", 0, NULL, &version_proc_fops);
    return 0;
}
fs_initcall(proc_version_init);

接口的实现也是比较简单的,主要就是从utsname这个结构体变量中获取对应的信息并输出,输出的格式在linux_proc_banner中。utsname它的全称应该叫做unix time share namespace,这个和各个进程的命名空间有关了,看下它的实现:

include/linux/utsname.h:
static inline struct new_utsname *utsname(void)
{
    return ¤t->nsproxy->uts_ns->name;
}
static inline struct new_utsname *init_utsname(void)
{
    return &init_uts_ns.name;
}

可以看到其实最终这个utsname就是当前current进程的uts namespace。对于kernel更老的版本实现中,utsname并不会按照进程做区分,而是所有进程共享同一个utsname,笔者也不是很清楚从哪个版本开始,utsname的实现是按照进程做了区分,我想这么做自然也有这么做的道理,比如可以把不同进程按照不同uts namespace进行隔离开。

如果没有修改过当前进程的utsname的话,默认utsname是和init_utsname保持一致的,因为init进程是所有进程的父进程,所有进程在初始化创建时都会直接从init进程中把utsname克隆过来。那么我们就跟踪到关键的数据结构了:init_uts_ns,它的定义如下:


kernel-4.4/init/version.c:
#include           //UST_XXX的宏定义头文件
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,
    .ns.inum = PROC_UTS_INIT_INO,
#ifdef CONFIG_UTS_NS
    .ns.ops = &utsns_operations,
#endif
};
EXPORT_SYMBOL_GPL(init_uts_ns);
/* FIXED STRINGS! Don't touch! */
const char linux_banner[] =
    "Linux version " UTS_RELEASE " (" LINUX_COMPILE_BY "@"
    LINUX_COMPILE_HOST ") (" LINUX_COMPILER ") " UTS_VERSION "\n";
const char linux_proc_banner[] =     //proc/version接口的数据打印格式
    "%s version %s"
    " (" LINUX_COMPILE_BY "@" LINUX_COMPILE_HOST ")"
    " (" LINUX_COMPILER ") %s\n";

通过上面的梳理,我们可以知道当我们cat /proc/version时最终数据是取得:

UTS_SYSNAME/UTS_RELEASE/UTS_VERSION/LINUX_COMPILE_HOST/LINUX_COMPILER/LINUX_COMPILE_BY。

这些宏定义是在include/generated/compile.h中定义的:

/* This file is auto generated, version 171 */
/* SMP PREEMPT */
#define UTS_MACHINE "arm64"
#define UTS_VERSION "#171 SMP PREEMPT Mon Apr 23 14:30:26 CST 2018"
#define LINUX_COMPILE_BY "xiehaocheng"
#define LINUX_COMPILE_HOST "xiehaocheng"
#define LINUX_COMPILER "gcc version 4.9 20150123 (prerelease) (GCC) "
#define ANDROID_VERSION "NGI77B test-keys"

不过有一点需要注意,就是上述头文件并不是固定不变的,你从github上拉取的kernel代码是不包含此头文件的,从它所在的目录generated我们也能猜到这个头文件是编译过程中生成的。

我们下面进一步来看是怎么生成的。

kernel-4.4/init/Makefile:
$(obj)/version.o: include/generated/compile.h
include/generated/compile.h: FORCE
    @$($(quiet)chk_compile.h)
    $(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkcompile_h $@ \   //mkcompile_h是一个shell脚本,用来生成compile.h头文件,这里执行它来生成对应的target
    "$(UTS_MACHINE)" "$(CONFIG_SMP)" "$(CONFIG_PREEMPT)" "$(CC) $(KBUILD_CFLAGS)"

看一下脚本实现:

kernel-4.4/scripts/mkcompile_h:
#!/bin/sh
TARGET=$1    //第一个参数为生成的目标文件名称
ARCH=$2      //arch类型,UTS_MACHINE
SMP=$3       //SMP,CONFIG_SMP
PREEMPT=$4   //CONFIG_PREEMPT
CC=$5        //$(CC)
......
 # Fix the language to get consistent output
 LC_ALL=C
 export LC_ALL

 if [ -z "$KBUILD_BUILD_VERSION" ]; then
     if [ -r .version ]; then
         VERSION=`cat .version`
     else
         VERSION=0
         echo 0 > .version
     fi
 else
     VERSION=$KBUILD_BUILD_VERSION
 fi

 if [ -z "$KBUILD_BUILD_TIMESTAMP" ]; then
     TIMESTAMP=`date`
 else
     TIMESTAMP=$KBUILD_BUILD_TIMESTAMP
 fi
 if test -z "$KBUILD_BUILD_USER"; then
     LINUX_COMPILE_BY=$(whoami | sed 's/\\/\\\\/')
 else
     LINUX_COMPILE_BY=$KBUILD_BUILD_USER
 fi
 if test -z "$KBUILD_BUILD_HOST"; then
     LINUX_COMPILE_HOST=`hostname`
 else
     LINUX_COMPILE_HOST=$KBUILD_BUILD_HOST
 fi

 UTS_VERSION="#$VERSION"
CONFIG_FLAGS=""
if [ -n "$SMP" ] ; then CONFIG_FLAGS="SMP"; fi
if [ -n "$PREEMPT" ] ; then CONFIG_FLAGS="$CONFIG_FLAGS PREEMPT"; fi
UTS_VERSION="$UTS_VERSION $CONFIG_FLAGS $TIMESTAMP"
# Truncate to maximum length
UTS_LEN=64
UTS_TRUNCATE="cut -b -$UTS_LEN"
# Generate a temporary compile.h  //生成一个临时文件 .tmpcompile
( echo /\* This file is auto generated, version $VERSION \*/
  if [ -n "$CONFIG_FLAGS" ] ; then echo "/* $CONFIG_FLAGS */"; fi
  echo \#define UTS_MACHINE \"$ARCH\"
  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 | grep ' version '`\"
  echo \#define ANDROID_VERSION \"`echo $BUILD_DISPLAY_ID`\"
) > .tmpcompile
......
 if [ -r $TARGET ] && \
       grep -v 'UTS_VERSION' $TARGET > .tmpver.1 && \
       grep -v 'UTS_VERSION' .tmpcompile > .tmpver.2 && \
       cmp -s .tmpver.1 .tmpver.2; then
    rm -f .tmpcompile
 else
    vecho "  UPD     $TARGET"
    mv -f .tmpcompile $TARGET //移动.tmpcompile为TARGET,也就是include/generated/compile.h
 fi
 rm -f .tmpver.1 .tmpver.2  //删除其余不需要的临时文件

综上所述,我们从入口函数一步一步跟踪,到后来窥探到整个的实现流程。

你可能感兴趣的:(如何在把主机及版本信息编译进内核中?)