Linux下ioctl的应用

1、ioctl简介

ioctl(input/output control)是Linux中的一个系统调用,主要用于设备驱动程序与用户空间应用程序之间进行设备特定的输入/输出操作。它提供了一种通用的机制,允许用户空间的应用程序通过文件描述符与设备进行交互,以执行标准文件操作(如读取、写入、打开和关闭)之外的特殊操作。

2、示例程序编写

本次示例程序包括应用程序和驱动程序。

2.1、应用程序编写

应用程序中,将用户输入的数据通过ioctl()传入到驱动程序,并通过ioctl()读取出来。

/* ioc_test.c */

#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

// 定义设备类型和 ioctl 命令
#define IOC_MAGIC  'M'
#define MY_IOCTL_GET_PARAM _IOR(IOC_MAGIC, 0, int)
#define MY_IOCTL_SET_PARAM _IOW(IOC_MAGIC, 1, int)

int main() 
{
    int fd;
    int param;
	int getparam;
	
    /* 1、打开设备节点 */
    fd = open("/dev/ioc", O_RDWR);
    if (fd < 0) {
        perror("open");
        return -1;
    }

    /* 2、获取用户输入 */  
	fprintf(stdout, "entry the number : ");
	scanf("%d", &param);
	
    /* 3、写入参数 */
    if (ioctl(fd, MY_IOCTL_SET_PARAM, &param) < 0) 
	{
        perror("ioctl");
        close(fd);
        return -1;
    }

	/* 4、读出参数 */
	if (ioctl(fd, MY_IOCTL_GET_PARAM, &getparam) < 0) 
	{
        perror("ioctl");
        close(fd);
        return -1;
    }
	fprintf(stdout, "getparam = %d\n", getparam);
	
    // 关闭文件
    close(fd);
    return 0;
}

2.2、驱动程序编写

重点主要有如下两个部分:

1、file_operations中指定ioctl操作函数:

static struct file_operations ioc_drv = {
	.owner	 = THIS_MODULE,
	.open    = ioc_drv_open,
	.read    = ioc_drv_read,
	.write   = ioc_drv_write,
	.release = ioc_drv_close,
	.unlocked_ioctl = my_device_ioctl,
};

2、填充ioctl函数:

#define IOC_MAGIC  'M'
#define MY_IOCTL_GET_PARAM _IOR(IOC_MAGIC, 0, int)
#define MY_IOCTL_SET_PARAM _IOW(IOC_MAGIC, 1, int)

static long my_device_ioctl(struct file *file, unsigned int cmd, unsigned long arg) 
{
    static int param = 0;

    switch (cmd) 
	{
        /* 设置参数命令 */
        case MY_IOCTL_SET_PARAM:
			printk(KERN_INFO "MY_IOCTL_SET_PARAM\n");
            if (copy_from_user(&param, (int __user *)arg, sizeof(param))) 
			{
                return -EFAULT;
            }
            printk(KERN_INFO "my_device: set param to %d\n", param);
            break;

        /* 读取参数命令 */
		case MY_IOCTL_GET_PARAM:
			printk(KERN_INFO "MY_IOCTL_GET_PARAM\n");
            if (copy_to_user((int __user *)arg, &param, sizeof(param))) 
			{
                return -EFAULT;
            }
			printk(KERN_INFO "my_device: get param %d\n", param);
            break;
			
        default:
            return -EINVAL; 
    }
	
    return 0;
}

3、完整的驱动程序如下:

/* ioc_drv.c */

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>

static int major = 0;
static struct class *ioc_class;

#define IOC_MAGIC  'M'
#define MY_IOCTL_GET_PARAM _IOR(IOC_MAGIC, 0, int)
#define MY_IOCTL_SET_PARAM _IOW(IOC_MAGIC, 1, int)

static long my_device_ioctl(struct file *file, unsigned int cmd, unsigned long arg) 
{
    static int param = 0;

    switch (cmd) 
	{
        case MY_IOCTL_SET_PARAM:
			printk(KERN_INFO "MY_IOCTL_SET_PARAM\n");
            if (copy_from_user(&param, (int __user *)arg, sizeof(param))) 
			{
                return -EFAULT;
            }
            printk(KERN_INFO "my_device: set param to %d\n", param);
            break;

		case MY_IOCTL_GET_PARAM:
			printk(KERN_INFO "MY_IOCTL_GET_PARAM\n");
            if (copy_to_user((int __user *)arg, &param, sizeof(param))) 
			{
                return -EFAULT;
            }
			printk(KERN_INFO "my_device: get param %d\n", param);
            break;
			
        default:
            return -EINVAL; 
    }
	
    return 0;
}

static ssize_t ioc_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

static ssize_t ioc_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

static int ioc_drv_open (struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

static int ioc_drv_close (struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}


static struct file_operations ioc_drv = {
	.owner	 = THIS_MODULE,
	.open    = ioc_drv_open,
	.read    = ioc_drv_read,
	.write   = ioc_drv_write,
	.release = ioc_drv_close,
	.unlocked_ioctl = my_device_ioctl,
};

static int __init ioc_init(void)
{
	int err;

	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	major = register_chrdev(0, "ioc", &ioc_drv);

	ioc_class = class_create(THIS_MODULE, "ioc_class");
	err = PTR_ERR(ioc_class);
	if (IS_ERR(ioc_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "ioc");
		return -1;
	}

	device_create(ioc_class, NULL, MKDEV(major, 0), NULL, "ioc"); /* /dev/ioc */

	return 0;
}

static void __exit ioc_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	device_destroy(ioc_class, MKDEV(major, 0));
	class_destroy(ioc_class);
	unregister_chrdev(major, "ioc");
}

