読者です 読者をやめる 読者になる 読者になる

高専カンファレンス100 in 東京 - 振り返り

2015/12/19, 20の二日間に渡って高専カンファレンス100 in 東京を開催しました。場所は東京都調布市にある電気通信大学(通称:でんつーだい)で、一棟をまるっと借り切っての開催でした。 今回の開催では私も運営スタッフとして参加させていただき、(表向きは)アイスブレークという企画の担当をさせていただきました。 私自身は高専カンファレンスの運営に携わるのはこれが4回目で、比較的運営経験が多い部類に入るのではないかと思っています。

運営として何をどうしたかというのは他のスタッフの方々がそれぞれにブログを書いていただいているので、ここでも私が何をどうしたかを書いてみたいと思います。

スタッフ最年長

実行委員長から声をかけられたのは9月の頭ごろ、ある程度やりたいことが見えてきて、スタッフもそれなりに集まってからでした。 私のおぼろげな記憶が確かならば実行委員長が直接声をかけたと思われるメンバーの中では私が一番最後に合流した人間になるのではないかと思います。 面識のあるスタッフが半分と少々、残りは初めて顔を合わせる方々でしかもみな若い。 ほとんどは高専を卒業しているので二十歳すぎではあるものの、私とは一回り近くの差があることになります。 これまでの大規模開催では私よりも年齢が上の経験豊富な方がスタッフにいらっしゃったのですが、今回その役割をするのが自分なんだなと認識しました。

私の仕事

Wikiをみていただくと私にアサインされているのがアイスブレークという企画の立案・設計・実装・運用ということになるわけですが、では実際にそれをやったのか?となると答えはノーです。 詳細はうなすけくんのブログを読んでいただければと思うのですが、実際にアイスブレークについて実務をこなしたのはうなすけくん(@yu_suke1994)と実行委員長であるなっちゃん@marin72_com)、そしてアイスブレークの初動を支えてくれたえむけーくん(@mktakuya)です。 私がやった実務的なことといえば、当日のスタッフ業を除けば美味しいカレー屋さんを紹介したり打ち合わせの場を提供したことくらいでしょうか。 きっと他のスタッフからは仕事しないおじさんだなーと思われていたのではないかと思います。 もしそう思われていたのなら思惑どおりということになりますし、そうでなかったのであればまだまだ修行が足りないということになりそうです。

年長者としての仕事

前述の通り実務については若いスタッフのみなさんがさくさくとこなしてくれたおかげで、私自身は本来のおじさん業がある程度できたのではないかなと思っています。 ここでいうおじさん業というのは、

  • 全体を見渡してリスクコントロールをする
  • 先を見通して先手を打つ
  • 各人が言い難いことを年長者という立場を利用して代弁する
  • 外部とのつなぎをする
  • 足りない物資をどうにかする
  • 足りないお金をどうにかする
  • 若いスタッフがやりたいことをやりたいようにできる空気を作る
  • 具体的な手続きや手法を教える
  • 相談役・レビュアーになる

といったようなことになります。 どれもあまり実務的なものではありませんが、大規模なイベントの運営を行う上では必要になってくるものかなと思っています。 今回の運営にあたって上記のようなことを特に誰かに何かを話たことはありませんが、私がやる必要があるのは実務ではなくこういった部分であると思って参加させていただきました。 こういった話をちゃんとしていなくてごめんなさい。でもきっとこういう話をしたらみんな身構えてしまい迷惑をかけないように一生懸命になりすぎてしまう気がします。なので結果としてこのやり方で良かったのではないかなと思っています。

今後の仕事

東京での開催の実行委員として参加するのは今回かあるいは次回くらいで最後で良いのかなと思っています。 今後はKCFundsとしての活動だったり、地方の未開催の高専があればそこでの開催をサポートしたり、母校である沼津での開催が再度できるようにサポートしたり、そういったことをやっていきたいと思っています。 何も心配しなくても次の世代が、若い人たちが、夢を持って頑張れる場所を作っていってくれているのですから、私も彼らに負けないようにやるべきことをやっていきたいと思います。

みなさんの夢が叶いますように!

謝辞

会場を提供してくださった電気通信大学様、また機材の確保や学校との手続き等をおこなってくださった水戸先生、いつもパワフルな高専カンファレンス顧問の島田先生、大変ありがとうございました。 基調講演を快く引き受けてくださった大日向さん、五十嵐さん、発表者の皆様、参加者の皆様、冬の寒いなか二日間にわたるカンファレンスにご参加いただきありがとうございました。 そして実行委員のみんな、パワフルで優しくて気の利くみなさん無くしてはこのカンファレンスは成り立たなかったでしょう。過去の大規模開催ではスタッフだけで30〜40人という規模だったことを考えると、この人数で今回のカンファレンスを実行できたことは本当に凄いことだと思います。 そのみなさんを引き込んだ実行委員長のなっちゃんなっちゃんの一途さがカンファレンスを実現させる原動力となりました。 みなさん本当にありがとうございました。

mrubyにGVLを使ったmulti-threadingのsupportを追加する

前回mrubyのことを書いてから既に1年以上が経ち、非常にいまさら感はあるのですが、
mrubyにGVL実装版のmulti-threadingのsupportを追加してみました。

ソースコードは下記で公開しています。


crimsonwoods/mruby at implement_multithreading_support_with_GVL · GitHub

ベースにしているのはmruby 1.1.0のtagで、
下記のような変更を加えています。

  • mrb_stateをmrb_vm_contextとmrb_thread_contextに分割
  • GVLの実装用にmutexのAPIを追加
  • GVL用のAPIを追加
  • thread用のAPIを追加
  • atomic命令用のAPIを追加
  • threadのcontext-switch用にtimer threadを追加
  • Thread class実装用のmruby-threadをmrbgemsに追加
  • thread/mutex APIのpthread実装をmruby-pthreadとしてmrbgemsに追加
  • thread/mutex APIwin32実装をmruby-win32-threadとしてmrbgemsに追加

