linux驱动开发(一):ioctl()函数

一、应用程序中的ioctl接口
首先,我们需要规定一些命令码,这些命令码在应用程序和驱动程序中需要保持一致。应用程序只需向驱动程序下发一条指令码,用来通知它执行哪条命令。如何解读这条指令和怎么实现相关操作,就是驱动程序自己要做的事。

应用程序的接口函数为ioctl,参考官方文档,函数原型为

#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);

下面我们解释各个参数的含义。
1)fd是文件描述符。当我们的设备作为特殊文件被open()函数打开后,会返回一个文件描述符,通过操作这个文件描述符达到操作设备文件的目的。

2)request是命令码,应用程序通过下发命令码来控制驱动程序完成对应操作。

3)第三个参数“…”是可变参数arg,一些情况下应用程序需要向驱动程序传参,参数就通过ag来传递。ioctl函数中的“…”只能传递一个参数,但内核不会检查这个参数的类型。那么,就有两种传参方式:只传一个整数,传递一个指针。

如果ioctl执行成功,它的返回值就是驱动程序中ioctl接口给的返回值,驱动程序可以通过返回值向用户程序传参。但驱动程序最好返回一个非负数,因为用户程序中的ioctl运行失败时一定会返回-1并设置全局变量errorno。

errono不同的值代表的含义如下:

EBADF:fd是一个无效的文件描述符。
EFAULT:在arg是指针的前提下,argp指向一个不可访问的内存空间。
EINVAL:request或argp是无效的。
ENOTTY:fd没有关联到一个字符特殊设备,或该request不适用于文件描述符fd引用的对象类型。(说人话就是fd没有指向一个字符设备,或fd指向的文件不支持ioctl操作)

二、驱动程序中的ioctl接口

在驱动程序的ioctl函数体中,实现了一个switch-case结构,每一个case对应一个命令码,case内部是驱动程序实现该命令的相关操作。

ioctl的实现函数要传递给file_operations结构体中对应的函数指针,函数原型为

#include <linux/ioctl.h>
long (*unlocked_ioctl) (struct file * fp, unsigned int request, unsigned long args);
long (*compat_ioctl) (struct file * fp, unsigned int request, unsigned long args);

unlocked_ioctl在无大内核锁(BKL)的情况下调用。64位用户程序运行在64位的kernel,或32位的用户程序运行在32位的kernel上,都是调用unlocked_ioctl函数。

compat_ioctl是64位系统提供32位ioctl的兼容方法,也在无大内核锁的情况下调用。即如果是32位的用户程序调用64位的kernel,则会调用compat_ioctl。如果驱动程序没有实现compat_ioctl,则用户程序在执行ioctl时会返回错误Not a typewriter。

另外,如果32位用户态和64位内核态发生交互时,第三个参数的长度需要保持一致,否则交互协议会出错。

int (*ioctl) (struct inode *inode, struct file *fp, unsigned int request, unsigned long args);

在2.6.35.7及以前的内核版本中,file_operations还定义了ioctl()接口,与unlocked_ioctl是等价的。但是在2.6.36以后就不再支持这个接口,全部使用unlocked_ioctl了。

以上函数参数的含义如下。
1)inode和fp用来确定被操作的设备。
2)request就是用户程序下发的命令。
3)args就是用户程序在必要时传递的参数。

返回值:可以在函数体中随意定义返回值,这个返回值也会被直接返回到用户程序中。通常使用非负数表示正确的返回,而返回一个负数系统会判定为ioctl调用失败。

他们之间的关系如下图所示,fd被转换为两个结构体,用来标识操作的设备文件,cmd被原封不动的传入了驱动中

三、用户与驱动之间的ioctl协议构成

也就是request或cmd,本质上就是一个32位数字,理论上可以是任何一个数,但为了保证命令码的唯一性,linux定义了一套严格的规定,通过计算得到这个命令吗数字。linux将32位划分为四段,如下图。

含义如下。

1)dir,即direction,表示ioctl命令的访问模式,分为无数据(_IO)、读数据(_IOR)、写数据(_IOW)、读写数据(_IOWR)四种模式。

