コグノスケ


link 未来から過去へ表示  link 過去から未来へ表示(*)

link もっと前
2022年5月26日 >>> 2022年6月25日
link もっと後

2022年5月26日

glibcのスレッドとスタック

目次: C言語とlibc

誰も興味ないglibcの話シリーズ、スレッドのスタックはどうやって確保するのか?を追います。

ソースコードはglibc-2.35を見ています。pthread_create() の実体はいくつかありますが、2.34以降ならば __pthread_create_2_1() という関数が実体です。

pthread_createの実体

// glibc/nptl/pthread_create.c

versioned_symbol (libc, __pthread_create_2_1, pthread_create, GLIBC_2_34);
libc_hidden_ver (__pthread_create_2_1, __pthread_create)
#ifndef SHARED
strong_alias (__pthread_create_2_1, __pthread_create)
#endif

主要な関数としては、スタックを確保するallocate_stack() とclone() を呼ぶcreate_thread() です。スレッド属性も見てますが、今回は特に使わないので無視します。

スレッド作成に関わる関数の呼び出し関係
__pthread_create_2_1
  allocate_stack
    get_cached_stack
  create_thread

スレッドのスタックは2通りの確保方法があります。1つはキャッシュ、もう1つはmmap() です。

スレッドのスタックを確保する処理

// glibc/nptl/pthread_create.c

