The ioctl system call is the classic escape hatch for anything that doesn’t fit neatly into a simple file read or write, and it remains one of the most practical tools in a free Linux kernel programming course for driver authors today. Whenever a driver needs to accept a structured command with multiple fields, or return a compound result, ioctl is usually the answer.
A regular read or write call is a single, fixed-purpose operation, but ioctl lets one file descriptor support an unlimited number of distinct commands, each identified by a unique numeric code, and each optionally carrying its own argument structure. This is what makes it a “multiplexer” — many logically different operations are routed through a single system call based on the command number you pass in.
| ioctl(fd, MYDRV_SET_MODE, &arg) | → | Driver’s unlocked_ioctl() switch statement | → | Correct handler runs for that command |
Never hardcode raw numbers for ioctl commands. Instead, use the standard macros so the direction and size are encoded safely and checked automatically:
#include <linux/ioctl.h>
#define MYDRV_MAGIC 'k'
#define MYDRV_SET_MODE _IOW(MYDRV_MAGIC, 1, int)
#define MYDRV_GET_STATUS _IOR(MYDRV_MAGIC, 2, int)
static long mydrv_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int value;
switch (cmd) {
case MYDRV_SET_MODE:
if (copy_from_user(&value, (int __user *)arg, sizeof(value)))
return -EFAULT;
/* apply value to hardware here */
return 0;
case MYDRV_GET_STATUS:
value = 1; /* read real status here */
if (copy_to_user((int __user *)arg, &value, sizeof(value)))
return -EFAULT;
return 0;
default:
return -ENOTTY;
}
}
The copy_from_user() and copy_to_user() functions are mandatory whenever data crosses the user-kernel boundary through a pointer, since the kernel can never dereference a user space pointer directly.
| Situation | Best Choice |
|---|---|
| Single simple value, human-readable | sysfs |
| Structured command with multiple fields | ioctl |
| Asynchronous event to many listeners | netlink |
- Hardcoding raw command numbers: always use
_IO,_IOR,_IOW,_IOWRmacros to avoid collisions and encode direction safely. - Skipping copy_from_user/copy_to_user: directly dereferencing a user pointer will crash or corrupt memory.
- Using the deprecated locked ioctl callback: modern drivers implement
unlocked_ioctl, not the old BKL-basedioctlfield.
Always validate the ioctl command number against your known set with a default -ENOTTY case, and treat every argument coming through arg as untrusted, since a malicious or buggy user space program can pass arbitrary values.
- ioctl multiplexes many distinct operations through one system call using command numbers.
- Always define commands with the standard _IO/_IOR/_IOW/_IOWR macros.
- Use copy_to_user/copy_from_user for every user-kernel data crossing.
- ioctl shines when a value doesn’t fit sysfs’s one-value-per-file model.
Q1. Is ioctl still used in modern kernel drivers?
Yes, ioctl remains a widely used and practical way to implement structured, multi-field commands in device drivers today.
Q2. Why shouldn’t I hardcode ioctl command numbers?
The _IO family of macros encodes direction and argument size, which helps prevent accidental collisions and adds a layer of safety checking.
Q3. What happens if I dereference a user pointer directly in ioctl?
It can crash the kernel or cause undefined behavior, which is exactly why copy_to_user() and copy_from_user() exist.
Q4. What’s the difference between ioctl and unlocked_ioctl?
unlocked_ioctl is the modern callback that doesn’t rely on the old Big Kernel Lock; current drivers should implement unlocked_ioctl exclusively.
Q5. Should I use ioctl or sysfs for a simple on/off switch?
For a simple single value like an on/off switch, sysfs is generally the cleaner and preferred choice.
Continue the free Linux kernel programming course with the next module on hardware chip memory access.
Continue Course
2 Comments