Linuxのバージョンを確認する方法

# はじめに
実用性があまりないネタ的な投稿ですのでご了承を。Linux-4.6あたりを見てます。

# 初心者向け
```bash
[rarul@tina ~]$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.1 LTS"
```

# ふつうの人向け
```bash
[rarul@tina ~]$ uname -a
Linux tina 4.4.0-59-generic #80-Ubuntu SMP Fri Jan 6 17:47:47 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
```

# システム屋さん
```bash
[rarul@tina ~]$ strace uname -a
execve("/bin/uname", ["uname", "-a"], [/* 22 vars */]) = 0
brk(NULL)                               = 0x1240000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0c65fd1000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=33482, ...}) = 0
mmap(NULL, 33482, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f0c65fc8000
(---SNIP---)
```

# ユーザランドが貧弱
```bash
[rarul@tina ~]$ more /proc/version
Linux version 4.4.0-59-generic (buildd@lgw01-11) (gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.4) ) #80-Ubuntu SMP Fri Jan 6 17:47:47 UTC 2017
```

# ログ解析班
```bash
[rarul@tina ~]$ dmesg |grep "Linux version"
[    0.000000] Linux version 4.4.0-59-generic (buildd@lgw01-11) (gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.4) ) #80-Ubuntu SMP Fri Jan 6 17:47:47 UTC 2017 (Ubuntu 4.4.0-59.80-generic 4.4.35)
```

# バイナリファイルしかねぇ
```bash
[rarul@tina ~]$ wget https://raw.githubusercontent.com/torvalds/linux/master/scripts/extract-vmlinux
[rarul@tina ~]$ sudo sh ./extract-vmlinux /boot/vmlinuz-4.4.0-59-generic |strings |grep "Linux version"
Linux version 4.4.0-59-generic (buildd@lgw01-11) (gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.4) ) #80-Ubuntu SMP Fri Jan 6 17:47:47 UTC 2017 (Ubuntu 4.4.0-59.80-generic 4.4.35)
```