ということをしています。

threadとmutexの実装はmrbgemから初期化時に拡張できるようになっているので、pthreadやwin32 thread以外の実装を追加することも可能です。
また、thread/mutexに関して実装者が知っておくべき事項としては下記の事項です。

  • mutexはnon-recursiveのみのサポートでOK
  • mutexへのtry-lockは今のところ未使用(なので実装しなくても大丈夫)
  • sleepに0を渡したときにthreadのtime sliceを放棄することを期待している

ちなみに、Rubyから扱えるMutexクラスは今のところ実装していません。

この変更をmruby本体にいれるかどうかは、今後githubissue上で議論するとして、ひとまずGVL版でよければmulti-threadをサポートしたmrubyをお試し頂けるのではないかと思います。

追記

build_config.rbの書き方を記載するのを忘れていたので書いておきます。

mrbgemの追加と、multi-threadingのサポートを有効化するためのmacro定義を追加します。

  conf.gem 'mrbgems/mruby-thread'
#  conf.gem 'mrbgems/mruby-win32-thread' # for win32
  conf.gem 'mrbgems/mruby-pthread'      # for Linux

  conf.cc.defines << %w(MRB_USE_THREAD_API MRB_USE_MUTEX_API MRB_USE_GVL_API MRB_USE_ATOMIC_API)

こんな感じです。
mruby-win32-threadとmruby-pthreadは排他なので、どちらかだけを選択してください。
あとはこれをhostとhost-debugの両方に記載すればOKです。

Android 5.0 (Lollipop) のJavaアプリケーションは32bitと64bitのどちらで動作するか?

Android 5.0 (Lollipop) では64bitプロセッサへの対応が行われており、
Linux Kernelはもちろん多くのプロセスが64bitプロセスとして動作します。
また、DalvikVMに変わって採用されたARTランタイムにおいても64bitプロセッサへの対応は行われています。
さて、それでは64bitプロセッサで動作するAndroid上では全てのJavaアプリケーションが64bitとして動作するのでしょうか?
その答えは「場合によっては32bitプロセスとして動作することもある」となります。

AndroidにおけるJavaアプリの起動

さて、ここからは実際にどういったロジックで32bit/64bitプロセスとして分かれていくのかを、順をおって見ていきたいと思います。

起動までの全体像

まずはおさらい的な内容になりますが、Javaアプリがどのように起動されるのか、
その大まかな流れを見てみたいと思います。

f:id:crimsonwoods:20141106234544p:plain

ざっくりとは上図のような流れになっており、箇条書きにすると下記のようになります。

  1. Intent発行
  2. ActivityManagerからZygoteへUnix domain socketを通じて通信
  3. Zygoteからfork(app_process)
  4. ZygoteからforkされたプロセスがJVMを起動してJavaの動作を開始

さてこの流れを頭にいれた上で、Lollipopでは何が違うのかを見て行きたいと思います。

Zygoteとapp_process

Androidの仕組みとして、全てのJavaアプリケーションプロセスがZygoteからforkされるというのは既にご理解頂けていると思います。
では、このZygoteの正体とは何でしょうか?それはapp_processというプログラムになります。
このapp_processはinit(一番最初に起動されるユーザーランドのプロセスで、初期化のためのいろいろな処理を行います)から起動され、
プロセスが不具合によってクラッシュしない限りは常にバックグランドで動作しています。

このZygoteの起動がAndroid 5.0になって、ちょっと変わっています。
どのように変わったのか、Android 4.4.4と比較してみましょう。
まず、Android 4.4.4のinit.rcです。

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart media
    onrestart restart netd
rootdir/init.rc - platform/system/core - Git at Google

Android 4.4.4ではinit.rcの中に直接Zygoteを起動するための処理が記載されていました。

続いてAndroid 5.0のinit.rcを見てみましょう。

import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /init.${ro.zygote}.rc
import /init.trace.rc
rootdir/init.rc - platform/system/core - Git at Google

こちらでは外部のZygote用のinit.xxx.rcを呼び出すように変更されています。
"ro.zygote"というシステムプロパティに設定されているのは64bitな環境であれば"zygote64_32"といったような値になりますので、
init.zygote64_32.rcというスクリプトがimportされることになります。

それではinit.zygote64_32.rcの中身を見てみましょう。

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
    class main
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart media
    onrestart restart netd
service zygote_secondary /system/bin/app_process32 -Xzygote /system/bin --zygote --socket-name=zygote_secondary
    class main
    socket zygote_secondary stream 660 root system
    onrestart restart zygote
rootdir/init.zygote64_32.rc - platform/system/core - Git at Google

Android 4.4.4ではapp_processのみをサービスとして起動していましたが、Android 5.0ではapp_process32とapp_process64という別々のプログラムをサービスとして起動しています。
このことから、Android 5.0 (Lollipop) では32bitプロセスとして起動する場合は32bitのZygoteを、64bitであれば64bitのZygoteを使うのだろうと予想されます。
(元々ZygoteはLibraryのLoadを省いて起動を高速化したりリソースを効率よく使用するための仕組みなので、上記の予想はそれほど的外れではないと思います)

また、app_processに渡される"--socket-name"オプションに異なる名前が渡されていますが、これは単純に32/64bitそれぞれで異なるUNIX domain socketを用意しておき、
32bitアプリケーションなら32bit版のZygoteを、64bitアプリケーションなら64bit版のZygoteを使うようになるということだと予想されます。

ZygoteとABI

