Linux内核的ioctl函数

时间:2021-02-04 19:05:04

关于ioctl的网上资源

一、 什么是ioctl。

ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。它的调用个数如下:

int ioctl(int fd, ind cmd, …);

其中fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,有或没有是和cmd的意义相关的。

ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就可以在用户程序中使用ioctl函数控制设备的I/O通道。


二、 ioctl的必要性

如果不用ioctl的话,也可以实现对设备I/O通道的控制,但那就是蛮拧了。例如,我们可以在驱动程序中实现write的时候检查一下是否有特殊约定的数据流通过,如果有的话,那么后面就跟着控制命令(一般在socket编程中常常这样做)。但是如果这样做的话,会导致代码分工不明,程序结构混乱,程序员自己也会头昏眼花的。

所以,我们就使用ioctl来实现控制的功能。要记住,用户程序所作的只是通过命令码告诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情。


三、 ioctl如何实现

在驱动程序中实现的ioctl函数体内,实际上是有一个switch{case}结构,每一个case对应一个命令码,做出一些相应的操作。怎么实现这些操作,这是每一个程序员自己的事情,因为设备都是特定的,这里也没法说。关键在于怎么样组织命令码,因为在ioctl中命令码是唯一联系用户程序命令和驱动程序支持的途径。

命令码的组织是有一些讲究的,因为我们一定要做到命令和设备是一一对应的,这样才不会将正确的命令发给错误的设备,或者是把错误的命令发给正确的设备,或者是把错误的命令发给错误的设备。这些错误都会导致不可预料的事情发生,而当程序员发现了这些奇怪的事情的时候,再来调试程序查找错误,那将是非常困难的事情。

所以在Linux核心中是这样定义一个命令码的:

____________________________________

| 设备类型 | 序列号 | 方向 |数据尺寸|

|----------|--------|------|--------|

| 8 bit    |  8 bit |2 bit |8~14 bit|

|----------|--------|------|--------|

这样一来,一个命令就变成了一个整数形式的命令码。但是命令码非常的不直观,所以Linux Kernel中提供了一些宏,这些宏可根据便于理解的字符串生成命令码,或者是从命令码得到一些用户可以理解的字符串以标明这个命令对应的设备类型、设备序列号、数据传送方向和数据传输尺寸。


幻数是一个字母,数据长度也是8,所以就用一个特定的字母来标明设备类型,这和用一个数字是一样的,只是更加利于记忆和理解。就是这样,再没有更复杂的了。



四、 cmd参数如何得出

cmd参数在用户程序端由一些宏根据设备类型、序列号、传送方向、数据尺寸等生成,这个整数通过系统调用传递到内核中的驱动程序,再由驱动程序使用解码宏从这个整数中得到设备的类型、序列号、传送方向、数据尺寸等信息,然后通过switch{case}结构进行相应的操作。


五、 小结

ioctl其实没有什么很难的东西需要理解,关键是理解cmd命令码是怎么在用户程序里生成并在驱动程序里解析的,程序员最主要的工作量在switch{case}结构中,因为对设备的I/O控制都是通过这一部分的代码实现的。


六、结合自己的代码例子,以PCM读写函数在android和kernel之间传递为例子

先看android的代码

int pcm_write(struct pcm *pcm, void *data, unsigned count)
{
struct snd_xferi x;

if (pcm->flags & PCM_IN)
return -EINVAL;

x.buf = data;
x.frames = (pcm->flags & PCM_MONO) ? (count / 2) : (count / 4);

for (;;) {
if (!pcm->running) {
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE))
return oops(pcm, errno, "cannot prepare channel");
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x))
return oops(pcm, errno, "cannot write initial data");
pcm->running = 1;
return 0;
}
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) {
pcm->running = 0;
if (errno == EPIPE) {
/* we failed to make our window -- try to restart */
pcm->underruns++;
continue;
}
return oops(pcm, errno, "cannot write stream data");
}
return 0;
}
}