# ソースコードしかねぇ
```bash
[rarul@tina ~]$ cd src/linux-stable/
[rarul@tina linux-stable]$ head -n 5 Makefile
VERSION = 4
PATCHLEVEL = 6
SUBLEVEL = 0
EXTRAVERSION =
NAME = Charred Weasel
```
ちなみに、「NAME = Charred Weasel」が[ウワサのコードネーム](http://qiita.com/satoru_takeuchi/items/e2b394db0d385608b379)です。

# gdbが好きな人
```bash
[rarul@tina out-linux]$ gdb
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.04) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb) file vmlinux
Reading symbols from vmlinux...done.
(gdb) p linux_banner
$1 = "Linux version 4.6.0 (rarul@tina) (gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.4) ) #2 SMP Sat Feb 11 19:03:03 JST 2017\n"
(gdb) p linux_proc_banner
$2 = "%s version %s (rarul@tina) (gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.4) ) %s\n"
```
...Qiita記法のコード挿入の対応言語に.gdbinitはなかった模様...

gdb用のデバッグ情報をvmlinuxに埋め込むには**CONFIG_DEBUG_INFO**が必要。

# 考古学者
```bash
[rarul@tina ~]$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git
[rarul@tina ~]$ cd linux-stable
[rarul@tina linux-stable]$ git log Makefile
```

# 歴史の教科書
https://kernelnewbies.org/LinuxVersions

# マジメにプログラミング(kernel編)
例えば[**linux/fs/ext4/ext4.h**](http://lxr.free-electrons.com/source/fs/ext4/ext4.h?v=4.6)にマクロを使った#ifの書き方がある。

```c:ext4.h
825 #if LINUX_VERSION_CODE < KERNEL_VERSION(4,20,0)
```

**LINUX_VERSION_CODE**は、今ビルドしようとしているkernelのソースのバージョン、**KERNEL_VERSION**はマクロで、「4.20.0」に相当するintegerに変換される。

# マジメにプログラミング(ユーザランド編)
まず、kernel編と同じく、**LINUX_VERSION_CODE**と**KERNEL_VERSION**を使った比較ができる。

次に、integerじゃなくて文字列としてほしい場合のサンプルがこんな感じとなる。

```c:version_string.c
#include  <stdio.h>
#include  <linux/version.h>
int main (int argc, char *argv[]){
#define LINUX_VERSION_CODE_RELEASE  (((LINUX_VERSION_CODE)>>16)&0xff)
#define LINUX_VERSION_CODE_MAJOR    (((LINUX_VERSION_CODE)>>8)&0xff)
#define LINUX_VERSION_CODE_MINOR    (((LINUX_VERSION_CODE)>>0)&0xff)
  printf ("Linux-%d.%d.%d\n",
          LINUX_VERSION_CODE_RELEASE,
          LINUX_VERSION_CODE_MAJOR,
          LINUX_VERSION_CODE_MINOR);
  return 0;
}
```
```bash
[rarul@tina version_string]$ ./version_string
Linux-4.4.35
```
最後に、ヘッダ(include/linux.version.h)もない場合、[**uname(2)**](https://linuxjm.osdn.jp/html/LDP_man-pages/man2/uname.2.html)システムコールを使うことになる。バージョン互換性とかいろいろ書かれているけど、私あまり詳しくないので、詳細はman見てね。

```c:utsname.c
#define _GNU_SOURCE
#include  <stdio.h>
#include  <sys/utsname.h>
int main(int argc, char *argv[]){
  struct utsname me;
  if (uname(&me) != 0){
    perror("uname");
  } else {
    printf("sysname = %s\n" "nodename = %s\n" "release = %s\n"
           "version = %s\n" "machine = %s\n" "domainname = %s\n",
           me.sysname, me.nodename, me.release,
           me.version, me.machine, me.domainname);
  }
  return 0;
}
```
```bash
[rarul@tina utsname]$ ./utsname
sysname = Linux
nodename = tina
release = 4.4.0-59-generic
version = #80-Ubuntu SMP Fri Jan 6 17:47:47 UTC 2017
machine = x86_64
domainname = (none)
```

# kernel hacker
**/proc/version**は[**linux/proc/version.c:version_proc_show()**](http://lxr.free-electrons.com/source/fs/proc/version.c?v=4.6)より、

```c:version.c
  8 static int version_proc_show(struct seq_file *m, void *v)
  9 {
 10         seq_printf(m, linux_proc_banner,
 11                 utsname()->sysname,
 12                 utsname()->release,
 13                 utsname()->version);
 14         return 0;
 15 }
```

**linux_banner[]**, **linux_proc_banner[]**の定義は[**linux/init/version.c**](http://lxr.free-electrons.com/source/init/version.c?v=4.6)より、

```c:version.c
 45 /* FIXED STRINGS! Don't touch! */
 46 const char linux_banner[] =
 47         "Linux version " UTS_RELEASE " (" LINUX_COMPILE_BY "@"
 48         LINUX_COMPILE_HOST ") (" LINUX_COMPILER ") " UTS_VERSION "\n";
 49 
 50 const char linux_proc_banner[] =
 51         "%s version %s"
 52         " (" LINUX_COMPILE_BY "@" LINUX_COMPILE_HOST ")"
 53         " (" LINUX_COMPILER ") %s\n";
 54 
```

マクロ名が気になるけど、あとでまとめて出てくるのでおいといて、先に**linux_proc_banner[]**の%sから。これは**utsname()**からくるが、デフォルトのutsname()は、[**linux/init/version.c**](http://lxr.free-electrons.com/source/init/version.c?v=4.6)で作られる。

```c:version.c
 25 struct uts_namespace init_uts_ns = {
 26         .kref = {
 27                 .refcount       = ATOMIC_INIT(2),
 28         },
 29         .name = {
 30                 .sysname        = UTS_SYSNAME,
 31                 .nodename       = UTS_NODENAME,
 32                 .release        = UTS_RELEASE,
 33                 .version        = UTS_VERSION,
 34                 .machine        = UTS_MACHINE,
 35                 .domainname     = UTS_DOMAINNAME,
 36         },
 37         .user_ns = &init_user_ns,
 38         .ns.inum = PROC_UTS_INIT_INO,
 39 #ifdef CONFIG_UTS_NS
 40         .ns.ops = &utsns_operations,
 41 #endif
 42 };
```

「デフォルトのutsname()」と書いたのは、実はutsname()はタスクごとに持てるため(namespace,utsnameが入ったLinux-3.0より)。このへんは「**struct nsproxy**」などでググればそれなりに見つかるのでそちらに任せる。


で、ここから先に飛ばしたマクロもまとめて。**LINUX_COMPILE_BY**, **LINUX_COMPILE_HOST**, **LINUX_COMPILER**, **UTS_VERSION**, **UTS_MACHINE**は[**linux/scripts/mkcompile_h**](http://lxr.free-electrons.com/source/scripts/mkcompile_h?v=4.6)より、

```bash
 29 if [ -z "$KBUILD_BUILD_VERSION" ]; then
 30         if [ -r .version ]; then
 31                 VERSION=`cat .version`
 32         else
 33                 VERSION=0
 34                 echo 0 > .version
 35         fi
 36 else
 37         VERSION=$KBUILD_BUILD_VERSION
 38 fi
 39 
 40 if [ -z "$KBUILD_BUILD_TIMESTAMP" ]; then
 41         TIMESTAMP=`date`
 42 else
 43         TIMESTAMP=$KBUILD_BUILD_TIMESTAMP
 44 fi
 45 if test -z "$KBUILD_BUILD_USER"; then
 46         LINUX_COMPILE_BY=$(whoami | sed 's/\\/\\\\/')
 47 else
 48         LINUX_COMPILE_BY=$KBUILD_BUILD_USER
 49 fi
 50 if test -z "$KBUILD_BUILD_HOST"; then
 51         LINUX_COMPILE_HOST=`hostname`
 52 else
 53         LINUX_COMPILE_HOST=$KBUILD_BUILD_HOST
 54 fi
 55 
 56 UTS_VERSION="#$VERSION"
 57 CONFIG_FLAGS=""
 58 if [ -n "$SMP" ] ; then CONFIG_FLAGS="SMP"; fi
 59 if [ -n "$PREEMPT" ] ; then CONFIG_FLAGS="$CONFIG_FLAGS PREEMPT"; fi
 60 UTS_VERSION="$UTS_VERSION $CONFIG_FLAGS $TIMESTAMP"
 61 
 62 # Truncate to maximum length
 63 
 64 UTS_LEN=64
 65 UTS_TRUNCATE="cut -b -$UTS_LEN"
 66 
 67 # Generate a temporary compile.h
 68 
 69 ( echo /\* This file is auto generated, version $VERSION \*/
 70   if [ -n "$CONFIG_FLAGS" ] ; then echo "/* $CONFIG_FLAGS */"; fi
 71 
 72   echo \#define UTS_MACHINE \"$ARCH\"
 73 
 74   echo \#define UTS_VERSION \"`echo $UTS_VERSION | $UTS_TRUNCATE`\"
 75 
 76   echo \#define LINUX_COMPILE_BY \"`echo $LINUX_COMPILE_BY | $UTS_TRUNCATE`\"
 77   echo \#define LINUX_COMPILE_HOST \"`echo $LINUX_COMPILE_HOST | $UTS_TRUNCATE`\"
 78 
 79   echo \#define LINUX_COMPILER \"`$CC -v 2>&1 | grep ' version '`\"
 80 ) > .tmpcompile
```

**UTS_SYSNAME**, **UTS_NODENAME**, **UTS_DOMAINNAME**のマクロは、[**linux/include/linux/uts.h**](http://lxr.free-electrons.com/source/include/linux/uts.h?v=4.6)より、

```c:uts.h
  4 /*
  5  * Defines for what uname() should return 
  6  */
  7 #ifndef UTS_SYSNAME
  8 #define UTS_SYSNAME "Linux"
  9 #endif
 10 
 11 #ifndef UTS_NODENAME
 12 #define UTS_NODENAME CONFIG_DEFAULT_HOSTNAME /* set by sethostname() */
 13 #endif
 14 
 15 #ifndef UTS_DOMAINNAME
 16 #define UTS_DOMAINNAME "(none)" /* set by setdomainname() */
 17 #endif
```

**UTS_RELEASE**は[**linux/Makefile**](http://lxr.free-electrons.com/source/Makefile?v=4.6)

```makefile:Makefile
404 # Read KERNELRELEASE from include/config/kernel.release (if it exists)
405 KERNELRELEASE = $(shell cat include/config/kernel.release 2> /dev/null)
406 KERNELVERSION = $(VERSION)$(if $(PATCHLEVEL),.$(PATCHLEVEL)$(if $(SUBLEVEL),.$(SUBLEVEL)))$(EXTRAVERSION)
```
```makefile:Makefile
1031 uts_len := 64
1032 define filechk_utsrelease.h
1033         if [ `echo -n "$(KERNELRELEASE)" | wc -c ` -gt $(uts_len) ]; then \
1034           echo '"$(KERNELRELEASE)" exceeds $(uts_len) characters' >&2;    \
1035           exit 1;                                                         \
1036         fi;                                                               \
1037         (echo \#define UTS_RELEASE \"$(KERNELRELEASE)\";)
1038 endef
```

自動生成される順などまで含めると若干不正確だがこんな感じとなっている。

[**scripts/gcc-version.sh**](http://lxr.free-electrons.com/source/scripts/gcc-version.sh?v=4.6)にもそれっぽいのがあって[**scripts/Kbuild.include**](http://lxr.free-electrons.com/source/scripts/Kbuild.include?v=4.6)から使われているが、ここで作られる**cc-name**, **cc-version**などは、開発者向けには解放されているものの([**Documentation/kbuild/makefiles.txt**](http://lxr.free-electrons.com/source/Documentation/kbuild/makefiles.txt?v=4.6)に言及がある)、直接これを使ってどうというのはないように読めた。

# 終わりに
ネタでさらっと書いて終わりにしようと思ってたのに、kernel hacker編どうしてこうなった...

# 参考
- [linux kernelのコードネームは無視していい - Qiita](http://qiita.com/satoru_takeuchi/items/e2b394db0d385608b379)
- [Linuxカーネルのコードネームの扱いが不憫なので/proc/sys/kernel/codenameで読めるようにした - φ(・・*)ゞ ｳｰﾝ カーネルとか弄ったりのメモ](http://kernhack.hatenablog.com/entry/2017/02/01/002433)
- [nsproxyとfork()周りのめも - φ(・・*)ゞ ウーン　カーネルとか弄ったりのメモ](http://kernhack.hatenablog.com/entry/2014/08/03/124519)