えどふの技術記事

社会の底辺が書きます

Linux4.0でシステムコールを追加する方法

 Linux4.0でシステムコールを追加する方法のメモです. 先日, Linux4.1がリリースされたようなので最新では無いですがほとんど方法は変わらないと思います.
※前回の記事でニューラルネットワークを使って手書き文字認識をすると書きましたが, あれは嘘だ. (また今度記事にします. )

システムコールについて

 ユーザランド側からカーネルの機能を呼び出したいときに, システムコールを発行することがあります. C言語を書いてる時にたまに使うread()やwrite()が代表的なシステムコールにあたります. 自分で追加したりというのはパフォーマンス的な理由もあり, まずないです. ちなみに, readやwriteが発行されるとVFSを経由してファイルシステムに投げられますが, これはまた後ほど.

システムコールの追加方法

 順を追って追加してコンパイルするところまでやります.

1.カーネルを引っ張ってくる

 The Linux Kernel Archivesからカーネル4.0を引っ張ってきます. ちなみに, ディストリビューションはUbuntu14.04を使いました.
 解凍したカーネルをどこでも良いですが, 分かりやすいように/usr/src/linuxに配置します.

root権限にします.

$sudo su

コピーします.

$cp -r 解凍したカーネル /usr/src/linux

2.新たなシステムコールの作成

今回はtest_callという名前で作ります.

$cd /usr/src/linux/arch/x86/kernel
$vim test_call.c

 中身は実行中プロセスのpidをログに出力する簡単なものを書いてみます. ちなみに, currentの中身はtask_struct構造体で, 現在実行中のプロセスの内容が格納されています. あたりまえですが, printfは使えないのでカーネルが用意してくれているprintkを使います.

test_call.c

#include <linux/kernel.h>
#include <linux/syscall.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/cred.h>
#include <asm/current.h>
#include <linux/sched.h>

asmlinkage long sys_test_call (void) {
        printk (KERN_INFO "current->pid = %d\n", current->pid);
}

 ちなみに, 引数を取ったり戻り値を与えることもできます. 戻り値を返すときは普通にreturnしてやれば良いですが, 引数を取るとき, 例えばポインタ変数を渡したとき, その引数はユーザランドのメモリ領域に格納されていることに留意しなければなりません.

3.ヘッダへtest_callを追加する

$cd /usr/src/linux/include/linux
$vim syscalls.h 

syscalls.hのいっちばーん下に, 次のプロトタイプ宣言を追加します.

asmlinkage long sys_test_call (void);

4.システムコールのテーブルにtest_callを追加

$cd /usr/src/linux/arch/x86/syscalls
$vim syscall_64.tbl 

たぶん, そのままだと321行目くらいに

322     64      execveat                 stub_execveat 

みたいなのがあると思うので, その下に次の一行を差し込みます.

323     common  test_call                sys_test_call

323が追加したシステムコールの番号となるので, もう一個追加したいときは324にします.

5.makefileを編集

 最後に, makefileを編集します.

$cd /usr/src/linux/arch/x86/kernel
$vim Makefile

たぶん, そのままだと112行目くらいに

obj-$(CONFIG_PMC_ATOM)            += pmc_atom.o

みたいなのがあると思うので, その下に次の一行を差し込みます.

obj-y                             += test_call.o

6.カーネルを焼く

 以上で, 作業項目はすべて終わりです. あとはカーネルを焼くだけです. 今回は初めてコンパイルするものと想定しているので全行程を書きます.

$cd /usr/src/linux
$make
$make modules_install
$make install

ちなみに, makeは

$make -j N

のNでコア数を指定できます. 複数コアを積んでいるマシンなら, 指定した方が圧倒的に早く終わると思います. また, 新しくコンパイルしたカーネルで起動したいときはコンパイル後にブートローダを設定してやればよいです.

$grub-mkconfig

あとは, 再起動して実際にシステムコールを呼び出してみます.

$reboot

7.システムコールを呼び出す

 新しいカーネルで起動したら, 実際にtest_callを呼び出してみます.
適当なディレクトリにtest.cを作ってやります. 中身は先ほどシステムコールテーブルに追加したシステムコール番号で呼び出すように作ってやります.

test.c

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main () {
        syscall (323);
}

実行してみます.

$cc test.c && ./a.out

当然のこと, 実行しても何も表示されずに終了されると思いますが, ログにはちゃんと吐かれていると思うので確認しに行きます.
|

$cd /var/log
$cat kern.log

"current->pid = XXXX"というように出力されていれば成功です.

まとめ

 普段は絶対にシステムコールを追加したりすることはないと思いますが, いざというときのために覚えておいてもいいかも.