1.Linux设备驱动分类
按管理的设备硬件来分:字符设备,块设备,网络设备。
1)字符设备
按字节流访问,能够顺序访问,也能够指定位置访问,如:按键 串口 终端 触摸屏 LCD等。
2)块设备
在Unix系统下,块设备按一定的数据块进行访问,数据块为512bytes 1K等。
在Linux下,块设备既可以按数据块的方式访问,也可以安字节流访问, 他和字符设备的区别在于linux系统中描述块设备和字符设备的数据结构和操作方法是不一样的。
3)网络设备
网卡,网络设备一般要结合tcp/ip协议栈来实现。
2.字符设备驱动
驱动程序的作用
1)管理对应的硬件。
2)给用户提供访问硬件的操作的方法(接口)。
3.应用程序如何访问硬件
硬件设备在linux系统下,会以设备文件的形式存在,设备文件(字符和块)在/dev,那么应用程序要访问硬件其实就是访问对应的设备文件。
4.应用程序如何访问设备文件
通过调用系统调用来实现对其的访问,访问设备文件和访问普通文件的方式是一样的
open read write ioctl mmap close......
5.应用程序如何通过设备文件在非常多的驱动中找到对应的硬件驱动
设备文件本身包含一些属性:
1)设备文件是字符设备文件(c)还是块设备文件(b)
2)设备文件还包括主设备号和次设备号这两个重要的属性
3)应用程序就是通过主设备号找到对应的驱动程序
4)一个驱动程序只有一个主设备号
6.次设备号的作用
次设备号用于找到具体要访问的设备个体
设备号:主设备号和次设备号
数据类型:
#include <linux/fs.h>
dev_t(unsigned int);//32byte
高12位:主设备号
低20位:次设备号
设备号操作宏
MAJOR:获取设备号的主设备号;
MINOR:获取设备号的次设备号;
MKDEV:通过主设备号和次设备号生成设备号。
设备号是属于系统资源,如果要实现驱动和设备号的绑定,首先必须向内核申请设备号资源,只有完成申请之后,才能和驱动程序绑定。
7.如何向内核申请设备号
静态分配和动态分配
7.1 静态分配
1)查看哪些主设备号已经被使用
cat /proc/devices
Character devices:字符设备
1 mem
5 /dev/tty
5 /dev/console
5 /dev/ptmx
10 misc
13 input
21 sg
Block devices:块设备
1 ramdisk
259 blkext
7 loop
8 sd
31 mtdblock
65 sd
2)然后根据你的设备个数分配次设备号,一般次设备号都从0开始
dev_t dev = MKDEV(主设备号,次设备号);
3)调用register_chrdev_region;向内核申请
注:主设备号不能为0
7.2 动态分配
调用alloc_chrdev_region 直接向内核申请
释放设备号
unregister_chrdev_region
8.linux字符设备驱动四个重要的数据结构
1)struct file
作用:描述文件打开以后的状态属性
生命周期:从open打开成功内核创建,到close关闭内核销毁
重要的成员:
const struct file_operations *f_op;
unsigned int f_flags; //文件的操作属性
loff_t f_pos; //文件的操作位置
2) struct file_operations
定义文件的操作函数集合,用户空间调用对应的系统调用时就会调用file结构体中file_operations成员中对应的函数
3) struct inode
作用:描述一个文件的物理结构
生命周期:文件存在,内核创建,文件销毁,内核销毁对应的inode
重要的成员:
dev_t i_rdev; //存放设备号
struct cdev *i_cdev; //指向一个字符设备
4) struct cdev
表示一个字符设备驱动
重要的数据成员:
const struct *ops; //字符设备驱动的操作函数集合
dev_t dev; //设备号
一个文件只有一个inode,可以有多个file
9. struct file如何找到cdev中的file_operations
1)应用程序调用open,最终调用sys_open
2)sys_open创建file结构体,描述文件的打开信息
3)通过主设备号找到对应的cdev
4)将cdev中的file_operations成员赋值给file中的file_operations成员
5)sys_open最后调用cdev中的file_operations中的open函数
6)以后用户空间对设备文件的所有操作访问的都是cdev中的file_operations成员的操作方法,也就是对硬件的操作.
read --> sys_read --> file->f_op->read --> cdev->ops->read
write --> sys_write --> file->f_op->write --> cdev->ops->write
10.如何将cdev添加到内核
1)分配初始化 struct file_operations
struct file_operations cdd_fops = {
.open = ...
.read = ....
.write = ...
.....
};
2)分配初始化struct cdev
struct cdev cdd_cdev;
cdev_init(&cdd_cdev,&cdd_fops);
//cdd_cdev.ops = cdd_fops;
3)将cdev添加到内核
cdev_add(&cdd_cdev,设备号,设备个数);
结果就是讲cdd_cdev添加到内核的cdev数组中,下标是以设备号为索引
一旦完成cdev的添加,内核中就有了一个真实的字符设备驱动
11.设备文件的创建
静态创建:
mknod cdd c 251 0
动态创建:
创建设备类:class_create
创建设备文件:device_create
12.实现一个字符设备驱动的步骤
1)申请设备号(静态/动态)
2)注册cdev到内核
3)创建设备类
4)创建设备文件
13.实例
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
MODULE_LICENSE("GPL v2");
#define CDD_MAJOR 100
#define CDD_MINOR 0
#define CDD_COUNT 1
dev_t dev;
u32 cdd_minor = 0;
//实例化cdev
struct cdev cdd_cdev;
struct class *dev_class = NULL;
struct device *dev_device = NULL;
int cdd_open(struct inode *inode, struct file *filp)
{
printk("enter cdd_open!\n");
return 0;
}
ssize_t cdd_read(struct file *filp, char __user *buf, size_t count, loff_t *offset)
{
printk("enter cdd_read!\n");
return 0;
}
ssize_t cdd_write(struct file *filp, const char __user *buf, size_t count, loff_t *offset)
{
printk("enter cdd_write!\n");
return 0;
}
int cdd_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long data)
{
printk("enter cdd_ioctl!\n");
return 0;
}
int cdd_release(struct inode *inode, struct file *filp)
{
printk("enter cdd_release!\n");
return 0;
}
struct file_operations cdd_fops =
{
.owner = THIS_MODULE,
.open = cdd_open,
.read = cdd_read,
.write = cdd_write,
.ioctl = cdd_ioctl,
.release = cdd_release,
};
int __init cdd_init(void)
{
int ret = 0;
if(cdd_minor)//静态分配
{
dev = MKDEV(CDD_MAJOR,CDD_MINOR);
//向内核申请
ret = register_chrdev_region(dev, CDD_COUNT, "cdd_demo");
}
else//动态分配
{
ret = alloc_chrdev_region(&dev, cdd_minor, CDD_COUNT, "cdd_demo");
}
if(ret<0)
{
printk("register_chrdev failed!\n");
goto faiure_register_chrdev;
}
//初始化cdev
cdev_init(&cdd_cdev, &cdd_fops);
//向内核注册cdev
ret = cdev_add(&cdd_cdev, dev, CDD_COUNT);
if(ret<0)
{
printk("cdev_add failed!\n");
goto failure_cdev_add;
}
/*动态创建设备文件*/
// 1.创建设备类
//会在/sys/class目录下创建"cdd_class"为名的文件夹
dev_class = class_create(THIS_MODULE, "cdd_class");
if(IS_ERR(dev_class))
{
ret = PTR_ERR(dev_class);
goto failure_class_create;
}
// 2.创建设备文件
//会在/dev目录下创建对应的设备文件
dev_device = device_create(dev_class, NULL, dev, NULL, "cdd");
if(IS_ERR(dev_device))
{
ret = PTR_ERR(dev_device);
goto failure_device_create;
}
return 0;
failure_device_create:
class_destroy(dev_class);
failure_class_create:
cdev_del(&cdd_cdev);
failure_cdev_add:
unregister_chrdev_region(dev, CDD_COUNT);
faiure_register_chrdev:
return ret;
}
void __exit cdd_exit(void)
{
//注销设备
device_destroy(dev_class, dev);
//注销设备类
class_destroy(dev_class);
//注销cdev
cdev_del(&cdd_cdev);
//注销设备号
unregister_chrdev_region(dev, CDD_COUNT);
}
module_init(cdd_init);
module_exit(cdd_exit);