FreeBSD character device module
Kernel/User Space Transitions
まずはカーネル/ユーザ間でのデータのやりとりについて
詳しくはman 9 copy
copyin copyinstr
copyin
とcopyinstr
は文字通りユーザ空間からカーネル空間へデータをコピーする.
#include <sys/types.h>
#include <sys/systm.h>
int
copyin(const void *uaddr, void *kaddr, size_t len);
int
copyinstr(const void *uaddr, void *kaddr, size_t len, size_t *done);
len
バイトのデータをコピーする.成功したら0を返す.
copyinstr
は,途中でNull終端されている場合はそこまでのデータをコピーし,done
には読み込まれたバイト数が書き込まれる.
copyout
copyout
はcopyin
の逆.カーネル空間からユーザ空間へデータをコピーする.
#include <sys/types.h>
#include <sys/systm.h>
int
copyout(const void *kaddr, void *uaddr, size_t len);
copystr
copystr
はカーネル空間の間で文字列をコピーするのに使われる.
#include <sys/types.h>
#include <sys/systm.h>
int
copystr(const void *kfaddr, void *kdaddr, size_t len, size_t *done);
Character Device Modules
モジュールが作れるので,当然キャラクタデバイスを作成する.
キャラクタデバイスモジュールには固有の3つの項目があり,それぞれについて説明する.
cdevsw
キャラクタデバイスはcharacter device switch table
のエントリとして登録される.
struct cdevsw
は<sys/conf.h>
で以下の様に定義されている
struct cdevsw {
int d_version;
u_int d_flags;
const char *d_name;
d_open_t *d_open; /* opens a device for I/O operations */
d_fdopen_t *d_fdopen;
d_close_t *d_close; /* closes a device */
d_read_t *d_read; /* reads data from a device */
d_write_t *d_write; /* write data to a device */
d_ioctl_t *d_ioctl; /* performs an operation other than a read or write */
d_poll_t *d_poll; /* polls a device to see if there is data to be read or space available for writing */
d_mmap_t *d_mmap;
d_strategy_t *d_strategy;
dumper_t *d_dump;
d_kqfilter_t *d_kqfilter;
d_purge_t *d_purge;
d_mmap_single_t *d_mmap_single;
int32_t d_spare0[3];
void *d_spare1[3];
/* These fields should not be messed with by drivers */
LIST_HEAD(, cdev) d_devs;
int d_spare2;
union {
struct cdevsw *gianttrick;
SLIST_ENTRY(cdevsw) postfree_list;
} __d_giant;
};
Linuxでいうfile_operations
みたいな感じか?
単純なread/write
が可能なキャラクタデバイスの定義をする際はこんな感じ
static struct cd_example_cdevsw = {
.d_version = D_VERSION, /* defined in <sys/conf.h> */
.d_open = open,
.d_close = close,
.d_read = read,
.d_write = write,
.d_name = "cd_sxample"
};
指定されていない操作については,「操作はサポートされていない」という事になる(EOPNOTSUPP
)
必須要素はd_version
とd_name
だけ
Character Device Functions
cdevsw
の各エントリに対応する関数を実装することで,キャラクタデバイスとして機能する.
それらの型は<sys/conf.h>
で定義されている.
例えばwrite
を実装するならこんな感じ
d_write_t write;
int
write(struct cdev *dev, struct uio *uio, int ioflag)
{
int error = 0;
error = copyinstr(uio->uio_iov->iov_base, &buf, 512, &len);
if (error != 0)
uprintf("Write to \"cd_example\" failed.\n");
return (error);
}
write
されると,ユーザ空間からカーネル空間のバッファへ文字列がコピーされる.
The Device Registration Routine
デバイスモジュールは,devfs
に登録する必要がある.
今までのようなevent handler
内で,make_dev
を呼び出す事で登録できる.
static struct cdev *sdev;
static int
load(struct module *module, int cmd, void *arg)
{
int error = 0;
switch (cmd) {
case MOD_UNLOAD:
sdev = make_dev(&cd_example_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "cd_example");
uprintf("Character device loaded!\n");
break;
case MOD_UNLOAD:
destroy(sdev);
uprintf("Character device unloaded!\n");
break;
default:
error = EOP_NOT_SUPP;
break;
}
return (error);
}
逆にdestroy
でデバイスは消える
こうする事で,/dev/cd_example
が生える.
Example
簡単なキャラクタデバイスモジュールを実装する
#include <sys/param.h>
#include <sys/proc.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/uio.h>
#define BUFSIZE 512 + 1
d_open_t open;
d_close_t close;
d_read_t read;
d_write_t write;
static struct cdevsw cd_example_cdevsw = {
.d_version = D_VERSION,
.d_open = open,
.d_close = close,
.d_read = read,
.d_write = write,
.d_name = "cd_example"
};
static char buf[BUFSIZE];
static size_t len;
int
open(struct cdev *dev, int flag, int otyp, struct thread *td)
{
memset(&buf, '\0', BUFSIZE);
len = 0;
return (0);
}
int
close(struct cdev *dev, int flag, int otyp, struct thread *td)
{
return (0);
}
int
write(struct cdev *dev, struct uio *uio, int ioflag)
{
int error = 0;
error = copyinstr(uio->uio_iov->iov_base, &buf, 512, &len);
if (error != 0)
uprintf("Write to \"cd_example\" failed.\n");
return (error);
}
int
read(struct cdev *dev, struct uio *uio, int ioflag)
{
int error = 0;
error = len <= 0 ? -1 : copystr(&buf, uio->uio_iov->iov_base, 513, &len);
return (error);
}
static struct cdev *sdev;
static int
load(struct module *module, int cmd, void *arg)
{
int error = 0;
switch (cmd) {
case MOD_LOAD:
sdev = make_dev(&cd_example_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "cd_example");
uprintf("Character device loaded.\n");
break;
case MOD_UNLOAD:
destroy_dev(sdev);
uprintf("Character device unloaded.\n");
break;
default:
error = EOPNOTSUPP;
break;
}
return (error);
}
DEV_MODULE(cd_example, load, NULL);
このコードでは,まずcdevsw
の各エントリとなる関数を登録し, buf
とlen
の2つのグローバル変数を定義している
buf
はこのキャラクタデバイスの実体となるバッファで, len
はその大きさを示す.
DEV_MODULE
は<sys/conf.h>
で以下の様に定義されている
#define DEV_MODULE(name, evh, arg) \
DEV_MODULE_ORDERD(name, evh, arg, SI_ORDER_MIDDLE)
#define DEV_MODULE_ORDERD(name, evh, arg, ord) \
static moduledata_t name##_mod = { \
#name, \
evh, \
arg, \
}; \
DECLARE_MODULE(name, name##_mod, SI_SUB_DRIVERS, ord)
ビルド,ロードすると,/dev/cd_example
を確認できる
$ make
machine -> /usr/src/11/sys/amd64/include
x86 -> /usr/src/11/sys/x86/include
Warning: Object directory not changed from original /home/vagrant/src/cdev
cc -O2 -pipe -fno-strict-aliasing -Werror -D_KERNEL -DKLD_MODULE -nostdinc -I. -I/usr/src/11/sys -fno-common -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -MD -MF.depend.cdev.o -MTcdev.o -mcmodel=kernel -mno-red-zone -mno-mmx -mno-sse -msoft-float -fno-asynchronous-unwind-tables -ffreestanding -fwrapv -fstack-protector -Wall -Wredundant-decls -Wnested-externs -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Winline -Wcast-qual -Wundef -Wno-pointer-sign -D__printf__=__freebsd_kprintf__ -Wmissing-include-dirs -fdiagnostics-show-option -Wno-unknown-pragmas -Wno-error-tautological-compare -Wno-error-empty-body -Wno-error-parentheses-equality -Wno-error-unused-function -Wno-error-pointer-sign -Wno-error-shift-negative-value -Wno-address-of-packed-member -mno-aes -mno-avx -std=iso9899:1999 -c cdev.c -o cdev.o
ld -m elf_x86_64_fbsd -d -warn-common -r -d -o cdev.ko cdev.o
:> export_syms
awk -f /usr/src/11/sys/conf/kmod_syms.awk cdev.ko export_syms | xargs -J% objcopy % cdev.ko
objcopy --strip-debug cdev.ko
$ sudo kldload ./cdev.ko
Character device loaded.
$ ls /dev/ |grep cd_example
cd_example
実際に挙動を確認する
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <err.h>
#define DEVICE "/dev/cd_example"
int
main(int argc, char **argv)
{
int len;
int fd;
if (argc != 2)
return printf("Usage: %s <string>\n", *argv), 1;
if ((len = strlen(argv[1])) +1 > 512)
return printf("string too long\n"), 1;
if ((fd = open(DEVICE, O_RDWR)) < 0)
err(1, "open:");
if (write(fd, argv[1], len) < 0)
err(1, "write:");
printf("write \"%s\" to %s succeed!\n", argv[1], DEVICE);
char buf[len];
if (read(fd, buf, len) < 0)
err(1, "read:");
printf("read \"%s\" from %s succeed!\n", buf, DEVICE);
}
$ gcc test.c -o test
$ sudo ./test
Usage: ./test <string>
$ sudo ./test "Hello, kernel!"
write "Hello, kernel!" to /dev/cd_example succeed!
read "Hello, kernel!" from /dev/cd_example succeed!
make_dev
$SYSDIR/kern/kern_conf.c
にある
Comments