【Linux 驱动】第六章 高级字符驱动程序操作 ----ioctl

2024-04-05 02:08

本文主要是介绍【Linux 驱动】第六章 高级字符驱动程序操作 ----ioctl,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、ioctl

       1)概念:#include<sys/ioctl.h>

       2)功 能: 控制I/O设备 ,提供了一种获得设备信息向设备发送控制参数的手段。用于向设备发控制和配置命令 ,有些命令需要控制参数,这些数据是不能用read / write 读写的,称为Out-of-band数据。也就是说,read / write 读写的数据是in-band数据,是I/O操作的主体,而ioctl 命令传送的是控制信息,其中的数据是辅助的数据。

    3)用 法: int ioctl(int handle, int cmd,[int *argdx, int argcx]);
       4)返回值:成功为0,出错为-1
  usr/include/asm-generic/ioctl.h中定义的宏的注释:
  #define _IOC_NRBITS 8 //序数(number)字段的字位宽度,8bits
  #define _IOC_TYPEBITS 8 //幻数(type)字段的字位宽度,8bits
  #define _IOC_SIZEBITS 14 //大小(size)字段的字位宽度,14bits
  #define _IOC_DIRBITS 2 //方向(direction)字段的字位宽度,2bits
  #define _IOC_NRMASK ((1 << _IOC_NRBITS)-1) //序数字段的掩码,0x000000FF
  #define _IOC_TYPEMASK ((1 << _IOC_TYPEBITS)-1) //幻数字段的掩码,0x000000FF
  #define _IOC_SIZEMASK ((1 << _IOC_SIZEBITS)-1) //大小字段的掩码,0x00003FFF
  #define _IOC_DIRMASK ((1 << _IOC_DIRBITS)-1) //方向字段的掩码,0x00000003
  #define _IOC_NRSHIFT 0 //序数字段在整个字段中的位移,0
  #define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS) //幻数字段的位移,8
  #define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS) //大小字段的位移,16
  #define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS) //方向字段的位移,30

        5)ioctl 系统调用原型
           int    (*ioctl) (struct inode *inode, struct file *filp,unsigned  int cmd, unsigned long arg);
          需要注意的是:不管可选的参数arg是否由用户给定为一个整数或一个指针,它都以一个unsigned long的形式传递。如果调用程序不传递arg参数, 被驱动收到的 arg 值是未定义的。因为在arg参数上的类型检查被关闭了,所以若一个非法参数传递给 ioctl,编译器是无法报警的,且任何关联的错误难以查找.


二,选择ioctl命令

        在编写ioctl代码前,需要选择对应不同命令的编号,多数程序员选择从0开始的一组小编号。为了防止向错误的设备使用正确的命令,命令号应该在系统范围内唯一。为方便程序员创建唯一的 ioctl 命令代号, 每个命令号被划分为多个位字段。要按 Linux 内核的约定方法为驱动选择 ioctl 的命令号, 应该首先看看 include/asm/ioctl.h 和 Documentation/ioctl-number.txt。 (linux第一个版本使用了一个16位编号,高八位:幻数 低八位:序列号)

       要使用的位字段符号定义在 :

           type(幻数):8 位宽(_IOC_TYPEBITS),参考ioctl-number.txt选择一个数,并在整个驱动中使用它。
          number(序数):顺序编号,8 位宽(_IOC_NRBITS)。
          direction(数据传送的方向):可能的值是 _IOC_NONE(没有数据传输)、_IOC_READ、 _IOC_WRITE和 _IOC_READ|_IOC_WRITE (双向传输数据)。该字段是一个位掩码(两位), 因此可使用 AND 操作来抽取_IOC_READ 和 _IOC_WRITE。
          size(数据的大小):宽度与体系结构有关,ARM为14位.可在宏 _IOC_SIZEBITS 中找到特定体系的值. 


    <linux/ipctl.h>中包含<asm/ioctl.h>中包含的 定义了一些构造命令编号的宏: 

          _IO(type,nr)/*没有参数的命令*/
          _IOR(type, nr, datatype)/*从驱动中读数据*/
          _IOW(type,nr,datatype)/*写数据*/
          _IOWR(type,nr,datatype)/*双向传送*/
         /*type 和 number 成员作为参数被传递, 并且 size 成员通过应用 sizeof 到 datatype 参数而得到*/
         这个头文件还定义了用来解开这个字段的宏:
          _IOC_DIR(nr)
          _IOC_TYPE(nr)
          _IOC_NR(nr)
          _IOC_SIZE(nr)


