本文主要转载自I/O Control in Linux。
Most drivers need 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
ioctlmethod, which implements the system call by the same time.
摘录自《Linux Device Drivers》第三版第六章。
Input/Output Control (ioctl, in short) is a common operation, or system call, available in most driver categories. It is a one-bill-fits-all kind of system call. If there is no other system call that meets a particular requirement, then
ioctl() is the one to use.
Practical examples include volume control for an audio device, display configuration for a video device, reading device registers, and so on — basically, anything to do with device input/output, or device-specific operations, yet versatile enough for any kind of operation (for example, for debugging a driver by querying driver data structures).
The question is: how can all this be achieved by a single function prototype? The trick lies in using its two key parameters: command and argument. The command is a number representing an operation. The argument is the corresponding parameter for the operation. The
ioctl() function implementation does a switch … case over the commmand to implement the corresponding functionality. The following has been its prototype in the Linux kernel for quite some time:
int ioctl(struct inode *i, struct file *f, unsigned int cmd, unsigned long arg);
However, from kernel 2.6.35, it changed to:
long ioctl(struct file *f, unsigned int cmd, unsigned long arg);
If there is a need for more arguments, all of them are put in a structure, and a pointer to the structure becomes the ‘one’ argument. Whether integer or pointer, the argument is taken as a long integer in kernel-space, and accordingly type-cast and processed.
ioctl() is typically implemented as part of the corresponding driver, and then an appropriate function pointer is initialised with it, exactly as in other system calls like
read(), etc. For example, in character drivers, it is the
unlocked_ioctl (since kernel 2.6.35) function pointer field in the struct
file_operations that is to be initialised.
Again, like other system calls, it can be equivalently invoked from user-space using the
ioctl() system call, prototyped in
int ioctl(int fd, int cmd, ...);
cmd is the same as what is implemented in the driver’s
ioctl(), and the variable argument construct (
...) is a hack to be able to pass any type of argument (though only one) to the driver’s
ioctl(). Other parameters will be ignored.
Note that both the command and argument type definitions need to be shared across the driver (in kernel-space) and the application (in user-space). Thus, these definitions are commonly put into header files for each space.
To better understand the boring theory explained above, here’s the code set for the “debugging a driver” example. This driver has three static global variables:
ego, which need to be queried and possibly operated from an application. The header file
query_ioctl.h defines the corresponding commands and argument type. A listing follows:
Using these, the driver’s
ioctl() implementation in
query_ioctl.c would be as follows:
And finally, the corresponding invocation functions from the application
query_app.c would be as follows:
Now try out
query_ioctl.c with the following operations:
- Build the
query_ioctl.kofile) and the application (
query_appfile) by running
make, using the following
# If called directly from the command line, invoke the kernel build system.
- Load the driver using insmod
- With appropriate privileges and command-line arguments, run the application
./query_app— to display the driver variables
./query_app -c— to clear the driver variables
./query_app -g— to display the driver variables
./query_app -s— to set the driver variables (not mentioned above)
- Unload the driver using
You may wondere about
_IO, etc., which were used in defining commands in
query_ioctl.h. These are usual numbers only, as mentioned earlier for an
ioctl() command. Just that, now additionally, some useful command related information is also encoded as part of these numbers using various macros, as per the POSIX standard for
ioctl. The standard talks about the 32-bit command numbers, formed of four components embedded into the [31:0] bits:
- The direction of command operation [bits 31:30] — read, write, both, or none — filled by the corresponding macro (
- The size of the argument [bits 29:16] — computed using
sizeof()with the argument’s type — the third argument to these macros.
- The 8-bit magic number [bits 15:8] — to render the commands unique enough — typically an ASCII character (the first argument to these macros).
- The original command number [bits 7:0] — the actual command number (1, 2, 3, …), defined as per our requirement — the second argument to these macros.
Check out the header <asm-generic/ioctl.h> for implementation details.