1. 前言

使用ioctl系统调用是用户空间向内核交换数据的常用方法之一,从ioctl这个名称上看,本意是针对I/O设备进行的控制操作,但实际并不限制是真正的I/O设备,可以是任何一个内核设备即可。

2. 基本过程

在内核空间中ioctl是很多内核操作结构的一个成员函数,如文件操作结构struct

file_operations(include/linux/fs.h)、协议操作结构struct

proto_ops(include/linux/net.h)等、tty操作结构struct

tty_driver(include/linux/tty_driver.h)等,而这些操作结构分别对应各种内核设备,只要在用户空间打开这些设备,如I/O设备可用open(2)打开,网络协议可用socket(2)打开等,获取一个文件描述符后,就可以在这个描述符上调用ioctl(2)来向内核交换数据。

3. ioctl(2)

ioctl(2)函数的基本使用格式为:

int ioctl(int fd, int cmd, void *data)

第一个参数是文件描述符;cmd是操作命令,一般分为GET、SET以及其他类型命令,GET是用户空间进程从内核读数据,SET是用户空间进程向内核写数据,cmd虽然是一个整数,但是有一定的参数格式的,下面再详细说明;第三个参数是数据起始位置指针,

cmd命令参数是个32位整数,分为四部分:

dir(2b) size(14b) type(8b)

nr(8b)

详细定义cmd要包括这4个部分时可使用宏_IOC(dir,type,nr,size)来定义,而最简单情况下使用_IO(type,

nr)来定义就可以了,这些宏都在include/asm/ioctl.h中定义

本文cmd定义为:

#define NEWCHAR_IOC_MAGIC 'M'

#define

NEWCHAR_SET _IO(NEWCHAR_IOC_MAGIC,

0)

#define NEWCHAR_GET _IO(NEWCHAR_IOC_MAGIC,

1)

#define

NEWCHAR_IOC_MAXNR 1

要定义自己的ioctl操作,可以有两个方式,一种是在现有的内核代码中直接添加相关代码进行支持,比如想通过socket描述符进行

ioctl操作,可在net/ipv4/af_inet.c中的inet_ioctl()函数中添加自己定义的命令和相关的处理函数,重新编译内核即可,不过这种方法一般不推荐;第二种方法是定义自己的内核设备,通过设备的ioctl()来操作,可以编成模块,这样不影响原有的内核,这是最通常的做法。

4. 内核设备

为进行ioctl操作最通常是使用字符设备来进行,当然定义其他类型的设备也可以。在用户空间,可使用mknod命令建立一个字符类型设备文件,假设该设备的主设备号为123,次设备号为0:

mknode /dev/newchar c 123 0

如果是编程的话,可以用mknode(2)函数来建立设备文件。

建立设备文件后再将该设备的内核模块文件插入内核,就可以使用open(2)打开/dev/newchar文件,然后调用ioctl(2)来传递数据,最后用close(2)关闭设备。而如果内核中还没有插入该设备的模块,open(2)时就会失败。

由于内核内存空间和用户内存空间不同,要将内核数据拷贝到用户空间,要使用专用拷贝函数copy_to_user();要将用户空间数据拷贝到内核,要使用copy_from_user()。

要最简单实现以上功能,内核模块只需要实现设备的open, ioctl和release三个函数即可,

下面介绍程序片断:

static int newchar_ioctl(struct inode *inode, struct file

*filep,

unsigned

int cmd, unsigned long arg);

static int newchar_open(struct inode *inode, struct file

*filep);

static int newchar_release(struct inode *inode, struct file

*filep);

// 定义文件操作结构,结构中其他元素为空

struct file_operations newchar_fops =

{

owner: THIS_MODULE,

ioctl: newchar_ioctl,

open: newchar_open,

release: newchar_release,

};

// 定义要传输的数据块结构

struct newchar{

int a;

int b;

};

#define MAJOR_DEV_NUM 123

#define DEVICE_NAME "newchar"

打开设备,非常简单,就是增加模块计数器,防止在打开设备的情况下删除模块,

当然想搞得复杂的话可进行各种限制检查,如只允许指定的用户打开等:

static int newchar_open(struct inode *inode, struct file

*filep)

{

MOD_INC_USE_COUNT;

return 0;

}

关闭设备,也很简单,减模块计数器:

static int newchar_release(struct inode *inode, struct file

*filep)

{

MOD_DEC_USE_COUNT;

return 0;

}

进行ioctl调用的基本处理函数

static int newchar_ioctl(struct inode *inode, struct file

*filep,

unsigned

int cmd, unsigned long arg)