さて、それでは次はZygoteに実際にJVMの起動をお願いする部分を見て行きたいと思います。
先に結論を書いてしまうと、
Zygoteの用意しているsocketに対してアクセスを行うのは、android.os.ProcessクラスにあるProcess#startメソッドになります。

    public static final ProcessStartResult start(final String processClass,
                                  final String niceName,
                                  int uid, int gid, int[] gids,
                                  int debugFlags, int mountExternal,
                                  int targetSdkVersion,
                                  String seInfo,
                                  String abi,
                                  String instructionSet,
                                  String appDataDir,
                                  String[] zygoteArgs) {
        try {
            return startViaZygote(processClass, niceName, uid, gid, gids,
                    debugFlags, mountExternal, targetSdkVersion, seInfo,
                    abi, instructionSet, appDataDir, zygoteArgs);
        } catch (ZygoteStartFailedEx ex) {
            Log.e(LOG_TAG,
                    "Starting VM process through Zygote failed");
            throw new RuntimeException(
                    "Starting VM process through Zygote failed", ex);
        }
    }
core/java/android/os/Process.java - platform/frameworks/base - Git at Google

Process#startViaZygoteだけ見てもちょっと判りませんが、Process#startViaZygoteの内部でProcess#openZygoteSocketIfNeededを呼び出した後、Process#zygoteSendArgsAndGetResultをによって、Zygoteのsocketへアクセスを行います。
このZygote socketへのアクセスの結果として、ZygoteではプロセスのforとkJVMの起動が行われます。
さて、それではこの時32/64bitのどちらのZygoteにアクセスするかを決めているのは何でしょうか?

    private static ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx {
        if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
            try {
                primaryZygoteState = ZygoteState.connect(ZYGOTE_SOCKET);
            } catch (IOException ioe) {
                throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe);
            }
        }
        if (primaryZygoteState.matches(abi)) {
            return primaryZygoteState;
        }
        // The primary zygote didn't match. Try the secondary.
        if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) {
            try {
            secondaryZygoteState = ZygoteState.connect(SECONDARY_ZYGOTE_SOCKET);
            } catch (IOException ioe) {
                throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe);
            }
        }
        if (secondaryZygoteState.matches(abi)) {
            return secondaryZygoteState;
        }
        throw new ZygoteStartFailedEx("Unsupported zygote ABI: " + abi);
    }

Process#openZygoteSocketIfNeededの中身を見てみると、Zygoteへの接続を予め確立しておき、32/64bit版それぞれに対してZygoteState.matchesメソッドを呼び出すことでどのsocketを使用するかを決定しています。
では、ZygoteState.matchesを見てみましょう。
ZygoteStateクラスはProcessクラスの内部に定義されています。

        boolean matches(String abi) {
            return abiList.contains(abi);
        }
core/java/android/os/Process.java - platform/frameworks/base - Git at Google

実に完結で、内部に保持しているABIのリストに該当するものがあるかどうかを見ているわけですね。
ではこの内部のabiListなるものはどうやって生成されるのか?ですが、これはZygoteState#connectの呼び出し時に解決されます。

        public static ZygoteState connect(String socketAddress) throws IOException {
            DataInputStream zygoteInputStream = null;
            BufferedWriter zygoteWriter = null;
            final LocalSocket zygoteSocket = new LocalSocket();
            try {
                zygoteSocket.connect(new LocalSocketAddress(socketAddress,
                        LocalSocketAddress.Namespace.RESERVED));
                zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream());
                zygoteWriter = new BufferedWriter(new OutputStreamWriter(
                        zygoteSocket.getOutputStream()), 256);
            } catch (IOException ex) {
                try {
                    zygoteSocket.close();
                } catch (IOException ignore) {
                }
                throw ex;
            }
            String abiListString = getAbiList(zygoteWriter, zygoteInputStream);
            Log.i("Zygote", "Process: zygote socket opened, supported ABIS: " + abiListString);
            return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter,
                    Arrays.asList(abiListString.split(",")));
        }
    private static String getAbiList(BufferedWriter writer, DataInputStream inputStream)
            throws IOException {
        // Each query starts with the argument count (1 in this case)
        writer.write("1");
        // ... followed by a new-line.
        writer.newLine();
        // ... followed by our only argument.
        writer.write("--query-abi-list");
        writer.newLine();
        writer.flush();
        // The response is a length prefixed stream of ASCII bytes.
        int numBytes = inputStream.readInt();
        byte[] bytes = new byte[numBytes];
        inputStream.readFully(bytes);
        return new String(bytes, StandardCharsets.US_ASCII);
    }
core/java/android/os/Process.java - platform/frameworks/base - Git at Google

ZygoteState#connectではProcess#getAbiListを呼び出して、動的にZygoteから対応しているABI(Application Binary Interface)のリストを取得しています。
ABIについての詳細はNDKに同梱されているCPU-ARCH-ABISのドキュメントを読んで頂ければよいかと思います。
さて、ここまでをまとめてみましょう。

  1. Zygoteは常時32bit版と64bit版が同時に動いている
  2. 32bit Javaアプリケーションは32bit版のZygoteを使用する(推測)
  3. 64bit Javaアプリケーションは64bit版のZygoteを使用する(推測)
  4. ZygoteへのJVMの起動依頼はProcess#startメソッドで行われる
  5. Process#startメソッドは渡されたABIから、32bit/64bit版のそれぞれのZygoteのうちABIに対応している方の接続を利用する。

あとは、「Process#startメソッドに渡されるABIがどのようにして決定されるのか?」という点と、「Zygoteが対応しているABIはどうやって決定されるのか?」が判れば、Javaアプリケーションが32/64bitのどちらで動作するのかが判るということになります。

ZygoteとABI

