通过前面我们对字符驱动的完善,已经可以完成基本的功能了,但是仍然有以下不足,这一节我们继续完善它。
(1)没有实现xxx_ioctl接口,没办法通过命令来控制dev_fifo
改善:增加xxx_ioctl函数接口,应用层可以通过ioctl系统调用,根据不同的命令来操作dev_fifo。
(2)只能驱动一个dev_fifo设备,多个dev_fifo设备无法驱动
改善 :通过给模块传递参数,驱动多个dev_fifo设备
一 实现xxx_ioctl接口
(1)为什么要实现xxx_ioctl ?
前面我们在驱动中已经实现了读写接口,通过这些接口我们可以完成对设备的读写。但是很多时候我们的应用层工程师除了要对设备进行读写数据之外,还希望还可以对设备进行控制。例如:对于串口设备,驱动层除了需要提供对串口的读写之外,还需提供对串口波特率的设置。
一句话总结一下了,实现xxx_ioctl函数接口,主要的目的是提供对设备的控制能力,增加驱动程序的灵活性。
(2)如何实现xxx_ioctl函数接口?
先来看看应用层的ioctl和驱动层的xxx_ioctl对应关系:
<1>应用层ioctl参数分析
第一个参数:打开设备文件的时候获得文件描述符
第二个参数:给驱动层传递的命令,需要注意的时候,驱动层的命令和应用层的命令一定要统一
第三个参数: "..."在C语言中,很多时候都被理解成可变参数。这里不是这个意思。
当我们通过ioctl调用驱动层xxx_ioctl的时候,有三种情况可供选择:
第一种情况:不传递数据给xxx_ioctl
第二种情况:传递数据给xxx_ioctl,希望它最终能把数据写入设备(例如:设置串口的波特率)
第三种情况:调用xxxx_ioctl希望获取设备的硬件参数(例如:获取当前串口设备的波特率)
好了,这三种情况中,有些时候需要传递数据,有些时候不需要传递数据。在C语言中,是无法实现函数重载的 。
那怎么办?用"..."来欺骗编译器了,"..."本来的意思是传递多参数。在这里的意思是带一个参数还是不带参数。
哎!说起来真费劲,重在意会,希望你能理解。
<2>驱动层xxx_ioctl参数分析
第一个参数和第二个参数就不说了,你懂得。
第三个参数 :用户空间传递的命令,可以根据不同的命令做不同的事情
第四个参数: 用户空间的数据,主要这个数据可能是一个地址值(用户空间传递的是一个地址),也可能是一个数值,也可能没值
<3>如何确定cmd 的值。
是不是都认为随便给cmd一个值就可以了,只要这个值驱动层能认识就可以。
这是不负责人的干的事情,最后只能引起一场悲剧。这种事情生活中太多了.....
想一想这种情况:
有两个个设备,你本来想操作第一个设备,但是你就是一个和"2"结了不解之缘,所以你打开了第二个设备。正好你传递的命令也是第二个设备驱动能识的,你很开心的操作它。本来你想通过这命令来打开磁盘驱动器(第一个设备)来看片的,由于"2",这个命令被电源设备(第二个设备)接收到,直接关机。疯了,熬了一晚上的代码没保存,本来想通过看片振奋一下精神的,现在全玩完....
你是不是想,就算我"2"打开了错误的设备,你也不应该识别我这个命令呀,返回一个错误给我,告诉我这是无效的命令(这个命令不能再这个设备上使用),为什么不让系统中每个驱动程序识别的命令都不一样呢?呜呜。。。
哎,说多了都是泪!所以我们在设计命令的时候,一定要做到唯一性哦,这样就算很"2"的人,也可以过的很幸福。。。
好了,扯了这么多,我想你应该明白了,命令不能随便指定,要做到规范。现在我就来看看,在Linux 内核中这个cmd是如何设计的吧!
貌似很麻烦,难道我设计一个命令的时候,还需要在不同的位域写不同的数字?
不需要的,Linux 系统已经给我们封装好了宏,我们只需要直接调用宏来设计命令即可。
通过Linux 系统给我们提供的宏,我们在设计命令的时候,只需要指定设备类型、命令序号,数据类型三个字段就可以了。
在这里需要注意的时候,我们不能随便指定的哦,我们最终设计的命令应该是Linux系统中没有的命令才符号规范。那怎么知道Linux 系统中已经设计了哪些命令呢?可以通过查阅Linux 源码中的Documentation/ioctl/ioctl-number.txt文件,看哪些命令已经被使用过了。
好了,还是举个例子来看看,如何设计一个唯一的命令吧。
代码实例:
二、驱动多个dev_fifo 设备
上附图,是最后的效果。我们在内核空间分配多个struct mycdev结构体,每个struct mycdev代表一个dev_fifo设备。由于这些设备都是属于dev_fifo类,所以我们在处理的时候,让这些设备的主设备号一样,次设备号不一样。
想一个问题:如果我打开/dev/dev_fifo0 进行操作的时候,我怎么知道dev_fifo0对应内存空间的地址呢?
以前我们只有一个dev_fifo设备好办,我们在分配好内存后用一个全局的指针变量保存它的地址。现在有多个dev_fifo设备难道是定义多个全局变量分别保存,就算这样干了,当我调到驱动层的 xxx_read和xxx_write函数,我怎么知道是对哪一个设备进行读写操作呢?
有问题是好的,我们带着问题出发,看看大牛们是怎么做的。
先分析contianer_of 接口,这简直就是一个神奇的实现呀!
举个例子,来简单分析一下container_of 内部实现机制。
例如:
struct test
{
int a;
short b;
char c;
};
struct test *p = (struct test *)malloc(sizeof(struct test));
test_function(&(p->b));
int test_function(short *addr_b)
{
//获取struct test结构体空间的首地址
struct test *addr;
addr = container_of(addr_b,struct test,b);
}
展开container_of宏,探究内部的实现:
(1)typeof ( ( (struct test *)0 )->b ) : 获取成员变量b的类型 ,这里获取的就是short 类型。这是GNU_C的扩展语法。
(2)typeof ( ( (struct test *)0 )->b ) *__mptr = addr_b : 用获取的变量类型,定义了一个指针变量 __mptr ,并且将成员变量 b的首地址赋值给它
(3)(struct test *)( (char *)__mptr - offsetof(struct test,b)) : 这里的offsetof(struct test,b)是用来计算成员b在这个struct test 结构体的偏移。__mptr 是成员b的首地址, 现在 减去成员b在结构体里面的偏移值,算出来的是不是这个结构体的首地址呀 。
现在能看懂上面xxx_open那段代码了吗?
每次我们从应用层调用到驱动层的函数接口时,VFS层都会把struct inode 和 struct file 结构体分配的内存空间首地址传递给对应的驱动层接口。
还记得,前面我分析应用层和驱动层是如何关联的吗?最终找到驱动层的字符设备,并且把内核为字符设备分配的内存首地址保存在了struct inode 的i_cdev指针变量中。
我们通过container_of获取了内核空间为dev_fifo设备分配的内存首地址,然后把它保存在了file->private_data成员变量中。
现在能回答"如果我打开/dev/dev_fifo0 进行操作的时候,我怎么知道dev_fifo0对应的dev_ffifo0所在的内存空间的首地址呢?" 这个问题了吗?
是的,我们通过open打开/dev/dev_fifo0这个设备文件时,Linux 内核最终找到了/dev/dev_fifo0对应在内核空间的dev_fifo0这个设备。在xxxx_open函数中我们把内核为这个设备分配的内存空间首地址保存在了file->private_data这成员变量中。下一次当我们通过read 或 write调用到驱动层的xxx_read或xxx_write时,VFS层都会把struct file *file 传递下来的哦。
三、完善上一次的字符设备驱动
-
#include <linux/init.h>
-
#include <linux/module.h>
-
#include <linux/cdev.h>
-
#include <linux/fs.h>
-
#include <linux/device.h>
-
#include <linux/slab.h>
-
#include <asm/uaccess.h>
-
#include "dev_fifo_head.h"
-
-
//指定的主设备号
-
#define MAJOR_NUM 250
-
-
//自己的字符设备
-
struct mycdev
-
{
-
int len;
-
unsigned char buffer[50];
-
struct cdev cdev;
-
};
-
-
MODULE_LICENSE("GPL");
-
-
//设备号
-
static dev_t dev_num = {0};
-
-
//全局gcd
-
struct mycdev *gcd;
-
-
//设备类
-
struct class *cls;
-
-
//获得用户传递的数据,根据它来决定注册的设备个数
-
static int ndevices = 1;
-
module_param(ndevices, int, 0644);
-
MODULE_PARM_DESC(ndevices, "The number of devices for register.\n");
-
-
-
//打开设备
-
static int dev_fifo_open(struct inode *inode, struct file *file)
-
{
-
struct mycdev *cd;
-
-
printk("dev_fifo_open success!\n");
-
-
//用struct file的文件私有数据指针保存struct mycdev结构体指针
-
cd = container_of(inode->i_cdev,struct mycdev,cdev);
-
file->private_data = cd;
-
-
return 0;
-
}
-
-
//读设备
-
static ssize_t dev_fifo_read(struct file *file, char __user *ubuf, size_t size, loff_t *ppos)
-
{
-
int n;
-
int ret;
-
char *kbuf;
-
struct mycdev *mycd = file->private_data;
-
-
printk("read *ppos : %lld\n",*ppos);
-
-
if(*ppos == mycd->len)
-
return 0;
-
-
//请求大大小 > buffer剩余的字节数 :读取实际记得字节数
-
if(size > mycd->len - *ppos)
-
n = mycd->len - *ppos;
-
else
-
n = size;
-
-
printk("n = %d\n",n);
-
//从上一次文件位置指针的位置开始读取数据
-
kbuf = mycd->buffer + *ppos;
-
-
//拷贝数据到用户空间
-
ret = copy_to_user(ubuf,kbuf, n);
-
if(ret != 0)
-
return -EFAULT;
-
-
//更新文件位置指针的值
-
*ppos += n;
-
-
printk("dev_fifo_read success!\n");
-
-
return n;
-
}
-
-
//写设备
-
static ssize_t dev_fifo_write(struct file *file, const char __user *ubuf, size_t size, loff_t *ppos)
-
{
-
int n;
-
int ret;
-
char *kbuf;
-
struct mycdev *mycd = file->private_data;
-
-
printk("write *ppos : %lld\n",*ppos);
-
-
//已经到达buffer尾部了
-
if(*ppos == sizeof(mycd->buffer))
-
return -1;
-
-
//请求大大小 > buffer剩余的字节数(有多少空间就写多少数据)
-
if(size > sizeof(mycd->buffer) - *ppos)
-
n = sizeof(mycd->buffer) - *ppos;
-
else
-
n = size;
-
-
//从上一次文件位置指针的位置开始写入数据
-
kbuf = mycd->buffer + *ppos;
-
-
//拷贝数据到内核空间
-
ret = copy_from_user(kbuf, ubuf, n);
-
if(ret != 0)
-
return -EFAULT;
-
-
//更新文件位置指针的值
-
*ppos += n;
-
-
//更新dev_fifo.len
-
mycd->len += n;
-
-
printk("dev_fifo_write success!\n");
-
return n;
-
}
-
-
//linux 内核在2.6以后,已经废弃了ioctl函数指针结构,取而代之的是unlocked_ioctl
-
long dev_fifo_unlocked_ioctl(struct file *file, unsigned int cmd,
-
unsigned long arg)
-
{
-
int ret = 0;
-
struct mycdev *mycd = file->private_data;
-
-
switch(cmd)
-
{
-
case DEV_FIFO_CLEAN:
-
printk("CMD:CLEAN\n");
-
memset(mycd->buffer, 0, sizeof(mycd->buffer));
-
break;
-
-
case DEV_FIFO_SETVALUE:
-
printk("CMD:SETVALUE\n");
-
mycd->len = arg;
-
break;
-
-
case DEV_FIFO_GETVALUE:
-
printk("CMD:GETVALUE\n");
-
ret = put_user(mycd->len, (int *)arg);
-
break;
-
-
default:
-
return -EFAULT;
-
}
-
-
return ret;
-
}
-
-
-
//设备操作函数接口
-
static const struct file_operations fifo_operations = {
-
.owner = THIS_MODULE,
-
.open = dev_fifo_open,
-
.read = dev_fifo_read,
-
.write = dev_fifo_write,
-
.unlocked_ioctl = dev_fifo_unlocked_ioctl,
-
};
-
-
-
//模块入口
-
int __init dev_fifo_init(void)
-
{
-
int i = 0;
-
int n = 0;
-
int ret;
-
struct device *device;
-
-
gcd = kzalloc(ndevices * sizeof(struct mycdev), GFP_KERNEL);
-
if(!gcd){
-
return -ENOMEM;
-
}
-
-
//设备号 : 主设备号(12bit) | 次设备号(20bit)
-
dev_num = MKDEV(MAJOR_NUM, 0);
-
-
//静态注册设备号
-
ret = register_chrdev_region(dev_num,ndevices,"dev_fifo");
-
if(ret < 0){
-
-
//静态注册失败,进行动态注册设备号
-
ret = alloc_chrdev_region(&dev_num,0,ndevices,"dev_fifo");
-
if(ret < 0){
-
printk("Fail to register_chrdev_region\n");
-
goto err_register_chrdev_region;
-
}
-
}
-
-
//创建设备类
-
cls = class_create(THIS_MODULE, "dev_fifo");
-
if(IS_ERR(cls)){
-
ret = PTR_ERR(cls);
-
goto err_class_create;
-
}
-
-
printk("ndevices : %d\n",ndevices);
-
-
for(n = 0;n < ndevices;n ++)
-
{
-
//初始化字符设备
-
cdev_init(&gcd[n].cdev,&fifo_operations);
-
-
//添加设备到操作系统
-
ret = cdev_add(&gcd[n].cdev,dev_num + n,1);
-
if (ret < 0)
-
{
-
goto err_cdev_add;
-
}
-
//导出设备信息到用户空间(/sys/class/类名/设备名)
-
device = device_create(cls,NULL,dev_num + n,NULL,"dev_fifo%d",n);
-
if(IS_ERR(device)){
-
ret = PTR_ERR(device);
-
printk("Fail to device_create\n");
-
goto err_device_create;
-
}
-
}
-
printk("Register dev_fito to system,ok!\n");
-
-
-
return 0;
-
-
err_device_create:
-
//将已经导出的设备信息除去
-
for(i = 0;i < n;i ++)
-
{
-
device_destroy(cls,dev_num + i);
-
}
-
-
err_cdev_add:
-
//将已经添加的全部除去
-
for(i = 0;i < n;i ++)
-
{
-
cdev_del(&gcd[i].cdev);
-
}
-
-
err_class_create:
-
unregister_chrdev_region(dev_num, ndevices);
-
-
err_register_chrdev_region:
-
return ret;
-
-
}
-
-
void __exit dev_fifo_exit(void)
-
{
-
int i;
-
-
//删除sysfs文件系统中的设备
-
for(i = 0;i < ndevices;i ++)
-
{
-
device_destroy(cls,dev_num + i);
-
}
-
-
//删除系统中的设备类
-
class_destroy(cls);
-
-
//从系统中删除添加的字符设备
-
for(i = 0;i < ndevices;i ++)
-
{
-
cdev_del(&gcd[i].cdev);
-
}
-
-
//释放申请的设备号
-
unregister_chrdev_region(dev_num, ndevices);
-
-
return;
-
}
-
-
-
module_init(dev_fifo_init);
-
module_exit(dev_fifo_exit);
头文件内容:
-
#ifndef _DEV_FIFO_HEAD_H
-
#define _DEV_FIFO_HEAD_H
-
-
#define DEV_FIFO_TYPE 'k'
-
#define DEV_FIFO_CLEAN _IO(DEV_FIFO_TYPE,0x10)
-
#define DEV_FIFO_GETVALUE _IOR(DEV_FIFO_TYPE,0x11,int)
-
#define DEV_FIFO_SETVALUE _IOW(DEV_FIFO_TYPE,0x12,int)
-
-
#endif
Makefile :
-
ifeq ($(KERNELRELEASE),)
-
-
KERNEL_DIR ?=/lib/modules/$(shell uname -r)/build
-
PWD :=$(shell pwd)
-
-
modules:
-
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules
-
-
.PHONY:modules clean
-
clean:
-
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) clean
-
else
-
-
obj-m := dev_fifo.o
-
endif
应用程序:
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <sys/types.h>
-
#include <string.h>
-
#include <sys/stat.h>
-
#include <fcntl.h>
-
-
int main(int argc, const char *argv[])
-
{
-
int fd ;
-
int n;
-
char buf[1024] = "hello word";
-
-
fd = open("/dev/dev_fifo0",O_RDWR);
-
if(fd < 0){
-
perror("Fail ot open");
-
return -1;
-
}
-
-
printf("open successful ,fd = %d\n",fd);
-
-
n = write(fd,buf,strlen(buf));
-
if(n < 0){
-
perror("Fail to write");
-
return -1;
-
}
-
-
printf("write %d bytes!\n",n);
-
-
n = write(fd,buf,strlen(buf));
-
if(n < 0){
-
perror("Fail to write");
-
return -1;
-
}
-
-
printf("write %d bytes!\n",n);
-
return 0;
-
}
阅读(1009) | 评论(0) | 转发(0) |