「オレは誰なんだよ…!? 言ってみろ!!」 自分の実行環境の情報を得る lscpu と,unameシステムコール,そしてByteorderの取得について.

「オレの名前を言ってみろ…!! オレは誰なんだよ」

…というのはスラムダンクの三井寿の山王工業戦での名セリフです.

自分で作成したシェルスクリプトやC/C++のプログラムの中で実行中の環境が何なのか、知りたい場合があります.

CPU の情報を知る lscpu と, そのソースコードからどのように CPU 情報等を得ているかを調べて、unameシステムコール、そしてCPUのByteorderの取得方法について書いてみます.

lscpu – display information about the CPU architecture

lscpu コマンドはCPUアーキテクチャ等の情報を表示するコマンドです.

以下は私の Chromebook での実行結果です.

$ lscpu 
 Architecture:                    aarch64
 CPU op-mode(s):                  32-bit, 64-bit
 Byte Order:                      Little Endian
 CPU(s):                          4
 On-line CPU(s) list:             0-3
 Thread(s) per core:              1
 Core(s) per socket:              2
 Socket(s):                       2
 Vendor ID:                       ARM
 Model:                           2
 Model name:                      Cortex-A53
 Stepping:                        r0p2
 BogoMIPS:                        26.00
 Vulnerability Itlb multihit:     Not affected
 Vulnerability L1tf:              Not affected
 Vulnerability Mds:               Not affected
 Vulnerability Meltdown:          Not affected
 Vulnerability Mmio stale data:   Not affected
 Vulnerability Retbleed:          Not affected
 Vulnerability Spec store bypass: Vulnerable
 Vulnerability Spectre v1:        Mitigation; __user pointer sanitization
 Vulnerability Spectre v2:        Mitigation; Branch predictor hardening, BHB
 Vulnerability Srbds:             Not affected
 Vulnerability Tsx async abort:   Not affected
 Flags:                           fp asimd evtstrm aes pmull sha1 sha2 crc32 cpuid

CPU アーキテクチャ、バイトオーダー、CPUコア数などの情報を表示します.

では、lscpu はどうやってこれらの情報を得ているのか、ソースコードを読んで調べてみました.

uname システムコール

lcpu は ulil-linux の一つとして開発されており, 2023年5月11日現在の最新バージョンは 2.39.

ソースコード はここから取得できます.

ソースコードを斜め読みしてみると、どうやらこの lscpu_read_architecture() でCPUアーキテクチャを取得しているようです.

struct lscpu_arch *lscpu_read_architecture(struct lscpu_cxt *cxt)
{
	struct utsname utsbuf;
	struct lscpu_arch *ar;
	struct lscpu_cputype *ct;

	assert(cxt);

	DBG(GATHER, ul_debug("reading architecture"));

	if (uname(&utsbuf) == -1)
		err(EXIT_FAILURE, _("error: uname failed"));

	ar = xcalloc(1, sizeof(*cxt->arch));
	ar->name = xstrdup(utsbuf.machine);

