setxkbmapがわからない

xmodmapがもうダメダメなので、 setxkbmap を使うことにした。

しかし、man を読んでも自分で設定する方法がよくわからん…

一応安定のArchLinux wikiで設定方法は載ってるんだが…

Xorg でのキーボード設定 - ArchWiki

XkbOptionsに色々書き足すか、設定ファイルを書き換えるかする方法。

後者はまぁもちろんそれは動くだろう。 /usr/share/X11/xkb以下にxkbで使われるcomponentが用意してある。それを書き換えればまぁ動く。結局わからなかったので最終的にこれで解決した。

前者は、ubuntuだと/etc/default/keyboardのXKBOPTIONSを書き換えてdpkg-reconfiugreすることになる。

これはまぁ動くんだろうけど、問題はoption(というかsymbol)をどうやって自分で定義し、システムに読み込ませるか。

rulesファイルにoptionに関する記述はあり、caps lockを無効にしてctrlとして使うオプションであるctrl:nocapsの定義とかを見て$HOME以下に書いてみたりしたんだけどうーんうまくいかない。

unix.stackexchange.com

-Iでinclude pathを指定する方法でうまくいかないって言ってる人はいるみたい。

ちなみに、 /etc/gdm3/XSession で $HOME/.Xkbdmap が使われるようになっているので、このファイルで -model ctrl:nocaps を指定してみたが効かず。

これは多分、起動後にコマンド叩くのを自動化する系の解決法の場合によく見る sleep 入れないとだめっていう問題のような気がする。わからんけど。

結局何もわからなかった

まぁ こういう方法→Ubuntu 16.04 で XKB を使ってキーマップをカスタマイズする を使えば自分でいくらでも設定をかけるはずだが、なんかなぁ。自分で定義したオプションをさくっと追加してオプションを追加するだけの方法が知りたかった

Brainf*ckのインタープリタとJITコンパイラ

Adventures in JIT compilation: Part 1 - an interpreter - Eli Bendersky's website

Adventures in JIT compilation: Part 2 - an x64 JIT - Eli Bendersky's website

これらの記事を読み、part2まで真似して書いてみた。

brainf*ck jit · GitHub

記事ではC++で書かれているが、自分はC++は全くわからないのでRustで書いた。

まずシンプルなインタープリタを書いて、遅いよねってなって、まずはインタープリタとして徐々に最適化していく。

で、結構速くなったよね、というところで JIT コンパイルしてみる。

そうすると頑張って最適化したインタープリタよりも速いねぇとなり、さらに JIT しつつ最適化もするともっと速くなるよね。となりpart2は終わり。

このあとLLVMをbackendにしてやってみるとかが続くが、まだやってない。

この記事はJIT初心者の練習の題材としてとても良くて、JITといえばなんとなく実行時にコンパイルしてるんだろうな、という程度にしか理解がなく、JITコンパイラを書いてみるとかいうと途端に意味不明になるようなレベルの人ならこれを書いてみるとなるほど、となるところがあるかもしれない。

個人的にはどういうところが良いと感じたかというと、

  • パースしたコードをインタープリタとしてそのまま実行するのではなくて
  • パースした結果を機械語コンパイル
  • 確保しておいた実行可能なメモリ領域に書き込んで
  • そのメモリ領域を実行する

というところを実際にコードを書いて体験できたので理解が進んだ。

ここで書いたのはbrainf*ckであり個々の命令が単純なので全部を変換したが、実際にはまずはインタープリタでブロックの呼び出し回数をトレースしつつ実行し、頻繁に呼び出されるものだけをコンパイルして実行する、とかになるんだろう。

LuaJITとかがまさにそうで、実行回数を数えておいて必要に応じてJITの処理をする。

参考: - LuaJIT 解析 - Non-public jit.* API

むしろ他にどういったものがあるのかとかも興味あるが、まずは雰囲気がわかってきたのでLuaJITとか、最近だとJavaで書かれたGraalとかがあるんでそのへん調べてみたい。

余談だけど機械語を手書きするときは Online x86 and x64 Intel Instruction Assembler がとても便利。 as とか nasm でやってもいいのかもしれないけど、まとまって書くわけでもないときはこれが楽。

Rustで実行可能なメモリを確保

最近JITコンパイラを書いていて、実行可能なメモリ領域に命令列を直接書き込んで実行、ということがしたかった。