「Zygoteが対応しているABIはどうやって決定されるのか?」については、Zygoteの元になっているapp_processのソースを読むことで知ることができます。
app_processが呼び出されると、SystemPropertyからABIのリストを取得し、それをそのままZygoteInitに渡すようになっています。
Zygoteの内部ではZygoteInitに渡されたパラメータを保持しておき、Zygote socketを経由して問い合わせがあると、保持していたパラメータを返すという構造になっています。

    Vector<String8> args;
    if (!className.isEmpty()) {
        // We're not in zygote mode, the only argument we need to pass
        // to RuntimeInit is the application argument.
        //
        // The Remainder of args get passed to startup class main(). Make
        // copies of them before we overwrite them with the process name.
        args.add(application ? String8("application") : String8("tool"));
        runtime.setClassNameAndArgs(className, argc - i, argv + i);
    } else {
        // We're in zygote mode.
        maybeCreateDalvikCache();
        if (startSystemServer) {
            args.add(String8("start-system-server"));
        }
        char prop[PROP_VALUE_MAX];
        if (property_get(ABI_LIST_PROPERTY, prop, NULL) == 0) {
            LOG_ALWAYS_FATAL("app_process: Unable to determine ABI list from property %s.",
                ABI_LIST_PROPERTY);
            return 11;
        }
        String8 abiFlag("--abi-list=");
        abiFlag.append(prop);
        args.add(abiFlag);
        // In zygote mode, pass all remaining arguments to the zygote
        // main() method.
        for (; i < argc; ++i) {
            args.add(String8(argv[i]));
        }
    }
    if (!niceName.isEmpty()) {
        runtime.setArgv0(niceName.string());
        set_process_name(niceName.string());
    }
    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args);
    } else if (className) {
        runtime.start("com.android.internal.os.RuntimeInit", args);
    } else {
        fprintf(stderr, "Error: no class name or --zygote supplied.\n");
        app_usage();
        LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
        return 10;
    }
cmds/app_process/app_main.cpp - platform/frameworks/base - Git at Google

ちょっと長いですが、肝心なのはproperty_getを呼び出している箇所以降になります。
property_getでSystemPropertyから取得したABIのリストが使用されるわけですが、ではこのSystemPropertyのキーはどうなっているのか?というと、下記のように定義されています。

#if defined(__LP64__)
static const char ABI_LIST_PROPERTY[] = "ro.product.cpu.abilist64";
static const char ZYGOTE_NICE_NAME[] = "zygote64";
#else
static const char ABI_LIST_PROPERTY[] = "ro.product.cpu.abilist32";
static const char ZYGOTE_NICE_NAME[] = "zygote";
#endif
cmds/app_process/app_main.cpp - platform/frameworks/base - Git at Google

わかりやすいですね。32/64bit版それぞれにABIのリストをもっているということになります。

各アプリケーションのABI

それでは最後の、「Process#startメソッドに渡されるABIがどのようにして決定されるのか?」についてを見て行きたいと思います。
Process#startメソッドが呼び出されているのはcom.android.server.am.ActivityManagerServiceのActivityManagerService.startProcessLockedメソッドになります。

            String requiredAbi = (abiOverride != null) ? abiOverride : app.info.primaryCpuAbi;
            if (requiredAbi == null) {
                requiredAbi = Build.SUPPORTED_ABIS[0];
            }
            String instructionSet = null;
            if (app.info.primaryCpuAbi != null) {
                instructionSet = VMRuntime.getInstructionSet(app.info.primaryCpuAbi);
            }
            // Start the process.  It will either succeed and return a result containing
            // the PID of the new process, or else throw a RuntimeException.
            boolean isActivityProcess = (entryPoint == null);
            if (entryPoint == null) entryPoint = "android.app.ActivityThread";
            checkTime(startTime, "startProcess: asking zygote to start proc");
            Process.ProcessStartResult startResult = Process.start(entryPoint,
                    app.processName, uid, uid, gids, debugFlags, mountExternal,
                    app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
                    app.info.dataDir, entryPointArgs);
services/core/java/com/android/server/am/ActivityManagerService.java - platform/frameworks/base - Git at Google

abiOverrideが指定されている場合はそのABIを、そうでなければApplicationInfoクラスのprimaryCpuAbiをABIとして渡しています。
現状ではabiOverrideはInstrumentationの場合にだけ使用されるオプションになるため、通常のアプリケーションの起動時にはabiOverrideはnullになります。
このことから、Process#startに渡されるのはアプリケーションごとのABIに関する情報で、その情報はProcessRecordクラスに保持されているということが判ります。
では、ProcessRecordはどのように生成され、primaryCpuAbiがどのように設定されるのでしょうか?
ProcessRecordを生成するのはActivityManagerService.newProcessRecordLockedメソッドになります。

    final ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess,
            boolean isolated, int isolatedUid) {
        String proc = customProcess != null ? customProcess : info.processName;
        BatteryStatsImpl.Uid.Proc ps = null;
        BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
        int uid = info.uid;
        if (isolated) {
            if (isolatedUid == 0) {
                int userId = UserHandle.getUserId(uid);
                int stepsLeft = Process.LAST_ISOLATED_UID - Process.FIRST_ISOLATED_UID + 1;
                while (true) {
                    if (mNextIsolatedProcessUid < Process.FIRST_ISOLATED_UID
                            || mNextIsolatedProcessUid > Process.LAST_ISOLATED_UID) {
                        mNextIsolatedProcessUid = Process.FIRST_ISOLATED_UID;
                    }
                    uid = UserHandle.getUid(userId, mNextIsolatedProcessUid);
                    mNextIsolatedProcessUid++;
                    if (mIsolatedProcesses.indexOfKey(uid) < 0) {
                        // No process for this uid, use it.
                        break;
                    }
                    stepsLeft--;
                    if (stepsLeft <= 0) {
                        return null;
                    }
                }
            } else {
                // Special case for startIsolatedProcess (internal only), where
                // the uid of the isolated process is specified by the caller.
                uid = isolatedUid;
            }
        }
        return new ProcessRecord(stats, info, proc, uid);
    }
