Linux驱动开发简易流程

推荐视频:

正点原子【第四期】手把手教你学 Linux之驱动开发篇
小智-学长嵌入式Linux&Android底层开发入门教程

能力矩阵

基础能力矩阵

熟悉c/c++、熟悉数据结构

熟悉linux系统,Shell脚本,Makefile/cmake/mk

文件IO、多线程、竞争、并发、阻塞、同步、中断、网络

熟悉调试工具,gdb、gdbserver、tcpdump

行业能力矩阵

平台相关:海思/君正/安霸MTK/NXP/高通/全志/瑞芯微/展讯等平台

系统相关:bootloader、kernel、文件系统,定制、移植、开发与适配

Android相关:HAL、Services、Framework

驱动相关:驱动设备模型、GPIO、I2C、SPL、UART、WIFl、LCD、USB

物联网相关:TCP/IP、UDP、COAP、MQTT、HTTP

流媒体相关:RTMP、RTSP、Iive555

视频编码:H264、H265、MJPEG

音频编码:PCM、AAC、G711

流媒体框架:FFMEPG、GStreamer、WebRTC

开发流程

  1. 编译系统,跑起来(编译从原厂拿来的SDK,看看有没有报错,做好一些必要的ignore管理)
  2. 按照需求将项目外设逐个调通(遥控,WIFI,蓝牙,LED灯光等等)
  3. 封装Android接口,让应用能用上驱动(这个一般是移植的芯片厂家的驱动,适配好就行)
  4. 应用实现(根据客户的需求定制各种功能)

Linux驱动开发思维

1、Liuⅸ下驱动开发直接操作寄存器不现实。

2、根据Linuⅸ下的各种驱动框架进行开发。一定要满足框架,也就是Linux下各种驱动框架的掌握。

3、驱动最终表现就是/dev/xxx文件。打开、关闭、读写。

4、现在新的内核支持设备树,这个一个dts文件,此文件描述了板子的设备信息。

linux驱动开发分类

linux驱动分为三大类:

1、字符设备驱动,最多的。

2、块设备驱动,存储

3、网络设备驱动,

一个设备不说是一定只属于某一个类型。比如USB WIFI,SDIO WIFI,属于网络设备驱动,因为他又有USB和SDIO,因此也属于字符设备驱动。

应用程序和驱动交互原理

用户空间(用户态)和内核空间(内核态):

Linux操作系统内核和驱动程序运行在内核空间、应用程序运行在用户空间。

应用程序想要访问内核资源,怎么办,有三种方法:系统调用、异常(中断)和陷入。我们一般都是系统调用的方式。

Linux 驱动属于内核的一部分,因此驱动运行于内核空间。

当我们在用户空间想要实现对内核的操作,比如使用 open 函数打开/dev/led 这个驱动,因为用户空间不能直接对内核进行操作,因此必须使用一个叫做"系统调用"的方法来实现从用户空间"陷入"到内核空间,这样才能实现对底层驱动的操作。

驱动的加载与卸载

Linux 驱动有两种运行方式。

  1. 将驱动编译进 Linux 内核中,这样当 Linux 内核启动的时候就会自动运行驱动程序。
  2. 将驱动编译成模块(Linux 下模块扩展名为.ko),在Linux 内核启动以后使用"insmod"命令加载驱动模块。
    驱动编译完成以后扩展名为.ko,有两种命令可以加载驱动模块:insmod和modprobe。insmod 命令不能解决模块的依赖关系,modprobe 会分析模块的依赖关系,然后会将所有的依赖模块都加载到内核中。驱动模块的卸载使用命令"rmmod"即可
    简单看懂驱动代码
java 复制代码
 /* 驱动入口函数 */ 
 static int __init xxx_init(void) 
 { 
 
/* 入口函数具体内容 */ 
 
return 0; 
 } 
  
 /* 驱动出口函数 */ 
 static void __exit xxx_exit(void) 
 { 
 
/* 出口函数具体内容 */ 
 } 
 
 /* 将上面两个函数指定为驱动的入口和出口函数 */ 
 module_init(xxx_init); 
 module_exit(xxx_exit);

