私自身、見よう見まねでいろいろなプログラムを書いて(打ち込んで)いるときに、時々 ioctl に出会っていました。 得たいの知れない定数やら得たいの知れない構造体やらを渡すので、私にとっては不気味な存在でした。 C の関数なら、普通はマニュアルを読めば引数と機能は書いてあるのに、ioctl だけは、使い方だけで中身については一切なしなのです。
ですが、デバイスドライバをつくるようになってから、一気に謎が解けました。 ioctl は read や write とは別系統のドライバとのやり取りの口です。 read, write が基本的には連続したデータのやりとりをするように使用されることが多いのに対して、ioctl はどちらかというとデバイスの状態を制御する単発データ用といった趣きです。 ここのページでは、どちらも同じような使い方をしているので、区別はつきにくいですが。
ioctl は man ioctl してみると、ファイル記述子の他に、request とか cmd とか書いてある整数引数(unsigned int) がひとつあり、もうひとつ何か引数ととる(取らないこともあり)ようになってます。 デバイスドライバ内部の ioctl を受け持つ関数にはこの 整数引数と何らかの引数が unsigned long の形で渡されます。 この関数の帰り値が負の数値の場合、ユーザプロセスの ioctl()には-1が返り、errno にその絶対値が設定されます。
さて、ioctl を受け持つ関数には なんの機能を実装すればいいのでしょうか? それを決めるのはドライバを設計するみなさんです。一応、例を挙げます。
使い方&デバイスによったら、コマンドがある分だけ read/write で実装するより簡単だと思います。
ファイル ocitest.h (ヘッダ)
// header file of module ocitest.o
#define OCI_NOP 1 // ioctl 用のコマンド数値
#define OCI_INT 2 // 一応、カッコ良く定義しておく
#define OCI_STRING 3
#define OCI_STRUCT 4
struct oci_struct
{
char *p;
int len;
};
ファイル ocitest.c (モジュール)
// gcc -c ocitest.c -Wall -Wstrict-prototypes -O -pipe -m486
// mknod /dev/ocitest c 60 0; chmod a+rw /dev/ocitest
#define MODULE
#define __KERNEL__
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/string.h>
#include "ocitest.h"
static int devmajor=60;
static char *devname="ocitest";
#if LINUX_VERSION_CODE > 0x20115
MODULE_PARM(devmajor, "i");
MODULE_PARM(devname, "s");
#endif
#if LINUX_VERSION_CODE >= 0x020100
#include <asm/uaccess.h>
#else
// Linux 2.0.x では memcpy_tofs/_fromfs だった関数が
// Linux 2.1.x 以降で copy_to/from_user に 変更。なので合わせる
static inline unsigned long
copy_to_user(void *to, const void *from, unsigned long n)
{
memcpy_tofs(to,from,n);
return 0;
}
static inline unsigned long
copy_from_user(void *to, const void *from, unsigned long n)
{
memcpy_fromfs(to,from,n);
return 0;
}
#endif
// Linux 2.0/2.2 共通
static int ocitest_open(struct inode * inode, struct file * file)
{
MOD_INC_USE_COUNT;
return 0;
}
// Linux 2.1 以降帰り値 int (事実上共通でも可: カーネル内で返り値使わず)
#if LINUX_VERSION_CODE >= 0x020100
static int ocitest_close(struct inode * inode, struct file * file)
#else
static void ocitest_close(struct inode * inode, struct file * file)
#endif
{
MOD_DEC_USE_COUNT;
#if LINUX_VERSION_CODE >= 0x020100
return 0;
#endif
}
// ioctl: Linux 2.0/2.2共通
static int ocitest_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
char buff[257];
struct oci_struct ocis;
int i;
printk("ocitest: ioctl: cmd=%04X, arg=%08lX\n",cmd,arg);
switch(cmd)
{
case OCI_NOP: printk(" Cmd: No operation\n"); return 0;
case OCI_INT: printk(" Cmd: Integer: %ld\n",arg); return 0;
case OCI_STRING:
for(i=0;i<256;i++) // arg をポインタと見なしてユーザデータ取得
{ // get_user はバージョン依存
#if LINUX_VERSION_CODE >= 0x020100
if(get_user(buff[i],(char *)(arg+i))) return -EFAULT;
#else
buff[i]=get_user((char *)(arg+i));
#endif
if(buff[i]=='\0') break;
}
buff[i]='\0'; // 安全止
printk(" Cmd: String: %s\n",buff);
for(i=0;buff[i];i++) // to lower
{ if((buff[i]>='A')&&(buff[i]<='Z')) buff[i]+=('a'-'A'); }
copy_to_user((void *)arg,buff,i); // データ返還
return 0;
case OCI_STRUCT:
copy_from_user(&ocis,(void *)arg,sizeof(ocis));
for(i=0;;i++) // 文字列長さカウント
{
#if LINUX_VERSION_CODE >= 0x020100
if(get_user(buff[0],ocis.p+i)) return -EFAULT;
#else
buff[0]=get_user(ocis.p+i);
#endif
if(buff[0]=='\0') break;
}
ocis.len=i;
copy_to_user((void *)arg,&ocis,sizeof(ocis)); // データ返還
return 0;
}
return -EINVAL;
}
#if LINUX_VERSION_CODE >= 0x020100
static struct file_operations ocitest_fops = { // Linux 2.2.10 より
NULL, // loff_t llseek(struct file *, loff_t, int)
NULL, // ssize_t read(struct file *, char *, size_t, loff_t *)
NULL, // ssize_t write(struct file *, const char *, size_t, loff_t *)
NULL, // int readdir(struct file *, void *, filldir_t)
NULL, // u. int poll(struct file *, struct poll_table_struct *)
ocitest_ioctl,// int ioctl(struct inode *, struct file *, u.int, u.long)
NULL, // int mmap(struct file *, struct vm_area_struct *)
ocitest_open, // int open(struct inode *, struct file *)
NULL, // int flush(struct file *)
ocitest_close,// int release(struct inode *, struct file *)
NULL, // int fsync(struct file *, struct dentry *)
NULL, // int fasync(int, struct file *, int)
NULL, // int check_media_change(kdev_t dev)
NULL, // int revalidate(kdev_t dev)
NULL, // int lock(struct file *, int, struct file_lock *)
};
#else
static struct file_operations ocitest_fops = { // Linux 2.0.36 より
NULL, // int lseek(struct inode *, struct file *, off_t, int)
NULL, // int read(struct inode *, struct file *, char *, int)
NULL, // int write(struct inode *, struct file *, const char *, int)
NULL, // int readdir(struct inode *, struct file *, void *, filldir_t)
NULL, // int select(struct inode *, struct file *, int, select_table *)
ocitest_ioctl,// int ioctl(struct inode *, struct file *, u.int, u.long)
NULL, // int mmap(struct inode *, struct file *, struct vm_area_struct *)
ocitest_open, // int open(struct inode *, struct file *)
ocitest_close, // void release(struct inode *, struct file *)
NULL, // int fsync(struct inode *, struct file *)
NULL, // int fasync(struct inode *, struct file *, int)
NULL, // int check_media_change(kdev_t dev)
NULL, // int revalidate(kdev_t dev)
};
#endif
int init_module(void)
{
printk("install '%s' into major %d\n",devname,devmajor);
if(register_chrdev(devmajor,devname,&ocitest_fops)) // 登録
{
printk("device registration error\n");
return -EBUSY;
}
return 0;
}
void cleanup_module(void)
{
printk("remove '%s' from major %d\n",devname,devmajor);
if (unregister_chrdev(devmajor,devname))
{
printk ("unregister_chrdev failed\n");
}
};
ファイル ocitest_p.c (アクセス試験用プログラム)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>
#include <unistd.h>
#include "ocitest.h"
int main(void)
{
int fd;
char buff[100];
struct oci_struct ocis;
fd=open("/dev/ocitest",O_RDWR);
if(fd<0)
{ fprintf(stderr,"cannot open device\n"); return 1; }
ioctl(fd,OCI_NOP);
ioctl(fd,OCI_INT,12345);
strcpy(buff,"ABCDEFGhijklmnOPQRSTUvwxyz0123456789");
printf("ioctl(fd,OCI_STRING,\"%s\"); ->\n",buff);
ioctl(fd,OCI_STRING,buff);
printf(" -> %s\n",buff);
ocis.p=buff;
ioctl(fd,OCI_STRUCT,&ocis);
printf("length of \"%s\"=%d\n",ocis.p,ocis.len);
close(fd);
return 0;
}
実行方法を以下に示します。
insdev をつかってみたりしてますが、基本的にはこれまでと同じ実行方法です。
ただ、cat ではなく 自前のocitest_p を実行します。
% gcc -c ocitest.c -Wall -Wstrict-prototypes -O -pipe -m486 % gcc -o ocitest_p ocitest_p.c # insdev ocitest /dev/ocitest さっそく使ってみたりする auto-selected devcie major(c)=60 device 60:0: /dev/ocitest % ./ocitest_p ioctl(fd,OCI_STRING,"ABCDEFGhijklmnOPQRSTUvwxyz0123456789"); -> -> abcdefghijklmnopqrstuvwxyz0123456789 length of "abcdefghijklmnopqrstuvwxyz0123456789"=36 # rmdev ocitest unlink '/dev/ocitest' % dmesg | tail install 'ocitest' into major 60 ocitest: ioctl: cmd=0001, arg=4000A3C8 Cmd: No operation ocitest: ioctl: cmd=0002, arg=00003039 Cmd: Integer: 12345 ocitest: ioctl: cmd=0003, arg=BFFFF874 Cmd: String: ABCDEFGhijklmnOPQRSTUvwxyz0123456789 ocitest: ioctl: cmd=0004, arg=BFFFF86C remove 'ocitest' from major 60このデバイスでは コマンドとして1〜4の整数を受け付けます。
長ったらしいプログラムに見えますが、実際のところ、ioctl の動作を受け持つ関数は、コマンド数値による switch の嵐で、やってることは単純です。
ただし、readやwriteのところでもやりましたが、ユーザプロセスから渡されたポインタにアクセスするためには
と、文字列を得る部分には
Linux2.0:asm/segment.h (unsigned) char get_user(const (unsigned) char *user); (unsigned) short get_user(const (unsigned) short *user); (unsigned) long get_user(const (unsigned) long *user); *user の値が帰ります Linux2.2:asm/uaccess.h int get_user((unsigned) char val, (unsigned) char *user); int get_user((unsigned) short val, (unsigned) short *user); int get_user((unsigned) long val, (unsigned) long *user); 変数 val に *user の値が代入されます。 (valはポインタではありません。マクロで処理されます) 定義上は void ですが、実際にはこんな形で呼び出されます。 帰り値は転送完了で0、不正な転送で -14 が帰ります。"unsigned" が () 書きなのは、これらはサイズのみをチェックするマクロであるため、unsigned はあってもなくともいいからです。
get があるからには put もあります。
Linux2.0:asm/segment.h void put_user(char value, char *user); Linux2.2:asm/uaccess.h int put_user(char value, char *user); (unsigned) short, long も同形式。 不正な転送でやはり -14が帰ります。補足