https://android.googlesource.com/platform/frameworks/base/+/android-5.0.0_r2/services/core/java/com/android/server/am/ActivityManagerService.java

ここではProcessRecordを作っているだけで、パラメータとして受け取っとApplicationInfoをそのまま使いまわしているように見えます。
ではこのApplicationInfoはどこからやってくるのでしょうか?
正解はcom.android.server.pm.PackageManagerServiceクラスです。
ApplicationInfoはPackageManagerServiceで生成され、ActivityManagerServiceからその情報を参照する形になっています。
ActivityManagerService内部でPackageManagerService.queryIntentAcitivitiesなどを呼び出すことで、ApplicationInfoを取得することができるようになっています。

さて、ではApplicationInfoのprimaryCpuAbiが設定されるのは、PackageManagerServiceのどこでしょうか?
答えはPackageManagerService.scanPackageDirtyLIメソッドの中になります。

                pkg.applicationInfo.primaryCpuAbi = null;
                pkg.applicationInfo.secondaryCpuAbi = null;
                if (isMultiArch(pkg.applicationInfo)) {
                    // Warn if we've set an abiOverride for multi-lib packages..
                    // By definition, we need to copy both 32 and 64 bit libraries for
                    // such packages.
                    if (pkg.cpuAbiOverride != null
                            && !NativeLibraryHelper.CLEAR_ABI_OVERRIDE.equals(pkg.cpuAbiOverride)) {
                        Slog.w(TAG, "Ignoring abiOverride for multi arch application.");
                    }
                    int abi32 = PackageManager.NO_NATIVE_LIBRARIES;
                    int abi64 = PackageManager.NO_NATIVE_LIBRARIES;
                    if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
                        if (isAsec) {
                            abi32 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_32_BIT_ABIS);
                        } else {
                            abi32 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
                                    nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS,
                                    useIsaSpecificSubdirs);
                        }
                    }
                    maybeThrowExceptionForMultiArchCopy(
                            "Error unpackaging 32 bit native libs for multiarch app.", abi32);
                    if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
                        if (isAsec) {
                            abi64 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_64_BIT_ABIS);
                        } else {
                            abi64 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
                                    nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS,
                                    useIsaSpecificSubdirs);
                        }
                    }
                    maybeThrowExceptionForMultiArchCopy(
                            "Error unpackaging 64 bit native libs for multiarch app.", abi64);
                    if (abi64 >= 0) {
                        pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[abi64];
                    }
                    if (abi32 >= 0) {
                        final String abi = Build.SUPPORTED_32_BIT_ABIS[abi32];
                        if (abi64 >= 0) {
                            pkg.applicationInfo.secondaryCpuAbi = abi;
                        } else {
                            pkg.applicationInfo.primaryCpuAbi = abi;
                        }
                    }
                } else {
                    String[] abiList = (cpuAbiOverride != null) ?
                            new String[] { cpuAbiOverride } : Build.SUPPORTED_ABIS;
                    // Enable gross and lame hacks for apps that are built with old
                    // SDK tools. We must scan their APKs for renderscript bitcode and
                    // not launch them if it's present. Don't bother checking on devices
                    // that don't have 64 bit support.
                    boolean needsRenderScriptOverride = false;
                    if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && cpuAbiOverride == null &&
                            NativeLibraryHelper.hasRenderscriptBitcode(handle)) {
                        abiList = Build.SUPPORTED_32_BIT_ABIS;
                        needsRenderScriptOverride = true;
                    }
                    final int copyRet;
                    if (isAsec) {
                        copyRet = NativeLibraryHelper.findSupportedAbi(handle, abiList);
                    } else {
                        copyRet = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
                                nativeLibraryRoot, abiList, useIsaSpecificSubdirs);
                    }
                    if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) {
                        throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                                "Error unpackaging native libs for app, errorCode=" + copyRet);
                    }
                    if (copyRet >= 0) {
                        pkg.applicationInfo.primaryCpuAbi = abiList[copyRet];
                    } else if (copyRet == PackageManager.NO_NATIVE_LIBRARIES && cpuAbiOverride != null) {
                        pkg.applicationInfo.primaryCpuAbi = cpuAbiOverride;
                    } else if (needsRenderScriptOverride) {
                        pkg.applicationInfo.primaryCpuAbi = abiList[0];
                    }
                }
services/core/java/com/android/server/pm/PackageManagerService.java - platform/frameworks/base - Git at Google

少々長いですが、このコードの中でやっているのは、APKがマルチアーキテクチャ構成かどうかをチェックして、マルチアーキテクチャ構成であれば32/64bitのネイティブライブラリを含んでいるかをそれぞれチェックし、primaryCpuAbiとsecondaryCpuAbiを設定するということです。
マルチアーキテクチャ構成の場合、64bitがサポートされている環境下で64bit版のネイティブライブラリを持っている場合はprimaryCpuAbiに64bit用のABIを設定、32bit版のネイティブライブラリを持っている場合はsecondaryCpuAbiに32bit用のABIを設定します。64bit版のネイティブライブラリを持っていない場合で、32bitがサポートされている環境下ではprimaryCpuAbiに32bit用のABIが設定されます。
マルチアーキテクチャ構成でない場合は、RenderScriptを使用するかどうかと、ネイティブライブラリの有無によって自動的に適切なABIが選択されます。

まとめ

Android 5.0 Lollipopにおいて、Javaアプリケーションが32/64bitのどちらで動作するかは、プラットフォームがサポートしているABIと、APKがマルチアーキテクチャ構成をとっているかどうかによります。