2)type,即device type,表示设备类型,也可翻译成“幻数”或“魔数”,可以是任意一个char型字符,如’a’、‘b’、‘c’等,其主要作用是使ioctl命令具有唯一的设备标识。不过在内核中’w’、‘y’、'z’三个字符已经被使用了。

3)nr,即number,命令编号/序数,取值范围0~255,在定义了多个ioctl命令的时候,通常从0开始顺次往下编号。

4)size,涉及到ioctl的参数arg,占据13bit或14bit,这个与体系有关,arm使用14bit。用来传递arg的数据类型的长度,比如如果arg是int型,我们就将这个参数填入int,系统会检查数据类型和长度的正确性。

在上面的四个参数都需要用户自己定义,linux系统提供了宏可以使程序员方便的定义ioctl命令码。

include/uapi/asm-generic/ioctl.h
--------------------------------------------
/* used to create numbers */
#define _IO(type,nr)        _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size)  _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size)  _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))

分别对应了四个dir:
_IO(type, nr):用来定义不带参数的ioctl命令。
_IOR(type,nr,size):用来定义用户程序向驱动程序写参数的ioctl命令。
_IOW(type,nr,size):用来定义用户程序从驱动程序读参数的ioctl命令。
_IOWR(type,nr,size):用来定义带读写参数的驱动命令。

当然了,系统也定义反向解析ioctl命令的宏。

include/uapi/asm-generic/ioctl.h
--------------------------------------------
/* used to decode ioctl numbers */
#define _IOC_DIR(nr)        (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
#define _IOC_TYPE(nr)       (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
#define _IOC_NR(nr)     (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
#define _IOC_SIZE(nr)       (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK

四、ioctl使用的简单实例——整数传参
本例中,我们让ioctl传递三个命令,分别是一个无参数、写参数、读参数三个指令。首先我们需要确定两个头文件,命名为ioctl_test.h和user_ioctl.h,用来分别定义内核空间和用户空间下的命令码协议。两个头文件中除了引用不同的头文件外,其他内容需要完全一致,以保证协议的一致性。

我们使用字符’a’作为幻数,三个命令的作用分别是用户程序让驱动程序打印一句话,用户程序从驱动程序读一个int型数,用户程序向驱动程序写一个int型数。

驱动程序如下:

#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <asm/mach/map.h>
#include <asm/io.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/device.h>
#include <linux/uaccess.h>

#include <linux/fs.h>
#define MYCTRL_CNT     1
#define MYCTRL_NAME    "myctrl"     
/* 用户必须按照驱动的ioctrl一致 */

/*
分别对应了四个dir:
_IO(type, nr):用来定义不带参数的ioctl命令。
_IOR(type,nr,size):用来定义用户程序向驱动程序写参数的ioctl命令。
_IOW(type,nr,size):用来定义用户程序从驱动程序读参数的ioctl命令。
_IOWR(type,nr,size):用来定义带读写参数的驱动命令
*/
#define CMD_IOC_MAGIC 'a'
#define CMD_IOC_0     _IO(CMD_IOC_MAGIC,0)
#define CMD_IOC_1     _IOR(CMD_IOC_MAGIC,1,int)
#define CMD_IOC_2     _IOW(CMD_IOC_MAGIC,2,int)


struct myioctrl_t
{
    int major;
    int minor;
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
};

static struct myioctrl_t myioctrl;


static int myctrl_open(struct inode *ind, struct file *fp)
{
    fp->private_data = &myioctrl;
    printk("open ok\r\n");
    return 0;
}

static int myctrl_release(struct inode *ind, struct file *fp)
{
    return 0;
}

