2016年10月アーカイブ

main()に来る前に呼ばれる関数の確認 (3/3)

解析の結果得られたよくわからない知見

● VDSOとは
https://linuxjm.osdn.jp/html/LDP_man-pages/man7/vdso.7.html
一部のsyscallを、syscallせずにユーザ空間で処理して返すことで処理高速化。
sysdeps/unix/sysv/linux/aarch64/init-first.c によれば
__vdso_gettimeofday, __vdso_clock_gettime, __vdso_clock_getres らしい

● __PRETTY_FUNCTION__
gcc拡張で、__PRETTY_FUNCTION__が使える
他に、__FILE__, __LINE__, __FUNCTION__, __DATE__, __TIME__, __FUNCDNAME__, __FUNCSIG__

● デフォルトのリンカスクリプト
$ ld --verbose するとよい → 参考: 革命の日々 その2: システムのデフォルトリンカスクリプトを調べる方法

● .ctor .dtor セクション
gcc-4.7から、init_array fini_array に変更された、互換性のためにからっぽセクションが作られる
...いや、新しいgccではもう作ってないな、新しい glibc は互換性のために見るようだ。

● preinit_array
なにに使われてるかよくわからない・・・
__attribute__((section(.preinit_array))) したらセグった、
ライブラリ呼び出しとかするにはアドレス変換とか必要なのかもしれない

● exit()
__libc_atexit には _IO_cleanup しか登録されていないので、
fflushしつつexitしたい場合は _IO_cleanup() だけよべばよさげ。
・・・と思ったけどシンボルないと怒られた、、_IO_cleanup() を呼ぶだけの fcloseall() を使えば良いみた
い。

● quick_exit()
あまりquickじゃないので、_exit()でよいと思う。

main()に来る前に呼ばれる関数の確認 (2/3)

dl-machine.h (glibc/sysdep/aarch64/dl-machne.h)
_dl_start()
 _dl_start_final()
  _dl_sysdep_start(arg, &dl_main);
    dl_mainはセクションの解釈とかっぽい
_dl_start_user
 _dl_init()
   dl_initfirst ... って何?(呼ばれてないっぽい)
   preinit_arrayを呼ぶ <--- ライブラリ側のpreinit_array、.preinit_arrayセクションリンカスクリプ
   call_init()
      mapごとのDT_INIT, DT_INIT_ARRAYを呼んでる?