プラットフォームが64bitをサポートしている場合

  • マルチアーキテクチャ構成で64bitのネイティブライブラリを含むと64bit
  • マルチアーキテクチャ構成で32bitのネイティブライブラリしか含まない場合は32bit
  • マルチアーキテクチャ構成でない場合は含まれているネイティブライブラリに合わせた方

プラットフォームが64bitをサポートしていない場合

  • どの構成でも32bit

ということになります。

Android 5.0 Lollipopで導入されたセキュリティ関連の変更

Material Designとかそういうのはきっと他の誰かがさくっと書いていると思うので、
あまり一般のエンジニアからは注目されないセキュリティのお話なんかを書いてみようかなと思います。

ここ最近のAndroidにおけるSecurity関連の変更については、下記のリンク先にまとまっています。

Security Enhancements in Android 5.0 | Android Developers
Security Enhancements in Android 4.4 | Android Developers
Security Enhancements in Android 4.3 | Android Developers
Security Enhancements in Android 4.2 | Android Developers

今回はAndroid 5.0 (Lollipop)での変更点に絞ってざっくりと日本語訳(意訳)したいと思います。

Encrypted by default (暗号化のデフォルトON)

Android 5.0として工場出荷されたデバイスは、出荷時点から完全な内蔵ストレージの暗号化が有効化されます。
Android 5.0に更新したデバイスについては従来通り設定画面から暗号化を有効化できます。

Improved full disk encryption (内蔵ストレージの完全な暗号化に関する改善)

scryptを使ったブルートフォースアタックからパスワードを保護します。
また、デバイスによって機能がサポートされている場合は、ハードウェアキーストアを使用して暗号化に使用される鍵を格納することで、デバイス外からの攻撃を防ぎます。

Android sandbox reinforced with SELinux (SELinuxによるAndroidサンドボックス環境の強化)

従来よりも多くの領域においてSELinuxによるエンフォーシングモード(ポリシーベースの強制アクセス制御)が要求されるようになりました。

Smart Lock (スマートロック)

デバイスをアンロックするためのより柔軟な安全な仕組みを提供します。
例えば、NFCBluetoothを通じて信頼された他のデバイスや顔認証による自動的なアンロックを許可することができます。

Multi user, restricted profile, and guest modes for phones & tablets (複数ユーザー、制限されたプロフィル、ゲストモード)

タブレットではないデバイスに対しても複数ユーザー対応が提供されます。また、複数ユーザーにはゲストモードも含まれ、これはあなたのデータやアプリに対するアクセスを許可すること無く、あなたへのデバイスに対する一時的で簡単なアクセスを提供するものです。

Updates to WebView without OTA (OTA以外でのWebViewの更新)

OTA (On-The-Air) アップデートによる更新によらないWebViewの更新が可能になります。

Updated cryptography for HTTPS and TLS/SSL (HTTPSTLS/SSLにおける暗号化機能の更新)

TLSv1.2とTLSv1.1が有効化されます。Forward Secrecyは推奨されています。AES-GCMが有効化されます。また、強度の低い暗号化に関するアルゴリズム (MD5, 3DESなど) は無効化されます。
API Levelでサポートされるアルゴリズムの詳細は「SSLSocket | Android Developers」を参照してください。

non-PIE linker support removed (位置独立でない実行バイナリに対するリンカーのサポートを停止)

全ての動的にリンクされる実行バイナリについて、PIE (Position Independent Executable)をサポートする必要があります。

# JNIのライブラリなどを作るときは-fpieとか-fPIEオプションが必要になります

FORTIFY_SOURCE improvements (FORTIFY_SOURCEオプションの改善)

libcの提供するいくつかの関数 (stpcpy, stpncpy, read, recvfrom, FD_CLR, FD_SET, FD_ISSET) についてFORTIFY_SOURCEによる保護が実装されました。
これによりメモリ破壊による脆弱性から保護されます。

Security Fixes (その他のセキュリティ上の問題の修正)

複数Android特有の脆弱性が修正されています。これらの脆弱性に関する情報はOpen Handset Allianceのメンバーに提供され、AOSP (Android Open Source Project) に修正が適用されています。



ということで、ざっと書いてみました。
個々の機能についてはまた時間のあるときにでも。

コード書くのが捗るBGM

たまには作業用BGMのご紹介でもしてみようかと思います。
作業用なので、基本的にインストです。

勢いでガツガツコード書きたいとき

映画を観た人なら何も言わなくても判るはず。
全て粉砕してくれる!って思いながらコードを書けます。


from here to there

from here to there

  • 桑原あい トリオ・プロジェクト
  • Jazz
  • ¥1800
The Sixth Sense

The Sixth Sense

  • 桑原あい トリオ・プロジェクト
  • Jazz
  • ¥1800

BET UP / ai kuwabara trio project - YouTube
ピアノ+ベース+ドラムのシンプルな構成ながら勢いのある曲調と、コロコロと変化する表情が楽しめます。
長時間聴いてるとちょっと疲れるかもしれません。


Bridge

Bridge

  • fox capture plan
  • Jazz
  • ¥1200
後ろから追い立てられるような感じがたまりません。

思考をシェイクしたいとき

Trinity

Trinity

  • fox capture plan
  • Jazz
  • ¥1200

fox capture plan / 衝動の粒子 - YouTube
大小の水の粒がきらきら輝いているようなイメージ。
キラキラしたイメージでコードを書きたい時におすすめ。
コードがキラキラするかどうかは判りません。


SCENES

SCENES

  • bohemianvoodoo
  • Jazz
  • ¥1500
Color & Monochrome - EP

Color & Monochrome - EP


