Jan 19, 2024
本文阅读量次【LED子系统深度剖析】三、硬件驱动层详解 # 上篇文章我们了解了子系统的框架,下面我们来分析驱动框架中每层的实现以及作用。
在LED子系统中,硬件驱动层相关文件在包括:kernel/drivers/leds/ 目录下,其主要的函数有:led-gpio.c、led-xxx.c,其中led-gpio.c为通用的平台驱动程序,led-xxx.c为不同厂家提供的平台驱动程序。
我们在这里主要分析led-gpio.c
1、gpio_led_probe分析 # 打开该文件,直接找到加载驱动的入口函数gpio_led_probe
1.1 相关数据结构 # 1.1.1 gpio_led_platform_data # struct gpio_led_platform_data { int num_leds; const struct gpio_led *leds; #define GPIO_LED_NO_BLINK_LOW 0 /* No blink GPIO state low */ #define GPIO_LED_NO_BLINK_HIGH 1 /* No blink GPIO state high */ #define GPIO_LED_BLINK 2 /* Please, blink */ gpio_blink_set_t gpio_blink_set; }; 结构体名称:gpio_led_platform_data
文件位置:include/linux/leds.h
主要作用:LED的平台数据,用于对LED硬件设备的统一管理
这个结构体用于父节点向子节点传递的数据时使用
1.1.2 gpio_leds_priv # struct gpio_leds_priv { int num_leds; struct gpio_led_data leds[]; }; 结构体名称:gpio_leds_priv
...
Jan 18, 2024
本文阅读量次【深入理解Linux内核锁】三、原子操作 # 1、原子操作思想 # 原子操作(atomic operation),不可分割的操作。其通过原子变量来实现,以保证单个CPU周期内,读写该变量不能被打断,进而判断该变量的值,来解决并发引起的互斥。
Atomic类型的变量可以在执行期间禁止中断,并保证在访问变量时的原子性。
简单来说,我们可以把原子变量看作为一个标志位,然后再来检测该标志位的值。
其原子性表现在:操作该标志位的值,不可被打断。
在Linux内核中,提供了两类原子操作的接口,分别是针对位和整型变量的原子操作。
2、整型变量原子操作 # 2.1 API接口 # 对于整形变量的原子操作,内核提供了一系列的 API接口
/*设置原子变量的值*/ atomic_t v = ATOMIC_INIT(0); /* 定义原子变量v并初始化为0 */ void atomic_set(atomic_t *v, int i); /* 设置原子变量的值为i */ /*获取原子变量的值*/ atomic_read(atomic_t *v); /* 返回原子变量的值*/ /*原子变量的加减*/ void atomic_add(int i, atomic_t *v); /* 原子变量增加i */ void atomic_sub(int i, atomic_t *v); /* 原子变量减少i */ /*原子变量的自增,自减*/ void atomic_inc(atomic_t *v); /* 原子变量增加1 */ void atomic_dec(atomic_t *v); /* 原子变量减少1 */ /*原子变量的操作并测试*/ int atomic_inc_and_test(atomic_t *v); /*进行对应操作后,测试原子变量值是否为0*/ int atomic_dec_and_test(atomic_t *v); int atomic_sub_and_test(int i, atomic_t *v); /*原子变量的操作并返回*/ int atomic_add_return(int i, atomic_t *v); /*进行对应操作后,返回新的值*/ int atomic_sub_return(int i, atomic_t *v); int atomic_inc_return(atomic_t *v); int atomic_dec_return(atomic_t *v); 2.
...
Jan 18, 2024
本文阅读量次我的圈子: 高级工程师聚集地 创作理念:专注分享高质量嵌入式文章,让大家读有所得! 亲爱的读者,你好: 感谢你对我的专栏的关注和支持,我很高兴能和你分享我的知识和经验。如果你喜欢我的内容,想要阅读更多的精彩技术文章,可以扫码加入我的社群。
欢迎关注【嵌入式艺术】,董哥原创!
Jan 17, 2024
本文阅读量次Linux用户态和内核态交互的几种方式 # Linux分为内核态Kernel Mode和用户态User Mode,其通信方式主要有:
系统调用System Call:最常见的用户态和内核态之间的通信方式。通过系统调用接口(open、read、write、fork等)请求内核执行特定的动作。 中断Interrupts:中断包括软中断和硬中断,每当中断到来的时候,CPU会暂停当前执行的用户态代码,切换到内核态来处理中断。 信号Signal:内核通过Signal通知用户态进程发生了某些事件,用户态注册信号处理函数,来响应特定的信号事件。如 SIGTERM、SIGINT 等。 共享内存Share Memory:允许多个进程在它们的地址空间中共享一块内存区域,从而实现用户态和内核态之间的高效通信。这种方式避免了用户态和内核态之间频繁切换的问题,但是也需要考虑到数据的同步问题,保证数据一致性。 用户态User Mode访问内核态Kernel Mode的数据交互的方式有:
procfs进程文件系统:一个伪文件系统,因为其不占用外部存储空间,只占有少量的内存,挂载在/proc目录下
sysctl:它也是一个Linux命令,主要用来修改内核的运行时参数,也就是在内核运行时,动态修改内核参数。
和 procfs 的区别在于:procfs 主要是输出只读数据,而 sysctl 输出的大部分信息是可写的。
sysfs虚拟文件系统:通过/sys来完成用户态和内核的通信,和 procfs 不同的是,sysfs 是将一些原本在 procfs 中的,关于设备和驱动的部分,独立出来,以 “设备树” 的形式呈现给用户。
netlink 接口:也是最常用的一种方式,本质是socket接口,使用netlink用于网络相关的内核和用户进程之间的消息传递。
共享内存Share Memory:允许多个进程在它们的地址空间中共享一块内存区域,从而实现用户态和内核态之间的高效数据传输。
欢迎关注【嵌入式艺术】,董哥原创!
Jan 17, 2024
本文阅读量次三、Uboot驱动模型 # 全文耗时一周,精心汇总,希望对大家有所帮助,感觉可以的点赞,关注,不迷路,后续还有更多干货!
看文章前,答应我,静下心来,慢慢品!
3.1、什么是Uboot驱动模型 # 学过Linux的朋友基本都知道Linux的设备驱动模型,Uboot根据Linux的驱动模型架构,也引入了Uboot的驱动模型(driver model :DM)。
**这种驱动模型为驱动的定义和访问接口提供了统一的方法。**提高了驱动之间的兼容性以及访问的标准型,uboot驱动模型和kernel中的设备驱动模型类似。
3.2、为什么要有驱动模型呢 # 无论是Linux还是Uboot,一个新对象的产生必定有其要解决的问题,驱动模型也不例外!
提高代码的可重用性:为了能够使代码在不同硬件平台,不同体系架构下运行,必须要最大限度的提高代码的可重用性。 高内聚,低耦合:分层的思想也是为了达到这一目标,低耦合体现在对外提供统一的抽象访问接口,高内聚将相关度紧密的集中抽象实现。 便于管理:在不断发展过程中,硬件设备越来越多,驱动程序也越来越多,为了更好的管理驱动,也需要一套优秀的驱动架构! 3.3、如何使用uboot的DM模型 # DM模型的使用,可以通过menuconfig来配置。
make menuconfig
①:menuconfig配置全局DM模型 # Device Drivers -> Generic Driver Options -> Enable Driver Model 通过上面的路径来打开Driver Model模型,最终配置在.config文件中,CONFIG_DM=y
②:指定某个驱动的DM模型 # 全局的DM模型打开后,我们对于不通的驱动模块,使能或者失能DM功能。如MMC驱动为例:
Device Drivers -> MMC Host controller Support -> Enable MMC controllers using Driver Model 最终反映在.config文件中的CONFIG_DM_MMC=y
在对应的驱动中,可以看到判断#if !CONFIG_IS_ENABLED(DM_MMC),来判断是否打开DM驱动模型。
在管理驱动的Makefile文件中,也能看到obj-$(CONFIG_$(SPL_)DM_MMC) += mmc-uclass.o,来判断是否将驱动模型加入到编译选项中。
总之,我们要打开DM模型,最后反映在几个配置信息上:
CONFIG_DM=y,全局DM模型打开 CONFIG_DM_XXX=y,某个驱动的DM模型的打开 可以通过Kconifg、Makefile来查看对应宏的编译情况 3.4、DM模型数据结构 # 要想了解DM模型整套驱动框架,我们必须先了解它的一砖一瓦!也就是组成驱动框架的各个数据结构。
① global_data # typedef struct global_data { .
...
Jan 17, 2024
本文阅读量次Linux内存管理 | 三、虚拟地址空间管理 # 上一节,我们主要了解了虚拟内存空间的布局情况,趁热打铁,我们直接从源代码的视角,来看一下Linux内核是如何管理虚拟内存空间的。
废话不多说,直接开始!
1、用户态空间管理 # 读完上一节我们知道,用户态的布局情况如下:
我们运行的可执行程序,被加载进内存后,会作为一个进程存在,这个进程Linux内核会将其抽象成一个结构体。没错,它就是task_struct。
1.1 task_struct结构体 # task_struct结构体是进程的抽象,进程所涉及到的内容非常多,下面只列举出一些重要的数据结构,方面理解。
// include/linux/sched.h struct task_struct { ... pid_t pid; // 进程PID pid_t tgid; // 线程PID struct files_struct *files; // 进程打开的文件信息 struct mm_struct *mm; // 进程虚拟内存空间的内存描述符 ... } 如上,进程抽象为task_struct结构体,通过mm_struct结构体来管理虚拟内存空间。
1.2 mm_struct结构体 # 每个进程都有唯一的 mm_struct 结构体,也就是前边提到的每个进程的虚拟地址空间都是独立,互不干扰的。
mm_struct的结构体如下:
// include/linux/mm_types.h struct mm_struct { ... struct { ... unsigned long task_size; /* size of task vm space */ ... unsigned long mmap_base; /* base of mmap area */ unsigned long total_vm; /* Total pages mapped */ unsigned long locked_vm; /* Pages that have PG_mlocked set */ unsigned long pinned_vm; /* Refcount permanently increased */ unsigned long data_vm; /* VM_WRITE & ~VM_SHARED & ~VM_STACK */ unsigned long exec_vm; /* VM_EXEC & ~VM_WRITE & ~VM_STACK */ unsigned long stack_vm; /* VM_STACK */ unsigned long start_code, end_code, start_data, end_data; unsigned long start_brk, brk, start_stack; unsigned long arg_start, arg_end, env_start, env_end; .
...
Jan 19, 2024
本文阅读量次【MMC子系统】四、MMC控制器驱动层 # MMC控制器驱动层一般为chip manufacturer做的事,不同的芯片实现方式不尽相同。
Linux内核源码,相当大的一部分都是由Device Drivers程序代码组成,其次另一大部分就是那些你从来都没有听说过的Filesystem Format组成,真正核心的代码非常短小精悍的。
当然,设备驱动程序也有一套既定的框架,按照框架来编写,实现对应的接口就可以了,在这里,我们主要分析一下MMC控制器驱动的实现框架,不拘泥于细节。
下文以sunxi-mmc.c为例来分析,基于Linux4.19
4.1 通用驱动框架 # static int sunxi_mmc_probe(struct platform_device *pdev) { ..... } static const struct of_device_id sunxi_mmc_of_match[] = { { .compatible = "allwinner,sun4i-a10-mmc", .data = &sun4i_a10_cfg }, { .compatible = "allwinner,sun5i-a13-mmc", .data = &sun5i_a13_cfg }, { .compatible = "allwinner,sun7i-a20-mmc", .data = &sun7i_a20_cfg }, { .compatible = "allwinner,sun8i-a83t-emmc", .data = &sun8i_a83t_emmc_cfg }, { .compatible = "allwinner,sun9i-a80-mmc", .data = &sun9i_a80_cfg }, { .compatible = "allwinner,sun50i-a64-mmc", .
...
Jan 19, 2024
本文阅读量次【Bluetooth|蓝牙开发】四、BLE协议之物理层浅析 # 1、前言 # 上文,通过对蓝牙协议框架进行整体了解,其包含BR/EDR((Basic Rate / Enhanced Data Rate))、AMP(Alternate MAC/PHYs)、LE(Low Energy)三种技术,不同技术对应不同的协议栈,本专栏目前对于BLE技术进行详解!
==下面我们将BLE部分单独抽离出来,单独对其进行研究。==
BLE的协议可分为Bluetooth Application和Bluetooth Core两大部分,而Bluetooth Core又包含BLE Controller和BLE Host两部分。
快把小本本拿起来,一定要记住!
我们先从Physical Layer开始分析
2、Physical Channel # 任何一个通信系统,首先要确定的就是通信介质(物理通道,Physical Channel),BLE也不例外。在BLE协议中,“通信介质”的定义是由Physical Layer负责。
Physical Layer是这样描述BLE的通信介质的:
BLE属于无线通信,则其通信介质是一定频率范围下的频带资源(Frequency Band)
BLE的市场定位是个体和民用,因此使用免费的ISM频段(频率范围是2.400-2.4835 GHz)
为了同时支持多个设备,将整个频带分为40份,每份的带宽为2MHz,称作RF Channel。
经过上面的定义之后,BLE的物理通道划分已经明了了! $$ 频点(f)=2402(MHz)+k*2(MHz),k=(0…39) $$ 每个Channel的带宽为2MHz,如下图:
3、Physical Channel的细分 # 上面我们已经知道了,物理层被划分为了40个赛道,由于传输数据量的不同,为了更加充分利用好物理资源,进一步对通道进行了划分!
40个Physical Channel物理通道分别划分为3个广播通道advertising channel,和37个Data Channel数据通道。
对于数据量少,发送不频繁,时延不敏感的场景,使用广播通道通信。
例如一个传感器节点(如温度传感器),需要定时(如1s)向处理中心发送传感器数据(如温度)。
针对这种场景,BLE的Link Layer采取了一种比较懒的处理方式—-广播通信:
对于数据量大,发送频率高,时延较敏感的场景,使用数据通道。
BLE为这种场景里面的通信双方建立单独的通道(data channel)。这就是连接(connection)的过程。
同时,为了增加信道容量,增大抗干扰能力,连接不会长期使用一个固定的Physical Channel,而是在多个通道(如37个)之间随机但有规律的切换,这就是BLE的跳频(Hopping)技术。
对物理层的了解先止步于此,再往下面深入分析,意义不大。我们把重点放在BLE的Link Layer
欢迎关注【嵌入式艺术】,董哥原创!
Jan 19, 2024
本文阅读量次【LED子系统深度剖析】四、核心层详解(一) # 1、前言 # 上篇文章我们了解了子系统的硬件驱动层,下面我们来分析驱动框架中核心层的实现以及作用。
在LED子系统框架中,核心层包括几个部分:核心层的实现部分(led-core.c)、sysfs文件节点创建(led-class.c)、触发功能实现(led-triggers.c、driver/leds/triggers/led-xxx.c)
其中,触发功能部分较为独立,我们暂且先不去分析。
我们先从led-class.c文件开始分析
2、leds_init分析 # 该函数其主要是为了创建LED设备文件节点,方便用户通过节点直接访问。
该文件,我们直接拉下底部,我们直接看入口函数:leds_init
2.1 相关数据结构 # 2.1.1 class # /** * struct class - device classes * @name: Name of the class. * @owner: The module owner. * @class_groups: Default attributes of this class. * @dev_groups: Default attributes of the devices that belong to the class. * @dev_kobj: The kobject that represents this class and links it into the hierarchy. * @dev_uevent: Called when a device is added, removed from this class, or a * few other things that generate uevents to add the environment * variables.
...
Jan 18, 2024
本文阅读量次【深入理解Linux内核锁】四、自旋锁 # 上两节主要讲解了中断屏蔽和原子操作,这两个作为最底层的操作,几乎在Linux内核中都不单独使用,下面我们来带大家了解一下常用的自旋锁!
1、什么是自旋锁? # 自旋锁是一种典型的对临界资源进行互斥访问的手段。
它的底层实现逻辑是:原子变量+判断检测。
原子变量我们可以理解为一把锁,通过操作原子变量(锁)的状态,并对其进行判断,如果锁未被锁定,我们就继续往下执行;如果锁已经被锁定,我们就原地自旋,直到等到锁被打开。
在ARM平台下,自旋锁的实现使用了ldrex、strex、以及内存屏障指令dmb、dsb、wfe、sev等。
2、自旋锁思想 # 自旋锁主要针对于SMP或者单CPU但内核可抢占的情况,对于单CPU内核不可抢占的情况时,自旋锁退化为空操作。 自旋锁实际为忙等锁,当锁不可用时,CPU一直处于等待状态,直到该锁被释放。 自旋锁可能会导致内核死锁,当递归使用自旋锁时,则将该CPU锁死。 在多核SMP的情况下,任何一个核拿到了自旋锁,该核上的抢占调度也暂时禁止了,但是没有禁止另外一个核的抢占调度。 在自旋锁锁定期间,不能调用引起进程调度的函数,如copy_from_user()、copy_to_user()、kmalloc()和msleep(),否则会导致内核崩溃 3、自旋锁的定义及实现 # 3.1 API接口 # // 定义自旋锁 spinlock_t lock; // 初始化自旋锁 spin_lock_init(&lock) // 获得自旋锁 spin_lock(&lock) // 获取自旋锁,如果立即获得锁,则直接返回,否则,自旋等待,直到锁被释放 spin_trylock(&lock) // 尝试获取自旋锁,如果立即获得锁,返回true,否则直接返回false,不原地等待 // 释放自旋锁 spin_unlock(&lock) 自旋锁保证了不受其他CPU或者单CPU内的抢占进程的干扰,但是对于临界区代码,仍然有可能会受到中断和底半部的影响。
为了解决这种问题,我们就要使用自旋锁的衍生。
spin_lock_irq() = spin_lock() + local_irq_disable() // 获取自旋锁并关中断 spin_unlock_irq() = spin_unlock() + local_irq_enable() // 释放自旋锁并开中断 spin_lock_irqsave() = spin_lock() + local_irq_save() // 获取自旋锁并关中断,保存中断状态 spin_unlock_irqrestore() = spin_unlock() + local_irq_restore()//释放自旋锁,开中断并恢复中断状态 spin_lock_bh() = spin_lock() + local_bh_disable() // 获取自旋锁并关底半部中断 spin_unlock_bh() = spin_unlock() + local_bh_enable() // 释放自旋锁并发开底半部中断 当我们的临界区代码,有可能被进程或者中断访问时,就需要在进程上下文中,调用spin_lock_irqsave()、spin_unlock_irqrestore(),在中断上下文中调用spin_lock()、spin_unlock(),如下图:
...