双河市网站建设_网站建设公司_跨域_seo优化
2026/1/5 19:35:25 网站建设 项目流程

文章目录

  • 前言
  • 一、Linux驱动基础
    • 1.Linux驱动分类
    • 2.Linux第一个驱动-Hello World
    • 3.Linux内核驱动程序的编译方式
    • 4.make menuconfig图形化配置
  • 二、杂项设备驱动
    • 1.Linux三大设备驱动
    • 2.杂项设备驱动
  • 三、应用层和内核层数据传输
    • 1.Linux一切皆文件
    • 2.假如file_operations中没有read,但在应用层调用了read设备节点会发生什么
    • 3.应用层和内核层不能直接进行数据传输
  • 四、Linux虚拟地址到物理地址映射
    • 1.单片机和裸机操作硬件
    • 2.使能MMU优点
    • 3.MMU非常复杂,如何完成物理地址到虚拟地址的转换
    • 4.如何查看哪些物理地址被映射过了
  • 五、第一个相对完整的驱动实践编写
    • 1.需求
    • 2.流程
    • 3.源码
    • 4.最简单的驱动
  • 六、驱动模块传递参数
    • 1.什么是驱动传参
    • 2.驱动传参有什么作用
    • 3.怎么给我们的驱动传参
    • 4.如果多传递进去参数会发生什么
  • 七、字符设备
    • 1.申请字符类设备号
    • 2.注册字符类设备
    • 3.自动创建设备节点
    • 4.字符设备和杂项设备总结回顾
  • 总结


前言

本文介绍了Linux驱动入门的基础知识


提示:以下是本篇文章正文内容,下面案例可供参考

一、Linux驱动基础

1.Linux驱动分类

① 字符设备驱动(最重要,工作中基本上遇到的都是字符设备驱动)
② 块设备驱动
③ 网络设备驱动

2.Linux第一个驱动-Hello World

驱动分为四个部分:

第一步,包含头文件
#include <linux/init.h> 包含宏定义的头文件#include <linux/moudle.h> 包含初始化加载模块的头文件
  • 驱动模块的入口和出口
第二步,驱动模块的入口和出口:
module_init();
module_exit();
  • 声明信息
第三步,声明模块拥有开源许可证:
MODULE_LICENSE("GPL");
  • 功能实现
//加载驱动打印Hello world 卸载驱动打印再见
//(内核模块加载的时候打印 hello world  内核模块卸载的时候打印bye bye)
内核打印函数不能用printf,因为内核没有办法使用C语言库
static int hello_init(void)
{
//内核打印函数
printk("hello world\n");
return 0;
}
static int hello_exit(void)
{
printk("bye bye\n");
}

3.Linux内核驱动程序的编译方式

  1. 第一种方法,把驱动编译成模块,然后使用命令把驱动加载到内核里去

    第一步:先写一个Makefile

    Makefile:
    obj-m(-m意思是把驱动编译成模块)
    KDIR:=/home/book/100ask_imx6ull-sdk/Linux-4.9.88(指定内核路径)
    PWD?=$(shell pwd)
    all:
    (tab)make -C  $(KDIR) M=$(PWD) modules
    //切换到内核的路径下(make会进入内核源码的路径下,编译模块需要这个环境,然后把当前路径下的代码给编译成模块)

    第二步:编译驱动

    编译驱动之前需要注意的问题:
    1.内核源码一定要先编译通过
    2.我们编译驱动模块用的内核源码一定要和我们的开发板上运行的内核镜像是同一套
    3.看一下乌邦图的环境是不是ARM的(内核源码路径下 make menuconfig)如果不是ARM要输入export ARCH=arm
    4.make
    设置环境变量:
    export ARCH=arm
    export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-gcc
    编译成功之后就可以看见ko文件,那么这个ko文件就是编译好的驱动
    5.加载驱动模块 insmod helloworld.ko,使用insmod命令
    6.查看驱动模块:lsmod
    7.卸载驱动模块:rmmod helloworld
    echo 8 | sudo tee /proc/sys/kernel/printk
    临时降低控制台日志阈值(因为直接printk可能看不到打印信息)
    使用案例:
    USB转串口(ch340驱动)
    1.先去内核源码搜索,如果有的话,我们可以直接选择这个驱动然后直接使用
    2.假设没有这个驱动,我们需要自己编译一个驱动,然后加载到内核里面去运行
    CH340 Linux驱动下载链接:
    ...(自己找)
    (驱动文件、Makefile文件)
    按照流程编译使用(修改名字和开发板对应的内核源码)
  2. 第二种方法,直接把驱动编译到内核,内核在运行就会自动加载驱动

    把helloworld驱动编译进内核:

    1.source “drivers/redled/Kconfig”
    他会包含drivers/redled/这个路径下的驱动文件,方便我们对菜单进行管理
    2.config LED_4412
    配置选项的名称,CONFIG_LED_4412,
    3.tristate表示驱动的状态,把驱动编译成模块(M),把驱动编译到内核(*),不编译(空)
    与之对应的是bool,把驱动编译到内核,不编译
    “Led Support for GPIO Led” —— make menuconfig显示的名字
    A depends on B
    表示只有在选择B的时候才可以选择A
    4.比如想直接去掉LED相关的,直接修改.config文件可以吗
    可以,但不推荐。如果有依赖的话直接修改.config文件是不成功的
    5.select
    反向依赖,改选项被选中时,后面的定义也会被选中
    6.
    help
    This option enable support for led
    帮助信息

