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

uchan note

プログラミングや電子工作の話題を書きます

【技術書典2】Linuxカーネルモジュール自作入門を出します(ダウンロード販売有)

執筆 Linux

皆さんは 技術書典2 というイベントをご存知でしょうか。2017年4月9日に秋葉原で開催される、技術書オンリーの同人誌即売会です。

この記事では、技術書典2のために書いた Linuxカーネルモジュール自作入門」 の目次をご紹介します。最後にダウンロード販売のお知らせもあります。

販売情報

  • 日時 2017年4月9日(日)
  • 場所 アキバ・スクエア
  • ブース き-09「tech@サイボウズ式」編集部
  • 書名 Linuxカーネルモジュール自作入門
  • ページ数 36(表紙含む)

目次

第1章「はじめに」より:

 本書を手にとって頂いてありがとうございます。この本はLinuxカーネルモジュールのビルド手順を説明する本です。Hello Worldを画面に表示するだけのモジュールを紹介します。さらに、カーネルブレークポイントを仕掛けるkprobesやカーネル内の変数を公開できるsysfsというLinuxの機能を自作モジュールから使ってみます。

 読者は基本的なLinuxの操作ができ、C言語の読み書きができることを前提としています。Linuxカーネルソースコードは読んだことがなくても大丈夫です。

  • 第1章 はじめに
    • 1.1 Linux カーネルモジュールとは
    • 1.2 動作確認環境
    • 1.3 著者について
    • 1.4 本書の取り扱い方法
  • 第2章 Hello World モジュール
  • 第3章 モジュール解剖
    • 3.1 カーネルヘッダ
    • 3.2 module 構造体
    • 3.3 モジュールファイル
    • 3.4 モジュール情報セクション
    • 3.5 シンボルバージョンセクション
  • 第4章 kprobes
    • 4.1 プローブの種類
    • 4.2 jprobe の登録
    • 4.3 コールバック関数の定義
    • 4.4 jprobe の実験
    • 4.5 全体ソースコード
  • 第5章 sysfs
    • 5.1 sysfs を覗いてみる
    • 5.2 sysfs に値を公開する
    • 5.3 コールバック
    • コラム scnprintf
    • 5.4 sysfs への登録
    • 5.5 ビルドと実行
    • コラム echo とdd
  • 参考文献

ダウンロード販売

同人誌即売会の雰囲気を大事にしたいので、当日ブースまで来ていただけると嬉しいです。しかし、何らかの理由により来られない場合、ダウンロード販売もしますのでご利用ください。

ダウンロード販売は技術書典2の開催後(多分当日の夕方ぐらい)に開始します。Amazonギフト券@uchan_nos までDMしていただければダウンロードURLをお教えします。(価格は未定です。決まればブログを更新します。)

自作OS Advent Calendar 2016 目次

自作OS

これは 自作 OS Advent Calendar 2016 の 25 日目の記事です。 1 日目から 24 日目までの記事をまとめた目次になっています。

それぞれの記事のURLの後に書いてある一言紹介は、uchanによるものです。 記事の著者さんに確認を取っておらず、もしかしたら誤った内容の可能性があります。

OSの機能拡張

OS自作用の処理系いろいろ

BIOS/UEFI

  • 12/4 neriring2

    • はりぼてOSをUEFIで起動する/総集編 - 借り初めのひみつきち
    • http://neriring.hatenablog.jp/entry/2016/12/04/180254
    • タイトルの話もさることながら、近年はレガシーデバイスがサポートされなくなってOS開発が厳しいね、という話が興味深い(と同時にちょっと悲しい)です。
  • 12/7 hikalium

  • 12/14 liva_jy

    • ディスクイメージをネットワークブートする – Raphine Project<サイト製作中>
    • https://raphine.wordpress.com/2016/12/14/netboot/
    • 開発マシンに立てた簡易HTTPサーバでOSイメージを配布し、ターゲットマシンでOSを起動させちゃうお話です。やってみたくなること間違い無し。
  • 12/17 ナカタニ

その他技術話

エッセイ

はりぼてOSでELFなアプリを起動する

自作OS

これは 自作 OS Advent Calendar 2016 の 18 日目の記事です。

概要

『30 日でできる!OS 自作入門』 の「はりぼて OS」が対応している実行可能形式は HRB 形式です。 HRB という名の通り「はりぼて OS」独自の形式で、.text、.data、.bss セクションに相当するデータを含むことができます。