三,返回值

        POSIX 标准规定:如果使用了不合适的 ioctl 命令号,应当返回-ENOTTY 。这个错误码被 C 库解释为"不合适的设备 ioctl。然而,它返回-EINVAL仍是相当普遍的。 


四,预定义命令
             
有一些ioctl命令是由内核识别的,当这些命令用于自己的设备时,他们会在我们自己的文件操作被调用之前被解码. 因此, 如果你选择一个ioctl命令编号和系统预定义的相同时,你永远不会看到该命令的请求,而且因为ioctl 号之间的冲突,应用程序的行为将无法预测。预定义命令分为 3 类:
           (1)用于任何文件(常规, 设备, FIFO和socket) 的命令
           (2)只用于常规文件的命令
           (3)特定于文件系统类型的命令 
下列 ioctl 命令是预定义给任何文件,包括设备特定文件:
           FIOCLEX :设置 close-on-exec 标志(File IOctl Close on EXec)。 
           FIONCLEX :清除 close-no-exec 标志(File IOctl Not CLose on EXec)。 
           FIOQSIZE :这个命令返回一个文件或者目录的大小; 当用作一个设备文件, 但是, 它返回一个 ENOTTY 错误。 
           FIONBIO:"File IOctl Non-Blocking I/O"(在"阻塞和非阻塞操作"一节中描述)。 

五,使用ioctl参数
        在使用ioctl的可选arg参数时,如果传递的是一个整数,它可以直接使用。如果是一个指针,就必须小心。当用一个指针引用用户空间, 我们必须确保用户地址是有效的,其校验(不传送数据)由函数 access_ok 实现,定义在 : 
                          int access_ok(int   type, const void *addr, unsigned long size); 

                                  type 应当是 VERIFY_READ(读)或VERIFY_WRITE(读写);

                                  addr 参数为用户空间地址,

                                  size 为字节数,可使用sizeof()。

                          access_ok 返回一个布尔值: 1 是成功(存取没问题)和 0 是失败(存取有问题)。如果它返回假,驱动应当返回 -EFAULT 给调用者。

         注意:首先, access_ok不做校验内存存取的完整工作; 它只检查内存引用是否在这个进程有合理权限的内存范围中,且确保这个地址不指向内核空间内存。其次,大部分驱动代码不需要真正调用 access_ok,而直接使用put_user(datum, ptr)和get_user(local, ptr),它们带有校验的功能,确保进程能够写入给定的内存地址,成功时返回 0, 并且在错误时返回 -EFAULT.。 

       

<asm/uaccess.h>   

          put_user(datum, ptr) 

          __put_user(datum, ptr) 
          get_user(local, ptr) 
         __get_user(local, ptr) 
       这些宏它们相对copy_to_user 和copy_from_user快, 并且这些宏已被编写来允许传递任何类型的指针,只要它是一个用户空间地址. 传送的数据大小依赖 prt 参数的类型, 并且在编译时使用 sizeof 和 typeof 等编译器内建宏确定。他们只传送1、2、4或8 个字节。如果使用以上函数来传送一个大小不适合的值,结果常常是一个来自编译器的奇怪消息,如"coversion to non-scalar type requested". 在这些情况中,必须使用 copy_to_user 或者 copy_from_user。 
        __put_user和__get_user 进行更少的检查(不调用 access_ok), 但是仍然能够失败如果被指向的内存对用户是不可写的,所以他们应只用在内存区已经用 access_ok 检查过的时候。作为通用的规则:当实现一个 read 方法时,调用 __put_user 来节省几个周期, 或者当你拷贝几个项时,因此,在第一次数据传送之前调用 access_ok 一次。