※ libgcc_s.so の DT_INIT から libc の call_initに来ている?
      下記はlibcのmap
        _init()
         __init_misc()
            extern char * __progname;
            extern char * __progname_full;
         __ctype_init()
           upperとかlowerとかdigitとかalphaとかを定義しているみたい
      libgcc_s.soは DT_INIT_ARRAYもある
        frame_dummy()
      libm
        もframe_dummy()?
      libstdc++
        もframe_dummy()?
        PREINIT_FUNCTION_WEAK() って何? のフックがある
      libmylib
        constructor(0)に登録した関数はここから呼ばれるみたい
        global変数クラスのコンストラクタ(init_priority(1)もここから
           __cxa_atexit()でデストラクタを登録するみたい
        register_tm_clones()
_dl_start_userから突き抜けて↓実行する
_dl_start()の返り値が本体側(ライブラリじゃない側)のエントリポイントとなる
それがユーザ側の _start(0x400850) となる
_start()
 __libc_start_main  (libcの関数)
  rtld_fini(==_dl_fini)を__cxa_atexitで登録
    ==mapごとのfini_array関数を呼ぶためのもの
  init()
   __libc_csu_init
     __preinit_array
     _init  PREINIT_FUNCTION_WEAK() って何? のフックがある
     __init_array <-- 本体側にかいたのはここから呼ばれる
 first_callならmainを呼ぶ
 そうじゃないならpthreadに違いない
 exit, __GI_exit, __run_exit_handlers
  __call_tls_dtors()    thread_atexitを呼ぶための関数・・・みたい
  __exit_funcsに登録された関数を順に呼ぶ (atexitとか、グローバルコンストラクタから登録された関数や)
   - ~MyClass
   - _dl_fini
      _dl_sort_fini
     結局、DT_FINI_ARRAY を呼ぶ  <--- ここで mydeinit関数が呼ばれる
     mainプログラムの場合
       __do_global_dtors_aux() <--- .ctor, .dtorセクションを使ってたらここでハンドルされるっぽい
        deregister_tm_clones()
       DT_FINI を呼ぶ、/* Next try the old-style destructor.  */ らしい
         何もしない _fini が呼ばれる
     libhogehogeの場合
       __do_global_dtors_aux() <--- はすべてのライブラリで呼ばれる
     libmの場合
       __do_global_dtors_aux() <--- はすべてのライブラリで呼ばれる
       __cxa_finalize <<

__libc_csu_fini <-- 実質使われていない
_fini() <-- 実質使われていない

main()に来る前に呼ばれる関数の確認 (1/3)

実際に動かして確かめてみる。

mylib.hpp
--------------------------------------------------------------------------------
#ifndef __MYLIB_HPP__
#define __MYLIB_HPP__
class MyLibClass {
private:
  const char * const myname;
public:
  MyLibClass(const char * const name);
  ~MyLibClass();
};
#endif /* __MYLIB_HPP__ */
--------------------------------------------------------------------------------

mylib.cpp
--------------------------------------------------------------------------------
#include  <stdio.h>
#include  "mylib.hpp"
MyLibClass::MyLibClass(const char * const name) : myname(name) {
  printf("ctor %s %s\n", __FUNCTION__, myname);
}
MyLibClass::~MyLibClass() {
  printf("dtor %s %s\n", __FUNCTION__, myname);
}
static MyLibClass __attribute__((init_priority(1))) mylibclass1("mylib1");
static MyLibClass __attribute__((init_priority(1))) mylibclass2("mylib2");

static void __attribute__((constructor(65535))) mylibinitfunc1(void) {
  printf("%s\n", __FUNCTION__);
}
static void __attribute__((constructor(0))) mylibinitfunc2(void) {
  printf("%s\n", __FUNCTION__);
}
static void __attribute__((destructor(65535))) mylibdeinitfunc1(void) {
  printf("%s\n", __FUNCTION__);
}
static void __attribute__((destructor(65534))) mylibdeinitfunc2(void) {
  printf("%s\n", __FUNCTION__);
}
--------------------------------------------------------------------------------

main.cpp
--------------------------------------------------------------------------------
#include <stdio.h>
#include "mylib.hpp"
static MyLibClass __attribute__((init_priority(65535))) myclass1("myclass1") ;
static MyLibClass __attribute__((init_priority(1))) myclass2("myclass2");
static void __attribute__((constructor(65535))) myinit1(void) {
  printf("%s\n", __FUNCTION__);
}
static void __attribute__((constructor(0))) mynit2(void) {
  printf("%s\n", __FUNCTION__);
}
static void __attribute__((destructor(65535))) mydeinit1(void) {
  printf("%s\n", __FUNCTION__);
}
static void __attribute__((destructor(0))) mydeinit2(void) {
  printf("%s\n", __FUNCTION__);
}
int main(int argc, char *argv[]){
  return 0;
}
--------------------------------------------------------------------------------

実行結果
--------------------------------------------------------------------------------
$ LD_LIBRARY_PATH=./ ./main
mylibinitfunc2
ctor MyLibClass mylib1
ctor MyLibClass mylib2
mylibinitfunc1
mynit2
ctor MyLibClass myclass2
myinit1
ctor MyLibClass myclass1
dtor ~MyLibClass myclass1
dtor ~MyLibClass myclass2
mydeinit1
mydeinit2
mylibdeinitfunc1
dtor ~MyLibClass mylib2
dtor ~MyLibClass mylib1
mylibdeinitfunc2
--------------------------------------------------------------------------------

固めたヤツ(mytest.tgz)おいておきます。

main()に来る前に呼ばれる関数の確認 (目次)

C++のグローバル変数なクラスはコンストラクタがmain()に来る前に呼ばれるけど、それって誰がどうやって呼んでるの?という疑問から調べ始めた結果、
- Q. main()に来る前に呼ばれる関数ってどんなのがあるの?
- A. んなもんglibcのソースコードでも読めや、バーヤ、バーヤ ヽ(`Д´)ノ
でした。

・・・というグチだけだと味気ないので、まとまりない形だけど調べた結果をベタベタはっておきます。
- main()に来る前に呼ばれる関数の確認 (1/3)
- main()に来る前に呼ばれる関数の確認 (2/3)
- main()に来る前に呼ばれる関数の確認 (3/3)
なおこれら内容は Linux で gcc-4.9 + glibc-2.21 + aarch64 で試した結果の私個人の見解であり、私が所属する団体(ry

ext4のmountが遅い

eMMC上のext4でmountするのに約700msec前後かかって遅い。正しくumountしておくと早い、1分以上何もせずにしてから落とすと早い、syncしてから落とすと早い、という症状からどうもjournalっぽいと推測していたけど、いろいろアレな圧力がかかったので、マジメに調べてみた。

その結果やっぱりjournalだったというのはいいとして、結局のところ、journal replay の結果を commit しようとしていてその書き込みを同期にしていたので書き込み待ちで時間を取られていた。周辺コード読む限り、journalはそのうちmount後の書き込みで使い回されるけどそれが該当journalを上書きする前にreplay結果が書き出される保障がない(それが起こった瞬間に電源断が走るとjournalで戻せなくなる)、という点を懸念してのようだった。うーん、ケースとしては起こりにくいんだけど、確かに起こりうる系だなぁ。

というわけで、該当箇所を非同期書き込みに変更するパッチ(linux_journal_replay_nosync_2.diff)を用意してみたものの、電源断が起こりうるものにはさすがにこれは適用しない方がいいと思われる。

ちなみに、ext4 は Read Only で mountしても journal replay結果をcommitする(ディスクに書き込みをする)という挙動をする。え?それってありなの・・・

(※2017/01/12(Thu)追記) ドキュメントのroオプションのところにちゃんと書いてあった。
Mount filesystem read only. Note that ext4 will
replay the journal (and thus write to the
partition) even when mounted "read only". The
mount options "ro,noload" can be used to prevent
writes to the filesystem.

/etc/profile - ログインシェルが読む、Bourne shell互換で書く
/etc/environment - PAM-envが読む、name=value 形式しか書けない
/etc/bash(/etc/bashrc) - インタラクティブシェルが読む、bash向け
~/profile - ユーザごとの /etc/profile
~/.pam_environment - ユーザごとの /etc/environment
~/.bashrc インタラクティブシェル、bash向け
~/.bash_profile (~/.bash_login, ~/.profile) ログインシェル、bash向け。
~/.bash_logout - logout時に読む、bash向け。
~/.xinitrc, ~/.xprofile, /etc/xprofile ↑のX版

...なるほど、よくわからん...

● 15年くらい前に思っていたこと
- なんでもオープンソースになってしまうと誰もソフトウェアにお金を払わなくなってしまう
- ソフトウェア開発者に資本主義的な価値がなくなってしまう
- オープンソースであることを強要するGPLの考え方は資本主義の敵なのではないだろうか

極論はともかく、資本主義のもとで企業が活動していく前提なら、オープンソースなんて主流にはならないだろうと思っていた。けど、現実は、思いの外オープンソースだらけな社会になっていた。

● オープンソースでもやっていける理由
a) 一部の競争力ある部分だけはオープンソースにしていない
b) インテグレート&サポートで儲ける
c) ソフトはハード買ったらついてくるおまけ

a)の例としてはOracle・・・と思ったけどOracleも b)的になってきた気はする。Microsoft Windowsもオープンソースではないけど競合があるという意味で b) に近いかもしれない。c) はLSIメーカが当てはまる。RenesasNVIDIAも普通にGithubでソース公開してるような時代だしねぇ。

ただGPLは嫌われている、というよりGNUFSFの原理主義的なGPLがといった方が正確かもしれない。GPLなソフトウェアの商用利用については潜在的な問題が指摘され続けれていたけど、それはBusyBox訴訟で現実のものになった。GPLなら直ちに危険というわけでもないが、相手のスタンスがわからないままではやはり潜在的なリスクなってしまう。その点Linuxは違う。「2016年8月29日 「SFCとブラッドリー・クーンは病原体」 ―Linus,26年目のスタートは毒舌から」まぁLinusが死んだらどう変わってしまうか問題は残るか。

いずれにしても、b)的なインテグレート&サポートはソフトウェア開発者の仕事として残っている。いくらソフトウェアがオープンソースでそこにあっても、それをインストールしたり設定したりチューニングしたり、場合によっては再現性確認して報告したり自分出直したりという作業は依然として十分な仕事になっている。いやでもとらえ方によってはこれはもうソフトウェア開発ではないのかもしれないけど。

このアーカイブについて

このページには、2016年10月に書かれたブログ記事が新しい順に公開されています。

前のアーカイブは2016年9月です。

次のアーカイブは2016年11月です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

月別 アーカイブ

ウェブページ

Powered by Movable Type 7.9.0