構造がシンプルなので、入門者にとっては実行可能形式を学ぶのに都合がいい形式であると思います。 しかし、独自形式ゆえ周辺のツールが整いづらく、実際に扱いやすい形式というわけでもありません。

本記事では、Linux 環境でスタンダードとなっている ELF 形式を「はりぼて OS」でも動かそうじゃないか、ということでやり方をご紹介します。 ELF 形式のアプリをロードして実行する方法(OS 側)、ELF 形式でアプリをコンパイルする方法(アプリ側)の 2 つの側面から解説します。

f:id:uchan_nos:20161218173129p:plain

画像はいらすとやから頂いたエルフです。

続きを読む

Windows で GCC 6.2.0 をビルドするメモ

f:id:uchan_nos:20161208235327j:plainこれは 自作 OS Advent Calendar 2016 の 8 日目の記事です。

概要

現時点で最新バージョンの GCC 6.2.0 を Windows 上でビルドするメモです。 このメモでは、i386-elf をターゲットにした GCC を作ります。 コンパイラ本体のみが対象で、標準ライブラリ等はビルドしません。 (将来、標準ライブラリも自作 OS 向けにビルドできるようになったら、また記事を書こうかと思います。っていつだよ!)

続きを読む

レベル変換の仕組み(オームの法則からMOS-FETを使ったレベル変換回路まで)

ソフトウェアエンジニアに贈る電子回路入門 電子工作

レベル変換

レベル変換とは、電圧の異なる信号線をつなぐ際に電圧を変換することです。

例えば Raspberry Pi という小さな Linux ボードコンピュータの電源電圧は 3.3V です。 そのため GPIO の入出力電圧も 3.3V となっており、電子工作でよくある 5V 駆動の装置を直接つなぐことができません。 著者は最近、秋月電子通商で売っている中で最安のキャラクタ液晶表示器 SC1602Raspberry Pi から制御しようと思ってレベル変換回路を作りました。

また、いわゆる「L チカ」といって LED を点滅させるだけの回路でも、LED によっては 3.5V 程度の電圧がないと十分に光らない品種があり (赤色 LED に比べ、青色 LED は高い電圧を求める傾向があります)、3.3V の出力では十分でないケースがあります。

レベル変換は低電圧側の回路から高電圧側の回路を制御するだけでなく、逆に高電圧側から低電圧側の回路を制御するのに用いる場合もあります。

ハードウェアエンジニアにとっては良く知られた回路ですが、ソフトウェア専門の人がちょっと電子工作しようとするとハマることが多いと思います。 この記事では、オームの法則を復習しつつ、レベル変換回路の基礎を見てみます。

続きを読む

osdev-jp という自作 OS のコミュニティを作りました

自作OS

これは 自作 OS Advent Calendar 2016 の初日の記事です。

自作 OS Advent Calendar といいつつ、初日は技術的なことは書かずにコミュニティの紹介をします。期待してくださった方にはすみません(^^;)

自作 OS コミュニティの昔と今

私が自作 OS というものを知ったのは 2003 年ごろのことです。 そのころ、ウェブには活発な自作 OS コミュニティがいくつかありました。 私は主に OSASK コミュニティで活動していましたが、周りには dev-j、OS-Wiki などの OS 系の情報サイトや Mona OS、MEG-OS もありました。

時は流れて 2016 年。 私はしばらく自作 OS から離れていましたが、あるきっかけでまた自作 OS 関連の活動を再開させようと思いました。

久しぶりの OS 熱に侵されながら、おぼろげな過去の記憶をもとに dev-j や OS-Wiki などを再訪してみました。 しかし、そこは荒れた荒野のような状態でした。 dev-j にいたっては、トップページさえ 403 エラーでアクセスできない状態になっています。

osdev-jp の紹介

そこで私は、自作 OS に再びかかわろうと思うきっかけを与えてくれた 2 人の若者 ―― @liva_jy さんと @hikalium さん ―― とともに osdev-jp というコミュニティを作ることにしました。

発足したのは 2016 年 3 月です。 発足当初、このコミュニティは(OS-Wiki のように)OS 関連の情報を集めるサイトの名前でした。 今は情報収集はあまり捗っていませんが、「自作 OS もくもく会」というオフ会を定期的に開催し、自作 OS 好きの人同士のつながりを作り出しています。

osdev-jp の主要なリソースは以下の通りです。

