Linux 커널 모듈 ioctl의 두 가지 다른 기능 프로토타입

Linux 커널 모듈 ioctl의 두 가지 다른 기능 프로토타입

지적한대로이 문제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;

하지만 여기서는 위의 두 가지 대체 프로토타입에 대한 어떤 단서도 찾을 수 없습니다.

답변1

다양한 컨텍스트에서 정의된 함수를 보고 있습니다. 세 번째:

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

시스템 호출 (즉, 사용자 공간에서 커널 공간으로의 호출)

다른 것들은 커널에 정의된 함수처럼 보입니다( struct filestruct 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.10, 그 struct 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.c, dpt_i2o.ccache.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 커널 모듈 내에 두 가지 버전의 기능이 있다고 생각하는 것은 실수입니다. 오직 unlocked_ioctl(버전 2)을 사용해야 합니다.

관련 정보