ioctl函数的作用与使用

ioctl函数的作用


特殊的read,write,   当你用read,write不能完成某一功能时,就用ioctl

我这里说的ioctl函数是在驱动程序里的,因为我不知道还有没有别的场合用到了ioctl,所以就规定了我们讨论的范围。为什么要写篇文章呢,是因为我前一阵子被ioctl给搞混了,这几天才弄明白它,于是在这里清理一下头脑。

一、 什么是ioctl。
ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。它的调用个数如下:
int ioctl(int fd, ind cmd, …);
其中fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,有或没有是和cmd的意义相关的。
ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就可以在用户程序中使用ioctl函数控制设备的I/O通道。 【所以说这些cmd不能和内核中的相同,否则就有冲突了】

二、 ioctl的必要性
如果不用ioctl的话,也可以实现对设备I/O通道的控制,但那就是蛮拧了。例如,我们可以在驱动程序中实现write的时候检查一下是否有特殊约定的数据流通过,如果有的话,那么后面就跟着控制命令(一般在socket编程中常常这样做)。但是如果这样做的话,会导致代码分工不明,程序结构混乱,程序员自己也会头昏眼花的。所以,我们就使用ioctl来实现控制的功能。要记住,用户程序所作的只是通过命令码告诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情。

三、 ioctl如何实现
这是一个很麻烦的问题,我是能省则省。要说清楚它,没有四五千字是不行的,所以我这里是不可能把它说得非常清楚了,不过如果有读者对用户程序怎么和驱动程序联系起来感兴趣的话,可以看我前一阵子写的《write的奥秘》。读者只要把write换成ioctl,就知道用户程序的ioctl是怎么和驱动程序中的ioctl实现联系在一起的了。
我这里说一个大概思路,因为我觉得《Linux设备驱动程序》这本书已经说的非常清楚了,但是得化一些时间来看。
在驱动程序中实现的ioctl函数体内,实际上是有一个switch{case}结构,每一个case对应一个命令码,做出一些相应的操作。怎么实现这些操作,这是每一个程序员自己的事情,因为设备都是特定的,这里也没法说。关键在于怎么样组织命令码,因为在ioctl中命令码是唯一联系用户程序命令和驱动程序支持的途径。
命令码的组织是有一些讲究的,因为我们一定要做到命令和设备是一一对应的,这样才不会将正确的命令发给错误的设备,或者是把错误的命令发给正确的设备,或者是把错误的命令发给错误的设备。这些错误都会导致不可预料的事情发生,而当程序员发现了这些奇怪的事情的时候,再来调试程序查找错误,那将是非常困难的事情。
所以在Linux核心中是这样定义一个命令码的组织结构:
____________________________________
| 设备类型 | 序列号 | 方向 |数据尺寸|
|----------|--------|------|--------|
| 8 bit | 8 bit |2 bit |8~14 bit|
|----------|--------|------|--------|

这样一来,一个命令就变成了一个整数形式的命令码。但是命令码非常的不直观,所以Linux Kernel中提供了一些宏,这些宏可根据便于理解的字符串生成命令码,或者是从命令码得到一些用户可以理解的字符串以标明这个命令对应的设备类型、设备序列号、数据传送方向和数据传输尺寸。

这些宏我就不在这里解释了,具体的形式请读者察看Linux核心源代码中的和,文件里给除了这些宏完整的定义。
这里我只多说一个地方,那就是"幻数"。幻数是一个字母,数据长度也是8,所以就用一个特定的字母来标明设备类型,这和用一个数字是一样的,只是更加利于记忆和理解。就是这样,再没有更复杂的了。
更多的说了也没有,读者还是看一看源代码吧,推荐各位阅读《Linux 设备驱动程序》所带源代码中的short一例,因为它比较短小,功能比较简单,可以看明白ioctl的功能和细节。