「自作 OS もくもく会」は今のところ 2 カ月に 1 回のペースで開催しています。 開催予告は GitHub Wikiイベントカレンダー に載せていくつもりです。

nfcpy で複数の System Code を持つ NFC タグを扱う方法

Linux 電子工作

Linux (Ubuntu 16.04) で nfcpy を使ってみました。落とし穴が多い気がしたので、ここにまとめます。 特に、複数の system code を持つような NFC タグの扱いが複雑ですので、詳しく説明します。

概要

LinuxNFC タグを読み書きするには nfcpy という Python ライブラリを使うのが割とよく用いられる方法のようです。 この記事では、nfcpy で NFC タグに記録されたブロックを取得する方法を説明します。

nfcpy に関する記事はいくつもあるのですが、どれも複数の System Code を持つ NFC カードの扱い方は書いてないようでした。 また、対応する nfcpy のバージョンが古かったりするので、この記事を書こうと思いました。

この記事で対象とする nfcpy のバージョンは 0.11.1 です。 また、動作検証は Sony 製の NFC リーダー・ライター PaSoRi RC-S380 を使って行いました。

nfcpy のインストール

公式ドキュメントは http://nfcpy.readthedocs.io/en/stable-0.11/ にあります。 (ただし、このドキュメントは少し古いです。現在 nfcpy は GitHub に移行したのですが、ドキュメントは lauchpad 時代のままです。)

nfcpy は PyPI に登録されているので、pip でインストールできます。 (nfcpy は現時点でまだ Python 3 系に対応してないので、pip3 は使わないでください。)

$ sudo pip install -U nfcpy

pip でインストールするとサンプルコードが手に入らないので、別途 GitHub からダウンロードします。

$ git clone git@github.com:nfcpy/nfcpy.git
$ cd nfcpy
$ git checkout stable/0.11

ここまでできたら、サンプルコードを実行して PaSoRi でタグを読み取ることができるはずです。

$ sudo examples/tagtool.py
[nfc.clf] searching for reader on path usb
[nfc.clf] using SONY RC-S380/P NFC Port-100 v1.11 at usb:002:013
** waiting for a tag **
Type3Tag 'FeliCa Standard (RC-S915)' ID=xxxxxxxxxxxxxxxx PMM=yyyyyyyyyyyyyyyy SYS=8108

NFC タグをタッチすると検出され、製造ID(IDm)、製造パラメータ(PMm)、システムコードが表示されます。(最終行)

おまけ:sudo を使わないで作業する方法

環境によって、USB 接続の NFC リーダーに一般ユーザが接続することができない場合があります。 毎回 sudo を使えば問題ないのですが、それは嫌だという方には公式ドキュメントで説明されている通り、回避策があります。

まず、一般ユーザで NFC リーダーが扱えないことを確認します。

$ examples/tagtool.py --device usb
[nfc.clf] searching for reader on path usb
[nfc.clf] no reader available on path usb
[main] no contactless reader found on usb
[main] no contactless reader available

この時点でちゃんとリーダーを検出できていれば、何もする必要はありません。 検出できないことが確認できたら、lsusb コマンドでデバイス ID を調べます。

$ lsusb
...
Bus 002 Device 013: ID 054c:06c3 Sony Corp.
...

それっぽいデバイスを見つけたら 054c:06c3 のところの番号を使って tagtool.py を実行しなおします。

$ examples/tagtool.py --device usb:054c:06c3
[nfc.clf] searching for reader on path usb:054c:06c3
[main] access denied for device with path usb:054c:06c3
[main] first match for path usb:054c:06c3 is usb:002:013
[main] usb:002:013 is owned by root but you are xxxxx
[main] members of the root group may use usb:002:013
[main] you may want to add a udev rule to access this device
[main] sudo sh -c 'echo SUBSYSTEM==\"usb\", ACTION==\"add\", ATTRS{idVendor}==\"054c\", ATTRS{idProduct}==\"06c3\", GROUP=\"plugdev\" >> /etc/udev/rules.d/nfcdev.rules'
[main] no contactless reader available

「指定した ID のデバイスは root の所有なのでアクセスできないよ」てなことが書いてあります。 下から 2 行目に解決策が提示されていますので、そのコマンドを実行します。

$ sudo sh -c 'echo SUBSYSTEM==\"usb\", ACTION==\"add\", ATTRS{idVendor}==\"054c\", ATTRS{idProduct}==\"06c3\", GROUP=\"plugdev\" >> /etc/udev/rules.d/nfcdev.rules'

