Linux编程之ioctl

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情

在和设备驱动程序通信时,ioctl是很常用的一个调用,常用来配置、查询或者修改设备的配置。反过来说,编写驱动程序时,ioctl也是经常要实现的一个接口,以便应用程序可以方便地控制设备驱动。

应用程序中的ioctl

ioctl函数的原型如下:

       #include <sys/ioctl.h>

       int ioctl(int fd, unsigned long request, ...);

可以看出该函数是一个可变参数的函数。第一个参数是一个文件描述符,通常用open调用来打开。在Linux中,一切皆文件,哪怕你调用open打开一个设备,也得到一个文件描述符。这一切要归功于内核里的VFS(虚拟文件系统)。第二个参数request是控制代码,或者说是控制命令、请求代码都可以。根据第二个参数的不同,后面所带的参数个数和类型都可能不一样。

内核中的ioctl

当引用进程调用ioctl时,Linux内核中实现的系统调用被调用。系统调用是Linux的一种标准机制,在不同的CPU体系结构中的具体实现方式可能不一样。ioctl系统调用定义于内核源码的fs/ioctl.c中,其定义如下:

SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
{
	struct fd f = fdget(fd);
	int error;

	if (!f.file)
		return -EBADF;

	error = security_file_ioctl(f.file, cmd, arg);
	if (error)
		goto out;

	error = do_vfs_ioctl(f.file, fd, cmd, arg);
	if (error == -ENOIOCTLCMD)
		error = vfs_ioctl(f.file, cmd, arg);

out:
	fdput(f);
	return error;
}

从ioctl系统调用的源码可以看出,它首先会调用do_vfs_ioctl,如果do_vfs_ioctl不支持这个控制命令,则调用vfs_ioctl

do_vfs_ioctl函数的源码可点击链接直接查看,它主要处理一些文件系统通用的控制命令,如FIOASYNC等,详细控制命令列表可查看该函数源码。另外,该函数还会判断文件节点是不是常规文件,如果是则调用file_ioctl函数。后者也处理一些跟文件相关的控制命令。

对于设备驱动的控制命令,一般do_vfs_ioctl并不会直接处理,而是调用vfs_ioctl,该函数代码如下:

long vfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int error = -ENOTTY;

	if (!filp->f_op->unlocked_ioctl)
		goto out;

	error = filp->f_op->unlocked_ioctl(filp, cmd, arg);
	if (error == -ENOIOCTLCMD)
		error = -ENOTTY;
 out:
	return error;
}
EXPORT_SYMBOL(vfs_ioctl);

可见,该函数最终会调用到filp>f_op->unlocked_ioctl函数。这里filp时一个文件对象,在open是创建。每个文件对象都又一个文件操作接口,即f_op。如果一个设备驱动程序支持虚拟文件系统VFS,那么它就会注册一个文件操作接口,即f_op,而这个接口中包含几个基本函数,如open、close、read、write、ioctl等。因此,通过这个调用,最终调用到了设备驱动程序的unlocked_ioctl函数。

以/dev/urandom设备为例,它是一个字符型设备,其源码位于drivers/char/random.c。它的文件操作接口定义如下:

const struct file_operations urandom_fops = {
	.read  = urandom_read,
	.write = random_write,
	.unlocked_ioctl = random_ioctl,
	.compat_ioctl = compat_ptr_ioctl,
	.fasync = random_fasync,
	.llseek = noop_llseek,
};

而该文件操作接口的注册则是在注册/dev/urandom设备是一起注册的。不过新内核的代码为了提高效率,把多个设备一起作为内存设备注册。具体代码位于drivers/char/mem.c

具体到ioctl接口,其实现如下:

