(八)linux驱动之ioctl的使用

标签: Linux驱动编程

这篇文章给大家讲解一下ioctl的简单使用,关于ioctl更详细的教程后面有机会单独写出来

(一)什么是ioctl

ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速、网卡速率等等。它的调用方式如下:

//在应用调用
int ioctl(int fd, int cmd, …);

其中fd是用户程序打开设备时使用open函数返回的文件标示符,cmd是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,这个参数的有无和cmd的意义相关。

(二)ioctl的作用

虽然在文件操作结构体"struct file_operations"中有很多对应的设备操作函数,但是有些命令是实在找不到对应的操作函数。如CD-ROM的驱动,想要一个弹出光驱的操作,这种操作并不是所有的字符设备都需要的,所以文件操作结构体也不会有对应的函数操作。出于这样的原因,ioctl就有它的用处了————一些没办法归类的函数就统一放在ioctl这个函数操作中,通过指定的命令来实现对应的操作。所以,ioctl函数里面都实现了多个的对硬件的操作,通过应用层传入的命令来调用相应的操作。

(3)ioctl的优点

控制I/O设备 ,提供了一种获得设备信息和向设备发送控制参数的手段。用于向设备发控制和配置命令 ,read/write函数处理的是数据,ioctl是控制命令
这样用的可以分工明确:数据是数据,命令是命令

(四)ioctl相关接口介绍

(1)系统层

   #include <sys/ioctl.h>
   int ioctl(int fd, unsigned long request, ...);
   int fd:设备对应的打开的文件描述符
unsigned long request:请求码
若有第三个参数,必须为无类型指针

请求码cmd是 unsigned int 是 32 位。下图给出了每位的含义

图一
系统中给定了一些请求码,下面列出一小部分

    // <include/asm-i386/socket.h>
       地址          宏名称       采用对应宏名字的时候所传递的第三个参数值

       0x00008901   FIOSETOWN    const int *

       0x00008902   SIOCSPGRP    const int *
       0x00008903   FIOGETOWN    int *
       0x00008904   SIOCGPGRP    int *
       0x00008905   SIOCATMAR    int *
       0x00008906   SIOCGSTAMP   timeval *

除了系统给定的,内核中也提供了创建请求码的宏:

/* 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)))
type: cmd 中魔数,用来代表一个驱动 8~15位。
nr  : cmd 中的***,0-7位
size: cmd 中的数据尺寸,16-29位

内核中提供了相应的解码请求码的宏:

/* used to decode ioctl numbers.. */
获取请求码是从内核获取数据或者写入数据到内核-----判定读写功能
#define _IOC_DIR(nr)	(((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)

获取请求码对应的操作设备类型
#define _IOC_TYPE(nr)	(((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
获取具体的请求码的功能值(传递的是1还是0,或者其他数据)
#define _IOC_NR(nr)		(((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
获取请求码的代销
#define _IOC_SIZE(nr)	(((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)

注意:
unlocked_ioctl 用来替换 ioctl 接口的, 在 Linux 3.0 以后 ioctl 已经在内核中消失。其对应系统调用接口不变。

(五)实例代码

cdev.c

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>

int i=0;
dev_t dev=0;
char stat[4]={-1,-1,-1,-1};
#define CDEVCOUNT 5
#define CDEVNAME "cdevdevice"
#define CDEVCLASS "myclass"
#define INODENAME "mycdev"
#define ADDRSIZE 8
unsigned int phy_dadr=0x110002E0;//映射的起始地址,为GPM4CON的偏移地址
unsigned  int *virt_addr=NULL; //用来接收映射后首地址
struct cdev * cdev=NULL;
struct class * cdevclass=NULL;
#define GPM4CON  (*(volatile unsigned int *)virt_addr)
#define GPM4DAT  (*(volatile unsigned int *)(virt_addr+1))