{

int ret;

// 首先检查cmd是否合法

if (_IOC_TYPE(cmd) != NEWCHAR_IOC_MAGIC) return -EINVAL;

if (_IOC_NR(cmd) >

NEWCHAR_IOC_MAXNR) return -EINVAL;

// 错误情况下的缺省返回值

ret = EINVAL;

switch(cmd)

{

case KNEWCHAR_SET:

// 设置操作,将数据从用户空间拷贝到内核空间

{

struct newchar nc;

if(copy_from_user(&nc,

(const char*)arg, sizeof(nc)) != 0)

return

-EFAULT;

ret =

do_set_newchar(&nc);

}

break;

case KNEWCHAR_GET:

// GET操作通常会在数据缓冲区中先传递部分初始值作为数据查找条件,获取全部

// 数据后重新写回缓冲区

// 当然也可以根据具体情况什么也不传入直接向内核获取数据

{

struct newchar nc;

if(copy_from_user(&nc,

(const char*)arg, sizeof(nc)) != 0)

return

-EFAULT;

ret =

do_get_newchar(&nc);

if(ret ==

0){

if(copy_to_user((unsigned

char *)arg, &nc,

sizeof(nc))!=0)

return

-EFAULT;

}

}

break;

}

return ret;

}

模块初始化函数,登记字符设备

static int __init _init(void)

{

int result;

// 登记该字符设备,这是2.4以前的基本方法,到2.6后有了些变化,

// 是使用MKDEV和cdev_init()来进行,本文还是按老方法

result = register_chrdev(MAJOR_DEV_NUM, DEVICE_NAME,

&newchar_fops);

if (result < 0) {

printk(KERN_WARNING __FUNCTION__ ": failed register character device for /dev/newchar\n");

return result;

}

return 0;

}

模块退出函数,登出字符设备

static void __exit _cleanup(void)

