设备驱动的种类
linux系统中的驱动分为三种:
字符设备驱动:按照字节流来访问,只能顺序访问不能无序访问的设备
(LED LCD(帧缓存) 鼠标 键盘 CAMERA(V4L2))
帧缓存的每一存储单元对应屏幕上的一个像素,整个帧缓存对应一帧图像 。帧缓冲(framebuffer)是Linux为显示设备提供的一个接口,把显存抽象后的一种设备,他允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作
V4L2 是专门为 linux 设备设计的一套视频框架,其主体框架在 linux 内核,可以理解为是整个 linux 系统上面的视频源捕获驱动框架
块设备驱动:按照BLOCK(以块为最小单位,一块是512字节)来访问,可以顺序访问也可以无序访问的设备
(EMMC FLASH U盘 硬盘)
网卡设备驱动:网卡设备驱动没有设备节点,通过软件代码操作网卡硬件实现数据收发的代码
在linux中没有关于网卡设备驱动的文件,是因为在linux源码中直接移植的其他系统的关于网卡的设备节点
(网卡芯片DM9000)
分步实现字符设备驱动
register_chrdev可以实现字符设备的注册,为什么还要分步实现呢?
这是因为register_chrdev一次申请256个设备号,我们在工作开发过程中并不会使用那么多的设备号,大大浪费了资源。分布实现可以根据自己的实际需求来申请。
为了更好地演示现象,这里也写了一个应用程序来验证。
mycdev.c
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#define CNAME "mycdev"
struct cdev* cdev;
unsigned int major = 0;
unsigned int minor = 0;
const int count = 3;
struct device* dev;
struct class* cls;
char kbuf[128] = { 0 };
int mycdev_open(struct inode* inode, struct file* fiel)
{
printk("%s %s %d\n", __FILE__, __func__, __LINE__);
return 0;
}
ssize_t mycdev_read(struct file* file, char __user* ubuf, size_t size, loff_t* offs)
{
int ret = 0;
printk("%s %s %d\n", __FILE__, __func__, __LINE__);
if (size > sizeof(kbuf)) {
size = sizeof(kbuf);
}
ret = copy_to_user(ubuf, kbuf, size);
if (ret) {
printk("copy data to user error\n");
return -EIO;
}
return size;
}
ssize_t mycdev_write(struct file* file, const char __user* ubuf,
size_t size, loff_t* offs)
{
int ret = 0;
printk("%s %s %d\n", __FILE__, __func__, __LINE__);
if (size > sizeof(kbuf)) {
size = sizeof(kbuf);
}
ret = copy_from_user(kbuf, ubuf, size);
if (ret) {
printk("copy data form user error\n");
return -EIO;
}
return size;
}
int mycdev_close(struct inode* iode, struct file* file)
{
printk("%s %s %d\n", __FILE__, __func__, __LINE__);
return 0;
}
const struct file_operations fops = {
.open = mycdev_open,
.read = mycdev_read,
.write = mycdev_write,
.release = mycdev_close,
};
static int __init mycdev_init(void)
{
// 1.分配对象
dev_t devno;
int ret = 0, i = 0;
cdev = cdev_alloc();
if (NULL == cdev) {
printk("cdev alloc error\n");
ret = -ENOMEM;
goto ERR1;
}
// 2.对象的初始化
cdev_init(cdev, &fops);
// 3.申请设备号
if (0 < major) {
//静态指定
ret = register_chrdev_region(MKDEV(major, minor),
count, CNAME);
if (ret) {
printk("static:regiseter chrdev error\n");
ret = -EAGAIN;
goto ERR2;
}
} else {
//动态申请
ret = alloc_chrdev_region(&devno, minor, count, CNAME);
if (ret) {
printk("dynamic: register chrdev error\n");
ret = -EAGAIN;
goto ERR2;
}
major = MAJOR(devno);
minor = MINOR(devno);
}
// 4.注册
ret = cdev_add(cdev, MKDEV(major, minor), count);
if (ret) {
printk("register char device error\n");
goto ERR3;
}
// 5.自动创建准备节点
cls = class_create(THIS_MODULE, CNAME);
if (IS_ERR(cls)) {
printk("class create error\n");
ret = PTR_ERR(cls);
goto ERR4;
}
for (i = 0; i < count; i++) {
dev = device_create(cls, NULL, MKDEV(major, i),
NULL, "mycdev%d", i);
if (IS_ERR(dev)) {
printk("device create error\n");
ret = PTR_ERR(dev);
goto ERR5;
}
}
return 0;
ERR5:
//防止出现中间创建失败的情况
for (--i; i >= 0; i--) {
device_destroy(cls, MKDEV(major, i));
}
class_destroy(cls);
ERR4:
cdev_del(cdev);
ERR3:
unregister_chrdev_region(MKDEV(major, minor), count);
ERR2:
kfree(cdev);
ERR1:
return ret;
}
static void __exit mycdev_exit(void)
{
int i = 0;
for (i = 0; i < count; i++) {
device_destroy(cls, MKDEV(major, i));
}
class_destroy(cls);
cdev_del(cdev);
unregister_chrdev_region(MKDEV(major, minor), count);
kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
test.c
#include <head.h>
int main(int argc, const char* argv[])
{
int fd;
char ubuf[128] = { 0 };
if ((fd = open("/dev/mycdev0", O_RDWR)) == -1)
PRINT_ERR("open error");
while (1) {
printf("input > ");
fgets(ubuf, sizeof(ubuf), stdin);
ubuf[strlen(ubuf) - 1] = '\0';
if (!strcmp(ubuf, "q")) {
break;
}
write(fd, ubuf, sizeof(ubuf));
memset(ubuf, 0, sizeof(ubuf));
read(fd, ubuf, sizeof(ubuf));
printf("ubuf = %s\n", ubuf);
}
close(fd);
return 0;
}
Makefile
arch?=x86
modname?=demo
ifeq ($(arch),x86)
KERNELDIR:= /lib/modules/$(shell uname -r)/build/ #ubuntu能够安装的驱动
else
KERNELDIR:= /home/linux/linux-5.10.61/ #开发板能够安装的驱动
endif
PWD:=$(shell pwd)
all:
make -C $(KERNELDIR) M=$(PWD) modules
clean:
make -C $(KERNELDIR) M=$(PWD) clean
obj-m:=$(modname).o