探秘“成人快手18”:不止于快,更在于品味生活的无限可能

核心内容摘要

荷兰庄园六个女儿
惊艳绽放:解锁“芃芃足底倒计时”训练法的逆龄肌密

【独家揭秘】78赛进13视频:官方原版高清震撼上线,带你穿越时空,重温经典!

In Chapter 3, we built a complete device driver that the user can write to and read from. But a real device usually offers more functionality than synchronous read and write. Now that we’re equipped with debugging tools should something go awry— and a firm understanding of concurrency issues to help keep things from going awry—we can safely go ahead and create a more advanced driver.在第 3 章中我们实现了一个支持用户态读写的完整设备驱动但实际设备提供的功能往往远超同步读写。

如今我们已经掌握了排查问题的调试工具也对并发问题有了清晰的认知能有效规避问题接下来可以放心地实现更高级的驱动了。

This chapter examines a few concepts that you need to understand to write fully featured char device drivers. We start with implementing the ioctl system call, which is a common interface used for device control. Then we proceed to various ways of synchronizing with user space; by the end of this chapter you have a good idea of how to put processes to sleep (and wake them up), implement nonblocking I/O, and inform user space when your devices are available for reading or writing. We finish with a look at how to implement a few different device access policies within drivers.本章将讲解编写功能完善的字符设备驱动所需的若干核心概念我们先从ioctl系统调用的实现入手它是设备控制的常用接口接着介绍与用户态同步的多种方式本章结尾你将掌握如何让进程睡眠及唤醒、实现非阻塞 I/O以及当设备可读写时如何通知用户态。

最后我们会探讨如何在驱动中实现多种不同的设备访问策略。

The ideas discussed here are demonstrated by way of a couple of modified versions of the scull driver. Once again, everything is implemented using in-memory virtual devices, so you can try out the code yourself without needing to have any particular hardware. By now, you may be wanting to get your hands dirty with real hardware, but that will have to wait until Chapter

本章的所有概念都会通过scull驱动的几个修改版本进行演示且所有实现均基于内存虚拟设备你无需任何专用硬件即可自行测试代码。

或许你现在已经迫不及待想操作真实硬件了但这要等到第 9 章才会讲解。

ioctl 系统调用Most drivers need—in addition to the ability to read and write the device—the ability to perform various types of hardware control via the device driver. Most devices can perform operations beyond simple data transfers; user space must often be able to request, for example, that the device lock its door, eject its media, report error information, change a baud rate, or self destruct. These operations are usually supported via the ioctl method, which implements the system call by the same name. In user space, the ioctl system call has the following prototype:大多数驱动除了支持设备读写外还需要支持通过驱动执行各类硬件控制操作。

大多数设备都能完成简单数据传输之外的功能例如用户态通常需要请求设备锁定仓门、弹出介质、上报错误信息、修改波特率或是执行自毁操作。

这些操作通常通过ioctl方法实现该方法对应同名的系统调用。

在用户态中ioctl系统调用的原型如下int ioctl(int fd, unsigned long cmd, ...);The prototype stands out in the list of Unix system calls because of the dots, which usually mark the function as having a variable number of arguments. In a real system, however, a system call can’t actually have a variable number of arguments. System calls must have a well-defined prototype, because user programs can access them only through hardware “gates.” Therefore, the dots in the prototype represent not a variable number of arguments but a single optional argument, traditionally identified as char *argp. The dots are simply there to prevent type checking during compilation. The actual nature of the third argument depends on the specific control command being issued (the second argument). Some commands take no arguments, some take an integer value, and some take a pointer to other data. Using a pointer is the way to pass arbitrary data to the ioctl call; the device is then able to exchange any amount of data with user space.这个原型在 Unix 系统调用列表中显得尤为特殊 —— 其中的省略号...通常表示函数支持可变参数。

但在实际的系统中系统调用无法真正支持可变参数系统调用必须有明确的原型因为用户程序只能通过硬件 “门控”硬件中断 / 陷阱访问它们。

因此原型中的省略号并非表示可变参数而是代表单个可选参数传统上记为char *argp。

省略号的存在仅仅是为了在编译阶段跳过类型检查。

第三个参数的实际类型取决于第二个参数cmd控制命令有些命令无需参数有些需要一个整数值还有些需要一个指向其他数据的指针。

使用指针是向ioctl传递任意数据的方式通过这种方式设备可以与用户态交换任意大小的数据。

The unstructured nature of the ioctl call has caused it to fall out of favor among kernel developers. Each ioctl command is, essentially, a separate, usually undocumented system call, and there is no way to audit these calls in any sort of comprehensive manner. It is also difficult to make the unstructured ioctl arguments work identically on all systems; for example, consider 64-bit systems with a userspace process running in 32-bit mode. As a result, there is strong pressure to implement miscellaneous control operations by just about any other means. Possible alternatives include embedding commands into the data stream (we will discuss this approach later in this chapter) or using virtual filesystems, either sysfs or driverspecific filesystems. (We will look at sysfs in Chapter

) However, the fact remains that ioctl is often the easiest and most traightforward choice for true device operations.ioctl调用的无结构化特性使其逐渐不受内核开发者青睐每个ioctl命令本质上都是一个独立的、通常无文档的系统调用无法以全面的方式对这些调用进行审计难以让无结构化的ioctl参数在所有系统上表现一致例如在 64 位系统上运行 32 位用户态进程的场景。