四、 cmd参数如何得出
这里确实要说一说,cmd参数在用户程序端由一些宏根据设备类型、序列号、传送方向、数据尺寸等生成,这个整数通过系统调用传递到内核中的驱动程序,再由驱动程序使用解码宏从这个整数中得到设备的类型、序列号、传送方向、数据尺寸等信息,然后通过switch{case}结构进行相应的操作。
要透彻理解,只能是通过阅读源代码,我这篇文章实际上只是一个引子。Cmd参数的组织还是比较复杂的,我认为要搞熟它还是得花不少时间的,但是这是值得的,驱动程序中最难的是对中断的理解。

五、 小结
ioctl其实没有什么很难的东西需要理解,关键是理解cmd命令码是怎么在用户程序里生成并在驱动程序里解析的,程序员最主要的工作量在switch{case}结构中,因为对设备的I/O控制都是通过这一部分的代码实现的。 
























ioctl 接口

大部分驱动需要 -- 除了读写设备的能力 -- 通过设备驱动进行各种硬件控制的能力. 大部分设备可进行超出简单的数据传输之外的操作; 用户空间必须常常能够请求, 例如, 设备锁上它的门, 弹出它的介质, 报告错误信息, 改变波特率, 或者自我销毁. 这些操作常常通过 ioctl 方法来支持, 它通过相同名子的系统调用来实现.

在用户空间, ioctl 系统调用有下面的原型:
int ioctl(int fd, unsigned long cmd, ...);

这个原型由于这些点而凸现于 Unix 系统调用列表, 这些点常常表示函数有数目不定的参数. 在实际系统中, 但是, 一个系统调用不能真正有变数目的参数. 系统调用必须有一个很好定义的原型, 因为用户程序可存取它们只能通过硬件的"门". 因此, 原型中的点不表示一个变数目的参数, 而是一个单个可选的参数, 传统上标识为 char *argp. 这些点在那里只是为了阻止在编译时的类型检查. 第 3 个参数的实际特点依赖所发出的特定的控制命令( 第 2 个参数 ). 一些命令不用参数, 一些用一个整数值, 以及一些使用指向其他数据的指针. 使用一个指针是传递任意数据到 ioctl 调用的方法; 设备接着可与用户空间交换任何数量的数据.

ioctl 调用的非结构化特性使它在内核开发者中失宠. 每个 ioctl 命令, 基本上, 是一个单独的, 常常无文档的系统调用, 并且没有方法以任何类型的全面的方式核查这些调用. 也难于使非结构化的 ioctl 参数在所有系统上一致工作; 例如, 考虑运行在 32-位模式的一个用户进程的 64-位 系统. 结果, 有很大的压力来实现混杂的控制操作, 只通过任何其他的方法. 可能的选择包括嵌入命令到数据流(本章稍后我们将讨论这个方法)或者使用虚拟文件系统, 要么是 sysfs 要么是设备特定的文件系统. (我们将在 14 章看看 sysfs). 但是, 事实是 ioctl 常常是最容易的和最直接的选择,对于真正的设备操作.


ioctl 驱动方法有和用户空间版本不同的原型:
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);

inode 和 filp 指针是对应应用程序传递的文件描述符 fd 的值, 和传递给 open 方法的相同参数. cmd 参数从用户那里不改变地传下来, 并且可选的参数 arg 参数以一个 unsigned long 的形式传递, 不管它是否由用户给定为一个整数或一个指针. 如果调用程序不传递第 3 个参数, 被驱动操作收到的 arg 值是无定义的. 因为类型检查在这个额外参数上被关闭, 编译器不能警告你如果一个无效的参数被传递给 ioctl, 并且任何关联的错误将难以查找.

如果你可能想到的, 大部分 ioctl 实现包括一个大的 switch 语句来根据 cmd 参数, 选择正确的做法. 不同的命令有不同的数值, 它们常常被给予符号名来简化编码. 符号名通过一个预处理定义来安排. 定制的驱动常常声明这样的符号在它们的头文件中; scull.h 为 scull 声明它们. 用户程序必须, 当然, 包含那个头文件来存取这些符号.


