Linux-Kernel

Linux核心模組ioctl的兩種不同的函式原型

  • November 27, 2021

正如在這個問題中指出的那樣,ioctlLinux核心模組內的函式原型是:

(版本 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. 上述兩個原型都適合這種情況嗎?如果是,為什麼?如果沒有,如何選擇合適的?

它們都不適合。目前核心中只有版本 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。如果使用版本 1my_ioctl()在核心模組中定義函式,則會生成編譯器錯誤:

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);

引入了一種新的使用ioctls 的方法,它不會鎖定核心。這裡舊ioctl的帶核心鎖和新的unlocked_ioctl並存。從2.6.36 開始,舊的ioctl已被刪除。所有驅動程序都應相應更新,僅使用unlocked_ioctl. 有關更多資訊,請參閱此答案

在最近的核心版本(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.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);

所以他們在新版本中使用舊版本(inode從可用數據中獲取,正如 Andy Dalton 在評論中所建議的那樣)。至於里面的文件fs:好像沒有用struct file_operations;此外,它們的功能未ioctl

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

因為它們採用不同的參數(fuse_priv_ioctlin fs/fuse/ioctl.csearch_ioctlin fs/btrfs/ioctl.cocfs2_reflink_ioctlin fs/ocfs2/refcounttree.c),所以它們可能只在驅動程序內部使用。

因此,連結問題ioctl中關於Linux 核心模組內的函式有兩個版本可用的假設是錯誤的。只能使用unlocked_ioctl版本 2)。

您正在查看在不同上下文中定義的函式。第三個:

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

引用自:https://unix.stackexchange.com/questions/679207