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);
我想在實現字元設備驅動程序的核心模組中使用它們。
- 上述兩個原型都適合這種情況嗎?如果是,為什麼?如果沒有,如何選擇合適的?
- 哪些標頭檔/源文件包含這些原型?換句話說:這些原型的官方參考文件是什麼?
我正在執行 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可用,所以這是應該使用的版本。
- 哪些標頭檔/源文件包含這些原型?換句話說:這些原型的官方參考文件是什麼?
它們位於
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。如果使用版本 1
my_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);
引入了一種新的使用
ioctl
s 的方法,它不會鎖定核心。這裡舊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_ioctl
infs/fuse/ioctl.c
、search_ioctl
infs/btrfs/ioctl.c
、ocfs2_reflink_ioctl
infs/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