4.make menuconfig图形化配置

① 怎么进入到make menuconfig图形化界面

首先要进入到内核源码的路径下
接着输入make menuconfig进入图形化界面

② make menuconfig图形化界面操作

{1.搜索功能:输入”/”即可弹出搜索界面,然后输入我们想要的搜索的内容即可2.配置驱动状态1)把驱动编译成模块,用 M 来表示2)把驱动编译到内核里面,用 * 来表示3)不编译。我们可以使用“空格”按键来配置这三种不同的状态3.退出退出分为保存退出和不保存退出4.和make menuconfig有关的文件1)Makefile里面是编译规则,告诉我们在make的时候要怎么编译,相当于怎么做2)Kconfig 内核配置的时候作为配置的选项,相当于菜单3).config配置完内核以后生成的配置选项,相当于点的菜5.make menuconfig会读取哪个目录下的Kconfig文件arch/    $ARCH目录下的Kconfig (export ARCH=...)/home/book/100ask_imx6ull-sdk/Linux-4.9.88/arch/arch/arm/configs目录下有很多配置文件(饭店特色菜,不知道怎么配置就使用默认的 : cp ...config .config(到内核源码目录下))6.为什么复制成.config而不是其他文件因为内核会默认读取Linux内核根目录下的.config作为默认的配置选项,所以不能改名字7.我们复制的这个默认的配置选项不符合我们的要求怎么办通过make menuconfig来调出Kconfig,配置完成后保存退出会自动更新到.config文件里8.怎么和Makefile文件建立的关系当我们make menuconfig保存退出以后,Linux会将所有的配置选项以宏定义的形式保存在include/generated下面的autoconf.h里面
}

二、杂项设备驱动

1.Linux三大设备驱动

字符设备: IO的传输过程是以字符为单位的,没有缓冲,比如I2C、SPI都是字符设备
块设备: IO的传输过程是以块为单位的,跟存储相关的都属于块设备,比如tf卡
网络设备: 与前两个不一样,是以socket套接字来访问的

2.杂项设备驱动

杂项设备是字符设备的一种,可以自动生成设备节点(dev目录下的就是设备节点)
系统里面有很多杂项设备 , 输入cat /proc/misc来查看

1.杂项设备除了比字符设备代码简单,还有别的区别吗

杂项设备的主设备号是相同的,均为10,次设备号是不同的。
主设备号相同就可以节省内核资源

2.主设备号和次设备号是什么

设备号包含主设备号和次设备号,主设备号在Linux系统里面是唯一的,次设备号不一定唯一。
设备号是计算机识别设备的一种方式,主设备号相同就被视为同一类设备
主设备号可以比作电话号码的区号。比如北京区号010
次设备号可以比作电话号码。
查看主设备号:cat /proc/devices

3.杂项设备的描述