1. 选择 ioctl 命令
在为 ioctl 编写代码之前, 你需要选择对应命令的数字. 许多程序员的第一个本能的反应是选择一组小数从0或1开始, 并且从此开始向上. 但是, 有充分的理由不这样做. ioctl 命令数字应当在这个系统是唯一的, 为了阻止向错误的设备发出正确的命令而引起的错误. 这样的不匹配不会不可能发生, 并且一个程序可能发现它自己试图改变一个非串口输入系统的波特率, 例如一个 FIFO 或者一个音频设备. 如果这样的 ioctl 号是唯一的, 这个应用程序得到一个 EINVAL 错误而不是继续做不应当做的事情.

为帮助程序员创建唯一的 ioctl 命令代码, 这些编码已被划分为几个位段. Linux 的第一个版本使用 16-位数: 高 8 位是关联这个设备的"魔"数, 低 8 位是一个顺序号, 在设备内唯一. 这样做是因为 Linus 是"无能"的(他自己的话); 一个更好的位段划分仅在后来被设想. 不幸的是, 许多驱动仍然使用老传统. 它们不得不: 改变命令编码会破坏大量的二进制程序,并且这不是内核开发者愿意见到的.

根据 Linux 内核惯例来为你的驱动选择 ioctl 号, 你应当首先检查 include/asm/ioctl.h 和 Documentation/ioctl-number.txt. 这个头文件定义你将使用的位段: type(魔数), 序号, 传输方向, 和参数大小. ioctl-number.txt 文件列举了在内核中使用的魔数,[20] 因此你将可选择你自己的魔数并且避免交叠. 这个文本文件也列举了为什么应当使用惯例的原因.


定义 ioctl 命令号的正确方法使用 4 个位段, 它们有下列的含义. 这个列表中介绍的新符号定义在 <linux/ioctl.h>.

type
魔数. 只是选择一个数(在参考了 ioctl-number.txt之后)并且使用它在整个驱动中. 这个成员是 8 位宽(_IOC_TYPEBITS).

number
序(顺序)号. 它是 8 位(_IOC_NRBITS)宽.

direction
数据传送的方向,如果这个特殊的命令涉及数据传送. 可能的值是 _IOC_NONE(没有数据传输), _IOC_READ, _IOC_WRITE, 和 _IOC_READ|_IOC_WRITE (数据在2个方向被传送). 数据传送是从应用程序的观点来看待的; _IOC_READ 意思是从设备读, 因此设备必须写到用户空间. 注意这个成员是一个位掩码, 因此 _IOC_READ 和 _IOC_WRITE 可使用一个逻辑 AND 操作来抽取.

size
涉及到的用户数据的大小. 这个成员的宽度是依赖体系的, 但是常常是 13 或者 14 位. 你可为你的特定体系在宏 _IOC_SIZEBITS 中找到它的值. 你使用这个 size 成员不是强制的 - 内核不检查它 -- 但是它是一个好主意. 正确使用这个成员可帮助检测用户空间程序的错误并使你实现向后兼容, 如果你曾需要改变相关数据项的大小. 如果你需要更大的数据结构, 但是, 你可忽略这个 size 成员. 我们很快见到如何使用这个成员.


头文件 <asm/ioctl.h>, 它包含在 <linux/ioctl.h> 中, 定义宏来帮助建立命令号, 如下: _IO(type,nr)(给没有参数的命令), _IOR(type, nre, datatype)(给从驱动中读数据的), _IOW(type,nr,datatype)(给写数据), 和 _IOWR(type,nr,datatype)(给双向传送). type 和 number 成员作为参数被传递, 并且 size 成员通过应用 sizeof 到 datatype 参数而得到.

这个头文件还定义宏, 可被用在你的驱动中来解码这个号: _IOC_DIR(nr), _IOC_TYPE(nr), _IOC_NR(nr), 和 _IOC_SIZE(nr). 我们不进入任何这些宏的细节, 因为头文件是清楚的, 并且在本节稍后有例子代码展示.