static int
allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
		void **stack, size_t *stacksize)
{

  //...

      /* Try to get a stack from the cache.  */
      reqsize = size;
      pd = get_cached_stack (&size, &mem);
      if (pd == NULL)    //★★キャッシュがなければこのif文が成立してmmapでメモリ確保★★
	{
	  /* If a guard page is required, avoid committing memory by first
	     allocate with PROT_NONE and then reserve with required permission
	     excluding the guard page.  */
	  mem = __mmap (NULL, size, (guardsize == 0) ? prot : PROT_NONE,
			MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);

  //...

    retval = create_thread (pd, iattr, &stopped_start, stackaddr,
			    stacksize, &thread_ran);

  //...

このget_cached_stack() がNULLを返す=失敗=キャッシュからスタックを確保できなかった、という意味です。確保できない場合はmmap() を呼んでカーネルから匿名ページを確保し、スタック領域として使います。

キャッシュに追加する人は誰?

キャッシュからスタックを確保できる場合は何か?というと、既に終了したスレッドのスタック領域がキャッシュにある場合です。スレッドの回収pthread_join() が終わった後のスレッドのスタックにアクセスしてはいけません。つまり誰もアクセスしない領域です。

スタック領域をカーネルに返しても良いですが、カーネルからメモリを確保したり解放するのは一般的に遅い処理のため、別のスレッドのスタックとして再利用してカーネルからのメモリ確保&解放の回数を減らし、効率を上げる仕組みと思われます。

まずは読み出す側であるget_cached_stack() 関数のコードから見ます。ちなみにコード内に頻出するtcbという単語はThread Controll Blockの略だそうです。

キャッシュからスタックを確保する処理

// glibc/nptl/allocatestack.c

static struct pthread *
get_cached_stack (size_t *sizep, void **memp)
{
  size_t size = *sizep;
  struct pthread *result = NULL;
  list_t *entry;

  lll_lock (GL (dl_stack_cache_lock), LLL_PRIVATE);

  /* Search the cache for a matching entry.  We search for the
     smallest stack which has at least the required size.  Note that
     in normal situations the size of all allocated stacks is the
     same.  As the very least there are only a few different sizes.
     Therefore this loop will exit early most of the time with an
     exact match.  */
  list_for_each (entry, &GL (dl_stack_cache))    //★★キャッシュのリストを全部調べる★★
    {
      struct pthread *curr;

      curr = list_entry (entry, struct pthread, list);
      if (__nptl_stack_in_use (curr) && curr->stackblock_size >= size)
	{
	  if (curr->stackblock_size == size)    //★★一致するサイズのスタックがあれば使う★★
	    {
	      result = curr;
	      break;
	    }

	  if (result == NULL
	      || result->stackblock_size > curr->stackblock_size)
	    result = curr;
	}
    }

  //...

カギを握るのはdl_stack_cacheというスタック領域をキャッシュするリストのようです。先程も言いましたが、このリストに要素が追加されるタイミングの1つはpthread_join() です。pthread_join() からコードを追います。

pthread_join() からスタックをキャッシュに追加する処理まで

// glibc/nptl/pthread_join.c

int
___pthread_join (pthread_t threadid, void **thread_return)    //★★pthread_join() の実体★★
{
  return __pthread_clockjoin_ex (threadid, thread_return, 0 /* Ignored */,
				 NULL, true);
}
versioned_symbol (libc, ___pthread_join, pthread_join, GLIBC_2_34);


// glibc/nptl/pthread_join_common.c

int
__pthread_clockjoin_ex (pthread_t threadid, void **thread_return,
                        clockid_t clockid,
                        const struct __timespec64 *abstime, bool block)
{
  struct pthread *pd = (struct pthread *) threadid;

  //...

  void *pd_result = pd->result;
  if (__glibc_likely (result == 0))
    {
      /* We mark the thread as terminated and as joined.  */
      pd->tid = -1;

      /* Store the return value if the caller is interested.  */
      if (thread_return != NULL)
	*thread_return = pd_result;

      /* Free the TCB.  */
      __nptl_free_tcb (pd);    //★★これ★★
    }
  else
    pd->joinid = NULL;

  //...


// glibc/nptl/nptl_free_tcb.c

void
__nptl_free_tcb (struct pthread *pd)
{
  /* The thread is exiting now.  */
  if (atomic_bit_test_set (&pd->cancelhandling, TERMINATED_BIT) == 0)
    {
      /* Free TPP data.  */
      if (pd->tpp != NULL)    //★余談TPP = Thread Priority Protectだそうです★
        {
          struct priority_protection_data *tpp = pd->tpp;

          pd->tpp = NULL;
          free (tpp);
        }

      /* Queue the stack memory block for reuse and exit the process.  The
         kernel will signal via writing to the address returned by
         QUEUE-STACK when the stack is available.  */
      __nptl_deallocate_stack (pd);    //★★これ★★
    }
}
libc_hidden_def (__nptl_free_tcb)


// glibc/nptl/nptl-stack.c

void
__nptl_deallocate_stack (struct pthread *pd)
{
  lll_lock (GL (dl_stack_cache_lock), LLL_PRIVATE);

  /* Remove the thread from the list of threads with user defined
     stacks.  */
  __nptl_stack_list_del (&pd->list);

  /* Not much to do.  Just free the mmap()ed memory.  Note that we do
     not reset the 'used' flag in the 'tid' field.  This is done by
     the kernel.  If no thread has been created yet this field is
     still zero.  */
  if (__glibc_likely (! pd->user_stack))
    (void) queue_stack (pd);    //★★これ★★
  else
    /* Free the memory associated with the ELF TLS.  */
    _dl_deallocate_tls (TLS_TPADJ (pd), false);

  lll_unlock (GL (dl_stack_cache_lock), LLL_PRIVATE);
}
libc_hidden_def (__nptl_deallocate_stack)


/* Add a stack frame which is not used anymore to the stack.  Must be
   called with the cache lock held.  */
static inline void
__attribute ((always_inline))
queue_stack (struct pthread *stack)
{
  /* We unconditionally add the stack to the list.  The memory may
     still be in use but it will not be reused until the kernel marks
     the stack as not used anymore.  */
  __nptl_stack_list_add (&stack->list, &GL (dl_stack_cache));  //★★キャッシュのリストに追加★★

  GL (dl_stack_cache_actsize) += stack->stackblock_size;
  if (__glibc_unlikely (GL (dl_stack_cache_actsize)
			> __nptl_stack_cache_maxsize))
    __nptl_free_stacks (__nptl_stack_cache_maxsize);
}

やっとリストdl_stack_cacheにスタック領域を追加している場所まで来ました。もっとあっさりかと思いましたが、意外と呼び出しが深かったです……。

編集者:すずき(2022/05/27 20:39)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2022年5月27日

PulseAudioで簡易リモート再生

目次: ALSA

遠くの部屋にあるPCで再生した音声を手元のスピーカーで聞きたい場合、スピーカーの線を延々と伸ばすよりリモート再生したほうが楽です。

リモート再生の方法はいくつかありますが、最近のLinuxディストリビューションであれば大抵はPulseAudioがインストールされていると思うので、PulseAudioのTCP送信機能を使うのが楽でしょう。

Cookieの設定

クライアントとサーバーが同じ内容のCookie(~/.config/pulse/cookieにある)を持っていないと、下記のようにAccess deniedといわれて接続できません。

クライアントとサーバーでCookieの内容が異なるときのエラー
$ PULSE_SERVER=192.168.1.10 speaker-test -D pulse

speaker-test 1.2.9

Playback device is pulse
Stream parameters are 48000Hz, S16_LE, 1 channels
Using 16 octaves of pink noise
ALSA lib pulse.c:242:(pulse_connect) PulseAudio: Unable to connect: Access denied

Playback open error: -111,Connection refused

クライアントにCookieファイルをコピーできない場合は、module-native-protocol-tcpにauth-anonymous=1を渡すと良いそうです。

サーバー側

サーバー側は音声を受け取ってスピーカーなどに送る役目を果たします。私はスピーカーの横にRaspberry Piを置いてサーバーにしています。PulseAudioの設定は簡単で、

PulseAudioの設定ファイルを変更
# vi /etc/pulse/default.pa 

load-module module-native-protocol-tcp

既にPulseAudioが起動している場合があるので、一度終了させます。

PulseAudioの終了、起動
$ pacmd

Welcome to PulseAudio 12.2! Use "help" for usage information.

>>> exit

# PulseAudioの再起動をします。

$ pulseaudio -D

PulseAudioの設定テストを行う場合は-Dなし(フォアグラウンド実行)すると良いです。Crtl-Cで終了できますので、起動&終了が素早くできて楽です。

クライアント側

クライアント側はMP3などをデコードし、音声をサーバーに送る役目を果たします。使い方は環境変数PULSE_SERVERを指定して再生するだけです。

クライアント側で音声再生
$ export PULSE_SERVER=192.168.1.10    # ★★PulseAudioサーバー側のIPアドレス★★
$ mplayer --no-video test.mp3

本当はmodule-zeroconf-discoverを正しく設定すれば環境変数をいちいち設定する必要はなく、PulseAudioの設定GUIから出力先の一つとして選択できるようになるはずです。が、どうも私の環境だとうまく動いてくれなくて挫折しました……。

編集者:すずき(2023/11/30 16:16)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2022年6月4日

Might and Magic Book One TAS US版の世界記録を更新

目次: Might and Magicファミコン版

Might and MagicのTASですが、US版でもやってみました。現在のTASVideosの世界記録(8m 06s)はYouTubeで見ることができます([TAS] NES Might and Magic: Secret of the Inner Sanctum "item glitch" by Dammit in 08:06.91)。

ほぼ初挑戦にも関わらず、現在の記録を34秒も大幅に更新できました(7m 32s)。解説をつけた動画をアップロードしましたので、ニコニコ動画へのリンクも張っておきます(【TAS】US版Might and Magic Book One 7分32秒 マップ付き)。

いきなり大幅更新できた要因は「JP版での積み重ね」でしょうね。

JP版のTAS動画作成にあたって、エンカウントの仕組みやマップの解析など色々調べました。実はこれらの知識はUS版でも通用します。なのでMAPは見放題、エンカウント予測もできます。苦労して見つけたメッセージスキップ、1.5倍速入力などもタイム短縮に役立ちました。

JP版とUS版の大きな違い

US版はJP版の2年後に発売されました。日本語→英語に変更するいわゆる普通のローカライズの他に、操作性がイマイチだったユーザーインタフェースを改修したようです。ところが改修にしくじったらしく、ひどいバグ(特定操作でアイテムが変化する)も追加されています……。

アイテム変化バグを突くと重要なイベントアイテムをゴミから捏造できて、真のアラマー王のクエストだけ達成すれば、あとはアストラルに特攻してゲームクリアです……。手紙、ゾム・ザム兄弟、オーラ、ボルカノ神殿、デューム城、などなどメインクエストは完全無視。ひどいなこれは。

てなわけでUS版はクリアタイムがめちゃくちゃ早いです。アイテム化けバグがなく正規ルートを通らざるを得ないJP版では達成できないと思います。

メモ: 技術系の話はFacebookから転記しておくことにした。大幅に修正。

編集者:すずき(2022/06/11 00:28)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2022年6月19日

オシロスコープ

メモ。TektronixのMSO22は薄くて軽くて良さそうですね(メーカーの製品紹介サイト: 2シリーズMSOミックスド・シグナル・オシロスコープ - テクトロニクス)。Tektronixのオシロスコープで24万円は安い部類ですが、値段の絶対値だけ見ると個人で買うのは躊躇します。

私が自宅で使っているTektronix TBS 1052Bは入門用の格安モデルで5万円くらいでした。お買い得と言うよりは、値段相応の性能と言った方が正しそうな機種です。入力が2chなのは良いとしても、帯域が50MHzなので精々16MHz程度(= 50MHz / 3)の信号観測が限度です。それ以上の周波数では誤差が大きすぎるでしょう。

一段か二段上の性能のオシロスコープを買おうとすると、入門用でも10万円超え。やはり個人で買うには躊躇する値段です……。

メモ: 技術系の話はFacebookから転記しておくことにした。加筆。

編集者:すずき(2022/06/22 00:20)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2022年6月21日

Might and Magic Book One TAS US版がTASVideosにAccept

目次: Might and Magicファミコン版

先月末〜今月頭くらいにMight and Magic Book One TAS US版のTASをTASVideosに投稿していました。今日見たらAcceptedを経てPublishedになっていました(YouTubeへのリンク)。良きかな良きかな。

これでJP版もUS版も記録を制覇したので、ある意味世界一ですわ〜。競っている人がほぼいない孤独な世界一ですけどね……。

以前のTASは2007年と相当昔に記録されたものでした。前と同じ調子でしたら15年は保つでしょう。もっと言えば2022年にMight and Magic Book Oneで遊ぶ人はほとんどいないと思いますから、20年くらいは記録をキープできるんじゃないですかね。はっはっは。

TASVideosに同じゲームで2つの記録を投稿できた理由

TASVideosの閲覧者は国籍関係なく基本的に英語で見るという前提なので、英語版のソフトが存在するなら英語版でTASを作ってくれ、と明確に書いてあります(MovieRules - TASVideos)。ローカライズ版(JP版など)のTASは特別な理由がない限り歓迎されません。

この原則に従うとMight and MagicのTASもUS版のみがAcceptされそうなものですよね。しかし今回はちょっと特殊というか、JP版は違う「枠」扱いでAcceptしてもらいました。

TASVideosでクリアまでの最速タイムを目指すStandardと呼ばれる部門では、バグを使わない記録(Baseline版と呼ばれるようです)と、バグを使った記録を分けて受け付けている場合が多いです。今回挑戦したMight and Magic Book Oneの場合、

  • Baseline: アイテム変化バグのないJP版が相当
  • item glitch: US版が相当

と理解してもらったので、両方の記録ともに受け付けてもらえた、という訳です。

編集者:すずき(2022/06/22 00:48)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2022年6月22日

車のバッテリーと無意味なドライブ

久しく車に乗っておらずそろそろバッテリーが上がりそうな予感がしたため、昼休みに無意味に車に乗って走ってきました。

充電を最優先するために、昼間に運転(ライトOFF)し、エアコンもOFFにして、車のバッテリーに優しい運転を心がけました。でも中にいる人間には優しくありません。まだ初夏とはいえ車内はクソ暑いので、車から降りたらシャツの色が変わるくらい汗だくでした……。

後付けモニタの電圧表示を見ると、充電中は14.4Vくらいで充電終了と思われる頃に12.2Vくらいに下がります。つまり充電に要した時間がなんとなくわかるのです。今回はどうも20分くらいで充電を諦めていたみたいでした。

夏は比較的バッテリーは上がりにくいとはいえ、20分で本当に充電されたのかなあ?不安ですね。

編集者:すずき(2022/06/24 00:54)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2022年6月23日

ナノイー発生器故障

家でPanasonicのナノイー発生器F-GME03を使っています。しばらく快調に動作していましたが、最近は運転開始するとすぐに「強」のLEDが点滅して止まってしまいます。故障ですかねえ?

この製品はプラスのネジ回し1本で簡単に分解できるので、破壊しない程度に分解してみました。

外側のケースを外すとシロッコファンが見えます。ホコリだらけですが、折れたり固着している様子はありませんでした。電源基板は部品の焼損等はなさそうでした。回路がわからんので導通は見ていませんが、電源が壊れていたらそもそもファンすら回らないでしょう……。たぶん……。


シロッコファン(下)、ナノイー発生器(上の銀の部分)

一通り見た中で最も怪しいのはナノイー発生器です。


ナノイー発生器


ナノイー発生器(上部から見た様子)

電源らしきの端子や配線は綺麗で、部品の曲がりや折れもなさそうです。気になるのは中央の金属部分と放電用と思しき針が赤や黒の錆で覆われていることです。

錆はヤスリで削れば落ちますが、高電圧が掛かる部分ですし素人が適当に弄るのは怖いですね。復活は厳しそうだなあ。寿命と言って違和感ないくらいの期間は使えてたから良しとしましょうか。

編集者:すずき(2022/06/24 01:34)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link もっと前
2022年5月26日 >>> 2022年6月25日
link もっと後

管理用メニュー

link 記事を新規作成

<2022>
<<<05>>>
1234567
891011121314
15161718192021
22232425262728
293031----

最近のコメント5件

  • link 24年6月17日
    すずきさん (06/23 00:12)
    「ありがとうございます。バルコニーではない...」
  • link 24年6月17日
    hdkさん (06/22 22:08)
    「GPSの最初の同期を取る時は見晴らしのい...」
  • link 24年5月16日
    すずきさん (05/21 11:41)
    「あー、確かにdpkg-reconfigu...」
  • link 24年5月16日
    hdkさん (05/21 08:55)
    「システム全体のlocale設定はDebi...」
  • link 24年5月17日
    すずきさん (05/20 13:16)
    「そうですねえ、普通はStandardなの...」

最近の記事3件

  • link 24年4月12日
    すずき (07/05 10:40)
    「[台湾東部沖地震に寄付] ささやかではありますが台湾東部沖地震に寄付しました。日本の赤十字社→台湾の赤十字(正式名称...」
  • link 24年4月16日
    すずき (07/05 10:40)
    「[Zephyr SDKのhosttoolsは移動してはいけない、その2 - インストール時のバイナリ書き換え] 目次: Zep...」
  • link 24年4月17日
    すずき (07/05 10:39)
    「[VSCodeとMarkdownとPlantUMLのローカルサーバー] 目次: LinuxVSCodeのPlantUML Ex...」
link もっとみる

こんてんつ

open/close wiki
open/close Linux JM
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 2020年
open/close 2021年
open/close 2022年
open/close 2023年
open/close 2024年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報

合計:  counter total
本日:  counter today

link About www.katsuster.net
RDFファイル RSS 1.0

最終更新: 07/05 10:40