定义在内核源码 include/linux/miscdevice.h
struct miscdevice  {
int minor; //次设备号
const char *name; //设备节点的名字
const struct file_operations *fops; //文件操作集
struct list_head list;
struct device *parent;
struct device *this_device;
const struct attribute_group **groups;
const char *nodename;
umode_t mode;
};
file_operations文件操作集定义在include/linux/fs.h下面
里面的一个结构体成员对应一个调用
在注册的时候要确定哪个次设备号没有用
#define MISC_DYNAMIC_MINOR      255 :实现自动分配次设备号
注册杂项设备
extern int misc_register(struct miscdevice *misc);
注销杂项设备
extern void misc_deregister(struct miscdevice *misc);

4.注册杂项设备的流程

① 填充miscdevice这个结构体
② 填充file_operations这个结构体
③ 注册杂项设备并生成设备节点(mknod)

编写一个杂项设备驱动:
按照杂项设备的注册流程注册一个杂项设备,并生成设备节点

#include <linux/init.h>#include <linux/module.h>#include <linux/miscdevice.h>#include <linux/fs.h>struct file_operations misc_fops = {.owner = THIS_MODULE};struct miscdevice misc_dev = {.minor = MISC_DYNAMIC_MINOR,.name = "hello_misc", //设备名称.fops = &misc_fops};static int misc_init(void){//注册到内核里int ret;ret = misc_register(&misc_dev);if(ret < 0){printk("misc registe is error\n");return -1;}printk("misc registe is succeed\n");return 0;}static void misc_exit(void){misc_deregister(&misc_dev);printk("misc bye bye\n");}module_init(misc_init);module_exit(misc_exit);MODULE_LICENSE("GPL");

三、应用层和内核层数据传输

1.Linux一切皆文件

文件对应的操作有打开、关闭,读写
设备节点对应的操作有打开、关闭和读写
open
read
write
ioctl
close
如果在应用层使用系统IO对设备节点进行打开、关闭、读写等操作会发生什么
常用:
当我们在应用层read设备节点时,就会触发我们驱动里面read这个函数
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
当我们在应用层write设备节点时,就会触发我们驱动里面write这个函数
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
当我们在应用层poll/select设备节点时,就会触发我们驱动里面poll/select这个函数
unsigned int (*poll) (struct file *, struct poll_table_struct *);
当我们在应用层ioctl设备节点时,就会触发我们驱动里面ioctl这个函数
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
当我们在应用层open设备节点时,就会触发我们驱动里面open这个函数
int (*open) (struct inode *, struct file *);
当我们在应用层close设备节点时,就会触发我们驱动里面close这个函数
int (*release) (struct inode *, struct file *);
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
int (*iterate_shared) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
loff_t, size_t, unsigned int);
int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
u64);
ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *,
u64);
};

应用层(上层应用):open
设备节点(连接上层应用和底层驱动的桥梁):/dev/hello_misc
内核层(底层驱动):触发内核层中的file_operations里的open函数
在这里插入图片描述

2.假如file_operations中没有read,但在应用层调用了read设备节点会发生什么

不会发生任何事

3.应用层和内核层不能直接进行数据传输

要通过以下两个函数来实现应用层和内核层之间的数据传输:

//从应用层获得数据
static inline long copy_from_user(void *to,const void __user * from, unsigned long n)
//把数据传给应用层
static inline long copy_to_user(void __user *to,const void *from, unsigned long n)
//二者都是from里面有数据,传给to,copy_from_user的数据来自于应用层,copy_to_user的数据来自驱动层

四、Linux虚拟地址到物理地址映射

1.单片机和裸机操作硬件

可以直接操作寄存器:
unsigned int *p = 0x12345678
*p = 0x87654321

但是在Linux上不可以,在Linux上想要操作硬件,需要先把物理地址转换成虚拟地址。因为Linux使能了MMU(把物理地址转换成了虚拟地址),所以我们在Linux上不能直接操作物理地址

MMU:内存管理单元

2.使能MMU优点

① 让虚拟地址成了可能
② 可以让系统更加安全,因为有了MMU,上层应用操作的都是虚拟内存不能直接访问硬件,所以保障了系统安全

3.MMU非常复杂,如何完成物理地址到虚拟地址的转换

内核提供了相关函数:

ioremap,iounmap
ioremap:把物理地址转换成虚拟地址
iounmap:释放掉ioremap映射的地址

