设备驱动基础学习--字符驱动实现
1.标识设备文件
当我们ls -l /dev/sd{a,b} /dev/ttyS{0,1}时,
可以看到,设备文件和普通文件区别不大,主要差异在于:
1)设备类型为b、c,分别表示块设备和字符设备。
2)有主设备号和从设备号。
由于系统可能包含几个同样类型的设备,由同意个设备驱动程序管理。主编号标识设备相连的驱动程序,次编号被内核用来决定引用哪个设备。
设备编号用dev_t表示(linux/types.h 32位,其中12位表示主设备号,20位表示次设备号)。
由dev_t获得主设备号或次设备号:MAJOR(dev_t dev); MINOR(dev_t dev)
已知主设备号和次设备号来获取dev_t类型:MKDEV(int major, int minor)
2.分配一个设备号范围
在建立一个字符驱动时你的驱动需要做的第一件事是获取一个或多个设备编号来使用. 为此目的的必要的函数是 register_chrdev_region, 在 <linux/fs.h>中声明:
int register_chrdev_region(dev_t first, unsigned int count, char *name);
这里, first 是你要分配的起始设备编号. first 的次编号部分常常是 0, 但是没有要求是那个效果. count 是你请求的连续设备编号的总数. 注意, 如果 count 太大, 你要求的范围可能溢出到下一个次编号; 但是只要你要求的编号范围可用, 一切都仍然会正确工作. 最后, name 是应当连接到这个编号范围的设备的名子; 它会出现在 /proc/devices 和sysfs 中.
也可以动态分配设备编号:int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name); 调用成功后dev会保存已分配的第一个编号。
释放设备编号:void unregister_chrdev_region(dev_t first, unsigned int count);
3.注册字符设备
内核使用struct cdev结构表示字符设备,所以在内核调用该设备操作之前,需要分配并注册一个或者多个该结构。
1)定义设备的结构:
struct my_dev {
struct cdev cdev; //此处如果定义的是指针类型,则需要申请分配内存
}my_dev;
//my_dev ->cdev = cdev_alloc(); //如果cdev是指针则需要这一步
my_dev->cdev.ops = &my_fops;//设备文件操作函数
my_dev->cdev.owner = THIS_MODULE;
2)再调用cdev_init(struct cdev *cdev, struct file_operations *fops);
3)调用cdev_add(struct cdev *dev, dev_t num, unsigned int count);//count表示设备提供的从设备号的数量。
为从系统去除一个字符设备, 调用: void cdev_del(struct cdev *dev);
4. 设备控制--ioctl
大部分设备驱动程序应该提供给应用程序控制设备的功能,即应用程序可以通过ioctl系统调用来控制设备。
1)接口
用户层的ioctl接口如下,用户程序所作的只是通过命令码告诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情。
int ioctl(int fd,unsigned long cmd,...);
/*
fd:文件描述符
cmd:控制命令
...:可选参数:插入*argp,具体内容依赖于cmd
*/
设备驱动所对应的ioctl 如下:
int (*ioctl) (struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg);
/*
inode与filp两个指针对应于应用程序传递的文件描述符fd,这和传递open方法的参数一样。
cmd 由用户空间直接不经修改的传递给驱动程序
arg 可选。
*/
在驱动程序中实现的ioctl函数体内,实际上是有一个switch {case}结构,每一个case对应一个命令码,做出一些相应的操作。
2)命令码
在Linux核心中是这样定义一个命令码的:
____________________________________
| 设备类型 | 序列号 | 方向 | 数据尺寸 |
|----------|--------|------|-------- |
| 8 bit | 8 bit |2 bit|8~14 bit|
|----------|--------|------|-------- |
这样一来,一个命令就变成了一个整数形式的命令码。但是命令码非常的不直观,所以Linux Kernel中提供了一些宏,这些宏可根据便于理解的字符串生成命令码,或者是从命令码得到一些用户可以理解的字符串以标明这个命令对应的设备类型、设备序列号、数据传送方向和数据传输尺寸。
//nr为序号,datatype为数据类型,如int
_IO(type, nr ) //没有参数的命令
_IOR(type, nr, datatype) //从驱动中读数据
_IOW(type, nr, datatype) //写数据到驱动
_IOWR(type,nr, datatype) //双向传送
4.应用程序与内核数据交换
copy_to_user -- Copy a block of data into user space.
copy_from_user -- Copy a block of data from user space.
get_user -- Get a simple variable from user space.
put_user -- Write a simple value into user space.
原型如下:
unsigned long copy_from_user (void * to, const void __user * from, unsigned long n);//to是kernel的目标地址,from是用户空间源地址,n是copy的bytes
unsigned long copy_to_user (void __user * to, const void * from, unsigned long n);//to是用户空间目标地址,from是kernel的源地址,n是copy的bytes
put_user ( x, ptr);//x是copy到用户空间的值,ptr是用户空间的目标地址
get_user ( x, ptr);//x是copy到kernel的值,ptr是用户空间的源地址
5.编译驱动
makfile 如下:
ifneq ($(KERNELRELEASE),)
obj-m := fellowcdev.o
module-objs := fellowcdev.o
else
KDIR:=/lib/modules/$(shell uname -r)/build
PWD:=$(shell pwd)
default:
make -C $(KDIR) M=$(PWD) modules
endif
obj-m表示编译出来的模块名为fellowcdev.ko
module-objs表示编译时的源代码为fellowcdev.c.如果有多个源文件,则module-obj := file1.o file2.o
make -C $(KDIR) M=$(PWD) modules,这个命令开始是改变它的目录到用 -C 选项提供的目录下( 就是说, 你的内核源码目录 ). 它在那里会发现内核的顶层 makefile. 这个 M=选项使 makefile在试图建立模块目标前, 回到你的模块源码目录.
这个 makefile 在一次典型的编译中要被读 2 次. 当从命令行中调用这个 makefile , KERNELRELEASE 变量没有设置.执行else后的过程,到存放内核的目录执行其makefile,在执行内核目录makefile过程中会定义KERNELRELEASE,然后M=$(PWD)表示返回到当前目录,再次执行makefile,modules表示编译成模块的意思。而此时KERNELRELEASE已定义在第 2 次读, makefile 设置 obj-m, 并且内核的 makefile 文件完成实际的编译模块工作.
6.实现
fellow_cdev.h
#ifndef _FELLOW_CDEV_H_
#define _FELLOW_CDEV_H_
#include <linux/ioctl.h>
//主设备号
#define FELLOW_CDEV_MAJOR 199
#define FELLOW_CDEV_NR 2
//命令码
#define FELLOW_CDEV_IOC_MAGIC 'f'
#define FELLOW_CDEV_IOC_PRINT _IO(FELLOW_CDEV_IOC_MAGIC, 1)
#define FELLOW_CDEV_IOC_GET _IOR(FELLOW_CDEV_IOC_MAGIC, 2, int)
#define FELLOW_CDEV_IOC_SET _IOW(FELLOW_CDEV_IOC_MAGIC, 3, int)
#define FELLOW_CDEV_IOC_MAXNR 3
#endif
fellowcdev.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include "fellow_cdev.h"
struct fellowcdev_dev{//设备文件结构体
struct cdev cdev;
int val;
char *data;
unsigned int size;
};
struct fellowcdev_dev *fellowcdev_devp;
int fellowcdev_open(struct inode *inode, struct file *filep)
{
//struct fellowcdev_dev *dev;
int minor = MINOR(inode->i_rdev);
if (minor >= FELLOW_CDEV_NR)
return -ENODEV;
// dev = container_of(inode->i_cdev, struct fellowcdev_dev, cdev);
// dev = &fellowcdev_devp[minor];
// filep->private_data = dev;
filep->private_data = fellowcdev_devp;
return 0;
}
int fellowcdev_release(struct inode *inode, struct file *filep)
{
return 0;
}
ssize_t fellowcdev_read(struct file *filep, char __user *buf, size_t count, loff_t *f_pos)
{
int ret = 0;
printk("fellowcdev_read\n");
loff_t pos = *f_pos;
struct fellowcdev_dev *devp = (struct fellowcdev_dev *)(filep->private_data);
if (pos > devp->size)
{
ret = -EINVAL;
goto fail;
}
if (count > devp->size - pos)
{
count = devp->size - pos;
}
pos += count;
if (copy_to_user(buf, devp->data + *f_pos , count))
{
ret = -EFAULT;
goto fail;
}
*f_pos = pos;
return count;
fail:
return ret;
}
ssize_t fellowcdev_write(struct file *filep,const char __user *buf, size_t count, loff_t *f_pos)
{
printk("fellowcdv_write:%s, pos:%d\n", buf,(int) *f_pos);
int ret = 0;
loff_t pos = *f_pos;
struct fellowcdev_dev *devp = (struct fellowcdev_dev *)(filep->private_data);
if (pos > devp->size)
{
ret = -EINVAL;
goto fail;
}
if (count > devp->size - pos)
{
count = devp->size - pos;
}
pos += count;
if (copy_from_user( devp->data + *f_pos, buf , count))
{
ret = -EFAULT;
goto fail;
}
printk("write data:%s\n", devp->data);
*f_pos = pos;
return count;
fail:
return ret;
}
loff_t fellowcdev_llseek(struct file *filep, loff_t off, int whence)
{
loff_t pos = filep->f_pos;
struct fellowcdev_dev *devp = (struct fellowcdev_dev *)(filep->private_data);
switch(whence)
{
case 0:
pos = off;
break;
case 1:
pos += off;
break;
case 2:
default:
return -EINVAL;
break;
}
if (pos > devp->size || pos < 0)
return -EINVAL;
filep->f_pos = pos;
return filep->f_pos;
}
long fellowcdev_ioctl(struct file *filep,unsigned int cmd,unsigned long arg)
{
int ret = 0;
struct fellowcdev_dev *devp = (struct fellowcdev_dev *)(filep->private_data);
if (_IOC_TYPE(cmd) != FELLOW_CDEV_IOC_MAGIC)
return -EINVAL;
if (_IOC_NR(cmd) > FELLOW_CDEV_IOC_MAXNR)
return -EINVAL;
switch(cmd)
{
case FELLOW_CDEV_IOC_PRINT:
printk("FELLOW_CDEV_IOC_PRINT\n");
printk("val:%d, size: %d, data: %s\n", devp->val, devp->size, devp->data);
break;
case FELLOW_CDEV_IOC_SET:
printk("FELLOW_CDEV_IOC_SET\n");
ret = get_user(devp->val, (int *)arg);
printk("set val:%d\n", devp->val);
break;
case FELLOW_CDEV_IOC_GET:
printk("FELLOW_CDEV_IOC_GET\n");
ret = put_user(devp->val, (int *)arg);
break;
default:
return -EINVAL;
}
return ret;
}
static const struct file_operations fellowcdev_fops ={
.owner = THIS_MODULE,
.open = fellowcdev_open,
.release = fellowcdev_release,
.read = fellowcdev_read,
.write = fellowcdev_write,
.llseek = fellowcdev_llseek,
.unlocked_ioctl = fellowcdev_ioctl,
};
static int fellowcdev_init(void)
{
printk("fellowcdev_init\n");
dev_t devno = MKDEV(FELLOW_CDEV_MAJOR, 0);
int ret = 0;
if ((ret = register_chrdev_region(devno, 1, "fellowcdev") < 0))
return ret;
fellowcdev_devp = kmalloc(sizeof(struct fellowcdev_dev), GFP_KERNEL);
if (!fellowcdev_devp)
{
ret = -ENOMEM;
goto fail;
}
fellowcdev_devp->size = 1024;
fellowcdev_devp->data = kmalloc(1024, GFP_KERNEL);
memset(fellowcdev_devp->data, 0, 1024);
cdev_init(&(fellowcdev_devp->cdev), &fellowcdev_fops);
fellowcdev_devp->cdev.owner = THIS_MODULE;
fellowcdev_devp->cdev.ops = &fellowcdev_fops;
cdev_add(&(fellowcdev_devp->cdev), devno, 1);
return 0;
fail:
unregister_chrdev_region(devno, 1);
return ret;
}
static void fellowcdev_exit(void)
{
cdev_del(&(fellowcdev_devp->cdev));
kfree(fellowcdev_devp);
unregister_chrdev_region(MKDEV(FELLOW_CDEV_MAJOR,0),1);
}
MODULE_AUTHOR("fellow");
MODULE_LICENSE("GPL");
module_init(fellowcdev_init);
module_exit(fellowcdev_exit);
app.c
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include "fellow_cdev.h"
int main(void)
{
int fd = open("/dev/fellowcdv", O_RDWR);
if (fd < 0)
{
printf("open fail:%s\n", strerror(errno));
return -1;
}
int ret = 0;
char data[1024] = {'\0'};
if ((ret = write(fd, "fellow_data", sizeof("fellow_data"))) < 0)
{
printf("write fail:%s\n", strerror(errno));
}
if ((ret = lseek(fd, 0, 0)) < 0)
{
printf("lseek fail:%s\n", strerror(errno));
}
if ((ret = read(fd, data, 1024)) < 0)
{
printf("read fail:%s\n", strerror(errno));
}
printf("read data:%s\n", data);
int val = 18;
if ((ret = ioctl(fd, FELLOW_CDEV_IOC_SET, &val)) < 0)
{
printf("ioctl set fail:%s\n", strerror(errno));
}
val = 0;
if ((ret = ioctl(fd, FELLOW_CDEV_IOC_GET, &val)) < 0)
{
printf("ioctl set fail:%s\n", strerror(errno));
}
printf("get val:%d\n", val);
val = 0;
if ((ret = ioctl(fd, FELLOW_CDEV_IOC_PRINT, &val)) < 0)
{
printf("ioctl set fail:%s\n", strerror(errno));
}
close(fd);
return ret;
}
用5中的Makefie编译就会在当前目录下生成fellowcdev.ko
sudo insmod fellowcdev.ko
insmod后,cat /proc/devices就可以看到fellowcdev.ko 加载成功。
要在/dev/下显示设备,需要建立设备节点
sudo mknod /dev/fellowcdv c 199 0
199即为主设备号,与设备驱动里面写的一致。
运行sudo ./app
查看驱动程序log
dmesg | tail -n 10
转载于:https://www.cnblogs.com/fellow1988/p/6219389.html
设备驱动基础学习--字符驱动实现相关推荐
- 树莓派Linux内核编译、文件系统、Linux内核驱动基础框架、驱动测试步骤、总线地址
树莓派高阶开发课程 1. ubuntu18.04版本安装 让程序猿搭建环境太搞笑了,轻松easy! ========================================= ...
- 字符设备驱动基础篇5——驱动如何操控硬件(动静态映射操作LED)
以下内容源于朱有鹏嵌入式课程的学习,,如有侵权,请告知删除. 参考资料:http://www.cnblogs.com/biaohc/p/6575074.html 这里的映射,是指物理地址和虚拟地址的对 ...
- 深入浅出linux驱动,Linux Kernel 字符驱动的深入浅出讲解
注:在写Linux 内核驱动,并将这一过程发生的技术,和菜鸟们可能会碰的问题进行一次解释,希望对后来都有用,阅读此文需要一定Linux基础,或者不要浪费时间,好品德看完全文要顶一下,看完此文你应该肻定 ...
- linux驱动初探之字符驱动
关键字:字符驱动.动态生成设备节点.helloworld linux驱动编程,个人觉得第一件事就是配置好平台文件,这里以字符设备,也就是传说中的helloworld为例~ 此驱动程序基于linux3. ...
- 字符设备驱动基础篇0——驱动开发初体验
以下内容源于朱有鹏嵌入式课程的学习,如有侵权,请告知删除. 参考资料:http://www.cnblogs.com/biaohc/p/6575074.html 1.驱动开发的准备工作 (1)内核源码树 ...
- 设备驱动基础学习--/proc下增加节点
在需要创建一个由一系列数据顺序组合而成的/proc虚拟文件或一个较大的/proc虚拟文件时,推荐使用seq_file接口. 数据结构struct seq_fille定义在include/linux/s ...
- Windows驱动开发学习记录-驱动中快速重启关闭计算机之一
引言 关于快速重启和关闭计算机,网上有不少软件在Ring3下调用ZwShutdownSystem (NtShutdownSystem)来实现,虽然速度很快,但还至少经历一些流程,比如向设备驱动发送停机 ...
- Linux字符驱动开发学习总结
linux驱动编写(虚拟字符设备编写) 昨天我们说了一些简单模块编写方法,但是终归没有涉及到设备的编写内容,今天我们就可以了解一下相关方面的内容,并且用一个实例来说明在linux上面设备是如何编写的. ...
- linux驱动程序开发指南-字符驱动介绍
概述: 在linux系统中设备驱动程序通常是作为应用层和设备层的中间层软件,驱动程序的主要功能是实现应用层访问硬件设备的具体操作接口,通过调用驱动程序,上层应用程序可以采用统一的接口访问各种硬件设备. ...
最新文章
- Java 源码中 unchecked 什么意思
- (35)23种设计模式研究之六【命令模式】
- iview table 方法若干
- T-SQL之公用表表达式(CTE)
- QCon速递:Xen漏洞热补丁修复、异地双活、ODPS新功能与金融互联网
- 如何解决gcc版本冲突?
- 软件运行 计算机硬件环境,软件运行环境该怎么写
- 远程培训教程之POWERPOINT2003
- IDEA快捷键大全 + 动图演示,提升效率
- 第六届蓝桥杯A组C/C++ 第三题 奇妙的数字
- HART协议命令与UART串口解析
- vue执行mounted_vue mounted方法执行多次问题的解决方案
- 网页嵌入flash动画视频的几种方法
- JS学习之BOM | 常见网页特效 | 轮播图 | 返回顶部 | 筋斗云案例
- 音视频播放器开发——实现变速播放
- U盘做为系统盘安装系统,出现start booting from usb device和boot failed解决方案
- 扩展数组方法 给数组原形链追加方法(原型链中的this)
- 中国法定节假日天数以及世界各国放假天数
- 相位干涉仪测向的基础理论
- redis并发处理慢
热门文章
- VB 源码 删除重复行程序 函数
- Windows下Redis安装的那些事儿!
- Chrome十周年,作了一次死
- 团队阅读之——Google’s Hybrid Approach to Research
- 9.4. Default Gateway
- 4.0之后的hibernate获取sessionFactory
- Git修改提交的用户名和Email
- 学习日记0802函数递归,三元表达式,列表生成式,字典生成式,匿名函数+内置函数...
- 从零开始写项目第八篇【将未完成的项目发布在Tomcat上】
- 2017年网络犯罪现状分析报告