IOCTL命令格式解析以及调用过程

144 篇文章 6 订阅

一、IOCTL的系统调用

1、应用程序中的ioctl(系统IO的内容)

#include 
int ioctl(int d, int request, ...);

应用程序向驱动程序发送命令(cmd),然后应用程序可以向驱动程序发送数据(args),也可以从驱动程序中读数据。

2、驱动程序中,对应的ioctl

struct file_operations {
	struct module *owner;
	int (*ioctl) (struct inode *inode, struct file *filep, unsigned int cmd, unsigned long args);
	long (*unlocked_ioctl) (struct file *filep, unsigned int cmd, unsigned long args);
	......
}

在驱动程序中,ioctlunlocked_ioctl的区别
在2.6.36以后linux的内核中,只支持unlocked_ioctl(),不支持ioctl()2.6.35.7内核中,两个函数都可以使用。


二、IOCTL的命令构成

为了高效的使用cmd参数传递更多的控制信息,一个unsigned int cmd被分为了4个段,每一段都有各自的意义,cmd的定义在。
unsigned int cmd位域拆分如下:

cmd[31:30]—数据(args)的传输方向(读写)
cmd[29:16]—数据(args)的大小
cmd[15:8]—>命令的类型,可以理解成命令的密钥,一般为ASCII码(0-255的一个字符,有部分字符已经被占用,每个字符的序号段可能部分被占用)
cmd[7:0] —>命令的序号,是一个8bits的数字(序号,0-255之间)

 #define _IOC_NRBITS 8
 #define _IOC_TYPEBITS 8
 #define _IOC_SIZEBITS 14
 #define _IOC_DIRBITS 2
 #define _IOC_NRSHIFT 0
 #define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS) //0+8=8
 #define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS)//8+8=16
 #define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS)//16+14=30

例:'S'为命令类型,0x0C表示12为S这个命令下序号为12的操作,unsigned long表示这个命令接收的参数类型。

#define ADC_INPUT_PIN	_IOW('S', 0x0c, unsigned long)

三、ioctl的命令编码##

