【Linux】ioctl()方法

在这里插入图片描述

博客主页:PannLZ
系列专栏:《Linux系统之路》
欢迎关注:点赞收藏✍️留言

文章目录

      • ioctl方法
        • 1.生成ioctl编号(命令)
        • 2.ioctl步骤


ioctl方法

典型的Linux系统包含大约350个系统调用(syscalls),但是其中只有少数几个链接到文件操作。有时设备可能需要实现系统调用未提供的特定命令,特别是与文件和设备文件相关的命令。在这种情况下,解决方案是使用输入/输出控制(ioctl),这种方法可以扩展与设备相关的系统调用列表(实际上是命令)。用它可以向设备发送特殊命令(重置、关机、配置等)。如果驱动程序没有定义这个方法,则内核将向ioctl()系统调用返回-ENOTTY错误。

为了有效和安全起见,ioctl命令需要由系统唯一的号码来标识。系统内ioctl号码的唯一性可以防止把正确的命令发送到错误的设备,或者将错误的参数传递给正确的命令(给出重复的ioctl号码)。

Linux提供了4个帮助宏来创建ioctl标识符,选用哪个取决于是否有数据传输和传输的方向。它们各自的原型如下:

_IO(MAGIC, SEQ_NO)
_IOW(MAGIC, SEQ_NO, TYPE)
_IOR(MAGIC, SEQ_NO, TYPE)
_IORW(MAGIC, SEQ_NO, TYPE)
  • _IO:ioctl不需要数据传输。

  • _IOW:ioctl需要写入参数(copy_from_user或get_user)。

  • _IOR:ioctl需要读取参数(copy_to_user或put_user)。

  • _IOWR:ioctl需要写入和读取参数。

参数的含义(按照传递顺序)如下:

(1)8位编码数字(0~255),称为魔数。
(2)序列号或命令ID,也是8位。
(3)数据类型(如果有的话),这会通知内核要复制的数据长度。

内核源码的Documentation/ioctl/ioctl-decoding.txt中会详细说明,现有的ioctl在Documentation/ioctl/ioctl-number.txt中列出,需要创建ioctl命令时,这是一个很好的开始。

1.生成ioctl编号(命令)

应该在专用头文件中生成自己的ioctl编号,这不是强制性的,但建议这样做,因为这个头文件在用户空间中也可以使用。换句话说,应该复制ioctl头文件,以便在内核中有一个,在用户空间中也有一个,该文件可以包含在用户应用程序中。现在以一个真实的例子来生成ioctl编号:

eep_ioctl.h:
#ifndef PACKT_IOCTL_H
#define PACKT_IOCTL_H
/*
* 需要为驱动选择一个数字,以及每个命令的序列号
*/
#define EEP_MAGIC 'E'
#define ERASE_SEQ_NO 0x01
#define RENAME_SEQ_NO 0x02
#define ClEAR_BYTE_SEQ_NO 0x03
#define GET_SIZE 0x04
/*
* 分区名必须是最大32字节
*/
#define MAX_PART_NAME 32
/*
* 定义ioctl编号
*/
#define EEP_ERASE _IO(EEP_MAGIC, ERASE_SEQ_NO)
#define EEP_RENAME_PART _IOW(EEP_MAGIC,RENAME_SEQ_NO, unsigned long)
#define EEP_GET_SIZE _IOR(EEP_MAGIC, GET_SIZE,int *)
#endif
2.ioctl步骤

看一下ioctl的原型,如下所示:

long ioctl(struct file *f, unsigned int cmd,unsigned long arg);

使用switch … case语句,在调用未定义的ioctl命令时返回-ENOTTY错误:

/*
* 用户空间代码还需要包括定义ioctls的头文件,这里是eep_iocl.h
*/
#include "eep_ioctl.h"
static long eep_ioctl(struct file *f, unsignedint cmd, unsigned long arg)
{
		int part;
		char *buf = NULL;
		int size = 1300;
		switch(cmd){
		case EEP_ERASE:
				erase_eepreom();
				break;
		case EEP_RENAME_PART:
				buf = kmalloc(MAX_PART_NAME,GFP_KERNEL);
				copy_from_user(buf, (char *)arg,MAX_PART_NAME);
				rename_part(buf);
				break;
		case EEP_GET_SIZE:
				copy_to_user((int*)arg, &size,sizeof(int));
				break;
		default:
		return -ENOTTY;
		}
		return 0;
}

如果认为ioctl命令需要多个参数,则应该把这些参数集合到结构中,把该结构的指针传递给ioctl。

现在,在用户空间中,必须使用与驱动程序代码中相同的ioctl头文件:

//my_main.c
    
#include 
#include 
#include 
#include 
#include "eep_ioctl.h" /* our ioctl header file*/

int main()
{
		int size = 0;
		int fd;
		char *new_name = "lorem_ipsum"; /* 不超过MAX_PART_NAME */
		fd = open("/dev/eep-mem1", O_RDWR);
		if (fd == -1){
				printf("Error while opening theeeprom\n");
				return -1;
		}
		ioctl(fd, EEP_ERASE); /* 调用ioctl来擦除分区*/
		ioctl(fd, EEP_GET_SIZE, &size); /* 调用ioctl获取分区大小 */
		ioctl(fd, EEP_RENAME_PART, new_name); /*调用ioctl来重命名分区 */
		close(fd);
		return 0;
}

你可能感兴趣的:(Linux系统之路,linux,服务器,linux内核,内核开发,内核,驱动开发)