{

int result;

result = unregister_chrdev(MAJOR_DEV_NUM, DEVICE_NAME);

if (result < 0)

printk(__FUNCTION__ ": failed

unregister character device for

/dev/newchar\n");

return;

}

module_init(_init);

module_exit(_cleanup);

5. 结论

用ioctl()在用户空间和内核空间传递数据是最常用方法之一,比较简单方便,而且可以在同一个ioctl中对不同的命令传送不同的数据结构,本文只是为描述方便而在不同命令中使用了相同的数据结构。

附:利用ioctl获取网卡信息

相关结构体声明:

C代码  

Struct ifconf{

intifc_len;// 缓冲区的大小

union{

caddr_t ifcu_buf;// input from user->kernel

structifreq *ifcu_req;// return of structures returned

}ifc_ifcu;

};

#define ifc_buf ifc_ifcu.ifcu_buf //buffer address

#define ifc_req ifc_ifcu.ifcu_req //array of structures returned

#define IFNAMSIZ 16

structifreq{

charifr_name[IFNAMSIZ];// interface name, e.g., “le0”

union{

structsockaddr ifru_addr;

structsockaddr ifru_dstaddr;

structsockaddr ifru_broadaddr;

shortifru_flags;

intifru_metric;

caddr_t ifru_data;

}ifr_ifru;

};

#define ifr_addr ifr_ifru.ifru_addr // address

#define ifr_dstaddr ifr_ifru.ifru_dstaddr // otner end of p-to-p link

#define ifr_broadaddr ifr_ifru.ifru_broadaddr // broadcast address

#define ifr_flags ifr_ifru.ifru_flags // flags

#define ifr_metric ifr_ifru.ifru_metric // metric

#define ifr_data ifr_ifru.ifru_data // for use by interface

Struct ifconf{

int ifc_len; // 缓冲区的大小

union{

caddr_t ifcu_buf; // input from user->kernel

struct ifreq *ifcu_req; // return of structures returned

}ifc_ifcu;

};

#define ifc_buf ifc_ifcu.ifcu_buf //buffer address

#define ifc_req ifc_ifcu.ifcu_req //array of structures returned

#define IFNAMSIZ 16

struct ifreq{

char ifr_name[IFNAMSIZ]; // interface name, e.g., “le0”

union{

struct sockaddr ifru_addr;

struct sockaddr ifru_dstaddr;

struct sockaddr ifru_broadaddr;

short ifru_flags;

int ifru_metric;

caddr_t ifru_data;

}ifr_ifru;

};

#define ifr_addr ifr_ifru.ifru_addr // address

#define ifr_dstaddr ifr_ifru.ifru_dstaddr // otner end of p-to-p link

#define ifr_broadaddr ifr_ifru.ifru_broadaddr // broadcast address

#define ifr_flags ifr_ifru.ifru_flags // flags

#define ifr_metric ifr_ifru.ifru_metric // metric

#define ifr_data ifr_ifru.ifru_data // for use by interface

ioctl系统调用通常用来控制设备,不能用上面介绍的其他函数进行的控制操作都可以用ioctl来进行,在Shell下输入“man

ioctl”可获取其函数原型如下:

#include

int ioctl(int fd,int request,...);

ioctl用来控制特殊设备文件的属性,第一个参数fd必须是一个已经打开的文件描述符,第三个参数一般为char

*argp,它随第二个参数request的不同而不同。参数request决定了参数argp是向ioctl传递数据还是从ioctl获取数据。

如下是获取网络设备的信息的程序,在编写网络相关程序时可能会用到。

C代码  

#include

#include

#include

#include

#include

#include

#include

#include

#include

unsignedcharg_eth_name[16];

unsignedcharg_macaddr[6];

unsignedcharg_subnetmask;

unsignedcharg_ipaddr;

unsignedcharg_broadcast_ipaddr;

voidinit_net(void)

{

inti;

intsock;

structsockaddr_in sin;

structifreq ifr;

sock = socket(AF_INET,SOCK_DGRAM,0);

if(sock == -1)

{

perror("socket");

}

strcpy(g_eth_name,"eth0");

strcpy(ifr.ifr_name,g_eth_name);

printf("eth name:\t%s\n",g_eth_name);

if(ioctl(sock,SIOCGIFHWADDR,&ifr)

{

perror("ioctl");

}

memcpy(g_macaddr,ifr.ifr_hwaddr.sa_data,6);

printf("local mac:\t");

for(i=0;i<5;i++)

{

printf("%.2x:",g_macaddr[i]);

}

printf("%.2x:\n",g_macaddr[i]);

//获取并打印IP地址

if(ioctl(sock,SIOCGIFADDR,&ifr)

{

perror("ioctl");

}

memcpy(&sin,&ifr.ifr_addr,sizeof(sin));

g_ipaddr = sin.sin_addr.s_addr;

printf("local eth0:\t%s\n",inet_ntoa(sin.sin_addr));

//获取并打印广播地址

if(ioctl(sock,SIOCGIFBRDADDR,&ifr)

{

perror("ioctl");

}

memcpy(&sin,&ifr.ifr_addr,sizeof(sin));

g_broadcast_ipaddr = sin.sin_addr.s_addr;

printf("broadcast:\t%s\n",inet_ntoa(sin.sin_addr));

//获取并打印子网掩码

if(ioctl(sock,SIOCGIFNETMASK,&ifr)

{

perror("ioctl");

}

memcpy(&sin,&ifr.ifr_addr,sizeof(sin));

g_subnetmask = sin.sin_addr.s_addr;

printf("subnetmask:\t%s\n",inet_ntoa(sin.sin_addr));

close(sock);

}

intmain()

{

init_net();

return0;

}

#include

#include

#include

#include

#include

#include

#include

#include

#include

unsigned char g_eth_name[16];

unsigned char g_macaddr[6];

unsigned char g_subnetmask;

unsigned char g_ipaddr;

unsigned char g_broadcast_ipaddr;

void init_net(void)

{

int i;

int sock;

struct sockaddr_in sin;

struct ifreq ifr;

sock = socket(AF_INET,SOCK_DGRAM,0);

if (sock == -1)

{

perror("socket");

}

strcpy(g_eth_name,"eth0");

strcpy(ifr.ifr_name,g_eth_name);

printf("eth name:\t%s\n",g_eth_name);

if (ioctl(sock,SIOCGIFHWADDR,&ifr) < 0)

{

perror("ioctl");

}

memcpy(g_macaddr,ifr.ifr_hwaddr.sa_data,6);

printf("local mac:\t");

for (i=0;i<5;i++)

{

printf("%.2x:",g_macaddr[i]);

}

printf("%.2x:\n",g_macaddr[i]);

//获取并打印IP地址

if (ioctl(sock,SIOCGIFADDR,&ifr) < 0)

{

perror("ioctl");

}

memcpy(&sin,&ifr.ifr_addr,sizeof(sin));

g_ipaddr = sin.sin_addr.s_addr;

printf("local eth0:\t%s\n",inet_ntoa(sin.sin_addr));

//获取并打印广播地址

if (ioctl(sock,SIOCGIFBRDADDR,&ifr) < 0)

{

perror("ioctl");

}

memcpy(&sin,&ifr.ifr_addr,sizeof(sin));

g_broadcast_ipaddr = sin.sin_addr.s_addr;

printf("broadcast:\t%s\n",inet_ntoa(sin.sin_addr));

//获取并打印子网掩码

if (ioctl(sock,SIOCGIFNETMASK,&ifr) < 0)

{

perror("ioctl");

}

memcpy(&sin,&ifr.ifr_addr,sizeof(sin));

g_subnetmask = sin.sin_addr.s_addr;

printf("subnetmask:\t%s\n",inet_ntoa(sin.sin_addr));

close(sock);

}

int main()

{

init_net();

return 0;

}

程序说明:程序先创建一个用于网络通信的套接字,然后利用ioctl对其操作,获取网络信息。程序中的函数net_ntoa用来将网络地址转换成字符串形式。

linux 内核ioctl,Linux ioctl与内核交换数据相关推荐

  1. 十天学Linux内核之第九天---向内核添加代码

    原文:十天学Linux内核之第九天---向内核添加代码 睡了个好觉,很晚才起,好久没有这么舒服过了,今天的任务不重,所以压力不大,呵呵,现在的天气真的好冷,不过实验室有空调,我还是喜欢待在这里,有一种 ...

  2. Linux中Netlink实现热插拔监控——内核与用户空间通信

    1.什么是NetLink? 它 是一种特殊的 socket,它是 Linux 所特有的,由于传送的消息是暂存在socket接收缓存中,并不被接收者立即处理,所以netlink是一种异步通信机制. 系统 ...

  3. Android内核和Linux内核的区别

    1.Android系统层面的底层是Linux,并且在中间加上了一个叫做Dalvik的Java虚拟机,从表面层看是Android运行库.每个Android应用都运行在自己的进程上,享有Dalvik虚拟机 ...

  4. linux ioctl及ioctl command

    在linux驱动中经常用到的函数为open.read.write用于对设备进行读取和写入数据,但是除了上述功能之外还有另外一个重要的功能ioctl,很多时候用户程序需要根据自己的需求配置所操纵的硬件, ...

  5. linux open dev/tty0 receive_buf,书写基于内核的linux键盘纪录器(p9-0e)(3)

    书写基于内核的linux键盘纪录器(p9-0e)(3) 2008-04-09 04:00:06来源:互联网 阅读 () 底层tty驱动调用receive_buf()这个函数用来发送硬件设备接收处理的字 ...

  6. linux ioctl网络参数设置,Linux 网络编程之ioctl函数

    1.介绍 Linux网络程序与内核交互的方法是通过ioctl来实现的,ioctl与网络协议栈进行交互,可得到网络接口的信息,网卡设备的映射属性和配置网络接口.并且还能够查看,修改,删除ARP高速缓存的 ...

  7. linux 驱动 内核模式,Linux内核模块和驱动的编写

    Linux内核是一个整体是结构,因此向内核添加任何东西,或者删除某些功能,都十分困难.为了解决这个问题引入了内核机制.从而可以动态的想内核中添加或者删除模块. 模块不被编译在内核中,因而控制了内核的大 ...

  8. linux内核与Linux发行版本区别

    linux内核:Linux内核指的是一个由Linus Torvalds负责维护,提供硬件抽象层.硬盘及文件系统控制及多任务功能的系统核心程序. linux发行版本:Linux发行版就是由Linux内核 ...

  9. 深度:一文看懂Linux内核,Linux内核架构和工作原理详解

    简介 作用是将应用层序的请求传递给硬件,并充当底层驱动程序,对系统中的各种设备和组件进行寻址.目前支持模块的动态装卸(裁剪).Linux内核就是基于这个策略实现的.Linux进程1.采用层次结构,每个 ...

最新文章

  1. 传潘石屹投资爱蜂潮 天猫不予评论
  2. db 文件 加密_有人说Kettle 数据库JNDI方式数据库密码不能加密,搞他!
  3. Altium Designer chapter6总结
  4. 网络营销外包专员浅析响应式网站建设应注意哪些网络营销外包细节
  5. android 中使用ExpandableListView控件结合服务器json文件的下载
  6. 不同的二叉搜索树—leetcode96
  7. 知名大学硕士生被通报,这件事千万别做!
  8. pic pwm 占空比可调 源码_PIC16F914输出可调占空比PWM波形程序
  9. 【计算机组成原理】I/O设备
  10. (转)Spring Boot(九):定时任务
  11. 关于ipxe启动的几个疑问
  12. Linux学习笔记-项目部署01
  13. 502php,php502是什么问题
  14. 深入探究Retinex
  15. Autosar之EB的安装与激活
  16. 知道一点怎么设直线方程_已知两点坐标怎样求直线方程
  17. 微信打开链接提示用浏览器打开
  18. 0.1 Typora 文档备份
  19. 概率论 —— 随机事件与概率
  20. Cadnece安装过程提示已经存及删除全部注册表方法

热门文章

  1. php contains,contains的用法及find()与filter()的区别
  2. 软件研发中敏捷开发和迭代开发的异同
  3. android仿微博头像_Android仿新浪微博个人信息界面及其他效果
  4. 斐波那契数列的递归解法
  5. Docker容器——查找自己想要的镜像和使用
  6. 咸鱼Maya笔记—晶格变形器
  7. SweetAlert入门教程
  8. micropython添加自定义模块_关于 k210 的 micropython 添加 ussl 模块,实现 https 访问支持的那些事。...
  9. [转载]傅里叶分析之掐死教程(完整版)
  10. 子比主题美化插件-PPHU美化插件