这里是一些 ioctl 命令如何在 scull 被定义的. 特别地, 这些命令设置和获得驱动的可配置参数.

/* Use 'k' as magic number */
#define SCULL_IOC_MAGIC 'k'
/* Please use a different 8-bit number in your code */

#define SCULL_IOCRESET _IO(SCULL_IOC_MAGIC, 0)
/*
* S means "Set" through a ptr,
* T means "Tell" directly with the argument value
* G means "Get": reply by setting through a pointer
* Q means "Query": response is on the return value
* X means "eXchange": switch G and S atomically
* H means "sHift": switch T and Q atomically
*/
#define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC, 1, int)
#define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC, 2, int)
#define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC, 3)
#define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC, 4)
#define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC, 5, int)
#define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC, 6, int)
#define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC, 7)
#define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC, 8)
#define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC, 9, int)
#define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC,10, int)
#define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC, 11)
#define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC, 12)

#define SCULL_IOC_MAXNR 14

真正的源文件定义几个额外的这里没有出现的命令.

我们选择实现 2 种方法传递整数参数: 通过指针和通过明确的值(尽管, 由于一个已存在的惯例, ioclt 应当通过指针交换值). 类似地, 2 种方法被用来返回一个整数值:通过指针和通过设置返回值. 这个有效只要返回值是一个正的整数; 如同你现在所知道的, 在从任何系统调用返回时, 一个正值被保留(如同我们在 read 和 write 中见到的), 而一个负值被看作一个错误并且被用来在用户空间设置 errno.[21]

"exchange" 和"shift"操作对于 scull 没有特别的用处. 我们实现"exchange"来显示驱动如何结合独立的操作到单个的原子的操作, 并且"shift"来连接"tell"和"query". 有时需要象这样的原子的测试-和-设置操作, 特别地, 当应用程序需要设置和释放锁.

命令的明确的序号没有特别的含义. 它只用来区分命令. 实际上, 你甚至可使用相同的序号给一个读命令和一个写命令, 因为实际的 ioctl 号在"方向"位是不同的, 但是你没有理由这样做. 我们选择在任何地方不使用命令的序号除了声明中, 因此我们不分配一个返回值给它. 这就是为什么明确的号出现在之前给定的定义中. 这个例子展示了一个使用命令号的方法, 但是你有自由不这样做.

除了少数几个预定义的命令(马上就讨论), ioctl 的 cmd 参数的值当前不被内核使用, 并且在将来也很不可能. 因此, 你可以, 如果你觉得懒, 避免前面展示的复杂的声明并明确声明一组调整数字. 另一方面, 如果你做了, 你不会从使用这些位段中获益, 并且你会遇到困难如果你曾提交你的代码来包含在主线内核中. 头文件<linux/kd.h> 是这个老式方法的例子, 使用 16-位的调整值来定义 ioctl 命令. 那个源代码依靠调整数因为使用那个时候遵循的惯例, 不是由于懒惰. 现在改变它可能导致无理由的不兼容.


2. 返回值
ioctl 的实现常常是一个 switch 语句, 基于命令号. 但是当命令号没有匹配一个有效的操作时缺省的选择应当是什么? 这个问题是有争议的. 几个内核函数返回 -ENIVAL("Invalid argument"), 它有意义是因为命令参数确实不是一个有效的. POSIX 标准, 但是, 说如果一个不合适的 ioctl 命令被发出, 那么 -ENOTTY 应当被返回. 这个错误码被 C 库解释为"设备的不适当的 ioctl", 这常常正是程序员需要听到的. 然而, 它仍然是相当普遍的来返回 -EINVAL, 对于响应一个无效的 ioctl 命令.

3. 预定义的命令
尽管 ioctl 系统调用最常用来作用于设备, 内核能识别几个命令. 注意这些命令, 当用到你的设备时, 在你自己的文件操作被调用之前被解码. 因此, 如果你选择相同的号给一个你的 ioctl命令, 你不会看到任何的给那个命令的请求, 并且应用程序获得某些不期望的东西, 因为在 ioctl 号之间的冲突.