マシンを再起動すれば、一般ユーザで使えるようになっているはずです。

NFC タグの中身を見てみる

nfcpy をインストールしたことですし、NFC タグの中身を表示するプログラムを作りましょう。 以下、NFC といいつつ FeliCa Standard を対象としたプログラムになっています。 他の規格のカードではうまく動かない可能性があります。

dump_simple.py は NFC タグ(の先頭 System)が持つサービスをすべて列挙するプログラムです。

import nfc


def check_services(tag, start, n):
    services = [nfc.tag.tt3.ServiceCode(i >> 6, i & 0x3f)
                for i in xrange(start, start+n)]
    versions = tag.request_service(services)
    for i in xrange(n):
        if versions[i] == 0xffff: continue
        print services[i], versions[i]


def on_connect(tag):
    print tag
    n = 32
    for i in xrange(0, 0x10000, n):
        check_services(tag, i, n)


def main():
    with nfc.ContactlessFrontend('usb') as clf:
        clf.connect(rdwr={'on-connect': on_connect})


if __name__ == '__main__':
    main()

取りあえず実行してみます。サービスの全列挙には実行には時間がかかりますのでしばらく待ちましょう。

$ python dump_simple.py
Type3Tag 'FeliCa Standard (RC-S915)' ID=xxxxxxxxxxxxxxxx PMM=yyyyyyyyyyyyyyyy SYS=8108
Service Code 0000h (Service 0 Type 000000b) 4097
Service Code 0100h (Service 4 Type 000000b) 4097
...

プログラムを説明します。

nfc.ContactlessFrontend('usb') で USB 接続の NFC リーダーを開きます。

clf.connectNFC リーダーを起動し、NFC タグのタッチ検出を開始します。 connect の引数で、NFC タグがタッチされたときに呼び出すコールバック関数 on_connect を指定します。

ドキュメントにはコールバック関数を設定しない(clf.connect(rdwr={}))場合、NFC タグをタッチするまでブロックするような記述がありますが、実験したところブロックしませんでした。 きちんと on-connect コールバックを設定する必要があるようです。

NFC タグがタッチされると on_connect にはそのタグを表すオブジェクト tag が渡ってきます。 以降、tag を使って NFC タグと通信し、様々なデータを読み取っていきます。

dump_simple.py プログラムは、タッチした NFC タグが持つすべてのサービスを列挙します。

tag.request_service にサービスコード(ServiceCode)を渡すと、タグがそのサービスを持つかどうかを調べられます。 タグが指定したサービスを持つなら 0xffff 以外の値が返ってきます。

サービスコードは 16 ビットの値で、上位 10 ビットがサービス番号、下位 6 ビットが属性値です。 16 ビット値ですから、0 から 0x10000 までを調べればすべてのサービスを網羅できます。 for i in xrange(0, 0x10000, n) はそういう意味です。

nfc.tag.tt3.ServiceCode(i >> 6, i & 0x3f) は、16 ビットの整数 i から ServiceCode オブジェクトを生成するやり方です。 i >> 6 で上位 10 ビットを取り出します。i & 0x3f で下位 6 ビットを取り出します。

tag.request_service は複数のサービスを一度に指定することができます。 実際に check_services 関数では n 個の ServiceCode のリストを作り、request_service 関数に渡しています。 1 つずつ調べるよりまとめて調べる方が速いので、32 個ずつ調べることにしたのです。

複数の System を扱う

dump_simple.py では System を切り替える処理を入れていなかったため、暗黙的に NFC タグの先頭の System を扱うことになります。 複数の System を持つタグの場合、明示的に扱う System を指定する処理が必要です。

今度は、すべての System を網羅的に調べるように改造した dump_all_systems.py を示します。

import nfc


def check_services(tag, start, n):
    services = [nfc.tag.tt3.ServiceCode(i >> 6, i & 0x3f)
                for i in xrange(start, start+n)]
    versions = tag.request_service(services)
    for i in xrange(n):
        if versions[i] == 0xffff: continue
        print services[i], versions[i]


def check_system(tag, system_code):
    idm, pmm = tag.polling(system_code=system_code)
    tag.idm, tag.pmm, tag.sys = idm, pmm, system_code
    print tag
    n = 32
    for i in xrange(0, 0x10000, n):
        check_services(tag, i, n)


def on_connect(tag):
    system_codes = tag.request_system_code()
    for s in system_codes:
        check_system(tag, s)