int cdev_open (struct inode *node, struct file *file)
{
  //先将配置寄存器清空
  GPM4CON &=~(0XFFFF<<0);
  //设置每个引脚为输出状态
  GPM4CON |=(0X1111<<0);
  //给指定的数据寄存器初始化
  GPM4DAT |=(0XF<<0);
  return 0;
}
ssize_t cdev_read (struct file *fp, char __user *buf, size_t size, loff_t *offset)
{
  int i=0,ret=0;

  if(size<=0 || *offset>4 )
    return -EINVAL;
  if(size>4-*offset)
    size=4-*offset;
  for(i=*offset;i<*offset+size;i++)
  {
    if(GPM4DAT&(1<<i))
    {
      stat[i]='0';
    }
    else if(!(GPM4DAT&(1<<i)))
    {
      stat[i]='1';
    }
  }
  ret=copy_to_user(&buf[*offset],&stat[*offset],size);
  printk("%s\n",stat);
  printk("cdev_read is install\n");
  return 0;
}
ssize_t cdev_write (struct file *fp, const char __user * buf, size_t size, loff_t *offset)
{
  int i=0;
  long ret=0;
  char str[4]={-1,-1,-1,-1};
  if(size<=0 || *offset>4 )
    return -EINVAL;
  if(size>4-*offset)
    size=4-*offset;
  ret=copy_from_user(&str[*offset],&buf[*offset],size);
  if(ret<0)
  {
    return -1;
  }
  for(i=*offset;i<*offset+size;i++)
  {
    if(str[i]=='0')
      GPM4DAT |=(1<<i);
    else if(str[i]=='1')
      GPM4DAT &=~(1<<i);
  }
  printk("cdev_write is install\n");
  return 0;
}
loff_t cdev_llseek (struct file *fp, loff_t off, int whence)
{
  //获取偏移量的操作需要whence和off结合
  loff_t newoff=0;
  switch(whence)
  {
    case SEEK_SET:newoff=off;break;
    case SEEK_CUR:newoff=fp->f_pos+off;break;
    case SEEK_END:newoff=off+4;break;
    default: break;
  }
  //上述操作newoff 的值可能为负数,也可能为大于4的数,大于4和小于0没有意义
  if(newoff<0)
  {
    newoff=0;
  }
  if(newoff>4)
  {
    newoff=4;
  }
  fp->f_pos=newoff;
  return fp->f_pos;
}
long cdev_ioctl (struct file *fp, unsigned int cmd,  unsigned long tmp)
{
  int  i=0,ret=0;
  if(_IOC_TYPE(cmd)!='D')
    return -EINVAL;
  //提取cmd中定义的读写方向
  if(_IOC_DIR(cmd)==_IOC_WRITE)
  {
    if(_IOC_NR(cmd)==1)
    {
      GPM4DAT &=~(1<<tmp);
    }
    if(_IOC_NR(cmd)==0)
    {
      GPM4DAT |= (1<<tmp);
    }
  }
  else if(_IOC_DIR(cmd)==_IOC_READ)
  {
    for(i=0;i<4;i++)
    {
      if(GPM4DAT&(1<<i))
      {
        stat[i]='0';
      }
      else if(!(GPM4DAT&(1<<i)))
      {
        stat[i]='1';
      }
    }
    if((ret=copy_to_user((unsigned long *)tmp, stat, 4))<0)
      return -EFAULT;
  }
  return 0;
}