预定义命令分为 3 类:
可对任何文件发出的(常规, 设备, FIFO, 或者 socket) 的那些.
只对常规文件发出的那些.
对文件系统类型特殊的那些.

最后一类的命令由宿主文件系统的实现来执行(这是 chattr 命令如何工作的). 设备驱动编写者只对第一类命令感兴趣, 它们的魔数是 "T". 查看其他类的工作留给读者作为练习; ext2_ioctl 是最有趣的函数(并且比预期的要容易理解), 因为它实现 append-only 标志和 immutable 标志.


下列 ioctl 命令是预定义给任何文件, 包括设备特殊的文件:

FIOCLEX
设置 close-on-exec 标志(File IOctl Close on EXec). 设置这个标志使文件描述符被关闭, 当调用进程执行一个新程序时.

FIONCLEX
清除 close-no-exec 标志(File IOctl Not CLose on EXec). 这个命令恢复普通文件行为, 复原上面 FIOCLEX 所做的. FIOASYNC 为这个文件设置或者复位异步通知(如同在本章中"异步通知"一节中讨论的). 注意直到 Linux 2.2.4 版本的内核不正确地使用这个命令来修改 O_SYNC 标志. 因为两个动作都可通过 fcntl 来完成, 没有人真正使用 FIOASYNC 命令, 它在这里出现只是为了完整性.

FIOQSIZE
这个命令返回一个文件或者目录的大小; 当用作一个设备文件, 但是, 它返回一个 ENOTTY 错误.

FIONBIO
"File IOctl Non-Blocking I/O"(在"阻塞和非阻塞操作"一节中描述). 这个调用修改在 filp->f_flags 中的 O_NONBLOCK 标志. 给这个系统调用的第 3 个参数用作指示是否这个标志被置位或者清除. (我们将在本章看到这个标志的角色). 注意常用的改变这个标志的方法是使用 fcntl 系统调用, 使用 F_SETFL 命令.

列表中的最后一项介绍了一个新的系统调用, fcntl, 它看来象 ioctl. 事实上, fcntl 调用非常类似 ioctl, 它也是获得一个命令参数和一个额外的(可选地)参数. 它保持和 ioctl 独立主要是因为历史原因: 当 Unix 开发者面对控制 I/O 操作的问题时, 他们决定文件和设备是不同的. 那时, 有 ioctl 实现的唯一设备是 ttys, 它解释了为什么 -ENOTTY 是标准的对不正确 ioctl 命令的回答. 事情已经改变, 但是 fcntl 保留为一个独立的系统调用.


4. 使用 ioctl 参数
在看 scull 驱动的 ioctl 代码之前, 我们需要涉及的另一点是如何使用这个额外的参数. 如果它是一个整数, 就容易: 它可以直接使用. 如果它是一个指针, 但是, 必须小心些.

当用一个指针引用用户空间, 我们必须确保用户地址是有效的. 试图存取一个没验证过的用户提供的指针可能导致不正确的行为, 一个内核 oops, 系统崩溃, 或者安全问题. 它是驱动的责任来对每个它使用的用户空间地址进行正确的检查, 并且返回一个错误如果它是无效的.

在第 3 章, 我们看了 copy_from_user 和 copy_to_user 函数, 它们可用来安全地移动数据到和从用户空间. 这些函数也可用在 ioctl 方法中, 但是 ioctl 调用常常包含小数据项, 可通过其他方法更有效地操作. 开始, 地址校验(不传送数据)由函数 access_ok 实现, 它定义在 <asm/uaccess.h>:

int access_ok(int type, const void *addr, unsigned long size);