static long random_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
{
	int size, ent_count;
	int __user *p = (int __user *)arg;
	int retval;

	switch (cmd) {
	case RNDGETENTCNT:
		/* inherently racy, no point locking */
		ent_count = ENTROPY_BITS(&input_pool);
		if (put_user(ent_count, p))
			return -EFAULT;
		return 0;
	case RNDADDTOENTCNT:
		if (!capable(CAP_SYS_ADMIN))
			return -EPERM;
		if (get_user(ent_count, p))
			return -EFAULT;
		return credit_entropy_bits_safe(&input_pool, ent_count);
	case RNDADDENTROPY:
		if (!capable(CAP_SYS_ADMIN))
			return -EPERM;
		if (get_user(ent_count, p++))
			return -EFAULT;
		if (ent_count < 0)
			return -EINVAL;
		if (get_user(size, p++))
			return -EFAULT;
		retval = write_pool(&input_pool, (const char __user *)p,
				    size);
		if (retval < 0)
			return retval;
		return credit_entropy_bits_safe(&input_pool, ent_count);
	case RNDZAPENTCNT:
	case RNDCLEARPOOL:
		/*
		 * Clear the entropy pool counters. We no longer clear
		 * the entropy pool, as that's silly.
		 */
		if (!capable(CAP_SYS_ADMIN))
			return -EPERM;
		input_pool.entropy_count = 0;
		return 0;
	case RNDRESEEDCRNG:
		if (!capable(CAP_SYS_ADMIN))
			return -EPERM;
		if (crng_init < 2)
			return -ENODATA;
		crng_reseed(&primary_crng, NULL);
		crng_global_init_time = jiffies - 1;
		return 0;
	default:
		return -EINVAL;
	}
}

应用程序ioctl示例

下面的例子通过ioctl获取/dev/urandom的熵池的大小,然后通过ioctl清除熵池。由于ioctl的命令代码是每个设备自定义的,因此针对每个设备的ioctl需要包含该设备的头文件。下面的例子中就是<linux/random.h>。需要注意的是清除熵池的操作需要管理员权限,因此运行时应该用sudo来运行。

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

int main(int argc, char *argv[])
{
	int ent_cnt;
	int fd = open("/dev/urandom", O_RDONLY);
	if (fd < 0) {
		perror("open");
	} else {
		if (ioctl(fd, RNDGETENTCNT, &ent_cnt) < 0) {
			perror("ioctl");
		} else {
			printf("entropy count (bits): %d\n", ent_cnt);
		}
		if (ioctl(fd, RNDCLEARPOOL) < 0) {
			perror("ioctl");
		}
		if (ioctl(fd, RNDGETENTCNT, &ent_cnt) < 0) {
			perror("ioctl");
		} else {
			printf("entropy count (bits) after clear: %d\n", ent_cnt);
		}
		close(fd);
	}
	return 0;
}

编译并运行该程序,可得到如下结果。

gcc -o rnd_ioctl rnd_ioctl.c
sudo ./test_ioctl
entropy count (bits): 2249
entropy count (bits) after clear: 0

驱动程序ioctl示例

下面这个例子演示如何在驱动程序中实现ioctl接口。

创建一个目录dummy,然后在下面创建dummy_main.c,为驱动程序源代码。驱动程序的入口用module_init指定,在安装驱动程序时入口被调用;驱动程序的卸载函数则用module_exit指定,卸载驱动时调用。这个驱动程序要做的事情很简单,就是注册一个dummy设备/dev/dummy,并且支持一个ioctl命令DUMMY_COUNT。这个ioctl命令的作用就是获取对/dev/dummy设备进行ioctl命令的次数。因为任何应用程序都可以对这个设备做ioctl,因此用一个原子变量来累计。其详细代码如下所示。注意在这个驱动程序中,在注册文件操作接口时,并没有指定open以及release等操作函数,但这不代表该设备没有这两个操作函数。所有支持文件系统操作接口的设备都必须实现open函数。在这个例子中,/dev/dummy设备是miscdevice设备,而miscdevice设备又属于字符类设备,内核中对字符类设备都提供默认的open和release操作函数实现。如果你的驱动程序不覆盖这个实现,则采用默认实现。

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
#include "dummy.h"

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("dummy driver");

