使用ioctl与内核交换数据
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获取网卡信息
相关结构体声明:
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 <sys/ioctl.h>
int ioctl(int fd,int request,...);
ioctl用来控制特殊设备文件的属性,第一个参数fd必须是一个已经打开的文件描述符,第三个参数一般为char
*argp,它随第二个参数request的不同而不同。参数request决定了参数argp是向ioctl传递数据还是从ioctl获取数据。
如下是获取网络设备的信息的程序,在编写网络相关程序时可能会用到。
#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
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()
{
/*initialize...*/
init_net();
/*do something*/
return 0;
}
程序说明:程序先创建一个用于网络通信的套接字,然后利用ioctl对其操作,获取网络信息。程序中的函数net_ntoa用来将网络地址转换成字符串形式。
相关推荐
这些函数在用户态与内核态之间交换数据。 ```c struct file_operations exam_file_ops = { .owner = THIS_MODULE, .read = exam_read, .write = exam_write, .ioctl = exam_ioctl, .mmap = exam_mmap, ....
这样,用户可以通过打开、读取或写入这个新创建的/proc文件来与内核进行数据交换。 "如何添加内核proc文件的支持.doc"文档会详细介绍这一过程,包括定义`struct proc_entry`结构体,设置回调函数,以及使用`proc_...
arg参数可以指向用户空间的数据,通过`copy_from_user()`和`copy_to_user()`函数在用户空间和内核空间之间进行数据交换。 在用户空间,我们可以通过`ioctl()`函数发起ioctl调用,如下所示: ```c int result; ...
由于ioctl涉及到用户空间和内核空间的数据交换,因此必须注意数据的安全性和效率。通常,内核会将数据拷贝到内核缓冲区,然后由驱动程序处理。如果数据量大,这可能会成为性能瓶颈。可以通过使用`mmap`来共享内存,...
4. **读写数据**:通过`read`和`write`系统调用来与EEPROM进行数据交换。 综上所述,Linux中的I2C驱动架构采用分层设计思想,通过不同的层次将复杂的硬件访问抽象化,使得驱动开发变得更加简单。同时,利用`ioctl`...
通过这种方式,内核和用户空间可以有效地交换数据,无论是简单的文本信息还是复杂的二进制流,都能通过标准化的接口进行传输。 配合教程链接(https://blog.csdn.net/szm1234/article/details/113487063),你可以...
本文将全面解析`ioctl`函数的工作原理、如何在内核模块中实现以及其在用户空间与内核空间通信中的重要性。 #### 1. ioctl函数简介 `ioctl`函数是用于设备驱动程序的一种通用接口,它允许用户空间应用程序通过文件...
`ioctl` 是在 Linux 操作系统中用于设备驱动程序通信的一种机制,允许用户空间程序向内核中的设备驱动发送特定的命令并交换数据。本文将详细介绍 `ioctl` 的使用方式和相关宏,以及如何解析 `ioctl` 命令。 首先,`...
《KRound内核工具详解——驱动编程与通信基础》 在计算机系统中,内核作为操作系统的核心,负责管理和调度硬件资源,以及提供给应用程序的基础服务。KRound内核工具是针对这一核心领域的辅助工具,旨在简化驱动编程...
Netlink 是Linux内核提供的一种通信机制,用于在内核空间和用户空间之间进行高效、灵活的数据交换。这种通信方式比传统的系统调用、ioctl或/proc文件系统更具有优势,因为它提供了异步通信、多播支持、模块化实现...
2. **字符设备驱动**:涉及open、close、read、write等基本操作函数,以及ioctl和mmap功能,用于用户空间与内核空间的数据交换。 3. **块设备驱动**:主要用于磁盘和其他存储设备,涉及bio结构体、请求队列和调度...
在操作系统中,内核空间(Kernel Space)与用户空间(User Space)之间的通信非常重要,尤其是在需要进行实时数据交换或状态通知等场景下。本文将详细介绍一种基于事件(Event)机制的通信方式,以及如何通过IOCTL...
综上所述,"vmwgfx_ioctl.rar_V2"包含的更新可能涉及了在Linux内核2.13.6版本下,如何通过vmwgfx驱动优化和扩展虚拟机的3D图形性能,特别是对于SVGA3D设备的能力限制进行了调整,确保能处理更大规模的图形数据交换。...
- **共享内存(Shared Memory)**:直接在内存中交换数据,效率高。 - **信号量(Semaphores)**:用于同步多个进程的访问。 #### 5. 通过伙伴系统申请内核内存的函数有哪些? - **`__get_free_pages()`**:获取指定...
设备驱动程序是内核与硬件设备之间的桥梁,它们负责处理硬件的低级操作,如初始化、读写数据、中断处理等。Linux支持字符设备驱动和块设备驱动,前者用于无缓冲或固定大小的数据传输,后者用于硬盘等大容量存储设备...
使用libusb,首先需要加载USB设备描述符,然后通过`libusb_open()`打开设备,`libusb_claim_interface()`获取接口,再使用`libusb_bulk_transfer()`或`libusb_interrupt_transfer()`进行数据交换,最后用`libusb_...
3. **数据传输**:驱动程序需要实现读写函数,用于通过USB总线与串口设备交换数据。这通常涉及到中断(Interrupt)、批量(Bulk)或控制(Control)传输类型。 4. **中断处理**:在USB设备上,中断端点用于通知主机...