第一个参数应当是 VERIFY_READ 或者 VERIFY_WRITE, 依据这个要进行的动作是否是读用户空间内存区或者写它. addr 参数持有一个用户空间地址, size 是一个字节量. 例如, 如果 ioctl 需要从用户空间读一个整数, size 是 sizeof(int). 如果你需要读和写给定地址, 使用 VERIFY_WRITE, 因为它是 VERIRY_READ 的超集.

不象大部分的内核函数, access_ok 返回一个布尔值: 1 是成功(存取没问题)和 0 是失败(存取有问题). 如果它返回假, 驱动应当返回 -EFAULT 给调用者.

关于 access_ok有多个有趣的东西要注意. 首先, 它不做校验内存存取的完整工作; 它只检查看这个内存引用是在这个进程有合理权限的内存范围中. 特别地, access_ok 确保这个地址不指向内核空间内存. 第2, 大部分驱动代码不需要真正调用 access_ok. 后面描述的内存存取函数为你负责这个. 但是, 我们来演示它的使用, 以便你可见到它如何完成.

scull 源码利用了 ioclt 号中的位段来检查参数, 在 switch 之前:

int err = 0, tmp;
int retval = 0;
/*
* extract the type and number bitfields, and don't decode
* wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
*/
if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC)
return -ENOTTY;
if (_IOC_NR(cmd) > SCULL_IOC_MAXNR)
return -ENOTTY;

/*
* the direction is a bitmask, and VERIFY_WRITE catches R/W
* transfers. `Type' is user-oriented, while
* access_ok is kernel-oriented, so the concept of "read" and
* "write" is reversed
*/
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
if (err)
return -EFAULT;

在调用 access_ok 之后, 驱动可安全地进行真正的传输. 加上 copy_from_user 和 copy_to_user_ 函数, 程序员可利用一组为被最多使用的数据大小(1, 2, 4, 和 8 字节)而优化过的函数. 这些函数在下面列表中描述, 它们定义在 <asm/uaccess.h>:

put_user(datum, ptr)
__put_user(datum, ptr)
这些宏定义写 datum 到用户空间; 它们相对快, 并且应当被调用来代替 copy_to_user 无论何时要传送单个值时. 这些宏已被编写来允许传递任何类型的指针到 put_user, 只要它是一个用户空间地址. 传送的数据大小依赖 prt 参数的类型, 并且在编译时使用 sizeof 和 typeof 等编译器内建宏确定. 结果是, 如果 prt 是一个 char 指针, 传送一个字节, 以及对于 2, 4, 和 可能的 8 字节.

put_user 检查来确保这个进程能够写入给定的内存地址. 它在成功时返回 0, 并且在错误时返回 -EFAULT. __put_user 进行更少的检查(它不调用 access_ok), 但是仍然能够失败如果被指向的内存对用户是不可写的. 因此, __put_user 应当只用在内存区已经用 access_ok 检查过的时候.

作为一个通用的规则, 当你实现一个 read 方法时, 调用 __put_user 来节省几个周期, 或者当你拷贝几个项时, 因此, 在第一次数据传送之前调用 access_ok 一次, 如同上面 ioctl 所示.

get_user(local, ptr)
__get_user(local, ptr)
这些宏定义用来从用户空间接收单个数据. 它们象 put_user 和 __put_user, 但是在相反方向传递数据. 获取的值存储于本地变量 local; 返回值指出这个操作是否成功. 再次, __get_user 应当只用在已经使用 access_ok 校验过的地址.

如果做一个尝试来使用一个列出的函数来传送一个不适合特定大小的值, 结果常常是一个来自编译器的奇怪消息, 例如"coversion to non-scalar type requested". 在这些情况中, 必须使用 copy_to_user 或者 copy_from_user.

















linux系统ioctl使用示例

程序1:检测接口的inet_addr, netmask, broad_addr
程序2:检查接口的物理连接是否正常
程序3:测试物理连接
程序4:调节音量

***************************程序1****************************************
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <net/if.h>

static void usage()
{
printf("usage : ipconfig interface \n");
exit(0);
}