static atomic_t dummy_ioctl_cnt = ATOMIC_INIT(0);
static long dummy_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int __user *p = (int __user *)arg;
	switch (cmd) {
		case DUMMY_COUNT:
			atomic_inc(&dummy_ioctl_cnt);
			if (put_user(atomic_read(&dummy_ioctl_cnt), p))
				return -EFAULT;
			break;
		default:
			return -EOPNOTSUPP;
	}
	return 0;
}
static const struct file_operations dummy_fops = {
	.owner = THIS_MODULE,
	.unlocked_ioctl = dummy_ioctl,
	.compat_ioctl = compat_ptr_ioctl,
};

static struct miscdevice dummy_dev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "dummy",
	.nodename = "dummy",
	.fops = &dummy_fops,
	.mode = 0666,
};
static int __init dummy_register(void)
{
	int err;
	err = misc_register(&dummy_dev);
	if (err == 0) {
		printk("dummy dev registered, minor %d\n", dummy_dev.minor);
	}
	return err;
}
static void __exit dummy_unregister(void)
{
	misc_deregister(&dummy_dev);
}

static int __init dummy_init(void)
{
	return dummy_register();
}
static void __exit dummy_exit(void)
{
	dummy_unregister();
}

module_init(dummy_init);
module_exit(dummy_exit);

再创建一个头文件dummy.h,在里面定义ioctl命令码,如下所示。

#ifndef _UAPI_LINUX_DUMMY_H
#define _UAPI_LINUX_DUMMY_H
#include <linux/ioctl.h>

#define DUMMY_COUNT	_IOR('R', 0x1, int)

#endif

编译驱动程序需要一个Makefile。编译驱动需要安装内核编译支持,并指定build目录;除此之外还需要指定当前驱动源码所在目录。由于dummy驱动不是built-in驱动,因此在Makefile中采用obj-m。如下所示。

obj-m:=dummy.o
dummy-objs:=dummy_main.o

.PHONY: all clean
all:
	make -C /lib/modules/`uname -r`/build M=`pwd` modules

clean:
	@rm -rf *.o *.ko *.mod *.mod.c *.order *.symvers

最后在dummy调用make命令即可编译。

make
make -C /lib/modules/`uname -r`/build M=`pwd` modules
make[1]: Entering directory '/usr/src/linux-headers-5.4.0-60-generic'
  CC [M]  dummy_main.o
  LD [M]  dummy.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC [M]  dummy.mod.o
  LD [M]  dummy.ko
make[1]: Leaving directory '/usr/src/linux-headers-5.4.0-60-generic'

编译完成会生成dummy.ko,可调用insmod安装。安装驱动需要管理员权限。安装完成后可查看/dev/dummy检查驱动程序是否正确加载。如下所示。

sudo insmod ./dummy.ko
ls -l /dev/dummy
crw-rw-rw- 1 root root 10, 57 1月  12 08:12 /dev/dummy

需要注意的是,这个例子中并没有对生成的dummy.ko做strip操作。在正式产品开发中,一般都需要在Makefile中定义一个安装步骤,在安装时做strip操作以减小驱动程序模块文件尺寸。

 

下面再用一个简单的应用程序来验证驱动程序的ioctl操作。在同一个目录下创建dummy_ioctl.c,并编译运行。运行了应用程序两次,可以看到ioctl操作在驱动程序中被正确累加,并由ioctl正确返回。

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

int main(int argc, char *argv[])
{
	int i;
	int dummy_cnt;
	int fd = open("/dev/dummy", O_RDONLY);
	if (fd < 0) {
		perror("open");
	} else {
		for (i = 0; i < 3; i++) {
			if (ioctl(fd, DUMMY_COUNT, &dummy_cnt) < 0) {
				perror("ioctl");
			} else {
				printf("#%d: dummy_cnt: %d\n", i, dummy_cnt);
			}
		}
		close(fd);
	}
	return 0;
}
 gcc -o dummy_ioctl dummy_ioctl.c