module_init(ioc_init);
module_exit(ioc_exit);

MODULE_LICENSE("GPL");

3、ioctl命令的构成

一个完整的ioctl命令码通常包含以下几个字段:

  1. 设备类型(通常是8位):用于标识设备或设备驱动程序的类型。这使得系统能够区分不同的设备或设备组。
  2. 序列号(也是8位):在同一设备类型下,用于区分不同的ioctl命令。每个命令都有一个唯一的序列号,以确保命令的唯一性。
  3. 方向位(2位):指示数据传输的方向。这可以是读操作、写操作或读写操作。方向位帮助设备驱动程序了解用户空间程序期望执行的操作类型。
  4. 数据大小(8~14位):指定用户空间与内核空间之间传输的数据的大小。这确保了数据在传输过程中的完整性和一致性。

在Linux内核中,为了简化ioctl命令的创建过程,提供了一些宏定义。可以看到在上面的应用程序和驱动程序中,都使用了宏来创建ioctl命令:

#define IOC_MAGIC  'M'
#define MY_IOCTL_GET_PARAM _IOR(IOC_MAGIC, 0, int)
#define MY_IOCTL_SET_PARAM _IOW(IOC_MAGIC, 1, int)

可用的宏有如下:

// _IO宏用于创建不带数据传输的ioctl命令
// type是设备类型,通常是一个字符常量,用于区分不同的设备或设备驱动程序
// nr是命令序号,用于在同一设备类型下区分不同的命令
#define _IO(type,nr)        _IOC(_IOC_NONE,(type),(nr),0)

// _IOR宏用于创建从设备读取数据的ioctl命令
// type和nr的含义与_IO宏相同
// size是数据类型大小
#define _IOR(type,nr,size)  _IOC(_IOC_READ,(type),(nr),sizeof(size))

// _IOW宏用于创建向设备写入数据的ioctl命令
// 其他参数的含义与_IOR宏相同
#define _IOW(type,nr,size)  _IOC(_IOC_WRITE,(type),(nr),sizeof(size))

// _IOWR宏用于创建既读取又写入数据的ioctl命令
// 其他参数的含义与_IOR和_IOW宏相同
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))

4、测试

1、编译驱动程序,加载驱动程序。

2、编译应用程序,运行应用程序。

### Linux 中 `ioctl` 函数的用法及其示例 #### 定义与基本概念 `ioctl` 是一种用于设备控制的操作接口,在 Linux 和 Unix 系统中广泛应用于硬件设备驱动程序。它允许应用程序向内核发送特定于设备的命令,从而实现对底层硬件资源的访问和配置[^3]。 其函数原型如下: ```c int ioctl(int fd, unsigned long request, ...); ``` - **fd**: 文件描述符,通常通过 `open()` 打开设备文件获得。 - **request**: 请求码,定义了具体要执行的操作。 - **...**: 可选参数,通常是传递给请求的数据指针。 #### 使用场景 `ioctl` 常见的应用场景包括但不限于以下几种: - 配置网络接口 (如设置 IP 地址、子网掩码等)[^4]。 - 控制字符设备或块设备的行为。 - 调整终端属性 (如更改波特率、流控模式等)[^5]。 #### 示例代码 下面是一个简单的例子,展示如何使用 `ioctl` 来获取串口的信息: ```c #include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <termios.h> #define SERIAL_PORT "/dev/ttyS0" int main() { int fd; struct termios tty; // 打开串口设备 fd = open(SERIAL_PORT, O_RDWR | O_NOCTTY | O_NDELAY); if (fd == -1) { perror("无法打开串口"); return -1; } // 获取当前串口设置 if (tcgetattr(fd, &tty) != 0) { perror("错误:无法获取串口属性"); close(fd); return -1; } printf("成功读取串口 %s 的初始状态\n", SERIAL_PORT); // 设置新的波特率为9600bps cfsetispeed(&tty, B9600); cfsetospeed(&tty, B9600); // 应用新设置到串口 if (tcsetattr(fd, TCSANOW, &tty) != 0) { perror("错误:无法应用串口属性"); close(fd); return -1; } printf("已将串口波特率更改为9600bps。\n"); close(fd); return 0; } ``` 上述代码展示了如何利用 `ioctl` 或者相关 API (`cfsetispeed`, `cfsetospeed`) 对串口进行初始化并修改波特率[^6]。 #### 错误处理 当调用失败时,`ioctl` 返回 `-1` 并设置全局变量 `errno` 表明具体的错误原因。常见的错误有: - `EINVAL`: 参数非法或者不支持该操作。 - `EBADF`: 提供了一个无效的文件描述符。 - `EFAULT`: 用户空间地址不可达。 #### 总结 尽管 `ioctl` 功能强大且灵活,但由于它的行为高度依赖于所使用的设备驱动程序,因此在跨平台开发过程中可能会遇到兼容性问题。建议尽可能采用标准化接口来替代自定义 `ioctl` 操作[^7]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值