int main(int argc,char **argv)
{
struct sockaddr_in *addr;
struct ifreq ifr;
char *name,*address;
int sockfd;

if(argc != 2)  usage();
else  name = argv[1];

sockfd = socket(AF_INET,SOCK_DGRAM,0);
strncpy(ifr.ifr_name,name,IFNAMSIZ-1);

if(ioctl(sockfd,SIOCGIFADDR,&ifr) == -1)
perror("ioctl error"),exit(1);

addr = (struct sockaddr_in *)&(ifr.ifr_addr);
address = inet_ntoa(addr->sin_addr);
printf("inet addr: %s ",address);

if(ioctl(sockfd,SIOCGIFBRDADDR,&ifr) == -1)
perror("ioctl error"),exit(1);

addr = (struct sockaddr_in *)&ifr.ifr_broadaddr;
address = inet_ntoa(addr->sin_addr);
printf("broad addr: %s ",address);

if(ioctl(sockfd,SIOCGIFNETMASK,&ifr) == -1)
perror("ioctl error"),exit(1);
addr = (struct sockaddr_in *)&ifr.ifr_addr;
address = inet_ntoa(addr->sin_addr);
printf("inet mask: %s ",address);

printf("\n");
exit(0);
}

******************************** 程序2*****************************************************
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <stdlib.h>
#include <unistd.h>
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned char u8;
#include <linux/ethtool.h>
#include <linux/sockios.h>

int detect_mii(int skfd, char *ifname)
{
struct ifreq ifr;
u16 *data, mii_val;
unsigned phy_id;

/* Get the vitals from the interface. */
strncpy(ifr.ifr_name, ifname, IFNAMSIZ);

if (ioctl(skfd, SIOCGMIIPHY, &ifr) < 0)
{
fprintf(stderr, "SIOCGMIIPHY on %s failed: %s\n", ifname, strerror(errno));
(void) close(skfd);
return 2;
}

data = (u16 *)(&ifr.ifr_data);
phy_id = data[0];
data[1] = 1;

if (ioctl(skfd, SIOCGMIIREG, &ifr) < 0)
{
fprintf(stderr, "SIOCGMIIREG on %s failed: %s\n", ifr.ifr_name, strerror(errno));
return 2;
}

mii_val = data[3];
return(((mii_val & 0x0016) == 0x0004) ? 0 : 1);
}

int detect_ethtool(int skfd, char *ifname)
{
struct ifreq ifr;
struct ethtool_value edata;
memset(&ifr, 0, sizeof(ifr));
edata.cmd = ETHTOOL_GLINK;

strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)-1);
ifr.ifr_data = (char *) &edata;

if (ioctl(skfd, SIOCETHTOOL, &ifr) == -1)
{
printf("ETHTOOL_GLINK failed: %s\n", strerror(errno));
return 2;
}

return (edata.data ? 0 : 1);
}

int main(int argc, char **argv)
{
int skfd = -1;
char *ifname;
int retval;

if( argv[1] )  ifname = argv[1];
else  ifname = "eth0";

/* Open a socket. */
if (( skfd = socket( AF_INET, SOCK_DGRAM, 0 ) ) < 0 )
{
printf("socket error\n");
exit(-1);
}

retval = detect_ethtool(skfd, ifname);
if (retval == 2)
retval = detect_mii(skfd, ifname);

close(skfd);

if (retval == 2)
printf("Could not determine status\n");
if (retval == 1)
printf("Link down\n");
if (retval == 0)
printf("Link up\n");

return retval;
}

*******************************程序3*****************************************************
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <net/if.h>
#include <linux/sockios.h>
#include <sys/ioctl.h>

#define LINKTEST_GLINK 0x0000000a

struct linktest_value {
unsigned int    cmd;
unsigned int    data;
};