六,权能与受限操作

         Linux 内核提供了一个更加灵活的系统, 称为权能(capability)。内核专为许可管理上使用权能并导出了两个系统调用 capget 和 capset,这样可以从用户空间管理权能,其定义在 中。对设备驱动编写者有意义的权能如下: 
        CAP_DAC_OVERRIDE /*越过在文件和目录上的访问限制(数据访问控制或 DAC)的能力。*/
        CAP_NET_ADMIN /*进行网络管理任务的能力, 包括那些能够影响网络接口的任务*/
        CAP_SYS_MODULE /*加载或去除内核模块的能力*/
        CAP_SYS_RAWIO /*进行 "raw"(裸)I/O 操作的能力. 例子包括存取设备端口或者直接和 USB 设备通讯*/
        CAP_SYS_ADMIN /*截获的能力, 提供对许多系统管理操作的途径*/
七,ioctl命令的实现
         
switch(cmd)
{case MEM_IOCSQUANTUM:retval = _get_user(scull_quantum,(int *)arg);break;case MEM_IOCGQUANTUM:retval = _put_user(scull_quantum,(int *)arg);break;default:return -EINVAL;
}
八,Linux系统使用ioctl实例
         
程序1:检测接口的 inet_addr,netmask,broad_addr
程序2:检查接口的物理连接是否正常程序3:更简单一点测试物理连接
程序4:调节音量
***************************程序1****************************************
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <net/if.h>
static void usage(){printf("usage : ipconfig interface \n");exit(0);
}
int main(int argc,char **argv)
{struct sockaddr_in *addr;struct ifreq ifr;char *name,*address;int sockfd;
if(argc != 2)usage();elsename = argv[1];
sockfd = socket(AF_INET,SOCK_DGRAM,0);strncpy(ifr.ifr_name,name,IFNAMSIZ-1);
if(ioctl(sockfd,SIOCGIFADDR,&ifr) == -1)perror("ioctl error"),exit(1);addr = (struct sockaddr_in *)&(ifr.ifr_addr);address = inet_ntoa(addr->sin_addr);printf("inet addr: %s ",address);
if(ioctl(sockfd,SIOCGIFBRDADDR,&ifr) == -1)perror("ioctl error"),exit(1);addr = (struct sockaddr_in *)&ifr.ifr_broadaddr;address = inet_ntoa(addr->sin_addr);printf("broad addr: %s ",address);
if(ioctl(sockfd,SIOCGIFNETMASK,&ifr) == -1)perror("ioctl error"),exit(1);addr = (struct sockaddr_in *)&ifr.ifr_addr;address = inet_ntoa(addr->sin_addr);printf("inet mask: %s ",address);
printf("\n");exit(0);
}
******************************** 程序2*****************************************************
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <stdlib.h>
#include <unistd.h>
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned char u8;
#include <linux/ethtool.h>
#include <linux/sockios.h>
int detect_mii(int skfd, char *ifname)
{struct ifreq ifr;u16 *data, mii_val;unsigned phy_id;
/* Get the vitals from the interface. */strncpy(ifr.ifr_name, ifname, IFNAMSIZ);if (ioctl(skfd, SIOCGMIIPHY, &ifr) < 0){fprintf(stderr, "SIOCGMIIPHY on %s failed: %s\n", ifname,strerror(errno));(void) close(skfd);return 2;}
data = (u16 *)(&ifr.ifr_data);phy_id = data[0];data[1] = 1;
if (ioctl(skfd, SIOCGMIIREG, &ifr) < 0){fprintf(stderr, "SIOCGMIIREG on %s failed: %s\n", ifr.ifr_name,strerror(errno));return 2;}
mii_val = data[3];
return(((mii_val & 0x0016) == 0x0004) ? 0 : 1);
}
int detect_ethtool(int skfd, char *ifname)
{struct ifreq ifr;struct ethtool_value edata;
memset(&ifr, 0, sizeof(ifr));edata.cmd = ETHTOOL_GLINK;
strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)-1);ifr.ifr_data = (char *) &edata;
if (ioctl(skfd, SIOCETHTOOL, &ifr) == -1){printf("ETHTOOL_GLINK failed: %s\n", strerror(errno));return 2;}
return (edata.data ? 0 : 1);
}
int main(int argc, char **argv)
{int skfd = -1;char *ifname;int retval;
if( argv[1] )ifname = argv[1];elseifname = "eth0";
/* Open a socket. */if (( skfd = socket( AF_INET, SOCK_DGRAM, 0 ) ) < 0 ){printf("socket error\n");exit(-1);}
retval = detect_ethtool(skfd, ifname);
if (retval == 2)retval = detect_mii(skfd, ifname);
close(skfd);
if (retval == 2)printf("Could not determine status\n");
if (retval == 1)printf("Link down\n");
if (retval == 0)printf("Link up\n");
return retval;
}
*******************************程序3*****************************************************
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <net/if.h>
#include <linux/sockios.h>
#include <sys/ioctl.h>
#define LINKTEST_GLINK 0x0000000a
struct linktest_value {unsigned int    cmd;unsigned int    data;
};
static
void
usage(const char * pname)
{fprintf(stderr, "usage: %s <device>\n", pname);fprintf(stderr, "returns: \n");fprintf(stderr, "\t 0: link detected\n");fprintf(stderr, "\t%d: %s\n", ENODEV, strerror(ENODEV));fprintf(stderr, "\t%d: %s\n", ENONET, strerror(ENONET));fprintf(stderr, "\t%d: %s\n", EOPNOTSUPP, strerror(EOPNOTSUPP));exit(EXIT_FAILURE);
}
static
int
linktest(const char * devname)
{struct ifreq ifr;struct linktest_value edata;int fd;
/* setup our control structures. */memset(&ifr, 0, sizeof(ifr));strcpy(ifr.ifr_name, devname);
/* open control socket. */fd=socket(AF_INET, SOCK_DGRAM, 0);if(fd < 0 ) {return -ECOMM;}
errno=0;edata.cmd = LINKTEST_GLINK;ifr.ifr_data = (caddr_t)&edata;
if(!ioctl(fd, SIOCETHTOOL, &ifr)) {if(edata.data) {fprintf(stdout, "link detected on %s\n", devname);return 0;} else {errno=ENONET;}}
perror("linktest");return errno;
}
int
main(int argc, char *argv[])
{if(argc != 2) {usage(argv[0]);}return linktest(argv[1]);
}
*************************************程序4*********************************************************
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include <stdio.h>
#include <unistd.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#define  BASE_VALUE 257
int main(int argc,char *argv[])
{int mixer_fd=0;char *names[SOUND_MIXER_NRDEVICES]=SOUND_DEVICE_LABELS;int value,i;
printf("\nusage:%s dev_no.[0..24] value[0..100]\n\n",argv[0]);printf("eg. %s 0 100\n",argv[0]);printf("    will change the volume to MAX volume.\n\n");printf("The dev_no. are as below:\n");for (i=0;i<SOUND_MIXER_NRDEVICES;i++){if (i%3==0) printf("\n");printf("%s:%d\t\t",names[i],i);}printf("\n\n");
if (argc<3)exit(1);
if ((mixer_fd = open("/dev/mixer",O_RDWR))){printf("Mixer opened successfully,working...\n");value=BASE_VALUE*atoi(argv[2]);
if (ioctl(mixer_fd,MIXER_WRITE(atoi(argv[1])),&value)==0)printf("successfully.....");else    printf("unsuccessfully.....");printf("done.\n");}elseprintf("can't open /dev/mixer error....\n");
exit(0);
}

