LoginSignup
4
2

More than 3 years have passed since last update.

mallocのパラメータが環境変数から設定される流れ

Posted at

はじめに

malloc関連のパラメータはmallopt(3)で変更することができます。
例えば、下記を実行することで、malloc内部でmmapを使うしきい値を1MBに変更することができます。

mallopt(M_MMAP_THRESHOLD, 1024*1024);

malloptで設定可能なパラメータのうちいくつかは、環境変数でも変更できます。
例えば、先述のM_MMAP_THRESHOLDは、MALLOC_MMAP_THRESHOLD_環境変数でも設定できます。

$ export MALLOC_MMAP_THRESHOLD_=1048576
$ ./a.out

環境変数であればプログラムの変更なしに適用できるので、システムのデフォルトを変更するときなどに有用です。
この環境変数によるmalloptの変更は、glibcのTunablesという仕組みを使っています。

本記事ではglibcのtunablesが設定される流れを
MALLOC_MMAP_THRESHOLD_環境変数を例に備忘メモとして残しておきます。
誤りなどありましたら、ご指摘いただければ幸いです。

環境は以下のとおりです。

$ arch
x86_64
$ uname -r
5.4.0-42-generic
$ lsb_release -d
Description:    Ubuntu 18.04.4 LTS
$ dpkg -l | grep libc-bin
ii  libc-bin             2.27-3ubuntu1.2 amd64           GNU C Library: Binaries

MALLOC_MMAP_THRESHOLD_環境変数が反映される流れ

1. ローダ(ld-linux.so)でauxiliary vectorを確認する

auxiliary vectorとは、getauxval(3)で取得することができる、
kernelのELF loaderから渡される実行ファイルの補助情報です。

ローダで、auxiliary vectorからAT_SECURE情報を取得し、
AT_SECUREが真の場合、global変数の__libc_enable_secureが立ちます。

ここでAT_SECUREは、少し勘違いしやすい名前ですが、
今のプロセスがセキュアに実行される必要があるということを意味します。
より具体的に言えば、下記のいずれかの場合にAT_SECUREが立ちます。

  • set-user-ID もしくは set-group-ID が有効なプログラム
  • capabilitiesを付与されたプログラム
glibc-2.27/elf/dl-sysdep.c
ElfW(Addr)
_dl_sysdep_start (void **start_argptr,
          void (*dl_main) (const ElfW(Phdr) *phdr, ElfW(Word) phnum,
                   ElfW(Addr) *user_entry, ElfW(auxv_t) *auxv))
{
...

  for (av = GLRO(dl_auxv); av->a_type != AT_NULL; set_seen (av++))
    switch (av->a_type)
      {
...
      case AT_SECURE:
        __libc_enable_secure = av->a_un.a_val;
        break;
...
      }

  __tunables_init (_environ);

...
  (*dl_main) (phdr, phnum, &user_entry, GLRO(dl_auxv));
  return user_entry;
}

2. ローダで、環境変数の値からtunable_list[]を更新

__tunables_initでは、tunable_list[]の中にある項目が環境変数に含まれている場合、
tunable_initialize()で環境変数の値が反映されます。

ただし、__libc_enable_secureが立っている場合(=AT_SECUREが立っている場合)は、
以下の3種類のsecurity_levelに応じた処理が行われます。

  • SXID_ERASE: AT_SECUREの場合、子プロセスが読まないように環境変数から削除する
  • SXID_IGNORE: AT_SECUREの場合、無視する(環境変数には残る)
  • NONE: 常に反映する
glibc-2.27/elf/dl-tunables.c
void
__tunables_init (char **envp)
{
...
  while ((envp = get_next_env (envp, &envname, &len, &envval,
                               &prev_envp)) != NULL)
    {
...
      for (int i = 0; i < sizeof (tunable_list) / sizeof (tunable_t); i++)
        {
          tunable_t *cur = &tunable_list[i];
...
              if (__libc_enable_secure)
                {
                  if (cur->security_level == TUNABLE_SECLEVEL_SXID_ERASE)
                    {
                      /* Erase the environment variable.  */
                      ...
                    }

                  if (cur->security_level != TUNABLE_SECLEVEL_NONE)
                    continue;
                }

              tunable_initialize (cur, envval);
              break;
            }
        }
    }
}

