正如指出的这个问题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;
但我在这里找不到有关上述两个替代原型的任何线索。
答案1
您正在查看在不同上下文中定义的函数。第三个:
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
- 上述两种原型都适合这种情况吗?如果是,为什么?如果没有,如何选择合适的?
他们并不都合适。仅有的版本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。如果函数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
.参考这个答案了解更多信息。
在最近的内核版本(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
按照安迪道尔顿在评论中的建议,从可用数据中获取)。至于里面的文件fs
:他们似乎没有使用struct file_operations
;另外,它们的功能也没有ioctl
定义在
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
因为它们采用不同的参数(fuse_priv_ioctl
in fs/fuse/ioctl.c
、search_ioctl
in fs/btrfs/ioctl.c
、ocfs2_reflink_ioctl
in fs/ocfs2/refcounttree.c
),所以它们可能只在驱动程序内部使用。
因此,假设在链接问题ioctl
认为Linux 内核模块内的函数有两个版本是错误的。仅有的unlocked_ioctl
(版本2)必须使用。