bohemianvoodoo/Adria Blue PV - YouTube
どこか郷愁を誘うような、それでいて異国情緒のある音楽。
気分がリフレッシュされ、いろいろと思考が進むかも?

少し気分が乗らないとき

Three Primary Sounds

Three Primary Sounds

  • Three Primary Colors
  • Jazz
  • ¥1050

【Official Trailer】Three Primary Colors "just call my name" - YouTube
メロウでグルーヴィーで、優しい感じ。
もし汚いコードを見ても、許してあげれるようになるかもしれません。


Pool

Pool

  • toconoma
  • Jazz
  • ¥1350

toconoma "Vermelho do sol" MV - YouTube
なんかちょっとノスタルジックな雰囲気。
馬鹿げたコードを見ても、人間そういうこともあるよねーって思える気がします。

その他(気分に合わせて)

MOVE ON

MOVE ON

  • 市原ひかりグループ
  • Jazz
  • ¥1650
UNITY

UNITY

  • 市原ひかりグループ
  • Jazz
  • ¥1650
ReInterpret the passage

ReInterpret the passage

  • Daisuke Takeuchi Trio
  • Jazz
  • ¥1200
Brooklyn Purple

Brooklyn Purple

  • 纐纈歩美
  • Jazz
  • ¥2000
YAMAMOTO REIKO TEMPUS FUGIT

YAMAMOTO REIKO TEMPUS FUGIT

Almah

Almah

  • Avishai Cohen
  • Jazz
  • ¥1300
GHIBLI meets JAZZ ~Beautiful Songs~

GHIBLI meets JAZZ ~Beautiful Songs~

  • Kazumi Tateishi Trio
  • Anime
  • ¥1800
Disney Adventures in Jazz

Disney Adventures in Jazz

mrubyにThread実装のための機能を追加したい - その後

ちょっと時間があいてしまいましたが、mrubyにThread実装のための機能を追加する件のその後のお話をしたいと思います。

あのあと、そんなにいろいろやったら別の処理系になっちゃうよ(意訳)という意見を頂いたので、
なるべくやること絞ってやろうということで、本当に必要なのは何?ってことで調査しつつ実装をしていました。

とりあえず結果だけを観たい人は、下記のgithubのURLに開発途中のbranchを公開しています。
このさきの何をどうしたという話をskipされる方はコードを読んでもらうのが早いかと思います。

crimsonwoods/mruby · GitHub
crimsonwoods/mruby-thread · GitHub

mrubyへの変更

さてそれでは、ここからはmrubyに加えた具体的な変更の話をしようと思います。

まず、今回の設計・実装のポリシーとしては、

  • なるべくmrubyのcoreに手を入れない
  • Thread/Lock APIはmrbgemで置き換え可能
  • multi-thread対応しなくて良い場合にover-headが極力発生しない

というものです。
で、このポリシーにそってどうしたらminimumな変更になるかを考えました。
その結果、大筋では下記のような機能を実装するということにしました。

  • LockのためのAPIの入り口だけmrubyのcoreで定義
  • ThreadのためのAPIの入り口だけmrubyのcoreで定義
  • resource shared RiteVMの実装
  • resource shared RiteVMの生成と破棄のAPIをmrubyのcoreで定義

ちなみに、このとき課題になったのは以下の点です。

  • Lock APIとしてmrbgemに何を要求するか?
  • Thread APIとしてmrbgemに何を要求するか?
  • mrbgemが一切取り込まれていない状態でもcompileが通ること

それぞれの課題について少し説明したいと思います。

Lock APIとしてmrbgemに要求すること

multi thread環境下でのlockやexclusive accessを提供する方法はいくつもあって、
例えばよく知られたものにはmutexやsemaphoreなどのlock APIがあります。
それ以外にもatomicやthrough putへの影響を押さえやすいread-write-lockなどがあります。
mrubyは組み込み機器も動作環境のターゲットとして含めていますし、何よりmulti-platformに対応しなければなりません。
そのため数あるlock architectureから、組み込み環境のようなcheapな環境であっても使えることが見込めて、かつmulti-platformで動作するようなものを選択しなければなりません。

とはいえ、実際にmulti-threadで動作をさせるためには、なんらかのthreading APIと最低限のlock API(恐らくmutex程度)の存在は期待できるため、それほど難しく考えないことにしました。
それに、最近は組み込み機器といってもLinuxが乗り、POSIXAPIがそのまま使えることも多々ありますし、mrubyを載せるような規模の組み込みソフトウェアが何らかのOSの上で動くことを期待するのは、それほど無理な話ではありません。
ということで、今回は、API的にはread-write-lockの形をとり、mutexしか提供されていないような環境下ではmutexを使用してread-write-lockを実装してもらうか、lockのupgrade/downgradeを諦めてmutexとしても使えるように実装するという方針を採用しました。

Thread APIとしてmrbgemに要求すること

Threadとresource shared RiteVMのマッピングの管理のために、Thread APIとしていくつかの要件を満たすようにmrbgemに要求する必要がありました。
具体的には、以下の様なものです。

  • 現在のThreadの識別子を取得できる機能の提供
  • Threadの識別子同士の同値比較機能の提供
  • Threadの停止を待機する機能の提供
  • 内部実装用のThread管理領域を破棄する機能の提供

このうち、Threadの識別子の取得は大抵のThread Libraryは提供しているはずなので問題はありません。
次のThreadの識別子同士の同値比較は、当初単純に比較すれば良い程度に考えていたのですが、Mac OSでのpthreadのように単純な比較ができないケースもあるため、この要求を追加することにしました。
Threadの停止はmruby側で子スレッドの待ちとかを実装したくなった時のために用意しています。
Thread管理領域の破棄については当然必要なのであまり気にする必要はありません。

mrbgemが一切取り込まれていない状態でもcompileが通ること

