实现对设备的控制,例如针对串口设备 驱动层除了需要提供对串口的读写之外还需提供对串口 波特率 、 奇偶校验位 、 终止位 的设置这些配置信**息需要从应用层传递一些基本数据** 仅仅是**数据类型不同。**
ioctl接口,可以传递数据并区分类型。
应用层:ioctl
#include<sys/ioctl.h>
int ioctl(int fd,unsigned long cmd,...);
cmd:给驱动层传递的命令,需要注意的时候,驱动层的命令和应用层的命令一定要统一
第三个参数:在 C 语言中,很多时候都被理解成可变参数。
返回值:失败-1,同时设置errno
内核层:unlocke_ioctl
long (*unlocked_ioctl)(struct file *,unsigned int,unsigned long);
file:vfs 层为打开字符设备文件的进程创建的结构体 用于存放文件的动态信息
cmd : 用户空间传递的命令 可以根据不同的命令做不同的事情
第三个参数 : 用户空间的数据,这个数据可能是一个地址值,用户空间传递的是一个地址;也可能是一个数值,也可能没值。
如果应用层传递的是一个整型值,内核层接收到也是整型值;
如果传递的是一个指针变量的地址,内核层需要把整型值强转成地址,再去读取地址里的值。
两者的调用关系
应用层fd对应内核态的file,cmd对应cmd。
cmd
设备类型——type
设备号——nr
封装命令
因为实现cmd命令,是需要进行位操作的,内核里提供宏定义便于实现
举个栗子
在documentation下有已经被占用的序列号,需要注意自己写的cmd不能和之前的重复。
如何检查命令、地址正确性?
•可以通过宏 _IOC_TYPE(nr)来判断应用程序传下来的命令 type 是否正确;
•可以通过宏 _IOC_DIR(nr)来得到命令是读还是写,然后再通过宏 access_ok(type,addr,size)来判断用户层传递的内存地址是否合法。
头文件:#include "beep.h"
#ifndef _BEFF_H
#define _BEFF_H
#define DEV_FIFO_TYPE 'l'
#define DEV_FIFO_CLEAN _IO(DEV_FIFO_TYPE,0)
#define DEV_FIFO_GETVALUE _IOR(DEV_FIFO_TYPE,1,int)
#define DEV_FIFO_PUTVALUE _IOW(DEV_FIFO_TYPE,2,int)
#endif
#ifndef
#define
…
#endif 用于防止头文件被重复包含
宏定义分三部分:设备类型,这是该组的第几个命令,操作值的类型
驱动:#include <linux/io.h>
static int knum = 10010;
long hello_ioctl (struct file *filep, unsigned int cmd, unsigned long arg)
{
/* 需要和用户层协调:所传的第三个参数需要为指针;但在内核中它以整型值形式接受这个指针;
因此需要强行装换类型*/
void __user *argp = (void __user *)arg;
int __user *p = argp;
int err = 0;
/* 以上的地址都是用户层地址,但是在内核中操作的 */
switch(cmd)
{
case DEV_FIFO_CLEAN:
printk("DEV_FIFO_CLEAN \n");
break;
case DEV_FIFO_PUTVALUE:
err = put_user(knum, p);
printk("DEV_FIFO_PUTVALUE %d\n",knum);
break;
case DEV_FIFO_GETVALUE:
err = get_user(knum,p);
printk("DEV_FIFO_GETVALUE %d\n",knum);
break;
default:
return -EINVAL;
}
return 0;
}
1、在定义cmd宏中,第三个参数已经定义了数据类型;但是内核中传参用的却是用unsigned long arg,因此需要进行格式的转换。
由于可以约定了第三个的数据类型(int),且参数arg一般代表指针,它现在就是指针的值(不是解指针的值),因此直接转换成int 指针类型。
void __user *argp = (void __user *)arg;
int __user *p = argp;
2、由于arg是由用户层传来的,所以为了保持类型一致,用户层也要和cmd保持一致。
用户层:
int num = 0;
ioctl(fd,DEV_FIFO_PUTVALUE,&num);
ioctl(fd,DEV_FIFO_GETVALUE,&num);
需要和cmd命令中格式保持一致;一般习惯传指针。
部分问题
result = register_chrdev(major, "hello", &hello_drv);
在注册设备时,register_chrdev的返回值并不代表设备号,设备号始终只储存在major中。
如果模块没有被重装,里面存储的值可以一直被修改且不会重置。APP层程序可以不断开打关闭重启重置,但是对驱动的修改并不会重启重置;除非你重装驱动。所以,可以通过APP层去实时修改驱动。