cならmmapなどで、PROT_EXECフラグを立ててメモリを確保するか、mprotectでPROT_EXECフラグを立ててやればいいという認識。

まず思いついたのは単純にhttps://crates.io/crates/libcを使う方法で、それは普通に思ったとおりにできて、最終的にそれ以外の方法はわからなかった。

こんなイメージ

Rust Playground

Rust 側で操作ができないと不便なので、 raw pointer として u8 で変数を宣言しておく。

で、別途 mmap でメモリ領域を確保して、 https://doc.rust-lang.org/std/mem/fn.transmute.html で u8 の pointer に coerce する。こうすると u8 を書き込める。

memmove や memcpy 相当の操作として copy_to(nonoverlapping) や copy_from(nonoverlapping) がある。 (cf. pointer - Rust )

https://doc.rust-lang.org/beta/std/primitive.slice.html#method.as_ptrでポインタを取り出して、確保した領域に u8 の配列に書き込んだ命令列をコピーする。

そしてまた transmute を使って命令列を書き込んだメモリ領域を関数ポインタに変換して、関数として呼び出す。 transmute, https://doc.rust-lang.org/nomicon/transmutes.htmlであり、危険で強力。

まぁlibc使うので当然だが、なんとも想定通りな感じ。

で、ちょうど Rust で JIT についての記事であり、メモリを確保して実行するもっとおしゃれでいい感じな方法を書いた記事がこちらにあったので、詳しくはこちら。

Building a simple JIT in Rust

windowsからvirtualboxで別パーティションのubuntuを起動したいけどできない話

やりたいことは、日本語だと

これとか、 Windows 上の VirtualBox で実パーティション上の Linux を起動する:中年の主張:So-netブログ

これとか、 Windons7のVirtualBoxでデュアルブート用のraw partitionにあるLinuxを起動させる | @knok blog

にかかれているようなことで、すでに先人が軽々とやってのけたことをやりたいだけなのである。しかしうまくいかない。

状況としては, windowsから見たとき下記のようなパーティションがあり、

PS C:\Users\takhi> Get-Partition


   DiskPath: \\?\scsi#disk&ven_nvme&prod_samsung_mzvlb1t0#5&2ab59ee0&0&000000#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}

PartitionNumber  DriveLetter Offset                                        Size Type
---------------  ----------- ------                                        ---- ----
1                            1048576                                     260 MB System
2                            273678336                                    16 MB Reserved
3                C           290455552                                522.43 GB Basic
6                            561249255424                             426.37 GB Unknown
5                            1019064877056                              3.81 GB Unknown
4                            1023160614912                              1000 MB Recovery

ちなみにUbuntuからWindowsを起動するのはできてる。これは普通にできた。

しかし、今windowsを使いたいときというのは都合により物理windowsでしか使えない(使っても意味がない)ソフトウェアを使いつつubuntuを使いたいからであり、かつ多くの場合はubuntuをメインで使いたくて必要なときしかwindowsを使うことはないため、VMではなく物理パーティションubuntuを起動したいというわけで、逆ができても意味がない。(わかりにくい)

試したこと

シンプルに createrawvmdk -partitions 5,6

boot media が見つからない。だめ

まぁそりゃそのはずで、 物理では UEFI で起動していてそのシステムパーティションは1なのにそれが含まれていないので、起動できるはずがない。という理解であっているのかはよくわかっていない

createrawvmdk -partitions 5,6 で -mbr を渡す

-mbr オプションで MBR を渡すことができるので、Linux を起動してパーティション1 から

dd if=/dev/nvme0p1n1 of=a.mbr bs=512 count=1

で取得した MBR を渡してみる。

が、だめ。

これもそれはそうで、そもそも正常な MBR ではない。