	if (cxt->noalive)
		/* reading info from any /{sys,proc} dump, don't mix it with
		 * information about our real CPU */
		;
	else {
#if defined(__alpha__) || defined(__ia64__)
		ar->bit64 = 1;	/* 64bit platforms only */
#endif

この関数の冒頭、uname() という関数が実行されてます.

uname コマンドは昔から使っておりましたが, uname() という関数があることは知りませんでした…

man 2 uname に掲載されているところを見るとシステムコールの様です.

NAME
        uname - get name and information about current kernel
SYNOPSIS
    #include <sys/utsname.h>
    int uname(struct utsname *buf);
                                                                

uname() システムコールを実行すると, struct utsname *buf に示されるアドレスに情報が格納されるようです.

struct utsname は sys/utsname.h に以下の様に定義されてます.

/* Structure describing the system and machine.  */
struct utsname
  {
    /* Name of the implementation of the operating system.  */
    char sysname[_UTSNAME_SYSNAME_LENGTH];

    /* Name of this node on the network.  */
    char nodename[_UTSNAME_NODENAME_LENGTH];

    /* Current release level of this implementation.  */
    char release[_UTSNAME_RELEASE_LENGTH];
    /* Current version level of this release.  */
    char version[_UTSNAME_VERSION_LENGTH];

    /* Name of the hardware type the system is running on.  */
    char machine[_UTSNAME_MACHINE_LENGTH];

#if _UTSNAME_DOMAIN_LENGTH - 0
    /* Name of the domain of this node on the network.  */
# ifdef __USE_GNU
    char domainname[_UTSNAME_DOMAIN_LENGTH];
# else
    char __domainname[_UTSNAME_DOMAIN_LENGTH];
# endif
#endif
  };

システムコール uname() を使った簡単なサンプルプログラムを書いてみました.

#include <stdio.h>
#include <sys/utsname.h>

int main ()
{
  struct utsname buf;
  int st = uname (&buf);
  if (st < 0)
    {
      perror ("uname");
      return -1;
    }

  printf ("sysname: %s\n", buf.sysname);
  printf ("nodename: %s\n", buf.nodename);
  printf ("release: %s\n", buf.release);
  printf ("version: %s\n", buf.version);
  printf ("machine: %s\n", buf.machine);

 return 0;
}

これを先ほどの 私の Chromebook で実行した結果です.

$ ./uname_sample 
 sysname: Linux
 nodename: penguin
 release: 5.10.136-19394-g7a24dee39fa0
 version: #1 SMP PREEMPT Wed Oct 12 18:46:02 PDT 2022
 machine: aarch64

uname() システムコールを使えば、かなり簡単にC/C++プログラムの中から実行中のCPU情報を得ることができます.

/proc/cpuinfo をオープンして読んでみる、という手もありますが、C/C++での文字列操作は目銅ですからね…

CPUのバイトオーダーを知る

さて、lscpu ではCPUのバイトオーダーも表示していましたが、その情報はどこから来るのか調べてみました.

どうやら以下に示す sysfs_get_byteorder() の中で取得しているようです.

enum sysfs_byteorder sysfs_get_byteorder(struct path_cxt *pc)
{
	int rc;
	char buf[BUFSIZ];
	enum sysfs_byteorder ret;

	rc = ul_path_read_buffer(pc, buf, sizeof(buf), _PATH_SYS_CPU_BYTEORDER);
	if (rc < 0)
		goto unknown;

	if (strncmp(buf, "little", sizeof(buf)) == 0) {
		ret = SYSFS_BYTEORDER_LITTLE;
		goto out;
	} else if (strncmp(buf, "big", sizeof(buf)) == 0) {
		ret = SYSFS_BYTEORDER_BIG;
		goto out;
	}

unknown:
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
	ret = SYSFS_BYTEORDER_LITTLE;
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
	ret = SYSFS_BYTEORDER_BIG;
#else
#error Unknown byte order
#endif

out:
	return ret;
}

おや? 少し前の util-linux から少しコードが追加されているようだ…

私の記憶に間違いがなければ、少し前のバージョンではCPUバイトオーダーの判定は処理系で定義されている __BYTE_ORDER__ マクロによって行われる、と思っていました.

__BYTE_ORDER__ が __ORDER_LIttLE_ENDIAN__ であれば Little Endian, __ORDER_BIG_ENDIAN__ であれば Big Endian と判断されます.

すなわちプログラムはコンパイルされた時点でエンディアンは決まってしまう、そう思ってました.

ですが、最新版の util-linux を見ると _PATH_SYS_CPU_BYTEORDER で定義されているパスから何やら読みだして CPU のバイトオーダーを判断している様子.

_PATH_SYS_CPU_BYTEORDER は /sys/kernel/cpu_byteorder を指してます.


#define _PATH_SYS_BLOCK		"/sys/block"
#define _PATH_SYS_DEVBLOCK	"/sys/dev/block"
#define _PATH_SYS_DEVCHAR	"/sys/dev/char"
#define _PATH_SYS_CLASS		"/sys/class"
#define _PATH_SYS_SCSI		"/sys/bus/scsi"
#define _PATH_SYS_CPU_BYTEORDER	"/sys/kernel/cpu_byteorder"
#define _PATH_SYS_ADDRESS_BITS	"/sys/kernel/address_bits"

ただ私のマシンには /sys/kernel/cpu_byteorder がありません. そのため, sysfs_get_byteorder() の中の ul_path_read_buffer() は失敗し, unknown タグの行に飛んで、従来通り __BYTE_ORDER__ マクロによる判定処理が行われている、と推測します.

/sys/kernel/cpu_byteorder とは?

/sys/kernel/cpu_byteorder とはなんだろ? としらべてみたところ、この様な記事が見つかりました.

Certain files in procfs are formatted in byteorder-dependent formats.
For example the IP addresses in /proc/net/udp.

When using emulation like qemu-user, applications are not guaranteed to
be using the same byteorder as the kernel.
Therefore the kernel needs to provide a way for applications to discover
the byteorder used in API-filesystems.
Using systemcalls is not enough because these are intercepted and
translated by the emulation.

Also this makes it easier for non-compiled applications like
shellscripts to discover the byteorder.

qemu-user のようなエミュレーションを使用する際にカーネルのバイトオーダーを取得したい、という要望があって、機能が追加されたようです.

そのカーネルの機能追加に対応したのが先の sysfs_get_byteorder() の様です. なかなか興味深いことです.

この手の話題はなんだかちょっと興奮します(笑)

気が付いたら 2023年でテックネタを書いたのはこれが初の様で…ちょっと反省しております.

ともかく、この手のハード・ソフト境界ネタは大好物で、コードを読むととても興奮します(笑)

この記事を書くにあたって参考にしたのがこれ, ソフトウェアデザイン.

技術雑誌が少なくなった今、とても貴重な存在です.

では