int cdev_release (struct inode *node, struct file *fp)
{
  printk("cdev_release is install\n");
  return 0;
}
struct file_operations fop={
  .open=cdev_open,
  //.read=cdev_read,
  //.write=cdev_write,
  .release=cdev_release,
  //.llseek=cdev_llseek,
  .unlocked_ioctl=cdev_ioctl,
};
static int __init cdev_module_init(void)
{
  int ret=alloc_chrdev_region(&dev, 0, CDEVCOUNT, CDEVNAME);
  if(ret)
  {
    return -1;
  }
  cdev=cdev_alloc();
  if (!cdev)
    goto out;
  cdev_init(cdev, &fop);
  if(cdev_add(cdev, dev, CDEVCOUNT))
  {
    goto out1;
  }
  printk("cdev_add success\n");
  cdevclass=class_create(THIS_MODULE,CDEVCLASS);
  if (IS_ERR(cdevclass))
  {
    goto out2;
  }
  printk("class_create success\n");
  for(i=0;i<5;i++)
    device_create(cdevclass,NULL, dev+i, NULL, "mycdev%d",i );
  virt_addr=ioremap(phy_dadr,ADDRSIZE);
  printk("device_create success\n");
  return 0;
out:
  unregister_chrdev_region(dev,CDEVCOUNT);
  return -2;
out1:
  unregister_chrdev_region(dev,CDEVCOUNT);
  kfree(cdev);
out2:
  cdev_del(cdev);
  unregister_chrdev_region(dev,CDEVCOUNT);
  kfree(cdev);
  return PTR_ERR(cdevclass);
}
static void __exit cdev_module_cleanup(void)
{
  iounmap(virt_addr);
  for(--i;i>=0;i--)
    device_destroy(cdevclass,dev+i);
  printk("device_destroy success\n");
  class_destroy(cdevclass);
  cdev_del(cdev);
  unregister_chrdev_region(dev,CDEVCOUNT);
  kfree(cdev);
  printk("kfree success\n");
}
module_init(cdev_module_init);
module_exit(cdev_module_cleanup);
MODULE_LICENSE("GPL");

cdev_app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include "cmd.h"
void display(int fd)
{
  char buf[4]={0};
  int i=0;
  //read(fd,buf,4);
  ioctl(fd,LED_R,buf);
  printf("%s\n",buf);
  for(i=0;i<4;i++)
  {
    printf("led%d is %s\n",i,(buf[i]-'0')?"on":"off");
  }
}
int main(int argc, char const* argv[])
{
  int i=0;
  int fd=open(argv[1],O_RDWR);
  if(fd==-1)
  {
    perror("open");
    return -1;
  }
  for(i=0;i<4;i++)
  {
    ioctl(fd,LED_ON,i);
    sleep(1);
    display(fd);
    ioctl(fd,LED_OFF,i);
    sleep(1);
  }
  printf("%s\n",stat);
  close(fd);
  return 0;
}

cmd.h

#ifndef __CMD_H_
#define __CMD_H_

#define  LED_OFF _IOW('D',0,sizeof(int))
#define  LED_ON _IOW('D',1,sizeof(int))
#define  LED_R  _IOR('D',2,sizeof(int))
#endif

Makefile

CFLAG=-C
TARGET=cdev
APP=cdev_app
KERNEL=/driver/linux-3.5
obj-m +=$(TARGET).o
all:
  make $(CFLAG) $(KERNEL) M=$(PWD)
  arm-linux-gcc -o $(APP) $(APP).c
clean:
  make $(CFLAG) $(KERNEL) M=$(PWD) clean
  rm $(APP)

1.首先使用_IOW或_IOR宏来合成32位的请求码,这个请求码可以单独定义在一个头文件里
2.在字符驱动模型的file_operation结构体里.unlocked_ioctl指向的函数中编写与ioctl相关的控制函数
(1)判断设备类型 _IOC_TYPE(nr)
(2)判断读写方向 _IOC_DIR(nr)
如果是读的话将数据拷贝到用户空间
3.在应用程序中调用ioctr(fd,请求码,参数)来控制设备
这里的请求码可以是我们第一步合成的


本文章仅供学习交流用禁止用作商业用途,文中内容来水枂编辑,如需转载请告知,谢谢合作

微信公众号:zhjj0729

微博:文艺to青年

版权声明:本文为weixin_43839785原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_43839785/article/details/105250923

智能推荐

linux应用编程:I/O操作的杂货箱 ioctl

