ioctl(),你懂的!
IOCTL(2) Linux Programmer's Manual IOCTL(2)
NAME top
ioctl - control device
SYNOPSIS top
#include <sys/ioctl.h> int ioctl(int d, int request, ...);
DESCRIPTION top
The ioctl() function manipulates the underlying device parameters of special files. In particular, many operating characteristics of character special files (e.g., terminals) may be controlled with ioctl() requests. The argument d must be an open file descriptor. The second argument is a device-dependent request code. The third argument is an untyped pointer to memory. It's traditionally char *argp (from the days before void * was valid C), and will be so named for this discussion. An ioctl() request has encoded in it whether the argument is an in parameter or out parameter, and the size of the argument argp in bytes. Macros and defines used in specifying an ioctl() request are located in the file <sys/ioctl.h>.
RETURN VALUE top
Usually, on success zero is returned. A few ioctl() requests use the return value as an output parameter and return a nonnegative value on success. On error, -1 is returned, and errno is set appropriately.
ERRORS top
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是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。它的调用个数如下:int ioctl(int fd, int cmd, …);其中fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,有或没有是和cmd的意义相关的。ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就能在用户程序中使用ioctl函数控制设备的I/O通道。
ioctl其实没有什么非常难的东西需要理解,关键是理解cmd命令码是怎么在用户程式里生成并在驱动程式里解析的,程式员最主要的工作量在switch{case}结构中,因为对设备的I/O控制都是通过这一部分的代码实现的。
这里确实要说一说,cmd参数在用户程式端由一些宏根据设备类型、序列号、传送方向、数据尺寸等生成,这个整数通过系统调用传递到内核中的驱动程式,再由驱动程式使用解码宏从这个整数中得到设备的类型、序列号、传送方向、数据尺寸等信息,然后通过switch{case}结构进行相应的操作。要透彻理解,只能是通过阅读原始码,我这篇文章实际上只是个引子。Cmd参数的组织还是比较复杂的,我认为要搞熟他还是得花不少时间的,不过这是值得的,驱动程式中最难的是对中断的理解。
如果不用IOCTL的话,也能实现对设备I/O通道的控制,但那就是蛮拧了。例如,我们可以在驱动程式中实现WRITE的时候检查一下是否有特别约定的数据流通过,如果有的话,那么后面就跟着控制命令(一般在SOCKET编程中常常这样做)。不过如果这样做的话,会导致代码分工不明,程式结构混乱,程式员自己也会头昏眼花的。所以,我们就使用IOCTL来实现控制的功能。要记住,用户程式所作的只是通过命令码告诉驱动程式他想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程式要做的事情。
1 #include <stdlib.h> 2 #include <stdio.h> 3 #include <sys/ioctl.h> 4 int main(void) { 5 ..int stat; 6 /* use func 8 to determine if the default drive is removable */ 7 ..stat = ioctl(0, 8, 0, 0); 8 ..if (!stat) 9 ....printf("Drive %c is removable.\n", getdisk() + 'A'); 10 ..else 11 ....printf("Drive %c is not removable.\n", getdisk() + 'A'); 12 ..return 0; 13 }
类别
|
Request
|
说明
|
数据类型
|
套
接
口
|
SIOCATMARK
SIOCSPGRP
SIOCGPGRP
|
是否位于带外标记
设置套接口的进程ID 或进程组ID
获取套接口的进程ID 或进程组ID
|
int
int
int
|
文
件
|
FIONBIO
FIOASYNC
FIONREAD
FIOSETOWN
FIOGETOWN
|
设置/ 清除非阻塞I/O 标志
设置/ 清除信号驱动异步I/O 标志
获取接收缓存区中的字节数
设置文件的进程ID 或进程组ID
获取文件的进程ID 或进程组ID
|
int
int
int
int
int
|
接
口
|
SIOCGIFCONF
SIOCSIFADDR
SIOCGIFADDR
SIOCSIFFLAGS
SIOCGIFFLAGS
SIOCSIFDSTADDR
SIOCGIFDSTADDR
SIOCGIFBRDADDR
SIOCSIFBRDADDR
SIOCGIFNETMASK
SIOCSIFNETMASK
SIOCGIFMETRIC
SIOCSIFMETRIC
SIOCGIFMTU
SIOCxxx
|
获取所有接口的清单
设置接口地址
获取接口地址
设置接口标志
获取接口标志
设置点到点地址
获取点到点地址
获取广播地址
设置广播地址
获取子网掩码
设置子网掩码
获取接口的测度
设置接口的测度
获取接口MTU
(还有很多取决于系统的实现)
|
struct ifconf
struct ifreq
struct ifreq
struct ifreq
struct ifreq
struct ifreq
struct ifreq
struct ifreq
struct ifreq
struct ifreq
struct ifreq
struct ifreq
struct ifreq
struct ifreq
|
ARP
|
SIOCSARP
SIOCGARP
SIOCDARP
|
创建/ 修改ARP 表项
获取ARP 表项
删除ARP 表项
|
struct arpreq
struct arpreq
struct arpreq
|
路
由
|
SIOCADDRT
SIOCDELRT
|
增加路径
删除路径
|
struct rtentry
struct rtentry
|
流
|
I_xxx
|
大部分驱动除了需要具备读写设备的能力之外,还需要具备对硬件控制的能力。
一、在用户空间,使用ioctl系统调用来控制设备,原型如下:
int ioctl(int fd,unsigned long cmd,...); /* fd:文件描述符 cmd:控制命令 ...:可选参数:插入*argp,具体内容依赖于cmd */
用户程序所作的只是通过命令码告诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情。
二、驱动ioctl方法:
int (*ioctl) (struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg); /* inode与filp两个指针对应于应用程序传递的文件描述符fd,这和传递open方法的参数一样。 cmd 由用户空间直接不经修改的传递给驱动程序 arg 可选。 */
在驱动程序中实现的ioctl函数体内,实际上是有一个switch {case}结构,每一个case对应一个命令码,做出一些相应的操作。怎么实现这些操作,这是每一个程序员自己的事情,因为设备都是特定的。关键在于怎么样组织命令码,因为在ioctl中命令码是唯一联系用户程序命令和驱动程序支持的途径。
在Linux核心中是这样定义一个命令码的:
____________________________________
| 设备类型 | 序列号 | 方向 | 数据尺寸 |
|----------|--------|------|-------- |
| 8 bit | 8 bit | 2 bit |8~14 bit|
|----------|--------|------|-------- |
这样一来,一个命令就变成了一个整数形式的命令码。但是命令码非常的不直观,所以Linux Kernel中提供了一些宏,这些宏可根据便于理解的字符串生成命令码,或者是从命令码得到一些用户可以理解的字符串以标明这个命令对应的设备类型、设备序列号、数据传送方向和数据传输尺寸。
1、定义命令:
内核提供了一些宏来帮助定义命令:
//nr为序号,datatype为数据类型,如int _IO(type, nr ) //没有参数的命令 _IOR(type, nr, datatype) //从驱动中读数据 _IOW(type, nr, datatype) //写数据到驱动 _IOWR(type,nr, datatype) //双向传送
定义命令例子:
#define MEM_IOC_MAGIC 'm' //定义类型 #define MEM_IOCSET _IOW(MEM_IOC_MAGIC,0,int) #define MEM_IOCGQSET _IOR(MEM_IOC_MAGIC, 1, int)
2、实现命令:
定义好了命令,下一步就是要实现ioctl函数了,ioctl的实现包括三个技术环节:
1)返回值;
ioctl函数的实现是根据命令执行的一个switch语句,但是,当命令不能匹配任何一个设备所支持的命令时,通常返回-EINVAL(非法参数);
2)参数使用;
用户使用 int ioctl(int fd,unsinged long cmd,...) 时,...就是要传递的参数;
再通过 int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) 中的arg传递;
如果arg是一个整数,可以直接使用;
如果是指针,我们必须确保这个用户地址是有效的,因此,使用之前需要进行正确检查。
内部有检查的,不需要检测的:
copy_from_user copy_to_user get_user put_user
需要检测的:
__get_user __put_user
检测函数access_ok():
static inline int access_ok(int type, const void *addr, unsigned long size) /* type :是VERIFY_READ 或者VERIFY_WRITE用来表明是读用户内存还是写用户内存; addr:是要操作的用户内存地址; size:是操作的长度。如果ioctl需要从用户空间读一个整数,那么size参数就等于sizeof(int); 返回值:Access_ok返回一个布尔值:1,是成功(存取没问题);0,是失败,ioctl返回-EFAULT; */
3)命令操作;
switch(cmd) { case: ... ... }
三、ioctl实例分析:
(1)memdev.h:
/*mem设备描述结构体*/ struct mem_dev { char *data; unsigned long size; }; /* 定义幻数 */ #define MEMDEV_IOC_MAGIC 'k' /* 定义命令 */ #define MEMDEV_IOCPRINT _IO(MEMDEV_IOC_MAGIC, 1) #define MEMDEV_IOCGETDATA _IOR(MEMDEV_IOC_MAGIC, 2, int) #define MEMDEV_IOCSETDATA _IOW(MEMDEV_IOC_MAGIC, 3, int) #define MEMDEV_IOC_MAXNR 3 #endif /* _MEMDEV_H_ */
(2)memdev.c:(驱动程序)
static int mem_major = MEMDEV_MAJOR; module_param(mem_major, int, S_IRUGO); struct mem_dev *mem_devp; /*设备结构体指针*/ struct cdev cdev; /*文件打开函数*/ int mem_open(struct inode *inode, struct file *filp) { struct mem_dev *dev; /*获取次设备号*/ int num = MINOR(inode->i_rdev); if (num >= MEMDEV_NR_DEVS) return -ENODEV; dev = &mem_devp[num]; /*将设备描述结构指针赋值给文件私有数据指针*/ filp->private_data = dev; return 0; } /*文件释放函数*/ int mem_release(struct inode *inode, struct file *filp) { return 0; } /*IO操作*/ int memdev_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) { int err = 0; int ret = 0; int ioarg = 0; /* 检测命令的有效性 */ if (_IOC_TYPE(cmd) != MEMDEV_IOC_MAGIC) return -EINVAL; if (_IOC_NR(cmd) > MEMDEV_IOC_MAXNR) return -EINVAL; /* 根据命令类型,检测参数空间是否可以访问 */ if (_IOC_DIR(cmd) & _IOC_READ) err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd)); else if (_IOC_DIR(cmd) & _IOC_WRITE) err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd)); if (err) return -EFAULT; /* 根据命令,执行相应的操作 */ switch(cmd) { /* 打印当前设备信息 */ case MEMDEV_IOCPRINT: printk("<--- CMD MEMDEV_IOCPRINT Done--->\n\n"); break; /* 获取参数 */ case MEMDEV_IOCGETDATA: ioarg = 1101; ret = __put_user(ioarg, (int *)arg); break; /* 设置参数 */ case MEMDEV_IOCSETDATA: ret = __get_user(ioarg, (int *)arg); printk("<--- In Kernel MEMDEV_IOCSETDATA ioarg = %d --->\n\n",ioarg); break; default: return -EINVAL; } return ret; } /*文件操作结构体*/ static const struct file_operations mem_fops = { .owner = THIS_MODULE, .open = mem_open, .release = mem_release, .ioctl = memdev_ioctl, }; /*设备驱动模块加载函数*/ static int memdev_init(void) { int result; int i; dev_t devno = MKDEV(mem_major, 0); /* 静态申请设备号*/ if (mem_major) result = register_chrdev_region(devno, 2, "memdev"); else /* 动态分配设备号 */ { result = alloc_chrdev_region(&devno, 0, 2, "memdev"); mem_major = MAJOR(devno); } if (result < 0) return result; /*初始化cdev结构*/ cdev_init(&cdev, &mem_fops); cdev.owner = THIS_MODULE; cdev.ops = &mem_fops; /* 注册字符设备 */ cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS); /* 为设备描述结构分配内存*/ mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL); if (!mem_devp) /*申请失败*/ { result = - ENOMEM; goto fail_malloc; } memset(mem_devp, 0, sizeof(struct mem_dev)); /*为设备分配内存*/ for (i=0; i < MEMDEV_NR_DEVS; i++) { mem_devp[i].size = MEMDEV_SIZE; mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL); memset(mem_devp[i].data, 0, MEMDEV_SIZE); } return 0; fail_malloc: unregister_chrdev_region(devno, 1); return result; } /*模块卸载函数*/ static void memdev_exit(void) { cdev_del(&cdev); /*注销设备*/ kfree(mem_devp); /*释放设备结构体内存*/ unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*释放设备号*/ } MODULE_AUTHOR("David Xie"); MODULE_LICENSE("GPL"); module_init(memdev_init); module_exit(memdev_exit);
(3)app-ioctl.c(应用程序)
#include <stdio.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include "memdev.h" /* 包含命令定义 */ int main() { int fd = 0; int cmd; int arg = 0; char Buf[4096]; /*打开设备文件*/ fd = open("/dev/memdev0",O_RDWR); if (fd < 0) { printf("Open Dev Mem0 Error!\n"); return -1; } /* 调用命令MEMDEV_IOCPRINT */ printf("<--- Call MEMDEV_IOCPRINT --->\n"); cmd = MEMDEV_IOCPRINT; if (ioctl(fd, cmd, &arg) < 0) { printf("Call cmd MEMDEV_IOCPRINT fail\n"); return -1; } /* 调用命令MEMDEV_IOCSETDATA */ printf("<--- Call MEMDEV_IOCSETDATA --->\n"); cmd = MEMDEV_IOCSETDATA; arg = 2007; if (ioctl(fd, cmd, &arg) < 0) { printf("Call cmd MEMDEV_IOCSETDATA fail\n"); return -1; } /* 调用命令MEMDEV_IOCGETDATA */ printf("<--- Call MEMDEV_IOCGETDATA --->\n"); cmd = MEMDEV_IOCGETDATA; if (ioctl(fd, cmd, &arg) < 0) { printf("Call cmd MEMDEV_IOCGETDATA fail\n"); return -1; } printf("<--- In User Space MEMDEV_IOCGETDATA Get Data is %d --->\n\n",arg); close(fd); return 0; }
linux系统ioctl使用示例
程序1:检测接口的inet_addr, netmask, broad_addr
程序2:检查接口的物理连接是否正常
程序3:测试物理连接
程序4:调节音量
下面是一个关于V4L视频采集中用到的用ioctl来配置视频采集设备(USB摄像头)的一些特性参数的例子:
1. 定义设备结构体
struct vdIn {
int fd ; //设备描述符
char *videodevice ; //设备节点,在linux下,通用的视频采集设备节点为/dev/video0
struct video_mmap vmmap;
struct video_capability videocap;
int mmapsize;
struct video_mbuf videombuf;
struct video_picture videopict;
struct video_window videowin;
struct video_channel videochan;
int cameratype ;
char *cameraname;
char bridge[9];
int sizenative; // available size in jpeg
int sizeothers; // others palette
int palette; // available palette
int norme ; // set spca506 usb video grabber
int channel ; // set spca506 usb video grabber
int grabMethod ;
unsigned char *pFramebuffer;
unsigned char *ptframe[4];
int framelock[4];
pthread_mutex_t grabmutex;
int framesizeIn ;
volatile int frame_cour;
int bppIn;
int hdrwidth;
int hdrheight;
int formatIn;
int signalquit;
};
2. 设备节点赋值, "/dev/video0"是真实的物理摄像头设备在linux中的表示
if (videodevice == NULL || *videodevice == 0)
{
videodevice = "/dev/video0";
}
3. 调用 设备 初始化函数
struct vdIn videoIn; //在spcav4l.h中定义
videodevice = "/dev/video0"; //节点
int width = 352; //宽
int height = 288; //高
int format = VIDEO_PALETTE_JPEG; //格式
int grabmethod = 1;
memset (&videoIn, 0, sizeof (struct vdIn));
if (init_videoIn(&videoIn, videodevice, width, height, format,grabmethod) != 0)
if(debug) printf (" damned encore rate !!\n");
4. 设备初始化函数传值
int init_videoIn (struct vdIn *vd, char *device, int width, int height,
int format, int grabmethod)
{
int err = -1;
int i;
if (vd == NULL || device == NULL)
return -1;
if (width == 0 || height == 0)
return -1;
if(grabmethod < 0 || grabmethod > 1)
grabmethod = 1; //read by default;
// check format
vd->videodevice = NULL;
vd->cameraname = NULL;
vd->videodevice = NULL;
vd->videodevice = (char *) realloc (vd->videodevice, 16);
vd->cameraname = (char *) realloc (vd->cameraname, 32);
snprintf (vd->videodevice, 12, "%s", device);
if(debug) printf("video %s \n",vd->videodevice);
memset (vd->cameraname, 0, sizeof (vd->cameraname));
memset(vd->bridge, 0, sizeof(vd->bridge));
vd->signalquit = 1;//信号设置
vd->hdrwidth = width;
vd->hdrheight = height;
/* compute the max frame size */
vd->formatIn = format; //传进来的 format = VIDEO_PALETTE_JPEG;
vd->bppIn = GetDepth (vd->formatIn);
vd->grabMethod = grabmethod; //mmap or read
vd->pFramebuffer = NULL;
/* init and check all setting */
err = init_v4l (vd); // V4L初始化函数
....................................................
}
5. V4L初始化函数
static int init_v4l (struct vdIn *vd)
{
int f;
int erreur = 0;
int err;
if ((vd->fd = open (vd->videodevice, O_RDWR)) == -1)//打开
exit_fatal ("ERROR opening V4L interface");
if (ioctl (vd->fd, VIDIOCGCAP, &(vd->videocap) ) == -1)
exit_fatal ("Couldn't get videodevice capability");
....................................
•name[32] //设备名称
•maxwidth ,maxheight,minwidth,minheight
•Channels //信号源个数
•type //是否能capture,彩色还是黑白,是否能裁剪等等。值如VID_TYPE_CAPTURE等
{
char name[32];
int type;
int channels; /* Num channels */
int audios; /* Num audio devices */
int maxwidth; /* Supported width */
int maxheight; /* And height */
int minwidth; /* Supported width */
int minheight; /* And height */
};
设备控制接口(ioctl 函数)
回想一下我们在字符设备驱动中介绍的struct file_operations 结构,这里我们将介绍一个新的方法:
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); |
这是驱动程序设备控制接口函数(ioctl函数)的内核原型定义,struct inode * 和struct file* 描述了操作的文件,unsigned int 描述了ioctl命令号,这是一个重要的参数,我们稍后会对它做详细介绍。最后一个参数是unsigned long数据类型,描述了ioctl命令可能带有的参数,它可能是一个整数或指针数据。
- ioctl命令号
- dir:
- type:
- nr:
- size:
_IO(type,nr) |
宏_IO用于无数据传输,宏_IOR用于从设备读数据,宏 _IOW用于向设备写数据,宏_IOWR用于同时有读写数据的IOCTL命令。相对的,Linux内核也提供了相应的宏来从ioctl命令号种解码相应的域值:
_IOC_DIR(nr) |
- ioctl返回值
- ioctl参数
unsigned long __must_check copy_to_user(void __user *to, |
copy_from_user和copy_to_user一般用于复杂的或大数据交换,对于简单的数据类型,如int或char,内核提供了简单的宏来实现这个功能:
#define get_user(x,ptr) |
其中,x是内核空间的简单数据类型地址,ptr是用户空间地址指针。
我们需要牢记:在内核中是无法直接访问用户空间地址数据的。因此凡是从用户空间传递过来的指针数据,务必使用内核提供的函数来访问它们。
举例
好了,是时候举个例子了。我们将扩展我们的helloworld驱动添加ioctl函数。
首先,我们添加一个头文件来定义ioctl接口需要用到的数据(hello.h):
#ifndef _HELLO_H |
然后为我们的驱动程序添加ioctl接口hello_ioctl,并实现这个函数:
static int hello_ioctl (struct inode *inode, struct file *filp, |
其他两个字段在这里不作深究。
值得一提的是LDD3里这么一段话:
The header also defines macros that may be used in your driver to decode the num-
bers: _IOC_DIR(nr), _IOC_TYPE(nr), _IOC_NR(nr), and _IOC_SIZE(nr). We won’t go
into any more detail about these macros because the header file is clear, and sample
code is shown later in this section.
根据ioctl.h文件的定义,很明显_IOC_DIR(nr), _IOC_TYPE(nr), _IOC_NR(nr),_IOC_SIZE(nr)这几个宏里面的参数就是一个IOCTL命令,即一个32位的二进制数,而文件中参数居然用nr表示! 当然用nr表示不是逻辑上的错误。但是别忘了文件中还有_IOW(type,nr,size)这样的定义IOCTL命令的宏,这其中的参数nr是IOCTL命令编号中的一个nr字段,一个8位的二进制数!我想很多新人都会对此产生莫大的疑惑!所以我认为把_IOC_DIR(nr), _IOC_TYPE(nr), _IOC_NR(nr),_IOC_SIZE(nr)这几个解码宏的参数改用cmd表示更恰当!
但是,我知道这不是LDD3作者的错,因为内核头文件里面也是这么表示的。我想内核开发者不可能没意识到这个问题。因此,我猜测这其中肯定有个历史原因: 大概以前版本的命令不管type是否一样,nr字段的值都是唯一的,于是仅靠nr字段就可以解码出一个IOCTL命令的其他字段吧?!但即使这样_IOC_DIR(nr), _IOC_TYPE(nr), _IOC_NR(nr),_IOC_SIZE(nr)也没必要保留这种写法啊!到底谁可以告诉我真相?
LDD3没有对_IOC_DIR(nr), _IOC_TYPE(nr), _IOC_NR(nr),_IOC_SIZE(nr)里面的nr作任何解释,只是实例中有如下用法:
if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;
if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY;
可见那个nr参数完全就是我所说的32位的IOCTL命令编码。靠,既然这样好歹也对着个confusion作一下简单的解释啊!如果LDD3一书那“既 不承认,也不否认”的暧昧态度让我真让我哭笑不得的话,那么国内某书(具体哪本我就不说了)简直令我抓狂,我摘书中的两段话如下:
_IO(type,nr):定义一个没有数据传输的命令编号。type为幻数,nr为命令编号。...
...
_IOC_DIR(nr): 获得命令编号的命令传输方向(direction)。这个宏的参数就是命令编号。
后记
到这里我们已经向您展示了Linux内核驱动程序的设备控制接口(ioctl接口),详细的介绍了它的使用,并给出了一个实际的例子,尽管它很简单,但已经足够了。到这里你可以写出一个标准的Linux驱动程序了。不过这里还有个问题,那就是我们不得不从/proc/devices文件里读取设备号然后手动创建设备节点。我们是否可以让系统自动的创建这个设备节点文件呢?当然可以。不过在那之前,我们必须深入了解Linux的设备驱动模型。后面的章节我们就详细的介绍Linux的设备驱动模型及Hotplug机制。
使用ioctl参数
在使用ioctl的可选arg参数时,如果传递的是一个整数,它可以直接使用。如果是一个指针,,就必须小心。当用一个指针引用用户空间,我们必须确保用户地址是有效的,其校验(不传送数据) 由函数access_ok实现,定义在 :
|
第一个参数应当是VERIFY_READ(读)或VERIFY_WRITE(读写);addr参数为用户空间地址,size 为字节数,可使用sizeof()。access_ok返回一个布尔值:1是成功 (存取没问题) 和0是失败 (存取有问题)。如果它返回假,驱动应当返回-EFAULT给调用者。
注意:首先,access_ok不做校验内存存取的完整工作;它只检查内存引用是否在这个进程有合理权限的内存范围中,且确保这个地址不指向内核空间内存。其次,大部分驱动代码不需要真正调用access_ok,而直接使用put_user(datum, ptr)和get_user(local, ptr),它们带有校验的功能,确保进程能够写入给定的内存地址,成功时返回0,并且在错误时返回-EFAULT。
|
这些宏它们相对copy_to_user和copy_from_user快,并且这些宏已被编写来允许传递任何类型的指针,只要它是一个用户空间地址,传送的数据大小依赖prt 参数的类型并且在编译时使用sizeof 和typeof 等编译器内建宏确定。他们只传送1、2、4或8 个字节。如果使用以上函数来传送一个大小不适合的值,结果常常是一个来自编译器的奇怪消息,如"coversion to non-scalar type requested"。在这些情况中,必须使用copy_to_user或者copy_from_user。
__put_user和__get_user进行更少的检查 (不调用access_ok),但是仍然能够失败如果被指向的内存对用户是不可写的,所以他们应只用在内存区已经用access_ok检查过的时候。作为通用的规则:当实现一个read方法时,调用__put_user来节省几个周期,或者当你拷贝几个项时,因此,在第一次数据传送之前调用access_ok一次。
1.介绍
Linux网络程序与内核交互的方法是通过ioctl来实现的,ioctl与网络协议栈进行交互,可得到网络接口的信息,网卡设备的映射属性和配置网络接口。并且还能够查看,修改,删除ARP高速缓存的信息,所以,我们有必要了解一下ioctl函数的具体实现。
2.相关结构体与相关函数
- #include <sys/ioctl.h>
- int ioctl(int d,int request,....);
参数:
d-文件描述符,这里是对网络套接字操作,显然是套接字描述符。
request-请求码
省略的部分对应不同的内存缓冲区,而具体的内存缓冲区是由请求码request来决定的,下面看一下具体都有哪些相关缓冲区。
(1)网络接口请求结构ifreq
- struct ifreq
- {
- #define IFHWADDRLEN 6 //6个字节的硬件地址,即MAC
- union{
- char ifrn_name[IFNAMESIZ];//网络接口名称
- }ifr_ifrn;
- union{
- struct sockaddr ifru_addr;//本地IP地址
- struct sockaddr ifru_dstaddr;//目标IP地址
- struct sockaddr ifru_broadaddr;//广播IP地址
- struct sockaddr ifru_netmask;//本地子网掩码地址
- struct sockaddr ifru_hwaddr;//本地MAC地址
- short ifru_flags;//网络接口标记
- int ifru_ivalue;//不同的请求含义不同
- struct ifmap ifru_map;//网卡地址映射
- int ifru_mtu;//最大传输单元
- char ifru_slave[IFNAMSIZ];//占位符
- char ifru_newname[IFNAMSIZE];//新名称
- void __user* ifru_data;//用户数据
- struct if_settings ifru_settings;//设备协议设置
- }ifr_ifru;
- }
- #define ifr_name ifr_ifrn.ifrn_name;//接口名称
- #define ifr_hwaddr ifr_ifru.ifru_hwaddr;//MAC
- #define ifr_addr ifr_ifru.ifru_addr;//本地IP
- #define ifr_dstaddr ifr_ifru.dstaddr;//目标IP
- #define ifr_broadaddr ifr_ifru.broadaddr;//广播IP
- #define ifr_netmask ifr_ifru.ifru_netmask;//子网掩码
- #define ifr_flags ifr_ifru.ifru_flags;//标志
- #define ifr_metric ifr_ifru.ifru_ivalue;//接口侧度
- #define ifr_mtu ifr_ifru.ifru_mtu;//最大传输单元
- #define ifr_map ifr_ifru.ifru_map;//设备地址映射
- #define ifr_slave ifr_ifru.ifru_slave;//副设备
- #define ifr_data ifr_ifru.ifru_data;//接口使用
- #define ifr_ifrindex ifr_ifru.ifru_ivalue;//网络接口序号
- #define ifr_bandwidth ifr_ifru.ifru_ivalue;//连接带宽
- #define ifr_qlen ifr_ifru.ifru_ivalue;//传输单元长度
- #define ifr_newname ifr_ifru.ifru_newname;//新名称
- #define ifr_seeting ifr_ifru.ifru_settings;//设备协议设置
如果想获得网络接口的相关信息,就传入ifreq结构体。
(2)网卡设备属性ifmap
- struct ifmap{//网卡设备的映射属性
- unsigned long mem_start;//开始地址
- unsigned long mem_end;//结束地址
- unsigned short base_addr;//基地址
- unsigned char irq;//中断号
- unsigned char dma;//DMA
- unsigned char port;//端口
- }
(3)网络配置接口ifconf
- struct ifconf{//网络配置结构体是一种缓冲区
- int ifc_len;//缓冲区ifr_buf的大小
- union{
- char__user *ifcu_buf;//绘冲区指针
- struct ifreq__user* ifcu_req;//指向ifreq指针
- }ifc_ifcu;
- };
- #define ifc_buf ifc_ifcu.ifcu_buf;//缓冲区地址
- #define ifc_req ifc_ifcu.ifcu_req;//ifc_req地址
(4)ARP高速缓存操作arpreq
- /* ARP高速缓存操作,包含IP地址和硬件地址的映射表,操作ARP高速缓存的命令字 SIOCDARP,SIOCGARP,SIOCSARP分别是删除ARP高速缓存的一条记录,获得ARP高速缓存的一条记录和修改ARP高速缓存的一条记录 */
- struct arpreq{
- struct sockaddr arp_pa;//协议地址
- struct sockaddr arp_ha;//硬件地址
- int arp_flags;//标记
- struct sockaddr arp_netmask;//协议地址的子网掩码
- char arp_dev[16];//查询网络接口的名称
- }
3. 请求码request
类别 | Request | 说明 | 数据类型 |
套 接 口 | SIOCATMARK SIOCSPGRP SIOCGPGRP | 是否位于带外标记 设置套接口的进程ID或进程组ID 获取套接口的进程ID或进程组ID | int int int |
文
件
| FIONBIN FIOASYNC FIONREAD FIOSETOWN FIOGETOWN
| 设置/清除非阻塞I/O标志 设置/清除信号驱动异步I/O标志 获取接收缓存区中的字节数 设置文件的进程ID或进程组ID 获取文件的进程ID或进程组ID | int int int int int |
接 口
| SIOCGIFCONF SIOCSIFADDR SIOCGIFADDR SIOCSIFFLAGS SIOCGIFFLAGS SIOCSIFDSTADDR SIOCGIFDSTADDR SIOCGIFBRDADDR SIOCSIFBRDADDR SIOCGIFNETMASK SIOCSIFNETMASK SIOCGIFMETRIC SIOCSIFMETRIC SIOCGIFMTU SIOCxxx | 获取所有接口的清单 设置接口地址 获取接口地址 设置接口标志 获取接口标志 设置点到点地址 获取点到点地址 获取广播地址 设置广播地址 获取子网掩码 设置子网掩码 获取接口的测度 设置接口的测度 获取接口MTU (还有很多取决于系统的实现) | struct ifconf struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq |
ARP | SIOCSARP SIOCGARP SIOCDARP | 创建/修改ARP表项 获取ARP表项 删除ARP表项 | struct arpreq struct arpreq struct arpreq |
路 由 | SIOCADDRT SIOCDELRT | 增加路径 删除路径 | struct rtentry struct rtentry |
流 | I_xxx |
|
|
4. 相关例子
(1)网络接口信息
选项获取填充struct ifreq的ifr_name:
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <linux/if.h>
- #include <arpa/inet.h>
- #include <linux/sockios.h>
- /**
- ioctl函数是与内核交互的一种方法,使用ioctl函数与内核协议栈进行交互
- ioctl函数可操作I/O请求,文件请求与网络接口请求
- 网络接口请求的几个结构体:
- struct ifreq{
- #define IFHWADDRLEN 6 //6个字节的硬件地址,即MAC
- union{
- char ifrn_name[IFNAMESIZ];//网络接口名称
- }ifr_ifrn;
- union{
- struct sockaddr ifru_addr;//本地IP地址
- struct sockaddr ifru_dstaddr;//目标IP地址
- struct sockaddr ifru_broadaddr;//广播IP地址
- struct sockaddr ifru_netmask;//本地子网掩码地址
- struct sockaddr ifru_hwaddr;//本地MAC地址
- short ifru_flags;//网络接口标记
- int ifru_ivalue;//不同的请求含义不同
- struct ifmap ifru_map;//网卡地址映射
- int ifru_mtu;//最大传输单元
- char ifru_slave[IFNAMSIZ];//占位符
- char ifru_newname[IFNAMSIZE];//新名称
- void __user* ifru_data;//用户数据
- struct if_settings ifru_settings;//设备协议设置
- }ifr_ifru;
- }
- #define ifr_name ifr_ifrn.ifrn_name;//接口名称
- #define ifr_hwaddr ifr_ifru.ifru_hwaddr;//MAC
- #define ifr_addr ifr_ifru.ifru_addr;//本地IP
- #define ifr_dstaddr ifr_ifru.dstaddr;//目标IP
- #define ifr_broadaddr ifr_ifru.broadaddr;//广播IP
- #define ifr_netmask ifr_ifru.ifru_netmask;//子网掩码
- #define ifr_flags ifr_ifru.ifru_flags;//标志
- #define ifr_metric ifr_ifru.ifru_ivalue;//接口侧度
- #define ifr_mtu ifr_ifru.ifru_mtu;//最大传输单元
- #define ifr_map ifr_ifru.ifru_map;//设备地址映射
- #define ifr_slave ifr_ifru.ifru_slave;//副设备
- #define ifr_data ifr_ifru.ifru_data;//接口使用
- #define ifr_ifrindex ifr_ifru.ifru_ivalue;//网络接口序号
- #define ifr_bandwidth ifr_ifru.ifru_ivalue;//连接带宽
- #define ifr_qlen ifr_ifru.ifru_ivalue;//传输单元长度
- #define ifr_newname ifr_ifru.ifru_newname;//新名称
- #define ifr_seeting ifr_ifru.ifru_settings;//设备协议设置
- struct ifmap{//网卡设备的映射属性
- unsigned long mem_start;//开始地址
- unsigned long mem_end;//结束地址
- unsigned short base_addr;//基地址
- unsigned char irq;//中断号
- unsigned char dma;//DMA
- unsigned char port;//端口
- }
- struct ifconf{//网络配置结构体是一种缓冲区
- int ifc_len;//缓冲区ifr_buf的大小
- union{
- char__user *ifcu_buf;//绘冲区指针
- struct ifreq__user* ifcu_req;//指向ifreq指针
- }ifc_ifcu;
- };
- #define ifc_buf ifc_ifcu.ifcu_buf;//缓冲区地址
- #define ifc_req ifc_ifcu.ifcu_req;//ifc_req地址
- (1)获得配置选项SIOCGIFCONF获得网络接口的配置情况 需要填充struct ifreq中ifr_name变量
- (2)其它选项获取填充struct ifreq的ifr_name
- **/
- int main(int argc,char*argv[]){
- int s;
- int err;
- s=socket(AF_INET,SOCK_DGRAM,0);
- if(s<0){
- perror("socket error");
- return;
- }
- //传入网络接口序号,获得网络接口的名称
- struct ifreq ifr;
- ifr.ifr_ifindex=2;//获得第2个网络接口的名称
- err=ioctl(s,SIOCGIFNAME,&ifr);
- if(err)
- perror("index error");
- else
- printf("the %dst interface is:%s\n",ifr.ifr_ifindex,ifr.ifr_name);
- //传入网络接口名称,获得标志
- memcpy(ifr.ifr_name,"eth0",5);
- err=ioctl(s,SIOCGIFFLAGS,&ifr);
- if(!err)
- printf("SIOCGIFFLAGS:%d\n",ifr.ifr_flags);
- //获得MTU和MAC
- err=ioctl(s,SIOCGIFMTU,&ifr);
- if(!err)
- printf("SIOCGIFMTU:%d\n",ifr.ifr_mtu);
- //获得MAC地址
- err=ioctl(s,SIOCGIFHWADDR,&ifr);
- if(!err){
- unsigned char* hw=ifr.ifr_hwaddr.sa_data;
- printf("SIOCGIFHWADDR:%02x:%02x:%02x:%02x:%02x:%02x\n",hw[0],hw[1],hw[2],hw[3],hw[4],hw[5]);
- }
- //获得网卡映射参数 命令字SIOCGIFMAP
- err=ioctl(s,SIOCGIFMAP,&ifr);
- if(!err)
- printf("SIOCGIFMAP,mem_start:%d,mem_end:%d,base_addr:%d,ifr_map:%d,dma:%d,port:%d\n",ifr.ifr_map.mem_start,ifr.ifr_map.mem_end,ifr.ifr_map.base_addr,ifr.ifr_map.irq,ifr.ifr_map.dma,ifr.ifr_map.port);
- //获得网卡序号
- err=ioctl(s,SIOCGIFINDEX,&ifr);
- if(!err)
- printf("SIOCGIFINDEX:%d\n",ifr.ifr_ifindex);
- //获取发送队列的长度
- err=ioctl(s,SIOCGIFTXQLEN,&ifr);
- if(!err)
- printf("SIOCGIFTXQLEN:%d\n",ifr.ifr_qlen);
- //获取网络接口IP
- struct sockaddr_in *sin=(struct sockaddr_in*)&ifr.ifr_addr;//保存的是二进制IP
- char ip[16];//字符数组,存放字符串
- memset(ip,0,16);
- err=ioctl(s,SIOCGIFADDR,&ifr);
- if(!err){
- inet_ntop(AF_INET,&sin->sin_addr.s_addr,ip,16);//转换的字符串保存到ip数组中,第二个参数是要转换的二进制IP指针,第三个参数是转换完成存放IP的缓冲区,最后一个参数是缓冲区的长度
- printf("SIOCGIFADDR:%s\n",ip);
- }
- //查询目标IP地址
- err=ioctl(s,SIOCGIFDSTADDR,&ifr);
- if(!err){
- inet_ntop(AF_INET,&sin->sin_addr.s_addr,ip,16);
- printf("SIOCGIFDSTADDR:%s\n",ip);
- }
- //查询子网掩码
- err=ioctl(s,SIOCGIFNETMASK,&ifr);
- if(!err){
- inet_ntop(AF_INET,&sin->sin_addr.s_addr,ip,16);
- printf("SIOCGIFNETMASK:%s\n",ip);
- }
- //设置IP地址,设置网络接口
- inet_pton(AF_INET,"222.27.253.108",&sin->sin_addr.s_addr);//将字符串IP转换成二进制
- err=ioctl(s,SIOCSIFADDR,&ifr);//发送设置本机ip地址请求命令
- if(!err){
- printf("check IP-----");
- memset(&ifr,0,sizeof(ifr));
- memcpy(ifr.ifr_name,"eth0",5);
- ioctl(s,SIOCGIFADDR,&ifr);
- inet_ntop(AF_INET,&sin->sin_addr.s_addr,ip,16);
- printf("%s\n",ip);
- }
- //得到接口的广播地址
- memset(&ifr,0,sizeof(ifr));
- memcpy(ifr.ifr_name,"eth0",5);
- ioctl(s,SIOCGIFBRDADDR,&ifr);
- struct sockaddr_in *broadcast=(struct sockaddr_in*)&ifr.ifr_broadaddr;
- //转换成字符串
- inet_ntop(AF_INET,&broadcast->sin_addr.s_addr,ip,16);//inet_ntop将二进制IP转换成点分十进制的字符串
- printf("BROADCAST IP:%s\n",ip);
- close(s);
- }
运行结果:
(2)查看arp高速缓存信息
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <net/if_arp.h>
- #include <string.h>
- #include <stdlib.h>
- #include <linux/sockios.h>
- /**
- ARP高速缓存操作,包含IP地址和硬件地址的映射表
- 操作ARP高速缓存的命令字 SIOCDARP,SIOCGARP,SIOCSARP分别是删除ARP高速缓存的一条记录,获得ARP高速缓存的一条记录和修改ARP高速缓存的一条记录
- struct arpreq{
- struct sockaddr arp_pa;//协议地址
- struct sockaddr arp_ha;//硬件地址
- int arp_flags;//标记
- struct sockaddr arp_netmask;//协议地址的子网掩码
- char arp_dev[16];//查询网络接口的名称
- }
- **/
- //根据IP地址查找硬件地址
- int main(int argc,char*argv[]){
- int s;
- int err;
- struct arpreq arpreq;
- struct sockaddr_in *addr=(struct sockaddr_in*)&arpreq.arp_pa;//IP地址
- s=socket(AF_INET,SOCK_DGRAM,0);
- if(s<0)
- perror("socket error");
- addr->sin_family=AF_INET;
- addr->sin_addr.s_addr=inet_addr(argv[1]);//转换成二进制IP
- if(addr->sin_addr.s_addr==INADDR_NONE)
- printf("IP地址格式错误\n");
- strcpy(arpreq.arp_dev,"eth0");
- err=ioctl(s,SIOCGARP,&arpreq);
- if(err==-1){
- perror("arp");
- return -1;
- }
- unsigned char* hw=(unsigned char*)&arpreq.arp_ha.sa_data;//硬件地址
- printf("%s\n",argv[1]);
- printf("%02x:%02x:%02x:%02x:%02x:%02x\n",hw[0],hw[1],hw[2],hw[3],hw[4],hw[5]);
- close(s);
- return 0;
- }
运行结果:
查看网关的MAC。在查看ARP高速缓存时要传入IP地址与接口信息。而获得接口信息要传入接口名ifr_name,如eth0。
总结:
本文主要介绍了获得网络接口请求信息,获得网卡设备映射属性、配置网络接口、获得ARP高速缓存等。其它ioctl函数还能对操作文件,操作I/O、操作路由等。最后对于网络接口的操作与ARP高速缓存的操作分别给出了实例。
参考:
http://b.baidu.com/view/1081282.htm
http://blog.csdn.net/gemmem/article/details/7268533
http://www.cnblogs.com/geneil/archive/2011/12/04/2275372.html
http://decimal.blog.51cto.com/1484476/447630
http://yehubilee.blog.51cto.com/1373999/963740
http://www.embeddedlinux.org.cn/ldd3note/_36.htm