static inline void __iomem *ioremap_uc(phys_addr_t offset, size_t size)
参数:
1.映射物理地址的起始地址
2.映射的物理地址的大小(映射多大的内存空间),单位是字节

成功返回虚拟地址首地址
失败返回NULL

static inline void iounmap(void __iomem *addr)
参数:
1.要取消映射的虚拟地址的首地址,也就是ioremap得到的虚拟地址,跟mmap类似

物理地址只能被映射一次,多次映射会失败

4.如何查看哪些物理地址被映射过了

使用命令:cat /proc/iomem 来查看

五、第一个相对完整的驱动实践编写

1.需求

1.使用杂项设备完成一个蜂鸣器驱动
2.完成一个上层测试应用
应用要求:在上层应用中传入参数0为打开蜂鸣器,传入参数1为关闭蜂鸣器

2.流程

① 查阅原理图看需要写的驱动模块所使用的GPIO引脚
② 查芯片手册看对应的时钟地址、GPIOx_GDIR、GPIOx_DR的地址
③ 在init里调用ioremap函数来进行内存映射
④ 对虚拟内存地址进行位操作
⑤ exit里调用ionumap释放内存映射
⑥ 应用层打开注册好的设备节点

注意:
使用引脚时正规流程是:
1.使能时钟(开启)
2.设置引脚(设置为GPIO模式) 1、2要查询使用的板子所对应的芯片手册
3.设置输入输出 GPIOx_GDIR
4.数据(写或读取,写则设置高电平或者低电平,读取则读取某个寄存器来获取引脚的电平状态)GPIOx_DR

3.源码

案例中所使用的是韦东山老师的imx6ull开发板,引脚是GPIO4_IO23,并不需要使能,和把引脚复用为GPIO模式
beep.c

#include <linux/init.h>#include <linux/module.h>#include <linux/miscdevice.h>#include <linux/fs.h>#include <linux/uaccess.h>#include <linux/io.h>#define GPIO4_DR 0x020A8000#define GPIO4_GDIR 0x020A8004//存放映射出的虚拟地址的首地址unsigned int *vir_gpio4_dr;unsigned int *vir_gpio4_gdir;int misc_open(struct inode *inode, struct file *file){/*1.使能gpio4(第一步在imx6ull中好像也不需要,回头补充这方面的知识点再进行修改)2.设置gpio4_io23为gpio模式 (这一步不需要,因为在imx6ull中gpio_io23已经被设置为gpio模式)3.设置gpio4_io23为输出模式*/*vir_gpio4_gdir |= (1 << 23); //3printk(KERN_INFO"hello misc_open\n");return 0;}int misc_release (struct inode *inode, struct file *file){printk(KERN_INFO"hello misc_release bye bye\n");return 0;}ssize_t misc_read (struct file *file, char __user *ubuf, size_t size, loff_t *lofft){char kbuf[64] = "hello i come from kernel";if(copy_to_user(ubuf,kbuf,strlen(kbuf)) != 0){printk(KERN_INFO"copy_to_user error\n");return -1;}printk(KERN_INFO"hello misc_release read\n");return 0;}ssize_t misc_write (struct file *file, const char __user *ubuf, size_t size, loff_t *lofft){char kbuf[64] = {0};if(copy_from_user(kbuf,ubuf,size)!=0){printk(KERN_INFO"copy_from_user error\n");return -1;}printk(KERN_INFO"kbuf is %s\n",kbuf);printk(KERN_INFO"hello misc_release write\n");if(kbuf[0] == 1){*vir_gpio4_dr |=(1 << 23); //其他位不变,23位置为1 就是操作GPIO4_23printk(KERN_INFO"%c\n",kbuf[0]);}else if(kbuf[0] == 0){*vir_gpio4_dr &= ~(1 << 23); //其他位不变,23位置为0		}return 0;}struct file_operations misc_fops = {.owner = THIS_MODULE,.open = misc_open,.release = misc_release,.read = misc_read,.write = misc_write};struct miscdevice misc_dev = {.minor = MISC_DYNAMIC_MINOR,.name = "hello_misc",.fops = &misc_fops};static int misc_init(void){//注册到内核里int ret;ret = misc_register(&misc_dev);if(ret < 0){printk(KERN_INFO"misc registe is error\n");return -1;}printk(KERN_INFO"misc registe is succeed\n");//映射 4个字节vir_gpio4_dr = ioremap(GPIO4_DR,4);vir_gpio4_gdir = ioremap(GPIO4_GDIR,4);if(vir_gpio4_dr == NULL){printk(KERN_INFO"GPIO4_DR ioremap error\n");return -EBUSY;}printk(KERN_INFO"GPIO4_DR ioremap ok\n");return 0;}static void misc_exit(void){misc_deregister(&misc_dev);iounmap(vir_gpio4_dr);iounmap(vir_gpio4_gdir);printk(KERN_INFO"misc bye bye\n");}module_init(misc_init);module_exit(misc_exit);MODULE_LICENSE("GPL")