static void usage(const char * pname)
{
fprintf(stderr, "usage: %s <device>\n", pname);
fprintf(stderr, "returns: \n");
fprintf(stderr, "\t 0: link detected\n");
fprintf(stderr, "\t%d: %s\n", ENODEV, strerror(ENODEV));
fprintf(stderr, "\t%d: %s\n", ENONET, strerror(ENONET));
fprintf(stderr, "\t%d: %s\n", EOPNOTSUPP, strerror(EOPNOTSUPP));
exit(EXIT_FAILURE);
}

static int linktest(const char * devname)
{
struct ifreq ifr;
struct linktest_value edata;
int fd;

/* setup our control structures. */
memset(&ifr, 0, sizeof(ifr));
strcpy(ifr.ifr_name, devname);

/* open control socket. */
fd=socket(AF_INET, SOCK_DGRAM, 0);
if(fd < 0 )
return -ECOMM;

errno=0;
edata.cmd = LINKTEST_GLINK;
ifr.ifr_data = (caddr_t)&edata;

if(!ioctl(fd, SIOCETHTOOL, &ifr))
{
if(edata.data)
{
fprintf(stdout, "link detected on %s\n", devname);
return 0;
} else
{
errno=ENONET;
}
}

perror("linktest");
return errno;
}

int main(int argc, char *argv[])
{
if(argc != 2)
{
usage(argv[0]);
}
return linktest(argv[1]);
}

*************************************程序4*********************************************************
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include <stdio.h>
#include <unistd.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#define  BASE_VALUE 257

int main(int argc,char *argv[])
{
int mixer_fd=0;
char *names[SOUND_MIXER_NRDEVICES]=SOUND_DEVICE_LABELS;
int value,i;

printf("\nusage:%s dev_no.[0..24] value[0..100]\n\n",argv[0]);
printf("eg. %s 0 100\n",argv[0]);
printf("will change the volume to MAX volume.\n\n");
printf("The dev_no. are as below:\n");

for (i=0;i<SOUND_MIXER_NRDEVICES;i++)
{
if (i%3==0) printf("\n");
printf("%s:%d\t\t",names,i);
}

printf("\n\n");

if (argc<3)  exit(1);

if ((mixer_fd = open("/dev/mixer",O_RDWR)))
{
printf("Mixer opened successfully,working...\n");
value=BASE_VALUE*atoi(argv[2]);

if (ioctl(mixer_fd,MIXER_WRITE(atoi(argv[1])),&value)==0)
printf("successfully.....");
else
printf("unsuccessfully.....");

printf("done.\n");
}
else
printf("can't open /dev/mixer error....\n");

exit(0);
}
  • 4
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
是的,用户空间与 ioctl 函数是相关的。ioctl(I/O control)函数是用于设备驱动程序与用户空间之间进行通信的一种机制。 ioctl 函数允许用户空间程序通过文件描述符(file descriptor)与设备驱动程序进行交互,发送控制命令和数据,以实现对设备的控制、配置和查询等操作。它可以用于各种设备,例如串口、磁盘、网络设备等。 ioctl 函数的原型如下: ```cpp int ioctl(int fd, unsigned long request, ...); ``` - `fd` 是一个文件描述符,表示要进行 ioctl 操作的设备或文件。 - `request` 是一个无符号长整型数,用于指定具体的 ioctl 命令。 - `...` 是可选的参数,用于传递额外的数据或者配置参数。 在用户空间中,可以通过打开设备节点(如 `/dev/xxx`)获取一个文件描述符,然后使用 ioctl 函数与设备驱动程序进行通信。用户空间程序可以发送不同的请求码(request code)给驱动程序,以达到特定的目的。 通常,在设备驱动程序中会定义一组常量作为 ioctl 命令的请求码,用户空间程序使用这些请求码来告知驱动程序执行特定的操作。驱动程序根据请求码的不同,执行相应的处理逻辑,并返回结果给用户空间。 需要注意的是,ioctl 函数使用需要谨慎,因为它是一种较为底层的操作,对设备的具体实现和内部结构有一定的依赖。在使用 ioctl 函数时,应该根据设备的文档或相关的驱动程序接口规范来了解具体的命令和参数的使用方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值