因此内核社区强烈建议通过其他方式实现各类杂项控制操作可行的替代方案包括将命令嵌入数据流中本章后续会讨论该方法使用虚拟文件系统要么是sysfs要么是驱动专属文件系统sysfs会在第 14 章讲解。

不过现实情况是对于真正的设备操作ioctl往往是最简单、最直接的选择。

驱动中的 ioctl 方法The ioctl driver method has a prototype that differs somewhat from the user-space version:驱动中的ioctl方法原型与用户态版本略有不同注原文此处代码未完整补充完整标准原型int (*unlocked_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg); // 旧版已废弃原型依赖大内核锁 BKL int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);The inode and filp pointers are the values corresponding to the file descriptor fd passed on by the application and are the same parameters passed to the open method. The cmd argument is passed from the user unchanged, and the optional arg argument is passed in the form of an unsigned long, regardless of whether it was given by the user as an integer or a pointer. If the invoking program doesn’t pass a third argument, the arg value received by the driver operation is undefined. Because type checking is disabled on the extra argument, the compiler can’t warn you if an invalid argument is passed to ioctl, and any associated bug would be difficult to spot.其中inode和filp指针对应应用程序传递的文件描述符fd与传递给open方法的参数相同新版unlocked_ioctl移除了inode参数因为多数场景下无需通过 inode 访问设备信息cmd参数从用户态直接传递而来未做任何修改可选参数arg以unsigned long类型传递无论用户态传入的是整数还是指针。

如果调用程序未传递第三个参数驱动收到的arg值是未定义的。

由于额外参数的类型检查被禁用若向ioctl传递了无效参数编译器无法给出警告相关 bug 也难以排查。

As you might imagine, most ioctl implementations consist of a big switch statement that selects the correct behavior according to the cmd argument. Different commands have different numeric values, which are usually given symbolic names to simplify coding. The symbolic name is assigned by a preprocessor definition. Custom drivers usually declare such symbols in their header files; scull.h declares them for scull. User programs must, of course, include that header file as well to have access to those symbols.大多数ioctl实现都包含一个大型switch语句根据cmd参数选择对应的行为。

不同的命令对应不同的数值为了简化编码这些数值通常会被赋予符号名称由预处理宏定义赋值。

自定义驱动通常会在其头文件中声明这些符号例如scull.h为scull驱动声明相关符号用户态程序也必须包含该头文件才能访问这些符号。

补充说明ioctl cmd 参数的规范设计为了避免不同驱动的cmd命令冲突内核建议cmd参数按固定格式拆分32 位位

命令的参数长度可选位

幻数驱动专属标识避免冲突如 S 代表 scull位

命令序号位

数据传输方向如_IOC_NONE无传输、_IOC_READ从设备读、_IOC_WRITE向设备写。

内核提供了宏_IO、_IOR、_IOW、_IOWR用于构造规范的cmd例如// 无参数命令 #define SCULL_IOCTL_RESET _IO(S,

// 从驱动读取数据用户态传入缓冲区指针 #define SCULL_IOCTL_GET_SIZE _IOR(S, 1, int)用户态与内核态数据传递的安全性当arg是用户态指针时驱动必须进行安全检查避免非法内存访问使用copy_from_user()将用户态数据拷贝到内核态使用copy_to_user()将内核态数据拷贝到用户态这两个函数会检查用户态指针的合法性若指针无效则返回非零值驱动应处理该错误。

unlocked_ioctl 与 compat_ioctl 的区别unlocked_ioctl新版无锁 ioctl 接口替代旧版依赖 BKL 的ioctl是驱动的首选compat_ioctl兼容接口用于 64 位内核运行 32 位用户态进程的场景处理参数位数兼容问题如 32 位指针转换为 64 位指针。

ioctl 的返回值规范执行成功返回 0不支持的 cmd 命令返回-ENOTTY传统约定意为 “设备不支持该 ioctl 命令”其他错误返回对应的负错误码如-EINVAL无效参数、-EFAULT用户态指针非法。

适用场景ioctl 优先用于设备的硬件控制操作无替代方案、一次性命令例如串口修改波特率、数据位、校验位块设备的分区检测、格式化触发字符设备的重置、状态查询。

避免用 ioctl 实现数据传输应使用read/write或频繁调用的操作。

总结ioctl是字符设备驱动中实现硬件控制的核心接口用户态原型带可选参数驱动态原型为固定参数cmdarg。

ioctl的核心缺陷是无结构化现代内核推荐优先使用sysfs等替代方案但它仍是设备控制的最简选择。

驱动实现ioctl时需注意规范设计cmd参数、安全处理用户态指针、返回符合约定的错误码优先使用新版unlocked_ioctl接口。

美国第1黄冈站-美国第1黄冈站应用

百度百家号客服电话人工服务

123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123