原文位址:https://blog.csdn.net/zifehng/article/details/59576539#commentBox
一個字元裝置驅動通常會實作正常的打開、關閉、讀、寫等功能,但在一些細分的情境下,如果需要擴充新的功能,通常以增設ioctl()指令的方式實作,其作用類似于“拾遺補漏”。在檔案I/O中,ioctl扮演着重要角色,本文将以驅動開發為側重點,從使用者空間到核心空間縱向分析ioctl函數。

使用者空間的ioctl()
#include <sys/ioctl.h>
int ioctl(int fd, int cmd, ...) ;
- 1
- 2
- 3
參數 | 描述 |
fd | 檔案描述符 |
cmd | 互動協定,裝置驅動将根據cmd執行對應操作 |
… | 可變參數arg,依賴cmd指定長度以及類型 |
ioctl()執行成功時傳回0,失敗則傳回-1并設定全局變量errorno值,如下:
EBADF d is not a valid descriptor.
EFAULT argp references an inaccessible memory area.
EINVAL Request or argp is not valid.
ENOTTY d is not associated with a character special device.
ENOTTY The specified request does not apply to the kind of object that the descriptor d references.
是以,在使用者空間使用ioctl時,可以做如下的出錯判斷以及處理:
int ret;
ret = ioctl(fd, MYCMD);
if (ret == -) {
printf("ioctl: %s\n", strerror(errno));
}
- 1
- 2
- 3
- 4
- 5
tips: 在實際應用中,ioctl出錯時的errorno大部分是ENOTTY(error not a typewriter),顧名思義,即第一個參數fd指向的不是一個字元裝置,不支援ioctl操作,這時候應該檢查前面的open函數是否出錯或者裝置路徑是否正确。
驅動中的ioctl()
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
- 1
- 2
在新版核心中,unlocked_ioctl()與compat_ioctl()取代了ioctl()。unlocked_ioctl(),顧名思義,應該在無大核心鎖(BKL)的情況下調用;compat_ioctl(),compat全稱compatible(相容的),主要目的是為64位系統提供32位ioctl的相容方法,也是在無大核心鎖的情況下調用。在《Linux Kernel Development》中對兩種ioctl方法有詳細的解說。
So Many Ioctls!
Not long ago, there existed only a single ioctlmethod. Today, there are three methods.unlocked_ioctl() is the same as ioctl(), except it is called without the Big KernelLock (BKL). It is thus up to the author of that function to ensure proper synchronization.Because the BKL is a coarse-grained, inefficient lock, drivers should implementunlocked_ioctl() and not ioctl().
compat_ioctl() is also called without the BKL, but its purpose is to provide a 32-bit compatible ioctl method for 64-bit systems. How you implement it depends on your existing ioctlcommands. Older drivers with implicitly sized types (such as long) should implement a compat_ioctl() method that works appropriately with 32-bit applications. This generally means translating the 32-bit values to the appropriate types for a 64-bit kernel. New driversthat have the luxury of designing their ioctl commands from scratch should ensure all their arguments and data are explicitly sized, safe for 32-bit apps on a 32-bit system, 32-bit apps on a 64-bit system, and 64-bit apps on a 64-bit system. These drivers can then point the compat_ioctl() function pointer at the same function as
unlocked_ioctl().
tips:在字元裝置驅動開發中,一般情況下隻要實作unlocked_ioctl()即可,因為在vfs層的代碼是直接調用unlocked_ioctl()。
// fs/ioctl.c
static long vfs_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
int error = -ENOTTY;
if (!filp->f_op || !filp->f_op->unlocked_ioctl)
goto out;
error = filp->f_op->unlocked_ioctl(filp, cmd, arg);
if (error == -ENOIOCTLCMD) {
error = -ENOTTY;
}
out:
return error;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
ioctl指令,使用者與驅動之間的協定
前文提到ioctl方法第二個參數cmd為使用者與驅動的“協定”,理論上可以為任意int型資料,可以為0、1、2、3……,但是為了確定該“協定”的唯一性,ioctl指令應該使用更科學嚴謹的方法指派,在linux中,提供了一種ioctl指令的統一格式,将32位int型資料劃分為四個位段,如下圖所示:
在核心中,提供了宏接口以生成上述格式的ioctl指令:
// include/uapi/asm-generic/ioctl.h
#define _IOC(dir,type,nr,size) \
(((dir) << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr) << _IOC_NRSHIFT) | \
((size) << _IOC_SIZESHIFT))
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- dir(direction),ioctl指令通路模式(資料傳輸方向),占據2bit,可以為_IOC_NONE、_IOC_READ、_IOC_WRITE、_IOC_READ | _IOC_WRITE,分别訓示了四種通路模式:無資料、讀資料、寫資料、讀寫資料;
-
type(device type),裝置類型,占據8bit,在一些文獻中翻譯為“幻數”或者“魔數”,可以為任意char型字元,例如‘a’、‘b’、‘c’等等,其主要作用是使ioctl指令有唯一的裝置辨別;
tips:Documentions/ioctl-number.txt記錄了在核心中已經使用的“魔數”字元,為避免沖突,在自定義ioctl指令之前應該先查閱該文檔。
- nr(number),指令編号/序數,占據8bit,可以為任意unsigned char型資料,取值範圍0~255,如果定義了多個ioctl指令,通常從0開始編号遞增;
- size,涉及到ioctl第三個參數arg,占據13bit或者14bit(體系相關,arm架構一般為14位),指定了arg的資料類型及長度,如果在驅動的ioctl實作中不檢查,通常可以忽略該參數。
通常而言,為了友善會使用宏_IOC()衍生的接口來直接定義ioctl指令:
// include/uapi/asm-generic/ioctl.h
/* used to create numbers */
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),)
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
_IO | 定義不帶參數的ioctl指令 |
_IOW | 定義帶寫參數的ioctl指令(copy_from_user) |
_IOR | 定義帶讀參數的ioctl指令(copy_to_user) |
_IOWR | 定義帶讀寫參數的ioctl指令 |
同時,核心還提供了反向解析ioctl指令的宏接口:
// include/uapi/asm-generic/ioctl.h
/* used to decode ioctl numbers */
#define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
#define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
#define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
#define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
ioctl-test,執行個體分析
本例假設一個帶寄存器的裝置,設計了一個ioctl接口實作裝置初始化、讀寫寄存器等功能。在本例中,為了攜帶更多的資料,ioctl的第三個可變參數為指針類型,指向自定義的結構體struct msg。
1、ioctl-test.h,使用者空間和核心空間共用的頭檔案,包含ioctl指令及相關宏定義,可以了解為一份“協定”檔案,代碼如下:
// ioctl-test.h
#ifndef __IOCTL_TEST_H__
#define __IOCTL_TEST_H__
#include <linux/ioctl.h> // 核心空間
// #include <sys/ioctl.h> // 使用者空間
/* 定義裝置類型 */
#define IOC_MAGIC 'c'
/* 初始化裝置 */
#define IOCINIT _IO(IOC_MAGIC, 0)
/* 讀寄存器 */
#define IOCGREG _IOW(IOC_MAGIC, 1, int)
/* 寫寄存器 */
#define IOCWREG _IOR(IOC_MAGIC, 2, int)
#define IOC_MAXNR 3
struct msg {
int addr;
unsigned int data;
};
#endif
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
2、ioctl-test-driver.c,字元裝置驅動,實作了unlocked_ioctl接口,根據上層使用者的cmd執行對應的操作(初始化裝置、讀寄存器、寫寄存器)。在接收上層cmd之前應該對其進行充分的檢查,流程及具體代碼實作如下:
// ioctl-test-driver.c
......
static const struct file_operations fops = {
.owner = THIS_MODULE,
.open = test_open,
.release = test_close,
.read = test_read,
.write = etst_write,
.unlocked_ioctl = test_ioctl,
};
......
static long test_ioctl(struct file *file, unsigned int cmd, \
unsigned long arg)
{
//printk("[%s]\n", __func__);
int ret;
struct msg my_msg;
/* 檢查裝置類型 */
if (_IOC_TYPE(cmd) != IOC_MAGIC) {
pr_err("[%s] command type [%c] error!\n", \
__func__, _IOC_TYPE(cmd));
return -ENOTTY;
}
/* 檢查序數 */
if (_IOC_NR(cmd) > IOC_MAXNR) {
pr_err("[%s] command numer [%d] exceeded!\n",
__func__, _IOC_NR(cmd));
return -ENOTTY;
}
/* 檢查通路模式 */
if (_IOC_DIR(cmd) & _IOC_READ)
ret= !access_ok(VERIFY_WRITE, (void __user *)arg, \
_IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
ret= !access_ok(VERIFY_READ, (void __user *)arg, \
_IOC_SIZE(cmd));
if (ret)
return -EFAULT;
switch(cmd) {
/* 初始化裝置 */
case IOCINIT:
init();
break;
/* 讀寄存器 */
case IOCGREG:
ret = copy_from_user(&msg, \
(struct msg __user *)arg, sizeof(my_msg));
if (ret)
return -EFAULT;
msg->data = read_reg(msg->addr);
ret = copy_to_user((struct msg __user *)arg, \
&msg, sizeof(my_msg));
if (ret)
return -EFAULT;
break;
/* 寫寄存器 */
case IOCWREG:
ret = copy_from_user(&msg, \
(struct msg __user *)arg, sizeof(my_msg));
if (ret)
return -EFAULT;
write_reg(msg->addr, msg->data);
break;
default:
return -ENOTTY;
}
return ;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
3、ioctl-test.c,運作在使用者空間的測試程式:
// ioctl-test.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "ioctl-test.h"
int main(int argc, char **argv)
{
int fd;
int ret;
struct msg my_msg;
fd = open("/dev/ioctl-test", O_RDWR);
if (fd < ) {
perror("open");
exit(-);
}
/* 初始化裝置 */
ret = ioctl(fd, IOCINIT);
if (ret) {
perror("ioctl init:");
exit(-);
}
/* 往寄存器0x01寫入資料0xef */
memset(&my_msg, , sizeof(my_msg));
my_msg.addr = ;
my_msg.data = ;
ret = ioctl(fd, IOCWREG, &my_msg);
if (ret) {
perror("ioctl read:");
exit(-);
}
/* 讀寄存器0x01 */
memset(&my_msg, , sizeof(my_msg));
my_msg.addr = ;
ret = ioctl(fd, IOCGREG, &my_msg);
if (ret) {
perror("ioctl write");
exit(-);
}
printf("read: %#x\n", my_msg.data);
return ;
}