./dummy_ioctl
#0: dummy_cnt: 1
#1: dummy_cnt: 2
#2: dummy_cnt: 3
./dummy_ioctl
#0: dummy_cnt: 4
#1: dummy_cnt: 5
#2: dummy_cnt: 6

 

 

 

Linuxioctl 目录 1. ioctl概念 2. 具体实现 3. 示例 4. 参考 1. ioctl概念 ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。 大部分驱动除了读写设备的能里,还需要通过设备驱动对硬件进行各种控制操作。例如,设备上锁,改变波特率,调整转速等等。所有这些命令都是通过ioctl来支持的。 用户空间原型: int ioctl(int fd, unsigned long cmd, ...); fd: 文件描述符,用户程序使用open函数打开时代表文件的控制符 cmd: 用 阅读详情

相关推荐

linux驱动开发-ioctl

TOCTOC在内核中,ioctl(input/output control)是一个系统调用,用于设备驱动程序和用户空间程序之间的通信。它允许用户空间程序向设备驱动程序发送命令,以执行特定的操作或获取设备的状态信息。ioctl 是一个非常灵活的接口,因为它可以根据设备类型和需求定义不同的命令。ioctl 的基本结构。

gopher9511的博客 1603

Linux ioctl函数详解及编程示例

接下来,我们使用ioctl函数发送GET_PARAM命令,获取设置后的参数值,并打印出来。综上所述,ioctl函数在Linux系统中是一个非常强大和灵活的函数,用于与设备驱动程序进行通信和控制。ioctl函数是Linux系统中一个非常有用的函数,用于与设备驱动程序进行通信和控制。需要注意的是,上述示例仅用于演示ioctl函数的基本用法,并假设设备文件已经存在且具有相应的驱动程序。在实际应用中,你需要根据具体的设备和驱动程序来定义请求编码和参数类型,并根据需要进行错误处理。可以是终端、文件或设备节点等。

ByteEchoX的博客 414

Linux·字符设备驱动实例-基于通用摸版

字符设备驱动实例

m0_64560763的博客 751

Linux下的ioctl()函数详解

我这里说的ioctl函数是指驱动程序里的,因为我不知道还有没有别的场合用到了它,所以就规定了我们讨论的范围。写这篇文章是因为我前一阵子被ioctl给搞混了,这几天才弄明白它,于是在这里清理一下头脑。 一、 什么是ioctl ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。它的调用个数如下: int ioctl(int fd, ind cmd, …); 其中fd是用户程序打开设备时...

shujuliu818的专栏 6152

linux 内核 - ioctl 函数详解

1. 概念 ioctl 是设备驱动程序中设备控制接口函数,一个字符设备驱动通常会实现设备打开、关闭、读、写等功能,在一些需要细分的情境下,如果需要扩展新的功能,通常以增设 ioctl() 命令的方式实现。 在文件 I/O 中,ioctl 扮演着重要角色,本文将以驱动开发为侧重点,从用户空间到内核空间纵向分析 ioctl 函数。 2. 用户空间 ioctl #include &...

岁月斑驳7的博客 18万+

Linux 网络编程ioctl函数

1.介绍 Linux网络程序与内核交互的方法是通过ioctl实现的,ioctl与网络协议栈进行交互,可得到网络接口的信息,网卡设备的映射属性和配置网络接口.并且还能够查看,修改,删除ARP高速缓存的信息,所以,我们有必要了解一下ioctl函数的具体实现. 2.相关结构体与相关函数 #include int ioctl(int d,int request,....); 参数: d

bruceyang2009lzu的专栏 1414

Linux怎样实现交互编程Ioctl函数)

https://blog.csdn.net/coolwriter/article/details/78242256(感谢之前的博主写的非常好)

WangY.H 424

Linux网络编程ioctl函数在网络编程中的应用(C语言实现一个ifconfig)

