Linux驱动开发基础
目录
一、内核态和用户态
二、字符设备驱动
三、Linux设备号
1.设备号的组成
2.设备号的分配
四、模块注册和卸载
五、字符设备注册与注销
六、LICENSE 和作者信息
七、测试指令
1.加载驱动模块
2.创建设备节点文件
3.设备测试
4.卸载驱动模块
一、内核态和用户态
内核态与用户态是操作系统的两种运行级别,cpu提供Ring0-Ring3三种级别的运行模式。Ring0级别最高,Ring3最低。
CPU是在两种不同的模式下运行的:
Kernel Mode(内核态),在内核模式下(执行内核空间的代码),具有ring0保护级别,代码具有对硬件的所有控制权限。可以执行所有CPU指令,可以访问任意地址的内存。内核模式是为操作系统最底层,最可信的函数服务的。在内核模式下的任何异常都是灾难性的,将会导致整台机器停机。
User Mode(用户态),在用户模式下(执行用户空间的代码),具有ring3保护级别,代码没有对硬件的直接控制权限,也不能直接访问地址的内存。它们只能访问映射其地址空间的页表项中规定的在用户态下可访问页面的虚拟地址。程序是通过调用系统接口(System Call APIs)来达到访问硬件和内存。在这种保护模式下,即时程序发生崩溃也是可以恢复的。在你的电脑上大部分程序都是在用户模式下运行的
用户态和内核态的切换:系统调用。具体详情参考这篇文章:
系统调用
二、字符设备驱动
字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。
Linux 下的应用程序调用驱动程序
在Linux下进行驱动开发,完全将驱动程序与应用程序隔开,中间通过C库函数以及系统调用完成驱动层和应用层的数据交换。
在 Linux 中一切皆为文件,驱动加载成功以后会在“/dev”目录下生成一个相应的文件,应用程序通过对“/dev/xxx” (xxx 是具体的驱动文件名字) 的文件进行相应的操作即可实现对硬件的操作。
比如 /dev/led 的驱动文件,此文件是 led 灯的驱动文件。应用程序使用 open 函数来打开文件/dev/led,使用完成以后使用 close 函数关闭/dev/led 这个文件。如果要点亮或关闭 led,那么就使用 write 函数来操作,也就是向此驱动写入数据。如果要获取led 灯的状态,就用 read 函数从驱动中读取相应的状态。
应用程序运行在用户空间,而 Linux 驱动属于内核的一部分,因此驱动运行于内核空间。当我们在用户空间想要实现对内核的操作,因为用户空间不能直接对内核进行操作,因此必须使用一个叫做“系统调用”的方法来实现从用户空间“陷入” 到内核空间,这样才能实现对底层驱动的操作。 open、 close、 write 和 read 等这些函数是由 C 库提供的,在 Linux 系统中,系统调用作为 C 库的一部分。
调用open()函数流程
应用程序使用到的函数在具体驱动程序中都有与之对应的函数,比如应用程序中调用了 open 函数,那么在驱动程序中也得有一个名为 open 的函数。每一个系统调用,在驱动中都有与之对应的一个驱动函数,在 Linux 内核文件 include/linux/fs.h 中有个叫做 file_operations 的结构体,此结构体就是 Linux 内核驱动操作函数集合。
三、Linux设备号
1.设备号的组成
为了方便管理, Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。 Linux 提供了一个名为 dev_t 的数据类型表示设备号, dev_t 定义在文件 include/linux/types.h 里面。dev_t 其实是unsigned int 类型,是一个 32 位的数据类型。其中高 12 位为主设备号, 低 20 位为次设备号。因此 Linux系统中主设备号范围为 0~4095。
设备号的操作函数
MAJOR 用于从 dev_t 中获取主设备号,将 dev_t 右移 20 位即可。
MINOR 用于从 dev_t 中获取次设备号,取 dev_t 的低 20 位的值即可。
MKDEV 用于将给定的主设备号和次设备号的值组合成 dev_t 类型的设备号。
系统中已经被驱动所使用的的主设备号可以在/proc/devices 文件中查询。
2.设备号的分配
静态设备号:注册字符设备需要给设备指定一个设备号,这个设备号可以是驱动开发者静态指定的设备号。但是需要注意该设备号没有被内核开发者分配掉。使用"cat /proc/devices"命令即可查看当前系统中所有已经使用了的设备号。
使用 register_chrdev 函数注册字符设备的时只需要给定一个主设备号即可
/* 注册字符设备驱动 */
retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
这是老版本字符设备注册函数,其0~1048575(2^20-1)这个区间的次设备号全部为设置为0。
动态分配设备号:使用设备号的时向 Linux 内核申请,需要几个就申请几个,由 Linux 内核分配设备可以使用的设备号。
如果没有指定设备号的话就使用如下函数来申请设备号:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
如果给定了设备的主设备号和次设备号就使用如下所示函数来注册设备号即可
int register_chrdev_region(dev_t from, unsigned count, const char *name)
参数 from 是要申请的起始设备号,也就是给定的设备号参数 count 是要申请的数量,一般都是一个
参数 name 是设备名字
一般采用动态分配设备号,模板如下:
/* 注册字符设备驱动 */
/* 1、创建设备号 */
if (newchrled.major)
{ /* 定义了设备号 */newchrled.devid = MKDEV(newchrled.major, 0);register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);
}
else
{ /* 没有定义设备号 */alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME); /* 申请设备号 */newchrled.major = MAJOR(newchrled.devid); /* 获取分配号的主设备号 */newchrled.minor = MINOR(newchrled.devid); /* 获取分配号的次设备号 */
}
printk("newcheled major=%d,minor=%d\r\n",newchrled.major, newchrled.minor);
注销设备号:
void unregister_chrdev_region(dev_t from, unsigned count)
四、模块注册和卸载
Linux 驱动有两种运行方式,第一种是将驱动编译进 Linux 内核中,当 Linux 内核启动的时就会自动运行驱动程序。第二种是将驱动编译成模块(Linux 下模块扩展名为.ko),在Linux 内核启动以后使用相应命令加载驱动模块。
模块有加载和卸载两种操作,模块的加载和卸载注册函数如下:
module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数
module_init 函数用来向 Linux 内核注册一个模块加载函数,参数 xxx_init 就是需要注册的具体函数,当使用“insmod”命令加载驱动的时, xxx_init 这个函数就会被调用
module_exit函数用来向 Linux 内核注册一个模块卸载函数,参数 xxx_exit 就是需要注册的具体函数,当使用“rmmod”命令卸载具体驱动的时候 xxx_exit 函数就会被调用
五、字符设备注册与注销
当驱动模块加载成功以需要注册字符设备,卸载驱动模块的时也需要注销掉字符设备
static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major, const char *name)
register_chrdev 函数用于注册字符设备,unregister_chrdev 注销字符设备。
一般字符设备的注册在驱动模块的入口函数 xxx_init 中进行,字符设备的注销在驱动模块的出口函数 xxx_exit 中进行。
六、LICENSE 和作者信息
在驱动中加入 LICENSE 信息和作者信息,其中 LICENSE 是必须添加的,否则的话编译的时候会报错,作者信息可以添加也可以不添加。
MODULE_LICENSE("GPL") //添加模块 LICENSE 信息 ,LICENSE 采用 GPL 协议
MODULE_AUTHOR("mingfei") //添加模块作者信息
七、测试指令
1.加载驱动模块
驱动模块一般挂载在/lib/modules/4.1.15/目录下,所以需要将编写的驱动模块和测试软件复制到根文件系统rootfs/lib/modules/4.1.15 目录下,通过tftp和nfs启动后,在开发板 的/lib/modules/4.1.15 目录下存在驱动模块和测试软件。
驱动编译完成以后扩展名为.ko,有两种命令可以加载驱动模块: insmod和 modprobe
加载驱动模块:
insmod xxx.ko
modprobe xxx.ko
insmod 命令不能解决模块的依赖关系,比如 drv.ko 依赖 first.ko 这个模块,就必须先使用insmod 命令加载 first.ko 这个模块,然后再加载 drv.ko 这个模块。
但是 modprobe 就不会存在这样的问题, modprobe 会分析模块的依赖关系,然后会将所有的依赖模块都加载到内核中。
若modprobe 提示无法打开“modules.dep”文件,驱动挂载失败,输入 depmod 命令即可自动生成
modules.dep
lsmod:查看当前系统中存在的模块
cat /proc/devices : 查看当前系统中所有的设备
2.创建设备节点文件
驱动加载成功需要在/dev 目录下创建一个与之对应的设备节点文件,应用程序就是通过操作设备节点文件来完成对具体设备的操作。
创建/dev/chrdev设备节点文件
mknod /dev/chrdev c 200 0
mknod:创建节点命令
/dev/chrdev:要创建的节点文件
c:字符设备
200 0:设备的主设备号和次设备号
创建完成后就会存在/dev/chrdev文件,可以使用 ls /dev/chrdev -l命令查看。如果测试软件(make生产的文件)想要读写chrdev设备,直接对/dev/chrdev进行读写操作即可。/dev/chrdev文件是 chrdev设备在用户空间中的实现
3.设备测试
运行测试程序即可
./xxx(程序) 参数1 参数2... (参数传给应用层的main函数)
4.卸载驱动模块
如果不再使用某个设备的话可以将其驱动卸载
rmmod xxx.ko
卸载后可以使用lsmod查看卸载模块是否成功。
Linux驱动开发基础相关推荐
- 【嵌入式Linux】嵌入式Linux驱动开发基础知识之Pinctrl子系统和GPIO子系统的使用
文章目录 前言 1.Pinctrl子系统 1.1.为什么有Pinctrl子系统 1.2.重要的概念 1.3.代码中怎么引用pinctrl 2.GPIO子系统 2.1.为什么有GPIO子系统 2.2.在 ...
- 【嵌入式Linux】嵌入式Linux驱动开发基础知识之按键驱动框架
文章目录 前言 1.APP怎么读取按键值 1.1.查询方式 1.2.休眠-唤醒方式 1.3.poll方式 1.3.异步通知方式 1.5. 驱动程序提供能力,不提供策略 2.按键驱动程序框架--查询方式 ...
- 【嵌入式Linux】嵌入式Linux驱动开发基础知识之LED模板驱动程序的改造:设备树
文章目录 前言 1.驱动的三种编写方法 2.怎么使用设备树写驱动程序 2.1.设备树节点要与platform_driver能匹配 2.2.修改platform_driver的源码 3.实验和调试技巧 ...
- 【嵌入式Linux】嵌入式Linux驱动开发基础知识之设备树模型
文章目录 前言 1.设备树的作用 2.设备树的语法 2.1.设备树的逻辑图和dts文件.dtb文件 2.1.1.1Devicetree格式 1DTS文件的格式 node的格式 properties的格 ...
- 【嵌入式Linux】嵌入式Linux驱动开发基础知识之总线设备驱动模型
文章目录 前言 1.驱动编写的三种方法 1.1.传统写法 1.2.总线驱动模型 1.3.设备树驱动模型 2.Linux实现分离:Bus/Dev/Drv模型 2.1.Bus/Dev/Drv模型 2.2. ...
- 【嵌入式Linux】嵌入式Linux驱动开发基础知识之驱动设计的思想:面向对象/分层/分离
文章目录 前言 1.分离设计 驱动程序分析---程序分层 通用驱动程序---面向对象 个性化驱动程序---分离 APP 程序分析 前言 韦东山嵌入式Linux驱动开发基础知识学习笔记 文章中大多内容来 ...
- 【嵌入式Linux】嵌入式Linux驱动开发基础知识之LED驱动框架--面向对象、分层设计思想
文章目录 前言 1.LED驱动程序框架 1.1.对于LED驱动,我们想要什么样的接口? 1.2.LED驱动要怎么写,才能支持多个板子?分层写 1.3.程序分析 驱动程序 应用程序 Makefile 1 ...
- 【嵌入式Linux】嵌入式Linux驱动开发基础知识之第一个驱动
文章目录 前言 1.Hello驱动 1.1.APP打开的文件在内核中如何表示? 1.2.打开字符设备节点时,内核中也有对应的struct file 1.3.如何编写驱动程序? 1.4.驱动程序代码 1 ...
- 嵌入式Linux驱动开发基础
文章目录 前言 休眠与唤醒 等待队列头和等待队列项 等待队列头 休眠函数 唤醒函数 一般框架 poll机制 驱动编写要点 应用程序编写要点 异步通知 驱动编写要点 应用编写要点 阻塞与非阻塞 驱动编写 ...
- linux课程_【课程完结】嵌入式Linux应用/驱动开发基础知识两大篇章已全部录制完毕 共72集...
完结撒花 <第四篇嵌入式Linux应用开发基础知识> <第五篇嵌入式Linux驱动开发基础知识> 两大篇章已全部录制完毕 共计 72 集 01 嵌入式Linux应用开发基础知识 ...
最新文章
- 网络工程师转售前的条件
- 科普丨深度学习 vs 概率图模型 vs 逻辑学
- JavaScript 笔记 ( Prototype )
- 《企业软件交付:敏捷与高效管理精要》——1.6 结论
- Redis中的I/O 多路复用(I/O Multiplexing)
- ASP.NET MVC+Spring.net+Nhibernate+EasyUI+Jquery开发案例(1)
- scala case class 继承_数字硬件系统设计之一:Scala快速入门(2)
- 第三十三章 机械化印刷
- sql server 2005管理员手册_执行一条sql语句都经历了什么?
- glide 设置宽高_Glide加载ImageView显示不全的问题(宽高比一致,以及fitxy/centerCrop)...
- 螺丝螺母垫片顺序图片_如何计算螺丝,螺栓和螺母的尺寸
- 计算机科学感言,计算机专业学生毕业感言
- E. Exits in Excess
- 软件工程(一)——软件开发模型和方法
- JSP_EL_JTEL
- ODBC连接数据库1
- 计算机无法ping其他设备,局域网ping不到其他设备怎么办
- 给ROCK64安装opencv3(Ubuntu,Debian)
- 虚拟机软件Parallels Desktop和VMware Fusion哪个好
- [开启C语言秃头之旅]扫雷游戏