これはmrubyのbuildプロセスの問題で、具体的にはmrbc(RubyのコードをC言語のコードに変換するプログラム)のcompileが可能な状態でなければいけないというものです。
これだけだとちょっとよく判らないと思うので、もう少しきちんと説明をします。
まず、mrubyのbuildにはmrbcが必要です。そして、このmrbcはmrubyのcore機能の一部を使ってcompileされます。
このときmrbcのcompile時にはmrbgemは一切取り込まれません。
つまり、Thread APIやLock APIはmrbgemとして提供されますが、その機能が静的にLinkされる構造を前提にしてしまうと、mrbcのcompile時にLink Errorが起きてしまうということになります。
このため、Thread APIやLock APIのための口はmrubyのcore側で静的に用意されているが、
実際のAPIへの呼び出しはmrbgemの実行時に動的にbindされるという仕組みが必要になるわけです。

まとめ

ということで、幾つか課題はあったものの、とりあえずそれらをクリアして動くサンプルを書いてみました。
Threadクラスの実装は、mattnさんのmruby-threadをベースに、今回追加したLock APIやThread APIの実装を追加しています。
とりあえずですが、mruby-thread/example/example.rbを実行すると、

in thread: x is foo
in thread: v is "foo"
in main: v is bar
in main: r is baz

のように表示されるのではないかと思います。
お試しあれ!

mrubyにThread実装のための機能を追加したい

LeapMotionのライブラリをmruby用に書いたり、SDL2.0のライブラリをmruby用に書いたりしていて、
mrubyのRiteVMにもThreadをサポートするための実装が欲しいなーと常々思っていました。
で、ふとTwitterでmrubyにもThreadのサポートが欲しいよー的なことを発言したところ、
Matzさんからこんなお返事をいただきました。

ということで、mrubyでThreadを使うために何が必要かをいろいろと考えてみようと思います。

背景

mrubyでは「Threadをsupportしません」というスタンス(というよりはISOで定義されていないものはサポート対象外という方針だったよう)でした。
そうした方針が採用される中で、mrbgemが登場しmruby本体の外側に多様なライブラリの実装を追加することが可能になりました。
このmrbgemの登場により、mruby-threadのような、外付けの拡張機能がユーザーの手によって追加されるようになりました。

こうした外付けの機能の中には、既存ライブラリをmrubyへbindするものも多く、私自身が開発して公開しているもののいくつかも、そうした既存ライブラリをmrubyから利用するためのものでした(mruby-openalとかmruby-sdl2とか)。
既存ライブラリをmrubyから利用する上で大きな問題として存在するのが、マルチスレッドが前提となる機能の場合に、安全にかつ低リソースでmrubyを利用することが難しいということです。

たとえば、mruby-threadの実装を読むと判りますが、現在のmrubyでは生成されたsub-thread上でのコードの実行のために、RiteVMのインスタンスを再生成する必要があります。
このため、シンボルテーブルやヒープなどの、本来であればスレッド間で共有して欲しいリソースがスレッドごとに存在してしまいます。

目的

mruby上で動くRubyのコードでThreadが実装可能なように、
RiteVM側にThreadを実装する上で必要な機能を実装したいと思います。
Threadクラス自体の実装は含みません(Threadの実装はプラットフォーム依存なので)。

必要な機能

Threadの実装にどんな機能が必要か思いつく限りあげて行こうと思います。
基本的にcompile optionでThreadのサポートを完全に外せるようにできたら良いなと思っています。

Thread関連APIの整備

  • Threadの生成/破棄のAPIの定義
    • スレッドサポートの有無のチェック: mrb_is_thread_supported
    • スレッドの生成: mrb_thread_create
    • スレッドの破棄: mrb_thread_join
  • 定義したThread APIの実装はpluggableにしたい
    • mrbgemでAPIの実装を追加する?
    • mrb_open_allocfみたいに実装用APIを実行時に注入する?

こうすることで、Threadクラスの実装はmrubyのThread APIを呼び出すだけにできて、
かつplatform dependentなcodeをユーザーが自由に定義しやすくなります。
Threadクラスの拡張(priority設定したりとか)もmrbgemを作ってユーザーが自由に行えます。

Threadの生成

  • Thread用のcontextの生成(Thread用のmrb_stateの生成)
  • 共有リソースのlock/unlock実装の指定(プラットフォーム依存なので、allocfのようにユーザが置き換えられるように実装する)
    • lock resourceの生成用ユーザー指定関数型の定義
    • lock resourceの破棄用ユーザー指定関数型の定義
    • lock resourceのロック用ユーザー指定関数型の定義
    • lock resourceのアンロック用ユーザー指定関数型の定義
    • 共有リソース個別のロック/アンロックAPIの追加(mrb_lock_vm_heapとかそんな感じ)
    • 可能ならread-write-lockのサポート

Threadの実行

  • VM heapへの安全なアクセス
  • symbol tableへの安全なアクセス
  • global variable tableへの安全なアクセス

Threadの破棄

  • Thread用contextの破棄
  • 全ての子Threadの終了の待機

Threadと例外

  • 例外のためのjump bufferのThreadごとの個別管理

ThreadとGC

  • 参照がなくなったが実行状態なThreadオブジェクトの破棄の遅延
  • VM heapへの安全なアクセスとGC
    • GC開始時に対象のスレッドの一時停止/再開
  • GC Threadの構築(これは必須ではないけど)

その他

  • arenaもThread個別に持っていた方が良いかな?冗長な気がするけど。少なくともthreadの数に比例してarenaの領域を拡大しないなら、thread個別管理が良い気がする。


パッと思いついたのはこのくらい。
他にもいろいろ抜けがありそうだけれど、最低限これだけの機能が実装できれば、
mrubyにThreadの実装を付け足すことができるようになるのではないかと思う。