linux系统用户态与内核态(包括设备、驱动)进行非数据流的相关控制信息交互,一般是通过ioctl函数实现。相同的,用户态与网络设备相关的控制信息也是通过ioctl实现。实质上,ifconfig、route底层最终是通过调用ioctl函数实现其功能的

只要思想不滑坡,想法总比问题多。 2837

linux ioctl网络参数设置,Linux 网络编程ioctl函数

1.介绍Linux网络程序与内核交互的方法是通过ioctl实现的,ioctl与网络协议栈进行交互,可得到网络接口的信息,网卡设备的映射属性和配置网络接口.并且还能够查看,修改,删除ARP高速缓存的信息,所以,我们有必要了解一下ioctl函数的具体实现.2.相关结构体与相关函数#includeint ioctl(int d,int request,....);参数:d-文件描述符,这里是对网络套接...

weixin_29830257的博客 972

Linux系统编程-fcntl和ioctl

fcntl():文件描述符所变的魔术几乎都来源于该函数。

m0_63690570的博客 412

linux ioctl()详解 与 linux c编程之fcntl

linux c编程之fcntl https://www.cnblogs.com/taiyang-li/p/5924121.html linux ioctl()详解 https://blog.csdn.net/coolwriter/article/details/78242256 (笔记)Linux下的ioctl()函数详解 https://www.cnblogs.com/td...

happylzs2008的专栏 468

Linux系统编程之设备文件与ioctl

Linux系统中,所有的硬件设备都被抽象为文件,这些文件被称为设备文件。通过使用标准的文件操作函数(比如:open、close、read、write),可以对硬件设备进行控制和数据传输。但对于某些复杂或特定的硬件功能,标准的文件操作可能无法满足需求。这时候,就需要使用ioctl函数来实现更精细的控制。

hope_wisdom的技术博客 1675

Linux系统IOCTL使用示例及编程

IOCTL是一种用于设备驱动程序和应用程序之间进行通信的接口。它允许应用程序向设备驱动程序发送各种控制命令,并获取设备驱动程序返回的信息。IOCTL命令通常用于设备的配置、状态查询和控制。

2301_79326254的博客 352

linux ioctl编程基础

在用户态中,很多时候除了要读写内核设备外,还要进行一些其他的控制。ioctl可以实现用户态和内核态的通信,即通过用户态向内核态发送命令,在内核态的switch  case中做出相应的处理,即可在用户态实现内核态的控制。那么ioctl是怎么实现的呢?        1. 首先用户态要向内核态发送命令,看看命令是怎么构成的。一个ioctl命令被分为多个字段,包括:类型(幻数)、序数、传送方向以及参

jasonsungblog的专栏 1002

Linux网络编程- struct ifreq & ioctl() 系统调用

是一个数据结构,用于各种与接口相关的输入/输出控制 (ioctl) 调用。它的主要用途是在网络编程中获取和设置网络接口的属性。这个结构体在头文件中定义。以下是: 一个字符数组,表示接口的名称,如 “eth0”, “wlan0” 等。: 一个类型的结构,表示接口的地址。: 同样是一个类型的结构,表示接口的网络掩码。: 表示接口的广播地址。: 表示接口的标志,如IFF_UP(接口正在运行)、(接口支持广播)、(接口在混杂模式下)等。: 表示接口的硬件(通常是 MAC)地址。

青衫客36的博客 6875

Linux之socket编程ioctl函数介绍(ifconfig例子)

ioctl函数一直作为那些不适合归入其他精细定义类别的特性的系统接口(即特殊的接口),POSIX一直致力拜托处于标准化过程中的特定功能的ioctl接口,如使用新的函数sockatmark函数取代SIOCATMARK,但是与网络相关并且依赖于实现的特性保留的ioctl请求为数依然不少,主要用于获取接口信息、访问路由表、访问ARP高速缓存等等。 函数的原型如下,其中返回0表示成功,返回-1表示失败。r...

Linux 5974
上一篇: Python常用音频库
下一篇: Python开发之jupyter notebook使用详解
rangzh
博客等级 码龄19年 29粉丝 21原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值