这篇关于【Linux 驱动】第六章 高级字符驱动程序操作 ----ioctl的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/877397

相关文章

防止Linux rm命令误操作的多场景防护方案与实践

《防止Linuxrm命令误操作的多场景防护方案与实践》在Linux系统中,rm命令是删除文件和目录的高效工具,但一旦误操作,如执行rm-rf/或rm-rf/*,极易导致系统数据灾难,本文针对不同场景... 目录引言理解 rm 命令及误操作风险rm 命令基础常见误操作案例防护方案使用 rm编程 别名及安全删除

Linux下MySQL数据库定时备份脚本与Crontab配置教学

《Linux下MySQL数据库定时备份脚本与Crontab配置教学》在生产环境中,数据库是核心资产之一,定期备份数据库可以有效防止意外数据丢失,本文将分享一份MySQL定时备份脚本,并讲解如何通过cr... 目录备份脚本详解脚本功能说明授权与可执行权限使用 Crontab 定时执行编辑 Crontab添加定

JavaScript中的高级调试方法全攻略指南

《JavaScript中的高级调试方法全攻略指南》什么是高级JavaScript调试技巧,它比console.log有何优势,如何使用断点调试定位问题,通过本文,我们将深入解答这些问题,带您从理论到实... 目录观点与案例结合观点1观点2观点3观点4观点5高级调试技巧详解实战案例断点调试:定位变量错误性能分

Java实现在Word文档中添加文本水印和图片水印的操作指南

《Java实现在Word文档中添加文本水印和图片水印的操作指南》在当今数字时代,文档的自动化处理与安全防护变得尤为重要,无论是为了保护版权、推广品牌,还是为了在文档中加入特定的标识,为Word文档添加... 目录引言Spire.Doc for Java:高效Word文档处理的利器代码实战:使用Java为Wo

使用docker搭建嵌入式Linux开发环境

《使用docker搭建嵌入式Linux开发环境》本文主要介绍了使用docker搭建嵌入式Linux开发环境,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录1、前言2、安装docker3、编写容器管理脚本4、创建容器1、前言在日常开发全志、rk等不同

从基础到高级详解Python数值格式化输出的完全指南

《从基础到高级详解Python数值格式化输出的完全指南》在数据分析、金融计算和科学报告领域,数值格式化是提升可读性和专业性的关键技术,本文将深入解析Python中数值格式化输出的相关方法,感兴趣的小伙... 目录引言:数值格式化的核心价值一、基础格式化方法1.1 三种核心格式化方式对比1.2 基础格式化示例

sysmain服务可以禁用吗? 电脑sysmain服务关闭后的影响与操作指南

《sysmain服务可以禁用吗?电脑sysmain服务关闭后的影响与操作指南》在Windows系统中,SysMain服务(原名Superfetch)作为一个旨在提升系统性能的关键组件,一直备受用户关... 在使用 Windows 系统时,有时候真有点像在「开盲盒」。全新安装系统后的「默认设置」,往往并不尽编

Python自动化处理PDF文档的操作完整指南

《Python自动化处理PDF文档的操作完整指南》在办公自动化中,PDF文档处理是一项常见需求,本文将介绍如何使用Python实现PDF文档的自动化处理,感兴趣的小伙伴可以跟随小编一起学习一下... 目录使用pymupdf读写PDF文件基本概念安装pymupdf提取文本内容提取图像添加水印使用pdfplum

Python从Word文档中提取图片并生成PPT的操作代码

《Python从Word文档中提取图片并生成PPT的操作代码》在日常办公场景中,我们经常需要从Word文档中提取图片,并将这些图片整理到PowerPoint幻灯片中,手动完成这一任务既耗时又容易出错,... 目录引言背景与需求解决方案概述代码解析代码核心逻辑说明总结引言在日常办公场景中,我们经常需要从 W

Android协程高级用法大全

《Android协程高级用法大全》这篇文章给大家介绍Android协程高级用法大全,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友跟随小编一起学习吧... 目录1️⃣ 协程作用域(CoroutineScope)与生命周期绑定Activity/Fragment 中手