LKMからのファイル出力
Rev.1を表示中。最新版はこちら。
LKMでの出力はprinkにてカーネルログとして出力させます。printkはメッセージをリングバッファに設定するだけです。カーネルのログの実際の表示先デバイスは、prinkのタイミングでは決定しようがありません。でないと、ユーザはどのコンソールからそのメッセージを見ていいのか悩んでしまうことになってしまいます。ユーザはsyslogd(dmesage等)で、指定するコンソールから確認できるわけです。で、知識を深めると言う興味的好奇心から、このメッセージをカレント端末に表示できないかと思いました。出力先を/dev/tty0にすれば、HELLOW LKM WORLDのメッセーをカレント端末に表示してくれます。
で、/dev/tty0をオープンして、その書き込みコールバック関数を呼べばいいだけです。当初下位レベルの関数を駆使してとやってみましたが、LKMからシンボルが参照できないものが、多々あって残念しました。
そこで、直接システムコールを呼ぶことにいたしました。X86の場合、sys_open()は、結果をユーザ空間への橋渡しをする処理が介在して、うまくいきませんでした。で、サンプルではopenシステムコールから呼ばれるdo_sys_open()をコールする事にいたしました。
do_sys_open()はLKMにシンボルを公開していません。従って/boot/配下のシンボルマップから、直接この関数をアドレス(システム依存)をハードコピーいたしました。ここで大切なのは、この第二引数がconst char __user*となっている点です。filenameはユーザ空間のアドレスでないといけません。do_sys_open()内で、getname()でユーザ空間のfilenameを、カーネル空間に取り込むのですが、その時filenameがユーザ空間かどうかチェックしています。
long do_sys_open(int dfd, const char __user *filename, int flags, int mode)とりあえず、カレントプロセスのメモリー空間から、do_mmap_pgoff()で空きメモリーリージョンを取得し、そこにまず、/dev/tty0を、複写して、do_sys_open()をコールします。
次に、それで取得した、ファイルIDから、fget()でFILE構造体を取得し、ファイルオペレーションをwriteコールバック関数をコールする事で実現しました。なお、ファイルオペレーションをwriteコールバック関数の、第二引数もユーザ空間である必要があります。先のdo_mmap_pgoff()で取得したアドレスを代用して、その出力メッセージをセットしています。
#include <linux/init.h> #include <linux/module.h> #include <linux/namei.h> #include <linux/kernel.h> #include <linux/mm.h> #include <linux/file.h> #include <linux/string.h> #include <linux/fs.h> #define PROT_READ 0x1 /* page can be read */ #define PROT_WRITE 0x2 /* page can be written */ #define MAP_PRIVATE 0x02 /* Changes are private */ MODULE_DESCRIPTION("syscall sample"); MODULE_AUTHOR("y.kitamura"); MODULE_LICENSE("GPL"); static int test_init(void) { long (*my_do_sys_open)(int dfd, const char __user *filename, int flags, int mode) =(void *)0xc04da3d9; long addr, fd, pos, ret; struct file *file; addr = 1 << PAGE_SHIFT; addr = do_mmap_pgoff(NULL, addr, 4012, PROT_READ | PROT_WRITE, MAP_PRIVATE, 0 >> PAGE_SHIFT); strcpy((char *)addr, "/dev/tty0"); fd = (*my_do_sys_open)(AT_FDCWD, (char *)addr, O_RDWR, 0); if (fd > 0) { file = fget(fd); pos = file->f_pos; strcpy((char *)addr, "HELLOW LKM WORLD!!\n"); ret = file->f_op->write(file, (char *)addr, strlen((char *)addr), &pos); printk("write %d\n", ret); } else { printk("open err\n"); } return 0; } static void test_exit(void) { } module_init(test_init); module_exit(test_exit);