パーティションテーブルを見ても起動フラグがどのパーティションにも立ってないし、というか「Disk Error…(ry」というメッセージが入っておりもはや MBR の体をなしてない。そんなもの食わせたらエラーになるに決まってる

( UEFIハマりポイント - syuu1228's blog を見るとUEFIブートなので、MBRパーティションテーブルでブートフラグが立っていないのは正しそうである)

createrawvmdk -partitions 1,5,6

UEFI のシステムパーティションを含めてみた形

Virtual Linux envirionments using real partitions for dual boot system にはそのように書いてある

no bootable mediumでだめ

UEFI有効にして起動すると、 required disk isn't connected or cannot access 的なエラーでリカバリー画面になる

createrawvmdk でパーティションを指定しない

安全のためパーティションを指定して起動しようとしていたが、もはやわからないのでパーティションを指定しなければいいのでは?

UEFI は grub2 を起動していて ubuntuwindows かいつも選択して起動しているので、起動画面が出れば ubuntu を指定して起動できるだろう?

と思ったけどもちろん(?)だめ

これもエラーが発生してリカバリが必要です、な画面になる。

同じだけどログ的には書き込みが権限エラーになっている様子。まぁそりゃそうだろう。というかエラーで済んで良かったという。

あとどうするか?

How to Run an Existing Linux Partition in VirtualBox - YouTube

これはあまりに古めかしいが、内容的には起動ディスクを別途作って起動するのはありえそうではある。

WindowsのVirtualboxで物理ディスクのLinuxを起動しようとしたら失敗した話 | ゲームの小ネタメモ帳 > スポンサー広告 > Linux

この方はparition5がespと書いてるように、UEFI で起動するならやはりシステムパーティションはもう1つ?必要か?

はたまた grub-install とかして MBR で起動できるようにすればいいのか…

とか考えてるけどわからん😇

nginx + ngx_mruby を homebrew でインストールしたかった

結論からいうとできたんだけど、ちょっとハマった。

ちょっと、本当にちょっとだけ ngx_mruby 試してみたくて、簡単に入ればいいなーと思って、探して↓を見て喜々として試したわけだけど

ngx_mrubyがHomebrewで超簡単にインストールできるようになった - 人間とウェブの未来

既に issue を上げてる人がいたけど、今 brew install nginx-full --with-mruby-module としても一発でインストールできない。

こんな感じで。どう考えても書き込み権限あるディレクトリに書き込めなくて死ぬ。

brew install nginx-full --with-mruby-module
==> Installing nginx-full from homebrew/nginx
==> Downloading https://nginx.org/download/nginx-1.12.1.tar.gz
######################################################################## 100.0%
==> git init
Last 15 lines from /Users/xxx/Library/Logs/Homebrew/nginx-full/01.git:
2017-08-13 15:21:22 +0900
git
init
error: could not lock config file /usr/local/Cellar/mruby-nginx-module/1.20.0/share/mruby-nginx-module/.git/config: Operation not permitted
fatal: could not set 'core.repositoryformatversion' to '0'

原因は --verbose するとわかるけど homebrew は sandbox-exec を使って sandbox 下でビルドするようになってて、それを想定していないこの formula がおかしくなってしまった様子。

homebrew の 1.0.0 が出たのが2016年9月で、1.0.0 から sandbox を使うようになったから恐らくそれからインストールできなくなってしまったんだろう。(きっと)

mruby の install スクリプトgit 管理されていることを想定されているとかコメントに書かれていたりするし homebrew のことも mruby のことも何も知らないので自分がちょっと調べて直せるってことはなさそうだったので、とりあえず今回は --no-sandbox オプションで回避した。

これでイケると思ったんだけど、まだうまくいかず。

ld でエラーが出てどうも openssl あたりのリンクがうまくいかない。

これもどうすればうまくいくのかわからなかったので、 forkしてオプションを追加してbrew edit してしまうというイケてない方法で回避した。

ちゃんと読んでないけど LIBS で -lcrypto は普通に指定されているような気がするので、これは自分の環境が悪いのかもしれないし、ちょっと今試したいだけで特に真面目にそこを調べるほど時間掛けたくはないのでとりあえず無視…

また使うことがあれば調べる。

あと今回は自宅の mac 上で使いたかったから brew で入れたかったんだけど普通に ngx_mruby 試すだけなら docker とかあるしそっち使うほうがいいだろうね。(というか mac 上でとはいえ docker でできない理由はなかった)

docker で Run `bundle install` to install missing gems になっちゃう件

Docker で Gemfile に変更があったときだけ bundle install を実行するために、 Gemfile と Gemfile.lock を先に ADD しておいて bundle install を先に実行しておくのはもはや常識だと思う。

ちょいちょい例にこういうのがある。(ここでは Dockerfile とアプリケーションが同じリポジトリディレクトリ)にあるとして、そのディレクトリで docker build してるとする)

WORKDIR /tmp
ADD Gemfile Gemfile
ADD Gemfile.lock Gemfile.lock

....

ADD . /app

どういうのかというと、 bundle install を先に実行してキャッシュを作るときのディレクトリと、実際にアプリが動くディレクトリを別にしているという例。

これは通常問題ないんだけど、bundle install の際につけるオプションによっては問題になる。

例えば bundle install –without development test とかする場合。

/tmp 以下には install 時に .bundle/config が作られてそこに BUNDLE_WITHOUT の情報が書き込まれるので、特定の group が除外されていることがちゃんと bundler に伝わる。

しかし、↑でいうと /app 以下で bundle exec とかするときにはその情報は伝わらないので、without オプションを指定して意図的に除外した group の gem がインストールされていないと、タイトルに書いたようなエラーが出てしまう。

解決策としては普通にアプリを動かすディレクトリで bundle install をやっておけばいい ( bundle config --global without development:test[:…] とする手もある )

Rust でマルチスレッドプログラミングのメモ

Rust でのマルチスレッドの勉強のためにぷよぷよっぽいゲームを書いているので、学んだことを書いておく。

Rust ではデータ競合は起こせない

Rust はデータ競合 ( data races ) がないことを保証している。( data races であって race conditions ではない。 Races - 参照

所有権のシステムによって同一のメモリを複数箇所から同時に書き換えることができないようになっており、これをコンパイル時にチェックすることで安全性を保証している。

これに違反している場合はコンパイルが通らない。

これはマルチスレッドでも適用されるので、安心してコードを書くことができるが、その分最初はコンパイルが通らなくて悩むことも多かった。

スレッドを使ってみる

単純な例

スレッドは標準ライブラリの thread::spawn を使う。

以下はスレッドを生成して1秒後に hello と出力する。

use std::thread;
use std::time::Duration;
fn main() {
    let t = thread::spawn(|| {
        thread::sleep(Duration::from_secs(1));
        println!("{}", "hello");
    });
    let _ = t.join();
}

https://is.gd/tnYGhZ

この場合はなんの問題もない。見た目通りの動作をしていると思う。

spawn に無名関数 (|| {..} は引数を取らないクロージャね) を渡していて、それが別スレッドで動く。

上のクロージャは外部の変数を使っていなかったが、変数を使うようにしてみる。

変数を出力するようにしてみる。

fn main() {
    let name = "John";
    let t = thread::spawn(|| {
        thread::sleep(Duration::from_secs(1));
        println!("hello {}", name);
    });
    let _ = t.join();
}

https://is.gd/deGnRU

これはエラーになる。

error[E0373]: closure may outlive the current function, but it borrows `name`, which is owned by the current function
 --> <anon>:5:27
  |
5 |     let t = thread::spawn(|| {
  |                           ^^ may outlive borrowed value `name`
6 |         thread::sleep(Duration::from_secs(1));
7 |         println!("hello {}", name);
  |                              ---- `name` is borrowed here
  |
help: to force the closure to take ownership of `name` (and any other referenced variables), use the `move` keyword, as shown:
  |     let t = thread::spawn(move || {

spawn の signature を見てみると、引数は F: FnOnce() -> T, F: Send + 'static とある。

pub fn spawn<F, T>(f: F) -> JoinHandle<T> 
where F: FnOnce() -> T, F: Send + 'static, T: Send + 'static

これはクロージャ'static な生存期間を持つことを意味している。

'static な生存期間を持つクロージャが、生存期間が関数内だけの name を借りようとしているのでクロージャのほうが長生きしてしまうよとエラーになる。

これはエラーで言われている通り、 move キーワードを使ってクロージャに所有権を渡してしまえば良く、ほとんどのケースでは move を使うことになる。

ただ、move キーワードを使うということは当然所有権はクロージャ内に移ってしまうので、そのスレッド以外でも同じデータを使いたい場合には困ってしまう。

位置情報を持てる Enemy 構造体を作り、スレッド内で敵の位置を使ってなんらかの処理をしつつ、メインのスレッドで描画する(この場合は位置を表示するだけ)ことを考えてみる。

こうすると、enemy の所有権が移った後に使おうとしているのでエラーになる

...

struct Enemy {
    x: i32,
    y: i32,
}

fn do_something(enemy: &Enemy) {
    println!("{}", enemy.x + enemy.y); 
}

fn main() {
    let enemy = Enemy { x: 0, y: 0};
    let _ = thread::spawn(move || {
        loop {
            thread::sleep(Duration::from_secs(1));
            do_something(&enemy);
        }
    });
    
    for _ in 0..5 { 
        thread::sleep(Duration::from_secs(1));
        println!("({},{})", enemy.x, enemy.y);
    }
}

https://is.gd/XJhXOZ

rustc 1.17.0 (56124baa9 2017-04-24)
error[E0382]: use of moved value: `enemy.x`
  --> <anon>:24:29
   |
15 |     let _ = thread::spawn(move || {
   |                           ------- value moved (into closure) here
...
24 |         println!("({},{})", enemy.x, enemy.y);
   |                             ^^^^^^^ value used here after move
   |

そういった場合には、Arc を使う。

Arc は、 Atomic でスレッドセーフな参照カウンタ付きポインタで、スレッド間で安全に所有権を共有することを可能にする。

Arc の中のデータはヒープ上にメモリが確保され、clone すると中の値へのポインタが作られる。

clone するたびに参照カウンタが増えていくが、それらの生存期間が終わるとカウンタは減り、0になるとデータが破棄される。

スレッド間での共有にはArc を使う

Arc を使うとこうなる。 ArcEnemy を包んで、使う前には clone する。

...
fn main() {
    // Arcで包む
    let enemy = Arc::new(Enemy { x: 0, y: 0});
    {
        // 使う時に clone する
        let enemy = enemy.clone();
        let _ = thread::spawn(move || {
            loop {
                thread::sleep(Duration::from_secs(1));
                do_something(&enemy);
            }
        });
    }
    
    let enemy = enemy.clone();
    for _ in 0..5 { 
        thread::sleep(Duration::from_secs(1));
        println!("({},{})", enemy.x, enemy.y);
    }
}

https://is.gd/e9clLQ

これはうまくいく。

もしあるスレッドでは敵を動かしたいんだという場合、これではうまくいかない。

&x で参照を共有するときと同じように、Arcで所有権を共有している場合データの競合を防ぐため、変更はできない。

ので、データを変更したい場合には更にもう1つ、 Mutex が必要になる。

データの変更も行う場合はMutex も使う

やろうと思ってもできないのはこんな感じ。敵を右に動かす

...
impl Enemy {
    fn move_right(&mut self) {
        self.x += 1;
    }
}

fn main() {
    let enemy = Arc::new(Enemy { x: 0, y: 0});
    {
        let mut enemy = enemy.clone();
        let _ = thread::spawn(move || {
            loop {
                thread::sleep(Duration::from_secs(1));
                // ここで敵を動かしたい
                enemy.move_right();
            }
        });
    }
....
}

これは、 clone の時に mut で変更可能な値として使おうとしているが、 immutable なのでできないよとエラーになる。

error: cannot borrow immutable borrowed content as mutable
  --> <anon>:23:17
   |
23 |                 enemy.move_right();
   |                 ^^^^^ cannot borrow as mutable

Mutex を使って排他制御をするとデータの競合を防げるので、データの変更ができるようになる。

Arc の中のデータを Mutex で包み、中のデータを使う際には lock でロックを取得する。

取得したロックは、他のリソースと同様スコープを出ると自動で解放される。

ちなみに自分は lock の型を見てロックが取れなければエラーを返すものだと思っていたが、それは try_lock

lock の場合はロックが取れるまでブロックするので、他のスレッドがロックを解放してくれない場合はデッドロックする。

Mutex を使うとこうなる。

fn main() {
    // Mutexを追加
    let enemy = Arc::new(Mutex::new(Enemy { x: 0, y: 0}));
    {
        let enemy = enemy.clone();
        let _ = thread::spawn(move || {
            loop {
                thread::sleep(Duration::from_secs(1));
                // 使う前にロックを取得する
                let mut enemy = enemy.lock().unwrap();
                enemy.move_right();
            }
        });
    }
    
    let enemy = enemy.clone();
    for _ in 0..5 { 
        thread::sleep(Duration::from_secs(1));
        // 変更はしないけどこちらもロックを取る必要はある
        let enemy = enemy.lock().unwrap();
        println!("({},{})", enemy.x, enemy.y);
    }
}

https://is.gd/6DymJC

これだとxが変更されていくので出力はこうなる

(0,0)
(1,0)
(2,0)
(3,0)
(4,0)