Linuxカーネルモジュールioctlの2つの異なる機能プロトタイプ

Linuxカーネルモジュールioctlの2つの異なる機能プロトタイプ

指摘したとおりこの問題ioctl、Linuxカーネルモジュールの関数プロトタイプは次のとおりです。

(バージョン1)

int ioctl(struct inode *i, struct file *f, unsigned int cmd, unsigned long arg);

または

(バージョン2)

long ioctl(struct file *f, unsigned int cmd, unsigned long arg);

文字デバイスドライバを実装するカーネルモジュールで使用したいと思います。

  1. 上記の両方のタイプがこの状況に適していますか?では、なぜそうなのでしょうか?そうでなければ正しいものを選ぶ方法は?
  2. これらのプロトタイプを含むヘッダー/ソースファイルは何ですか?つまり、これらのプロトタイプの公式参照文書は何ですか?

私はUbuntu 20.04を実行していますx86_64が、次は私が使用できるヘッダーファイルです。

/usr/include/asm-generic/ioctl.h
/usr/include/linux/ioctl.h
/usr/include/linux/mmc/ioctl.h
/usr/include/linux/hdlc/ioctl.h
/usr/include/x86_64-linux-gnu/sys/ioctl.h
/usr/include/x86_64-linux-gnu/asm/ioctl.h

唯一重要な行は次のとおりです/usr/include/x86_64-linux-gnu/sys/ioctl.h

extern int ioctl (int __fd, unsigned long int __request, ...) __THROW;

しかし、ここでは、上記の2つの代替プロトタイプのどの手がかりも見つかりません。

答え1

さまざまなコンテキストで定義された関数を見ています。 3番目:

extern int ioctl (int __fd, unsigned long int __request, ...) __THROW;

システムコール (つまり、ユーザー空間からカーネル空間への呼び出し)

他のものはカーネルで定義されている関数のように見えます(struct fileどちらstruct inodeもカーネルデータ構造です)。

一部のユーザー空間プログラムでシステムコールを呼び出すと

   +-------------------+
   | userspace program |
   +-------------------+
            |
ioctl(fd, requestType, arg);
            |
            |                                           userspace
-------------------------------------------------------------------
            |                                           kernelspace
            v