app.c

#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>int main(int argc , char **argv){int fd;char buf[64] = {0};fd = open("/dev/hello_misc",O_RDWR);if(fd < 0){perror("open error\n");return fd;}//这个函数作用是把字符串转换成整型buf[0] = atoi(argv[1]);write(fd,buf,sizeof(buf));close(fd);return 0;}

4.最简单的驱动

流程:
1.构造file_operations结构体
2.内核:
chrdevs:有第0-n项
3.注册结构体:
n = register_chrdev (第n项,name,&led_drv);

传入0自动分配
n:major(主设备号)

4.入口函数:内核会调用它,入口函数会调用register_chrdev
5. 出口函数:unregister_chrdev

六、驱动模块传递参数

1.什么是驱动传参

驱动传参就是传递参数给我们的驱动
例:
insmod led.ko a=1

2.驱动传参有什么作用

① 设置驱动相关参数,比如设置缓冲区大小
② 设置安全校验,防止我们写的驱动被人盗用

3.怎么给我们的驱动传参

① 传递普通的参数,比如char,int类型的

module_param(name,type,perm);
参数:
name:要传递的参数的名称
type:传递进去参数的类型
perm: 参数读写的权限
module_param(a,int,S_IRUSR);

② 传递数组

module_param_array(name,type,nump,perm);
参数:
name:传递的数组名称
type:传递的数组类型
nump:实际传入进去的个数
perm:参数读写权限
module_param_array(b,int,&count,S_IRUSR);

4.如果多传递进去参数会发生什么

报错

七、字符设备

1.申请字符类设备号

1)字符设备和杂项设备区别

杂项设备主设备号是固定的,固定为10
字符类设备就需要自己或者系统来给我们分配
杂项设备设备可以自动生成设备节点
字符设备需要手动生成设备节点

2)注册字符类设备号的两个方法

第一种:静态分配一个设备号
extern int register_chrdev_region(dev_t, unsigned, const char *);
需要明确知道我们系统里面哪些设备号没有用

参数:
第一个:设备号起始值 类型是dev_t的类型
第二个:次设备号的个数
第三个:设备的名称(cat /proc/devices)
返回值:
成功返回0
失败返回负数

dev_t类型:
dev_t是用来保存设备号的,是一个32位数
高12位:主设备号 , 低12位:次设备号

typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;

Linux提供了宏定义来操作设备号:
(kdev_t.h)
#define MINORBITS
20 ,次设备号位数,一共20位

#define MINORMASK ((1U << MINORBITS) - 1)
次设备号的掩码

#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
在dev_t里面获取主设备号

#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
在dev_t里面获取次设备号

#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
将我们的主设备号、次设备号组成一个dev_t的类型 第一个参数是主设备号,第二个参数是次设备号

第二种:动态分配
extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);

参数:
第一个 :保存生成的设备号
第二个:我们请求的第一个次设备号,通常为0
第三个:连续申请的设备号的个数
第四个:设备名称
成功返回 0
失败返回负数

使用动态分配会优先使用255 - 234

注销设备号:
extern void unregister_chrdev_region(dev_t, unsigned);

参数:
第一个:设备号的起始地址
第二个:申请的连续设备号的个数

建议使用动态申请避免造成冲突

2.注册字符类设备

注册杂项设备:
misc_register(&misc_dev)
注销杂项设备
misc_deregister(&misc_dev)

注册字符类设备(把申请到的字符类设备注册到内核):
cdev结构体:描述字符设备的一个结构体

struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};

步骤一:定义一个cdev结构体