static long myctrl_unlocked_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
{
    int rc = 0;
    int val = 0;
    /* 判断ioctrl的类型对不对*/
    if(_IOC_TYPE(cmd) != CMD_IOC_MAGIC)
    {
        printk("input type [%c] invalid\r\n",CMD_IOC_MAGIC);
        return -ENAVAIL;
    }


    switch (cmd)
    {
    case CMD_IOC_0:
        printk("cmd 0:no arg\r\n");
        rc = 0;
        break;
    case CMD_IOC_1:
        val = arg;
        printk("cmd 1:ioctrl read=%d\r\n",(int)val);
        rc = 1;
        break;
    case CMD_IOC_2:
        val = arg;
        printk("cmd 2:ioctrl write=%d\r\n",(int)val);
        rc = 2;
        break;
    }
    return rc;
} 

const struct file_operations myctrl_fops={
    .owner = THIS_MODULE,
    .open = myctrl_open,
    .release = myctrl_release,
    .unlocked_ioctl = myctrl_unlocked_ioctl,
    
};

static int __init demo_init(void)
{
    int val = 0;
    printk("my ioctrl init ok\r\n");
    val = alloc_chrdev_region(&myioctrl.devid,0,MYCTRL_CNT,MYCTRL_NAME);
    if(val < 0)
    {
        printk("allo_chrdev_region failed\r\n");
        goto devid_fail;
    }
    myioctrl.major = MAJOR(myioctrl.devid);
    myioctrl.minor = MINOR(myioctrl.devid);
    printk("%s major:%d minor:%d\r\n",MYCTRL_NAME,myioctrl.major,myioctrl.minor);
    cdev_init(&myioctrl.cdev,&myctrl_fops);
    val = cdev_add(&myioctrl.cdev,myioctrl.devid,MYCTRL_CNT);
    if(val < 0)
    {
        printk("cdev add failed\r\n");
        goto cdev_fail;
    }
    /* 3.class */
    myioctrl.class = class_create(THIS_MODULE,MYCTRL_NAME);
    if(IS_ERR(myioctrl.class))
    {
        printk("class err\r\n");
         return PTR_ERR(myioctrl.class);
    }
    /* 4.device */
    myioctrl.device = device_create(myioctrl.class, NULL, myioctrl.devid, NULL, MYCTRL_NAME);
    if(IS_ERR(myioctrl.device))
    {
        printk("device err\r\n");
        return PTR_ERR(myioctrl.device);
       
    }
    return 0;
cdev_fail:
    unregister_chrdev_region(myioctrl.devid,MYCTRL_CNT);
devid_fail:
    return val;
}

static void __exit demo_exit(void)
{
    /* 卸载设备号 */
    unregister_chrdev_region(myioctrl.devid,MYCTRL_CNT);
    cdev_del(&myioctrl.cdev);
    device_destroy(myioctrl.class,myioctrl.devid);
    class_destroy(myioctrl.class);
    printk("my ioctrl exit ok\r\n");
}

module_init(demo_init);
module_exit(demo_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("deng");

编写用户程序main.c,主要作用就是打开设备节点,依次下发三条指令,打印参数和ioctl的返回值,关闭设备节点。

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

#define CMD_IOC_MAGIC 'a'
#define CMD_IOC_0 _IO(CMD_IOC_MAGIC, 0)
#define CMD_IOC_1 _IOR(CMD_IOC_MAGIC, 1, int)
#define CMD_IOC_2 _IOW(CMD_IOC_MAGIC, 2, int)

int main(int argc, char const *argv[])
{
    int fd = 0;
    int val = 0;
    if (argc != 2)
    {
        printf("input arg invalid\r\n");
        exit(-1);
    }
    fd = open(argv[1], O_RDWR);
    if (fd < 0)
    {
        perror("open fail");
        exit(-1);
    }
    val = ioctl(fd, CMD_IOC_0);
    if (val < 0)
        perror("ioctl fail");

    val = ioctl(fd, CMD_IOC_1);
    if (val < 0)
        perror("ioctl fail");

    val = ioctl(fd, CMD_IOC_2);

    if (val < 0)
        perror("ioctl fail");


    close(fd);
    exit(0);
}

最后编写Makefile:

KERNELDIR :=/home/deng/linux/IMX6ULL/linux/my_linux/

CURRENT_PATH := $(shell pwd)
obj-m := myioctrl.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

编译,运行结果:

最终运行结果:

  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DENG YIRU

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值