设备号的分配

  1. 静态分配设备号
    注册字符设备的时候需要给设备指定一个设备号,这个设备号可以是驱动开发者静态的指定一个设备号,比如选择 200 这个主设备号。有一些常用的设备号已经被 Linux 内核开发者给分配掉了,具体分配的内容可以查看文档 Documentation/devices.txt。
    使用"cat /proc/devices"命令即可查看当前系统中所有已经使用了的设备号。
  2. 动态分配设备号
    静态分配设备号需要我们检查当前系统中所有被使用了的设备号,然后挑选一个没有使用的。而且静态分配设备号很容易带来冲突问题,Linux 社区推荐使用动态分配设备号,在注册字符设备之前先申请一个设备号,系统会自动给你一个没有被使用的设备号,这样就避免了冲突。
    设备号的申请函数如下:
    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
    注销字符设备之后要释放掉设备号,设备号释放函数如下:
    void unregister_chrdev_region(dev_t from, unsigned count)

内核打印

在驱动中可以使用printk 来输出信息,而不用printf。因为在 Linux 内核中没有 printf 这个函数。printk 相当于printf 的孪生兄妹,printf运行在用户态,printk 运行在内核态。在内核中想要向控制台输出或显示一些内容,必须使用printk 这个函数。不同之处在于,printk 可以根据日志级别对消息进行分类,一共有 8 个消息级别,这 8 个消息级别定义在文件 include/linux/kern_levels.h 里面,定义如下:

java 复制代码
#define KERN_SOH "\001" 
#define KERN_EMERG KERN_SOH "0" /* 紧急事件,一般是内核崩溃 */ 
#define KERN_ALERT KERN_SOH "1" /* 必须立即采取行动 */ 
#define KERN_CRIT KERN_SOH "2" /* 临界条件,比如严重的软件或硬件错误*/ 
#define KERN_ERR KERN_SOH "3" /* 错误状态,一般设备驱动程序中使用 KERN_ERR 报告硬件错误 */ 
#define KERN_WARNING KERN_SOH "4" /* 警告信息,不会对系统造成严重影响 */ 
#define KERN_NOTICE KERN_SOH "5" /* 有必要进行提示的一些信息 */ 
#define KERN_INFO KERN_SOH "6" /* 提示性的信息 */ 
#define KERN_DEBUG KERN_SOH "7" /* 调试信息 */

一共定义了 8 个级别,其中 0 的优先级最高,7 的优先级最低。如果要设置消息级别,参考如下示例:

java 复制代码
printk(KERN_EMERG "gsmi: Log Shutdown Reason\n");

上述代码就是设置"gsmi: Log Shutdown Reason\n"这行消息的级别为 KERN_EMERG。在具体的消息前面加上 KERN_EMERG 就可以将这条消息的级别设置为 KERN_EMERG。

如果使用 printk 的 时 候 不 显 式 的 设 置 消 息 级 别 , 那 么 printk 将 会 采 用 默 认 级 别MESSAGE_LOGLEVEL_DEFAULT,MESSAGE_LOGLEVEL_DEFAULT 默认为 4

在控制台修改内核打印级别:

java 复制代码
echo 7 > echo 7 > /proc/sys/kernel/printk

在uboot下改变内核打印级别:

java 复制代码
env set loglevel7 
env save

编译驱动程序和测试 APP

编译驱动程序

当我们编写完一个驱动程序之后会生成.c文件,比如chrdevbase.c 这个文件。我们需要将其编译为.ko 模块.

  1. 创建Makefile 文件,然后在其中输入如下内容:
java 复制代码
 KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek 
 CURRENT_PATH := $(shell pwd) 
 obj-m := chrdevbase.o 
  
 build: kernel_modules 
  
 kernel_modules: 
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules 
 clean:  
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean 