步骤二:使用cdev_init初始化cdev结构体成员变量
void cdev_init(struct cdev *, const struct file_operations *);

参数:
第一个:要初始化的cdev
第二个:文件操作集
cdev->ops = fops //实际上就是把文件操作集写给ops

步骤三:使用cdev_add函数注册到内核
int cdev_add(struct cdev *, dev_t, unsigned);

参数:
第一个:cdev的结构体指针
第二个:设备号
第三个:次设备号的数量

步骤四:注销字符设备
void cdev_del(struct cdev *);

字符设备注册完成后不会自动生成设备节点
使用命令mknod创建设备节点
格式:
mknod 名称 类型 主设备号 次设备号
mknod /dev/test c 247 0
(c 代表字符类设备)

3.自动创建设备节点

在Linux驱动实验中,通过insmod命令加载模块后,还需要mknod命令来手动创建设备节点。
接下来学习一下如何实现自动创建设备节点,当加载模块时,在/dev目录下自动创建相应的设备文件

1)怎么自动创建一个设备节点
在嵌入式Linux中使用mdev来实现设备节点文件的自动创建与删除

2)什么是mdev
mdev是udev的简化版,是busybox中所带的程序,最适合用在嵌入式系统

3)什么是udev
udev是一种工具,它能根据系统中的硬件设备的状态动态更新设备文件,包括设备文件的创建,删除等。设备文件通常放在/dev目录下。使用udev后,在/dev目录下就只包含系统中真正存在的设备。udev一般用在PC上的linux中,相对mdev来说要复杂一些

4)怎么自动创建设备节点
自动创建设备节点分为两个步骤:
步骤一:使用class_create函数创建一个class的类
步骤二:使用device_create函数在我们创建的类下面创建一个设备

5)创建和删除类函数
在Linux驱动程序中一般通过两个函数来完成设备节点的创建删除。首先要创建一个class类结构体,
class结构体定义在include/linux/device.h里面。class_create类创建函数class_create是个宏定义,内容:

#define class_create(owner, name) \
({ \ 
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
struct class *__class_create(struct module *owner, const char *name,
struct lock_class_key *key
参数:
第一个owner:一般是THIS_MODULE
第二个:参数name 是类的名字
返回值是个指向结构体class的指针,也就是创建的类

ls /sys/class/ :可以查看创建的类

卸载驱动程序的时候需要删除类,类删除函数为class_destory
void class_destroy(struct class *cls);
参数cls就是要删除的类

6)创建设备函数
device_create函数在创建的类下创建一个设备
device_create函数原型:

struct device *device_create(struct class *class,
struct device *parent,
dev_t devt,
void *drvdata,
const char *fmt, ...)
device_create是个可变参数函数
参数:
第一个class:指定设备创建在哪个类下面
第二个parent:父设备,一般为NULL,也就是没有父设备
第三个 devt:是设备号
第四个 drvdata:是设备可能会使用的一些数据,一般为NULL
第五个 fmt:设备名字,如果设置 fmt=xxx 的话,就会生成/dev/xxx 这个设备文件。
返回值就是 创建好的设备。

卸载驱动的时候要删除创建的设备
设备删除函数device_destroy,函数原型如下:
void device_destroy(struct class *class, dev_t devt)

参数:
class:删除设备所处的类
devt:要删除的设备号

ls /dev/ : 可以查看创建的设备节点

4.字符设备和杂项设备总结回顾

在这里插入图片描述
字符设备是三大类设备(字符设备、块设备、网络设备)中的一类,其驱动程序完成的工作主要是初始化、添加和删除cdev结构体,申请和释放设备号,以及填充file_operations结构体中的操作函数,实现file_operations结构体中的read(),write()和ioctl()等函数是驱动设计的主要内容
在这里插入图片描述
字符设备驱动框架和杂项设备驱动框架构建file_operation结构体是一样的,应用层执行read、open、write,会触发file_operation结构体中的XXX_read、XXX_open、XXX_write,这几个函数都是操作硬件设备的,这样就实现了应用层操作硬件设备的流程。
设备节点是连接驱动和应用的桥梁
在这里插入图片描述


总结

以上就是有关于Linux驱动入门的一些基础,后续也会继续补充新的知识在里面

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询