QA@IT

ioctl, Process/Kernel間で不具合

3396 PV

ioctlでProcess、Kernel間の連絡を取っているのですが不調です。

Process側で
unsigned int cmd = 0;
unsigned long arg = 0x85c000;
ret = ioctl(fd, cmd, arg);
を発行、ret=0が得られます。fd = 3がそれ以前に得られています。

Kernel側ではmy_ioctlが対応するはずです:
int my_ioctl (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
printk("cmd arg %x %x\n", cmd, arg);

switch(cmd)
    case 0:
        __raw_write(arg,linuxAddr0);
    case 1:
        __raw_write(arg,linuxAddr2);
    ....................

}

Process側のcmd, argとkernel側のそれは1対1に対応しているものと期待していたのですがそうはなりませんでした。Kernel側で見ると
cmd = 0x85c000, arg = 0x3f057cc0
つまりProcess側argの値がcmdに入り、argにはPointerと思われるゴミが入りました。
Process側cmdの値は消えてしまっています。
Process側を
ioctl (fd, IOC_WRITE, cmd, arg);
としてみたのですが結果は変わりません。

OSはPetaLinux v2015.2.1というやや特殊なものでXilinx社が推奨しています。
Debianの場合は全く問題ありませんでした。

Kernel Setupの過程に問題がある可能性もあります。

file_operations my_fops = {
owner: THIS_MODULE,
open: my_open,
// ioctl: my_ioctl, // Errorになる
unlocked_ioctl: my_ioctl,
release: my_close,
}

このmy_openも実行されています。それから
static int __init mydev_init(void)
{
...............
ret = register_chrdev(DEV_MAJOR, DRIVER_NAME, $my_fops);
...............
}
でもret=0が返ります。

手続きには特段の問題はないようにも思えるのですが、どこかで思い違いあるいは単純なミスがあるのかもしれません。
ご示唆いただければ幸いです。

回答

blunder3様
再度コメントいただけて感謝いたしております。

ご指摘のようにkernel側第1引数を省き、周辺を再度整理して実行したところ、cmd,argとも無事にKernel側に届きました。
long変数サイズの件ですが、64BitPC上のCentOSで開発していますが、Xilinx社が推奨するpetalinux環境でBuildしているので問題ないと思います。
それからioctlに関する整理されたDocumentの情報ありがとうございました。これを参考に再度整理するつもりです。
これでようやく次に進めます。ありがとうございました。

編集 履歴 (0)

blunder3様
回答有難うございます。

旧ioctlの先頭引数inodeを削除してCompileできました。しかし結果は全く同じです。
次に
#define HAVE_UNLOCKED_IOCTL 1
を追加したのですが変化はありません。
どうも仕組みを理解していないのでこれから先へ進めません。
ご示唆いただけることがあればお願いいたします。

編集 履歴 (0)
  • こちらがわかる範囲のことを回答の方に追記しておきました。
    -

include/linux/fs.hのstruct file_operationsを見ると

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

になっているので、旧ioctlの先頭引数inodeがなくなったせいでしょうかね。

追記
ちなみに新ioctlについての説明はここにあります。

The new way of ioctl()
http://lwn.net/Articles/119652/

追記(2016/01/30)
HAVE_UNLOCKED_IOCTLの定義はinclude/linux/fs.hの中にあります。もし未定義ならunlocked_ioctl(新ioctl)対応ではないという話です。自分で定義はしないで下さい。

include/linux/fs.h

/* These macros are for out of kernel modules to test that
 * the kernel supports the unlocked_ioctl and compat_ioctl
 * fields in struct file_operations. */
#define HAVE_COMPAT_IOCTL 1
#define HAVE_UNLOCKED_IOCTL 1
...
struct file_operations {
    ...
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    ...
};

次に仕組みの話ですが、わかりやすい解説記事があったので、リンクを貼っておきます。

http://opensourceforu.efytimes.com/2011/08/io-control-in-linux/

この記事では新旧ioctlを両方サポートしているので、LINUX_VERSION_CODEを見て切り替えていますが、HAVE_UNLOCKED_IOCTLを見て切り替える方法もあると思います。また古いカーネルをサポートする必要がないなら、新ioctl(unlocked_ioctl)のみでよいと思います。

上の記事からの引用

#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35))
static int my_ioctl(struct inode *i, struct file *f, unsigned int cmd, unsigned long arg)
#else
static long my_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
#endif

記事だと初期化の方法がgammodlerさんの方法とは少し違いますが、それが影響するかどうかは私にはよくわかりません。たぶんgammodlerさんの方法でもよさそうな気がしますが...

あとこれも関係するかどうかわかりませんが、カーネルが64ビットで、ユーザ空間のプログラムが32ビットだと引数の型の調整が必要になると思います。ioctlの引数に含まれるポインタおよびlongのサイズが異なる(4バイトか8バイトか)ためです。この話はcompat_ioctlの方と関係してきます。

でも現象から見ると、カーネルにはmy_ioctlをunlocked_ioctl(新ioctl)として登録しているのに、my_ioctlの方には余計な第1引数(inode)があるせいで(旧ioctl仕様)、準拠している仕様に矛盾があって、そのため引数が1個ずれて参照されているように見えます。私が思いつく原因はそれ位しかないんですよね。

編集 履歴 (2)
ウォッチ

この質問への回答やコメントをメールでお知らせします。