幻数( 幻数(TYPE ):0~0xff,占8bit(_IOC_TYPEBITS)。也就是cmd[15:8]这一部分。为一个ASCII字符,内核文档给出一些推荐的或者已经被使用的幻数,也可以叫做魔数

序数(NR):为功能号,给自己的命令编号,占8bit(_IOC_NRBITS)。也就是cmd[7:0]这一部分。

数据传输方向( 数据传输方向(DIR ):占2bit(_IOC_DIRBITS)。如果涉及到要传参,内核要求描述一下传输的方向,传输的方向是以应用层的角度来描述的。(IOR,IORW,IOW等之中的IO后面的字符表示)

 #include 
 _IO(type,nr) //没有参数的命令
 _IOR(type,nr,size) //该命令是从驱动读取数据
 _IOW(type,nr,size) //该命令是从驱动写入数据
 _IOWR(type,nr,size) //双向数据传输

上面的命令已经定义了方向,我们要传的是幻数(type)、序号(nr)和大小(size)。在这里szie的参数只需要填参数的类型,如int,上面的命令就会帮你检测类型的正确然后赋值sizeof(int)

数据大小( 数据大小(SIZE):arg参数大小,占14bit(_IOC_SIZEBITS)

针对unsigned int cmd的四段位域,linux中提供以下用于位域操作的宏。也就是说cmd这个32位整数,实际是由4个字段拼出来的。

/*
*_IOC 宏将dir,type,nr,size四个参数组合成一个cmd参数:
*/


#define _IOC(dir,type,nr,size) \

       (((dir)  << _IOC_DIRSHIFT) | \ //左移30位

        ((type) << _IOC_TYPESHIFT) | \ //左移8位

        ((nr)   << _IOC_NRSHIFT) | \  //左移0位

        ((size) << _IOC_SIZESHIFT)) //左移16位

四、生成cmd的函数 ##

/* 定义幻数 */
#define DATA_MAGIC 'B‘
/* 定义命令 */
#define NODATA_CMD _IO(MAGIC_DATA, 0x20)
#define GETDATA_CMD  _IOR(MAGIC_DATA, 0x21, int)
#define SETDATA_CMD  _IOW(MAGIC_DATA, 0x22, int)

五、生成cmd的函数

1、应用程序和驱动程序之间不传递参数

#define _IO(type,nr)		_IOC(_IOC_NONE,(type),(nr),0)

2、应用程序从驱动程序中读参数

#define _IOR(type,nr,size)	_IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))

3、应用程序想驱动程序写参数

#define _IOW(type,nr,size)	_IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))

4、参数的传递是双向的。

#define _IOWR(type,nr,size)	_IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))

参数说明:
type—>命令的类型
nr —>命令的序号
size —>参数的大小


六、系统调用

1.系统调用号定义

arch/arm/include/asm/unistd.h
	#define __NR_ioctl			(__NR_SYSCALL_BASE+ 54)

2.系统调用入口

arch/arm/kernel/calls.S

	/* 55 */	CALL(sys_ioctl)

3.声明系统调用sys_ioctl()

/include/linux.h
	asmlinkage long sys_ioctl(unsigned int fd, unsigned int cmd, unsigned long arg);

4.系统调用安装

	SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
	
	其中:
	/include/linux/syscalls.h
	#define SYSCALL_DEFINE3(name, ...)  SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
	#define SYSCALL_DEFINEx(x, sname, ...)	__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
	#define __SYSCALL_DEFINEx(x, name, ...)	asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))

5、内核系统调用接口

SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
{
	struct file *filp;
	int error = -EBADF;
	int fput_needed;

	filp = fget_light(fd, &fput_needed);//由fd得带filp指针
	if (!filp)
		goto out;

	error = security_file_ioctl(filp, cmd, arg);
	if (error)
		goto out_fput;

	error = do_vfs_ioctl(filp, fd, cmd, arg);
 out_fput:
	fput_light(filp, fput_needed);
 out:
	return error;
}

6、do_vfs_ioctl函数

int do_vfs_ioctl(struct file *filp, unsigned int fd, unsigned int cmd,
	     unsigned long arg)
{
	int error = 0;
	int __user *argp = (int __user *)arg;
	struct inode *inode = filp->f_path.dentry->d_inode;

	switch (cmd) {
	case FIOCLEX:
		set_close_on_exec(fd, 1);
		break;

	case FIONCLEX:
		set_close_on_exec(fd, 0);
		break;

	case FIONBIO:
		error = ioctl_fionbio(filp, argp);
		break;

	case FIOASYNC:
		error = ioctl_fioasync(fd, filp, argp);
		break;

	case FIOQSIZE:
		if (S_ISDIR(inode->i_mode) || S_ISREG(inode->i_mode) ||
		    S_ISLNK(inode->i_mode)) {
			loff_t res = inode_get_bytes(inode);
			error = copy_to_user(argp, &res, sizeof(res)) ?
					-EFAULT : 0;
		} else
			error = -ENOTTY;
		break;

	case FIFREEZE:
		error = ioctl_fsfreeze(filp);
		break;

	case FITHAW:
		error = ioctl_fsthaw(filp);
		break;

	case FS_IOC_FIEMAP:
		return ioctl_fiemap(filp, arg);

	case FIGETBSZ:
		return put_user(inode->i_sb->s_blocksize, argp);

	default:
		if (S_ISREG(inode->i_mode))//是否为常规文件若是常规文件
			error = file_ioctl(filp, cmd, arg);
		else
			error = vfs_ioctl(filp, cmd, arg);//调用vfs_ioctl
		break;
	}
	return error;
}

7、调用vfs_ioctl()

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;
unlocked_ioctl
	error = filp->f_op->unlocked_ioctl(filp, cmd, arg);//调用unlocked_ioctl()
	if (error == -ENOIOCTLCMD)
		error = -EINVAL;
 out:
	return error;
}	
  • 2
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ioctl() 是一个系统调用函数,用于向设备驱动程序发送控制命令。它的原型为: ```c int ioctl(int fd, unsigned long request, ...); ``` 其中,fd 是设备文件描述符,request 是要发送的命令,... 是可变参数,用于传递命令所需要的参数。 ioctl() 函数常用于设备驱动程序中,用于实现设备控制的功能。它通过向设备驱动程序发送命令,来控制设备的各种属性和操作。具体来说,它可以用于以下几个方面: 1. 设置设备的属性:比如设置串口的波特率、数据位、停止位等。 2. 获取设备的状态:比如查询打印机的打印状态、查询磁盘的空间使用情况等。 3. 控制设备的操作:比如向硬盘发送读写命令、向显示器发送刷新命令等。 ioctl() 的 request 参数通常是一个整数,用于表示要发送的命令。它通常由一个宏定义来定义,以方便使用。比如,下面是一些常用的 ioctl() 命令: - FIONREAD:获取输入缓冲区中的字节数。 - FIONBIO:设置或清除非阻塞模式。 - TIOCGWINSZ:获取终端窗口大小。 - TIOCSWINSZ:设置终端窗口大小。 - SNDCTL_DSP_SPEED:设置音频采样率。 - SNDCTL_DSP_SETFMT:设置音频采样格式。 需要注意的是,ioctl() 函数的使用比较复杂,因为不同的设备驱动程序可能对命令的解释和参数的传递方式有所不同。因此,使用 ioctl() 函数时需要参考相应的设备驱动程序的文档或示例代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值