#define SNDRV_PCM_IOCTL_WRITEI_FRAMES _IOW('A', 0x50, struct snd_xferi)#define SNDRV_PCM_IOCTL_READI_FRAMES _IOR('A', 0x51, struct snd_xferi)#define SNDRV_PCM_IOCTL_WRITEN_FRAMES _IOW('A', 0x52, struct snd_xfern)#define SNDRV_PCM_IOCTL_READN_FRAMES _IOR('A', 0x53, struct snd_xfern)

kernel的代码

#define SNDRV_PCM_IOCTL_WRITEI_FRAMES	_IOW('A', 0x50, struct snd_xferi)
#define SNDRV_PCM_IOCTL_READI_FRAMES _IOR('A', 0x51, struct snd_xferi)
#define SNDRV_PCM_IOCTL_WRITEN_FRAMES _IOW('A', 0x52, struct snd_xfern)
#define SNDRV_PCM_IOCTL_READN_FRAMES _IOR('A', 0x53, struct snd_xfern)

static long snd_pcm_playback_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct snd_pcm_file *pcm_file;

pcm_file = file->private_data;

if (((cmd >> 8) & 0xff) != 'A')
return -ENOTTY;

return snd_pcm_playback_ioctl1(file, pcm_file->substream, cmd,
(void __user *)arg);
}

static int snd_pcm_playback_ioctl1(struct file *file,				   struct snd_pcm_substream *substream,				   unsigned int cmd, void __user *arg){	if (snd_BUG_ON(!substream))		return -ENXIO;	if (snd_BUG_ON(substream->stream != SNDRV_PCM_STREAM_PLAYBACK))		return -EINVAL;	switch (cmd) {	case SNDRV_PCM_IOCTL_WRITEI_FRAMES:	{		struct snd_xferi xferi;		struct snd_xferi __user *_xferi = arg;		struct snd_pcm_runtime *runtime = substream->runtime;		snd_pcm_sframes_t result;		if (runtime->status->state == SNDRV_PCM_STATE_OPEN)			return -EBADFD;		if (put_user(0, &_xferi->result))			return -EFAULT;		if (copy_from_user(&xferi, _xferi, sizeof(xferi)))			return -EFAULT;		result = snd_pcm_lib_write(substream, xferi.buf, xferi.frames);		__put_user(result, &_xferi->result);		return result < 0 ? result : 0;	}	case SNDRV_PCM_IOCTL_WRITEN_FRAMES:	{		struct snd_xfern xfern;		struct snd_xfern __user *_xfern = arg;		struct snd_pcm_runtime *runtime = substream->runtime;		void __user **bufs;		snd_pcm_sframes_t result;		if (runtime->status->state == SNDRV_PCM_STATE_OPEN)			return -EBADFD;		if (runtime->channels > 128)			return -EINVAL;		if (put_user(0, &_xfern->result))			return -EFAULT;		if (copy_from_user(&xfern, _xfern, sizeof(xfern)))			return -EFAULT;		bufs = memdup_user(xfern.bufs,				   sizeof(void *) * runtime->channels);		if (IS_ERR(bufs))			return PTR_ERR(bufs);		result = snd_pcm_lib_writev(substream, bufs, xfern.frames);		kfree(bufs);		__put_user(result, &_xfern->result);		return result < 0 ? result : 0;	}	case SNDRV_PCM_IOCTL_REWIND:	{		snd_pcm_uframes_t frames;		snd_pcm_uframes_t __user *_frames = arg;		snd_pcm_sframes_t result;		if (get_user(frames, _frames))			return -EFAULT;		if (put_user(0, _frames))			return -EFAULT;		result = snd_pcm_playback_rewind(substream, frames);		__put_user(result, _frames);		return result < 0 ? result : 0;	}	case SNDRV_PCM_IOCTL_FORWARD:	{		snd_pcm_uframes_t frames;		snd_pcm_uframes_t __user *_frames = arg;		snd_pcm_sframes_t result;		if (get_user(frames, _frames))			return -EFAULT;		if (put_user(0, _frames))			return -EFAULT;		result = snd_pcm_playback_forward(substream, frames);		__put_user(result, _frames);		return result < 0 ? result : 0;	}	}	return snd_pcm_common_ioctl1(file, substream, cmd, arg);}


android 和 kernel 两边各自的宏定义是一样的,通过这些用32bit数据代表的数据传递,实现了从android到kernel的接口连接