目录 一、概述 二、应用层接口 三、例程 一、概述 ioctl 为什么会称为I/O操作的杂货箱?那时因为不能用之前 文件I/O 操作的地方,通常都能用ioctl来进行操作。 下面看ioctl 从应用到驱动的操作机制(网络截图) 从上面可以看出,ioctl通过cmd命令和传入arg的方式来进行文件的特殊操作,此机制的应用往往体现在应用程序对硬件设备的控制上面(比如设置串口的波特率、点击电机转速、控制...

linux 驱动开发之平台设备驱动设备树 input子系统的使用:按键中断驱动

这一章我们linux input 子系统 在linux中,input子系统有三部分组成:驱动层,输入子系统核心,事件处理层.一个输入时间,比如按键按下通过:驱动->input core->event handler->user space的顺序到达用户空间的应用程序 驱动层:将底层的硬件输入转化为统一的事件形式,向输入核心(input core)汇报 核心层:承上启下,为驱动层提...

Android 炫酷的横向和环形进度条的实例

一、概述 最近需要用进度条,秉着不重复造轮子的原则,上github上搜索了一番,看了几个觉得比较好看的ProgressBar,比如:daimajia的等。简单看了下代码,基本都是继承自View,彻彻底底的自定义了一个进度条。盯着那绚丽滚动条,忽然觉得,为什么要通过View去写一个滚动条,系统已经提供了ProgressBar以及属于它的特性,我们没必要重新去构建一个,但是系统的又比较丑,不同版本变现...

pcap包结构&SNI字段的解析

    pcap文件格式是常用的数据报存储格式,包括wireshark在内的主流抓包软件都可以生成这种格式的数据包。     文件格式:     Pcap文件头(24字节)+数据包头(wireshark增加的)+数据包(网络中抓取的)+…… 1.pcap文件头结构 各字段说明: Magic:4B:0×...

MC9S12XEP100的IIC模块(IICV3)

最近在写DS3231时钟芯片的驱动,这个芯片使用IIC进行通讯,以前没有用过IIC模块,照着教材和示例程序写程序后发现各种问题。没办法,还是官方数据手册靠谱,遂把相应部分又翻译了一遍。果然发现示例程序纯粹就是个玩具,一点用都没有。。。 第15章 集成电路总线(IICV3) 译者注:译者博客(http://blog.csdn.net/lin_strong),转载请保留这条。此为 MC9S12XEP1...

猜你喜欢

Eureka服务注册与发现

文章目录 二、SpringCLoud中遇到的技术 (一)、Eureka 1、功能: 2、什么是服务治理. 3、什么是服务注册与发现 4、Eureka包含两个组件: Eureka Server和Eureka Client 5、单机Eureka构建步骤 5.1 建model:cloud-eureka-server7001 5.2 写pom 5.3 写yml 5.4 主启动类 5.5 测试 5.6 将消...

Hard Problem【HDU-3551】【一般图最大匹配 带花树】

题目链接   题意:有N个点,M条边,给出每个点的度限制,问能不能用M条边中的几条达成这个目的?   很明显的就是一个建图的问题,很明显的,少于等于度为1的,是可以直接连的,不用限制增广,而大于度为1的,需要限制增广,就可以用这样的限流的方法:  ...

idea 打包 报错maven-shade-plugin:2.4.3:shade (default) on project xxx : Error creating shaded jar: null

idea 打包 报错maven-shade-plugin:2.4.3:shade (default) on project xxx : Error creating shaded jar: null 文章目录 idea 打包 报错maven-shade-plugin:2.4.3:shade (default) on project xxx : Error creating shaded jar: ...

015_swiftui_刘海屏适配

swiftUi创建的视图是默认在刘海屏幕的安全区域以内的。 之前我们设置图片也用到了这个个属性。 现在我们再来使用下吧 下面就是图片了。 ...

大数据入门:Spark+Kudu的广告业务项目实战笔记(一)

1.简介 这是PK老师在Spark-SQL课程中的项目实战笔记 2.项目需求 数据ETL:原始文件为JSON格式数据,需原始文件与IP库中数据进行解析 统计各省市的地域分布情况 统计广告投放的地域分布情况 统计广告投放APP分布情况 3.项目架构 4.日志字段 5.IP规则库解析 本项目利用IP规则库进行解析,在生产中应该需要专门的公司提供的IP服务。IP规则库中的一条如下: 其中第三列是该段ip...