免费网站正能量小说,网站的设计 更新,做一些网站的弹出页面,wordpress中搜索页面模板先说结论#xff0c;能#xff0c;肯定能#xff0c;必须能#xff01;
但是#xff0c;问题重点在于坚持#xff0c;程序员这一行 #xff0c;下班回家一般都要10点了#xff0c;再刷两个小时枯燥的学习视频#xff0c;我想大多数人是坚持不下来的。
但是#xff…先说结论能肯定能必须能
但是问题重点在于坚持程序员这一行 下班回家一般都要10点了再刷两个小时枯燥的学习视频我想大多数人是坚持不下来的。
但是我要说但是linux驱动开发其实并不难难的是市面上没有靠谱的书籍和教学视频就好像是你向一个半瓶水的模拟电路工程师请教电路设计原理张口就是经验值问就是别人都是这么设计的能用就行他能给你讲明白才有鬼了。
之所以说linux驱动开发不难是因为内核中已经实现了一套非常简洁通用的驱动框架自打2.6版本以后就没怎么变过足以说明该驱动框架的优秀。而目前市面上的书籍和教学视频根本没有足够重视讲解驱动框架的内容只是硬扣单个驱动的细节。作为单片机工程师你跟linux驱动工程师之间差的就只是一个驱动框架而已。
说了这么多是时候上干货了。还是坚持我一贯的理念学习任何新鲜东西都应该是由远及近先整体掌握全局再深入探究细节。原则上你只要认真看完下面的内容就差不多算是入门了。废话不多说上菜。 前置知识
1. linux驱动模块整体是以面向对象思想来设计的驱动中的每个节点都描述成一个对象。
2. 对象通常采用结构体的形式描述结构体中的变量表示对象属性函数指针表示对象行为。
3. 对象之间的继承关系采用内嵌父类结构体对象的形式体现。
4. 驱动中的同类对象一般采用链表的形式串联在一起链表使用内嵌struct list_head的形式表示。
5. 内核中大量使用container_of宏实现通过已知结构体对象内部元素的地址获取整个结构体起始地址的功能。
例继承实现方法
/* 父类 */ struct ANIMAL { int age; int weight; }; /* 子类 */ struct DOG { struct ANIMAL animal; /* 通过内嵌父类对象来实现继承关系 */ int variety } /* 通过dog对象中animal对象的地址获取dog对象的起始地址 */ struct DOG *dog container_of(ptr_animal, struct DOG, animal);
例链表的使用方法
struct xxx_dev { int id; int num; struct list_head node; /* 通过在对象中嵌入struct list_head节点来实现链表功能 */ }; /* 遍历链表的时候已知node地址借助container_of宏可以获取到外层对象xxx_dev的起始地址。*/ struct xxx_dev *dev container_of(prt_node, struct xxx_dev, node); 核心驱动框架
linux内核中对不同的组成部分高度抽象采用 总线-设备-驱动模型来组织某一层驱动代码多层之间可以叠加。模型结构如下总线作为桥梁和纽带连接设备和对应的驱动。 (核心驱动框架)
设备挂载在各个总线上的的硬件设备或者虚拟设备比如挂在I2C总线上的温湿度传感器 挂载在平台总线上I2C控制器等都被抽象描述成设备对象。使用结构体struct device表示设备基类用来描述硬件设备的各种参数具体各类设备可以通过内嵌基类对象实现继承和扩展。
驱动记录硬件设备状态的变量和控制硬件工作的函数的集合负责对硬件设备进行初始化并向上层代码提供操作接口比如SOC中各类总线控制器驱动以及外挂的总线设备驱动等使用结构体struct device_driver表示驱动基类具体各类驱动可以通过内嵌基类对象实现继承和扩展。
总线表示各种物理或者虚拟总线。总线作为桥梁和纽带用来连接设备和驱动并提供驱动注册设备发现设备注册/卸载等功能。常见的总线例如平台总线I2C总线SPI总线USB总线等。其中平台总线是驱动工程师最长接触的总线类型有些书籍把平台总线叫做虚拟总线说是那些没有实际物理总线的都归类为平台总线我认为这个说法不对。平台总线应该是指SOC中那些内部互联用的总线比如ARM SOC中的AXI AHB APB总线等这些总线上连接的大量的控制器都可以通过地址映射直接访问所以这些控制器一般被称为平台设备连接的总线被称为平台总线。使用struct bus_type表示总线基类具体各类总线可以通过内嵌基类实现继承和扩展。
驱动框架继承关系
如前所述Linux驱动框架中分别定义了总线-设备-驱动各对象的基类其他各子类都是从基类继承而来继承关系如下图 继承关系
用户接口
所有的硬件都是为了实现某些具体功能而生的驱动程序操作硬件设备就是为了给上层应用提供服务但是linux内核为了安全把运行空间分成了内核空间kernel space和用户空间user space两部分其中内核代码运行在内核空间应用程序运行在用户空间。应用程序通过系统调用接口调用内核以及驱动提供的各种服务示意图如下 系统调用示意图
但是硬件种类多种多样对应的驱动数量也不胜枚举而且还在不断的变化中不可能为每种驱动都提供系统调用接口好在多数设备的操作步骤都很类似主要可以概括为初始化读写关闭等基本步骤。根据设备的功能属性和使用方式不同内核中把设备大体分为字符设备块设备和网络设备三个大类。其中字符设备和块设备因为操作步骤跟文件操作很相似所以复用了VFS虚拟文件系统提供的系统调用接口openreleasereadwrite ioctl等接口 其在内核中分别使用 struct cdev和struct block_device表示 在用户空间以特殊文件形式存在于/dev目录下使用ls -ls /dev 可以查看各文件的属性其中属性crw-rw-rw-以c打头的表示字符设备属性brw-rw----以b打头的表示块设备。
croscros-pc:~$ ls -ls /dev/ total 0 0 crw------- 1 root root 5, 1 5月 28 00:04 console 0 crw-rw-rw- 1 root root 1, 7 5月 28 00:04 full 0 crw-rw---- 1 root kvm 10, 232 5月 28 00:04 kvm 0 brw-rw---- 1 root disk 8, 0 5月 28 00:04 sda 0 brw-rw---- 1 root disk 8, 1 5月 28 00:04 sda1 0 brw-rw---- 1 root disk 8, 2 5月 28 00:04 sda2
网络设备因为操作方式不同无法复用VFS的系统接口所以只能单独提供几个独享的系统调用接口如下SYSCALL_DEFINEx宏的第一个参数就是系统调用的名字
croscros-pc:~/home/cros/kernel$ grep -rn SYSCALL_DEFINE* net/socket.c 1213:SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol) 1254:SYSCALL_DEFINE4(socketpair, int, family, int, type, int, protocol, 1363:SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen) 1392:SYSCALL_DEFINE2(listen, int, fd, int, backlog) 1425:SYSCALL_DEFINE4(accept4, int, fd, struct sockaddr __user *, upeer_sockaddr, 1506:SYSCALL_DEFINE3(accept, int, fd, struct sockaddr __user *, upeer_sockaddr, 1524:SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr, 1556:SYSCALL_DEFINE3(getsockname, int, fd, struct sockaddr __user *, usockaddr, 1587:SYSCALL_DEFINE3(getpeername, int, fd, struct sockaddr __user *, usockaddr, 1619:SYSCALL_DEFINE6(sendto, int, fd, void __user *, buff, size_t, len, 1663:SYSCALL_DEFINE4(send, int, fd, void __user *, buff, size_t, len, 1675:SYSCALL_DEFINE6(recvfrom, int, fd, void __user *, ubuf, size_t, size, 1720:SYSCALL_DEFINE4(recv, int, fd, void __user *, ubuf, size_t, size, 1731:SYSCALL_DEFINE5(setsockopt, int, fd, int, level, int, optname, 1765:SYSCALL_DEFINE5(getsockopt, int, fd, int, level, int, optname, 1795:SYSCALL_DEFINE2(shutdown, int, fd, int, how) 1988:SYSCALL_DEFINE3(sendmsg, int, fd, struct user_msghdr __user *, msg, unsigned int, flags) 2057:SYSCALL_DEFINE4(sendmmsg, int, fd, struct mmsghdr __user *, mmsg, 2154:SYSCALL_DEFINE3(recvmsg, int, fd, struct user_msghdr __user *, msg, 2272:SYSCALL_DEFINE5(recvmmsg, int, fd, struct mmsghdr __user *, mmsg, 2317:SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
另外内核为了方便用户层操作设备引入了sys文件系统位于/sys目录下其分别从总线设备, 类等不同角度描述整个驱动框架,如下所示:
croscros-pc:~$ ls -ls /sys/ total 0 0 drwxr-xr-x 2 root root 0 6月 2 10:25 block 0 drwxr-xr-x 43 root root 0 6月 2 10:25 bus 0 drwxr-xr-x 68 root root 0 6月 2 10:25 class 0 drwxr-xr-x 4 root root 0 6月 2 10:25 dev 0 drwxr-xr-x 24 root root 0 5月 28 00:04 devices 0 drwxr-xr-x 6 root root 0 5月 28 00:04 firmware 0 drwxr-xr-x 10 root root 0 5月 28 00:04 fs 0 drwxr-xr-x 2 root root 0 6月 2 10:25 hypervisor 0 drwxr-xr-x 15 root root 0 5月 28 00:04 kernel 0 drwxr-xr-x 182 root root 0 6月 2 10:25 module 0 drwxr-xr-x 3 root root 0 6月 2 10:25 power
完整的用户接口如下图 (用户接口框架) 代码模板
内核模块模板
内核驱动模块基本通过如下模板注册初始化函数和卸载函数作为驱动代码的入口和出口。
/* 内核模块初始化函数 */ static int __init xxx_init(void) { } /* 内核模块注销函数 */ static void __exit xxx_exit(void) { } /* 注册初始化函数使得自动或者手动安装驱动时自动执行初始化函数 */ module_init(xxx_init); /* 注册注销函数使得自动或者手动安装驱动时自动执行注销函数 */ module_exit(xxx_exit);
总线代码模板
/* 总线类型结构体 */ struct bus_type platform_bus_type { .name platform, .dev_groups platform_dev_groups, .match platform_match, .uevent platform_uevent, .pm platform_dev_pm_ops, }; /* 内核模块初始化函数 */ int __init platform_bus_init(void) { int error; /* 这里以平台总线为例演示总线注册流程 */ error bus_register(platform_bus_type); return error; } /* 注册初始化函数使得自动或者手动安装驱动时自动执行初始化函数 */ module_init(platform_bus_init); /* 因为平台总线是内核中必不可少的基础总线所以没有提供卸载函数 */
驱动代码模板
/* 设备驱动结构体 */ static struct platform_driver at91_twi_driver { /* probe函数负责解析设备对象提供的参数进行硬件初始化并向上层提供操作接口 */ .probe at91_twi_probe, /* 设备卸载执行的操作 */ .remove at91_twi_remove, .id_table at91_twi_devtypes .driver { .name at91_i2c, /* 用于跟设备匹配用的字段 */ .of_match_table of_match_ptr(atmel_twi_dt_ids), .pm at91_twi_pm_ops, }, }; /* 内核模块初始化函数 */ static int __init at91_twi_init(void) { /* 以平台设备驱动为例演示驱动注册过程 */ return platform_driver_register(at91_twi_driver); } /* 内核模块注销函数 */ static void __exit at91_twi_exit(void) { /* 以平台设备驱动为例演示驱动卸载过程 */ platform_driver_unregister(at91_twi_driver); } /* 注册初始化函数使得自动或者手动安装驱动时自动执行初始化函数 */ module_init(at91_twi_init); /* 注册注销函数使得自动或者手动安装驱动时自动执行注销函数 */ module_exit(at91_twi_exit);
设备代码模板
PS新版本内核中因为引入了设备树绝大多数设备都在设备树中描述了内核初始化过程中会自动解析设备树生成并注册设备所以一下代码目前很少见了此处只是为了解释原理和基本流程。
/* 平台设备结构体 */ static struct platform_device s3c24xx_uart_device0 { .id 0, }; static struct platform_device s3c24xx_uart_device1 { .id 1, }; static struct platform_device s3c24xx_uart_device2 { .id 2, }; static struct platform_device s3c24xx_uart_device3 { .id 3, }; struct platform_device *s3c24xx_uart_src[4] { s3c24xx_uart_device0, s3c24xx_uart_device1, s3c24xx_uart_device2, s3c24xx_uart_device3, }; /* 模块初始化函数 */ static int __init s3c_arch_init(void) { int ret; /* 以平台设备为例演示设备注册过程 */ ret platform_add_devices(s3c24xx_uart_src, nr_uarts); return ret; } /* 注册模块初始化函数类似功能的宏还有很多名字各不相同 */ arch_initcall(s3c_arch_init);
平台设备驱动框架
平台设备驱动是开发人员接触最多也是修改最多的一类驱动因为其主要包括SOC内置的各种总线控制器以及PWMRTCWDT等内置功能模块。基本都是跟芯片强相关的内容所以每个SOC都需要单独开发对应驱动。
平台设备驱动举例 总线设备驱动框架
总线设备驱动相比于平台设备设备来说更复杂一些一般包含两层驱动底层是总线控制器驱动上层是总线设备驱动。另外因为总线控制器多种多样为了统一上层的编程接口驱动中会在中间增加core层实现对总线控制器的抽象并对上层提供统一的总线操作接口类似于设计模式中的适配器模式。典型如I2C驱动框架中的struct i2c_adapter以及SPI驱动框架中的struct spi_master。如下是I2C驱动框架大家可以仔细品一下。 I2C设备驱动框架
内核中还有很多支持热插拔的设备驱动例如USB驱动同一个USB接口可能接了设备也可能没有接设备可能接了个U盘也可能接了个鼠标。例如mmc驱动mmc接口可能插了个MMC卡也可能插了个SD卡还可能插了个SDIO网卡。我们无法假设接口上到底接的是什么设备但是我们可以通过电平信号判断是否接了设备。为了能够判断接口上接的是什么设备以及设备具有怎样的参数一般对应的协会都会指定一套完善的协议标准例如USB协议SD协议。驱动代码中只要按照协议规定跟设备进行通信获取到对方提供的信息然后根据协议进行解析就可以获得所接硬件的详细信息。然后加载对应的驱动就可以正常使用硬件了。以下是mmc驱动框架相比于I2C驱动框架主要是多了协议解析部分你再细品 MMC驱动框架
总结
还是重点强调一点学习新东西一定是要由远及近逐渐深入。先知道每个模块是干什么的然后在学会怎么使用 最后才是深入去研究工作原理以及如何修改。学习驱动开发更是这样熟悉基本的驱动框架和各个模块的具体框架才是你第一步需要做的剩下工作就是配置寄存器初始化硬件设备了这不就是单片机工程师现在正在做的事情吗