def main():
    with nfc.ContactlessFrontend('usb') as clf:
        clf.connect(rdwr={'on-connect': on_connect})


if __name__ == '__main__':
    main()

実行するとこんな感じになります。複数の SYS= を調べているのが分かりますね。

$ python dump_all_systems.py
Type3Tag 'FeliCa Standard (RC-S915)' ID=xxxxxxxxxxxxxxxx PMM=yyyyyyyyyyyyyyyy SYS=8108
Service Code 0000h (Service 0 Type 000000b) 4097
Service Code 0100h (Service 4 Type 000000b) 4097
...
Type3Tag 'FeliCa Standard (RC-S915)' ID=xxxxxxxxxxxxxxxx PMM=yyyyyyyyyyyyyyyy SYS=FE00
Service Code 1748h (Service 93 Random RW with key) 4097
Service Code 174Bh (Service 93 Random RO w/o key) 5963
...

プログラムの変更のポイントはこんな感じです。

  • tag.request_system_code 関数で NFC タグが持っている System の一覧を得る
  • 今まで on_connect でやっていた処理を check_system 関数に移して、polling 処理を加えた

polling の処理がミソとなる部分ですので、少し詳しく説明します。

ポーリングは、NFC リーダーが NFC タグを探す処理のことです。 ポーリング時に System Code を指定することで衝突防止を実現しています。 指定したコードと同じコードを持つ NFC タグだけがポーリングに反応することで、複数の NFC タグが探索範囲に存在しても衝突しないようになっています。

さて、tag.polling 関数に System Code を渡すと、そのコードを持つ NFC タグの IDm, PMm を取得できます。 取得した IDm と PMm、それからポーリングに使った System Code を tag.idm, tag.pmm, tag.sys に設定することで、以降のデータ通信をその System に対して行えるようになります。

データブロックの読み込み

NFC タグがもつすべての System とすべてのサービスが分かったので、次はいよいよ任意のサービスが持つデータブロックを読み込んでみます。

dump_block.py

import nfc
from binascii import hexlify


def on_connect(tag):
    idm, pmm = tag.polling(system_code=0xfe00)
    tag.idm, tag.pmm, tag.sys = idm, pmm, 0xfe00

    sc = nfc.tag.tt3.ServiceCode(93, 0x0b)  # 174B
    bc = nfc.tag.tt3.BlockCode(1, service=0)
    data = tag.read_without_encryption([sc], [bc])
    print 'str:', data
    print 'hex:', hexlify(data)


def main():
    with nfc.ContactlessFrontend('usb') as clf:
        clf.connect(rdwr={'on-connect': on_connect})


if __name__ == '__main__':
    main()

このプログラムはシステム 0xfe00 内のサービス 0x174B が持つブロック 1 のデータを読み込みます。 システムコードとサービスコードは、先ほどの探索で得た値を使っています。

このあたりは read_without_encryption のドキュメント に載っているサンプルを参考にしています。 ドキュメントにもありますが、BlockCode の生成時に渡す service=0 はサービスコードそのものではなく、read_without_encryption の引数 service_list 内でのインデックスです。

read_without_encryption([sc1, sc2, ..., scN], [...]) などと N 個のサービスを指定したとき、BlockCodeservice は 0 から N-1 までの値を取れます。

1 つのサービスは 16 バイトの大きさのブロックを複数個持ち、サービスの中で 0 から連番が付いています。

| Service X   |
|    ---------|
|   | Block 0 |
|    ---------|
|   | Block 1 |
|    ---------|
|   |  ...    |

サービスコードの下位 6 ビットは属性値で、次のようになっています。 この中で、パスワードなしで読み書きできるのは "w/o key" (without key = キーなし)となっているサービスだけです。 例えば 2 つのサービス "1748h" と "174Bh" のうち、読み取れるのは "174Bh" の方だけです。

ビット 意味
001000 Random RW with key
001001 Random RW w/o key
001010 Random RO with key
001011 Random RO w/o key
001100 Cyclic RW with key
001101 Cyclic RW w/o key
001110 Cyclic RO with key
001111 Cyclic RO w/o key
010000 Purse Direct with key
010001 Purse Direct w/o key
010010 Purse Cashback with key
010011 Purse Cashback w/o key
010100 Purse Decrement with key
010101 Purse Decrement w/o key
010110 Purse Read Only with key
010111 Purse Read Only w/o key