tunableの各項目のsecurity_levelは、環境変数名や型と併せて、.listファイルで定義されています。

glibc-2.27/elf/dl-tunables.list
glibc {
  malloc {
    ...
    mmap_threshold {
      type: SIZE_T
      env_alias: MALLOC_MMAP_THRESHOLD_
      security_level: SXID_IGNORE
    }
    ...
  }
}

scripts/gen-tunables.awkによってdl-tunables.listからdl-tunable-list.hを自動生成し、
そこにtunable_list[]が定義されます。

dl-tunable-list.h
static tunable_t tunable_list[] attribute_relro = {
  ...
  {TUNABLE_NAME_S(glibc, malloc, mmap_threshold), {TUNABLE_TYPE_SIZE_T, 0, SIZE_MAX}, {}, NULL, TUNABLE_SECLEVEL_SXID_IGNORE, "MALLOC_MMAP_THRESHOLD_"},
  ...
};

3. 初回malloc時に、tunable_list[]からmp_に反映

ここからは、tunableの話ではなく、mallocパラメータ固有の話になります。
また、実行タイミングもローダではなく、普通のmain関数に入ってから初めてmallocが呼び出されたときです。

プログラムで初めてメモリ確保関数を呼び出したときには、malloc_hook_iniが呼び出され、
そこからptmalloc_initを呼び出します。

glibc-2.27/malloc/hooks.c
static void *
malloc_hook_ini (size_t sz, const void *caller)
{
  __malloc_hook = NULL;
  ptmalloc_init ();
  return __libc_malloc (sz);
}

ptmalloc_initでは、2.で設定したtunable_listから、mmap_thresholdを取得し、
mallocパラメータをまとめて保持する構造体mp_に設定します。

glibc-2.27/malloc/arena.c
static void
ptmalloc_init (void)
{
...
  TUNABLE_GET (mmap_threshold, size_t, TUNABLE_CALLBACK (set_mmap_threshold));
...
}
glibc-2.27/elf/dl-tunables.c
void
__tunable_get_val (tunable_id_t id, void *valp, tunable_callback_t callback)
{
  tunable_t *cur = &tunable_list[id];

  switch (cur->type.type_code)
    {
...
    case TUNABLE_TYPE_SIZE_T:
    {
      *((size_t *) valp) = (size_t) cur->val.numval;
      break;
    }
...

  if (cur->initialized && callback != NULL)
    callback (&cur->val);
}

実際に設定するのはdo_set_mmap_threshold関数です。
ここを見ると実は設定値の上限があったり、
mmap_thresholdを設定するときには動的なしきい値制御を無効にしていることがわかります。

glibc-2.27/malloc/malloc.c
static inline int
__always_inline
do_set_mmap_threshold (size_t value)
{
  /* Forbid setting the threshold too high.  */
  if (value <= HEAP_MAX_SIZE / 2)
    {
      LIBC_PROBE (memory_mallopt_mmap_threshold, 3, value, mp_.mmap_threshold,
          mp_.no_dyn_threshold);
      mp_.mmap_threshold = value;
      mp_.no_dyn_threshold = 1;
      return 1;
    }
  return 0;
}

これで晴れて、mmapするかどうかの判断に、MALLOC_MMAP_THRESHOLD_環境変数の値が使われるようになります。
めでたしめでたし。

glibc-2.27/malloc/malloc.c
static void *
sysmalloc (INTERNAL_SIZE_T nb, mstate av)
{
...
  if (av == NULL
      || ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
      && (mp_.n_mmaps < mp_.n_mmaps_max)))
    {
...
          mm = (char *) (MMAP (0, size, PROT_READ | PROT_WRITE, 0));
    }
}

おわりに

mallocパラメータを環境変数で設定する場合、デバッガなりでちゃんと設定が反映されているか確認したほうが良いですね。

参考

mallocの動作を追いかける(環境変数編) - Qiita
mallopt(3) - Linux manual page
The GNU C Library
getauxval(3) - Linux manual page

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2