SYSCALL_DEFINE3(ioctl...) /* ${kernel_root}/fs/ioctl.c */
            |
            v
      do_vfs_ioctl(...)
            |
 /*
   look at fd, map it to the device driver.  Call the ioctl
   registered for that device type.

   for example: drivers/char/random.c:

   const struct file_operations random_fops = {
        ...
        .unlocked_ioctl = random_ioctl,
        ...
   };
            |
            V
static long random_ioctl(struct file *f, unsigned int cmd, unsigned long arg)

なぜいくつかstruct fileとは違うのですstruct inodeか?わかりませんが、指定されたファイル記述子(システムコールの引数)に関連fdするデバイスの種類によって異なります。 VFS層は、さまざまな種類の登録済みドライバに渡すことができます。たとえば、デバイスドライバは.を使用し、struct fileファイルシステムドライバはstruct inode.

編集する

あなたの質問どのように書くのですか?特徴システムコールをサポートするデバイスドライバioctl、以下は簡単な例です。

#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>

static int example_device_major_number;
static const char example_device_name[] = "example-driver";

#define LOG(fmt, ...) printk(KERN_NOTICE "%s[%s:%d]: " fmt "\n", example_device_name, __FUNCTION__, __LINE__, ##__VA_ARGS__)

static long example_module_ioctl(struct file *, unsigned int cmd, unsigned long arg)
{
    LOG("cmd: %d, arg: %lu", cmd, arg);
    return 0;
}

static struct file_operations example_module_fops =
{
    .owner          = THIS_MODULE,
    .unlocked_ioctl = example_module_ioctl,
};

static int example_module_init(void)
{
    int result = 0;

    result = register_chrdev(0, example_device_name, &example_module_fops);
    if (result < 0)
    {
            LOG("Can't register character device with error code = %d", result);
            return result;
    }

    example_device_major_number = result;

    LOG("Registered character device with major number = %d", example_device_major_number);

    return 0;
}

static void example_module_exit(void)
{
    if (example_device_major_number != 0)
    {
        unregister_chrdev(example_device_major_number, example_device_name);
    }
    LOG("Module removed");
}

module_init(example_module_init);
module_exit(example_module_exit);
MODULE_LICENSE("GPL");

モジュールをコンパイルしてロードすると、出力に次のものが表示されますdmesg

[1325403.600381] example-driver[example_module_init:35]: Registered character device with major number = 238

ここでは、カーネルが新しく追加された文字デバイスドライバに238という主要なデバイス番号を割り当てたことがわかります。

これで、対応するメジャー番号を使用して文字デバイスファイルを作成できます。

$ sudo mknod mydevice c 238 0
$ ls -l mydevice
crw-r--r-- 1 root root 238, 0 Nov 26 17:03 mydevice

次に、(1)装置ファイルを開き、(2)ioctl()結果ファイル記述子を呼び出すユーザ空間プログラムを作成することができる。

#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <unistd.h>

int main(void)
{
    int fd = open("mydevice", O_RDWR);
    if (fd < 0) {
        perror("open");
        return 1;
    }

    int rc = ioctl(fd, 1, 2);
    if (rc < 0) {
        perror("ioctl");
    }

    (void) close(fd);

    return 0;
}

以前にロードされたモジュールを使用してユーザー空間アプリケーションをコンパイルして実行すると、出力に次のものが表示されます。dmesg

[1325593.158303] example-driver[example_module_ioctl:12]: cmd: 1, arg: 2

答え2

  1. 上記の両方のタイプがこの状況に適していますか?では、なぜそうなのでしょうか?そうでなければ正しいものを選ぶ方法は?

すべてに適しているわけではありません。ただバージョン2現在カーネルで利用可能なので、このバージョンを使用する必要があります。

  1. これらのプロトタイプを含むヘッダー/ソースファイルは何ですか?つまり、これらのプロトタイプの公式参照文書は何ですか?

これは定義内にありますinclude/linux/fs.h(カーネルソースルートへの相対パス)struct file_operations

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

つまり、メンバーはunlocked_ioctl関数へのポインターでなければなりません。

long ioctl(struct file *f, unsigned int cmd, unsigned long arg);

これが正しいバージョン2。関数がmy_ioctl()カーネルモジュール内で定義されている場合バージョン1代わりにコンパイラエラーが生成されます。

error: initialization of ‘long int (*)(struct file *, unsigned int,  long unsigned int)’ from incompatible pointer type ‘long int (*)(struct inode *, struct file *, unsigned int,  long unsigned int)’ [-Werror=incompatible-pointer-types]
  .unlocked_ioctl = my_ioctl
                    ^~~~~~~~

いくつかの追加コメント

バージョン1前までは唯一の人でした。カーネル 2.6.10struct file_operationsそのうち

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

ただし、このioctl関数はBKL(大きなカーネルロック)を生成します。つまり、実行時にカーネル全体をロックします。これは望ましくありません。だから、2.6.11

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

ioctlカーネルをロックしないsを使用する新しい方法が導入されました。これには、カーネルロックがある古いバージョンioctlと新しいバージョンがunlocked_ioctl共存します。 ~から2.6.36、前の項目がioctl削除されました。unlocked_ioctl.referenceのみを使用するには、すべてのドライバを適切に更新する必要があります。この回答より多くの情報を知りたいです。

最近のカーネルバージョン(5.15.2)には、まだ古いバージョンを使用しているファイルがほとんどないようですioctl

linux-5.15.2$ grep -r "ioctl(struct inode" *
Documentation/cdrom/cdrom-standard.rst: int cdrom_ioctl(struct inode *ip, struct file *fp,
drivers/staging/vme/devices/vme_user.c:static int vme_user_ioctl(struct inode *inode, struct file *file,
drivers/scsi/dpti.h:static int adpt_ioctl(struct inode *inode, struct file *file, uint cmd, ulong arg);
drivers/scsi/dpt_i2o.c:static int adpt_ioctl(struct inode *inode, struct file *file, uint cmd, ulong arg)
fs/fuse/ioctl.c:static int fuse_priv_ioctl(struct inode *inode, struct fuse_file *ff,
fs/btrfs/ioctl.c:static noinline int search_ioctl(struct inode *inode,
fs/ocfs2/refcounttree.h:int ocfs2_reflink_ioctl(struct inode *inode,
fs/ocfs2/refcounttree.c:int ocfs2_reflink_ioctl(struct inode *inode,
net/sunrpc/cache.c:static int cache_ioctl(struct inode *ino, struct file *filp,

vme_user.cdpt_i2o.cおよびcache.c、しかし、以下があります。

static const struct file_operations adpt_fops = {
        .unlocked_ioctl = adpt_unlocked_ioctl,

それから

static long adpt_unlocked_ioctl(struct file *file, uint cmd, ulong arg)
{
        struct inode *inode;
        long ret;

        inode = file_inode(file);

        mutex_lock(&adpt_mutex);
        ret = adpt_ioctl(inode, file, cmd, arg);

そのため、彼らは新しいバージョンから古いバージョンを使用します(inodeAndy Daltonがコメントで提案した利用可能なデータから取得されます)。内部ファイルはfs使用されていないようです。struct file_operationsその機能はioctl定義されていません。

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

fuse_priv_ioctl異なる引数 ( in fs/fuse/ioctl.c, search_ioctlin fs/btrfs/ioctl.c, ocfs2_reflink_ioctlin ) を使用するため、fs/ocfs2/refcounttree.cドライバで内部的にのみ使用できます。

したがって、次のように仮定します。リンクの問題ioctlLinuxカーネルモジュール内に2つのバージョンの機能があると思うのは間違いです。のみunlocked_ioctlバージョン2)を使用する必要があります。

関連情報