第 1 行,KERNELDIR 表示开发板所使用的 Linux 内核源码目录,使用绝对路径,大家根据自己的实际情况填写即可。

第 2 行,CURRENT_PATH 表示当前路径,直接通过运行"pwd"命令来获取当前所处路径。

第 3 行,obj-m 表示将 chrdevbase.c 这个文件编译为 chrdevbase.ko 模块。

第 8 行,具体的编译命令,后面的 modules 表示编译模块,-C 表示将当前的工作目录切换到指定目录中,也就是 KERNERLDIR 目录。M 表示模块源码目录,"make modules"命令中加入 M=dir 以后程序会自动到指定的 dir 目录中读取模块的源码并将其编译为.ko 文件。

  1. Makefile 编写好以后输入"make"命令编译驱动模块,

  2. 编译成功以后就会生成一个叫做 chrdevbaes.ko 的文件,此文件就是 chrdevbase 设备的驱动模块。至此,chrdevbase 设备的驱动就编译成功。

编译测试 APP

测试 APP 比较简单,只有一个文件,因此就不需要编写 Makefile 了,直接输入命令编译。

因为测试 APP 是要在 ARM 开发板上运行的,所以需要使用 arm-linux-gnueabihf-gcc 来编译,输入如下命令:

java 复制代码
arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp

编译完成以后会生成一个叫做 chrdevbaseApp 的可执行程序,输入如下命令查看chrdevbaseAPP 这个程序的文件信息:

java 复制代码
file chrdevbaseApp

运行测试

  1. 拷贝文件

  2. 加载 chrdevbase.ko 驱动文件

java 复制代码
insmod chrdevbase.ko 

或者

java 复制代码
modprobe chrdevbase.ko

如果使用 modprobe 加载驱动的话,可能会出现如下的提示:

modprobe 提示无法打开"modules.dep"这个文件,因此驱动挂载失败了。我们不用手动创建 modules.dep 这个文件,直接输入 depmod 命令即可自动生成modules.dep,有些根文件系统可能没有 depmod 这个命令,如果没有这个命令就只能重新配置busybox,使能此命令,然后重新编译 busybox。输入"depmod"命令以后会自动生成 modules.alias、modules.symbols 和 modules.dep 这三个文件,

然后重新使用modprobe 加载 chrdevbase.ko

输入如下命令查看当前系统中有没有 chrdevbase 这个设备:

java 复制代码
cat /proc/devices

创建设备节点文件

驱动加载成功需要在/dev 目录下创建一个与之对应的设备节点文件,应用程序就是通过操作这个设备节点文件来完成对具体设备的操作。输入如下命令创建/dev/chrdevbase 这个设备节点文件:

java 复制代码
mknod /dev/chrdevbase c 200 0

其中"mknod"是创建节点命令,"/dev/chrdevbase"是要创建的节点文件,"c"表示这是个字符设备,"200"是设备的主设备号,"0"是设备的次设备号。创建完成以后就会存在/dev/chrdevbase 这个文件,可以使用"ls /dev/chrdevbase -l"命令查看,

如果 chrdevbaseAPP 想要读写 chrdevbase 设备,直接对/dev/chrdevbase 进行读写操作即可。

设备操作测试

java 复制代码
./chrdevbaseApp /dev/chrdevbase 1
java 复制代码
./chrdevbaseApp /dev/chrdevbase 2

卸载驱动模块

如果不再使用某个设备的话可以将其驱动卸载掉,比如输入如下命令卸载掉 chrdevbase 这个设备

java 复制代码
rmmod chrdevbase.ko
相关推荐
A小辣椒1 天前
TShark:Wireshark CLI 功能
linux
A小辣椒1 天前
TShark:基础知识
linux
AlfredZhao1 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao2 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334662 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠3 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush43 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5203 天前
Linux 11 动态监控指令top
linux
不会C语言的男孩3 天前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言