Linux驱动程序可用的内核辅助工具(一)

原创 一起学嵌入式 2023-08-02 07:46

扫描关注一起学嵌入式,一起学习,一起成长


Linux 内核是独立的软件,他没有使用任何 C 语言库,他自己实现了很多工具和辅助工具。

本系列文章将盘点一些内核提供的辅助工具函数。在编写驱动程序时,我们可以利用内核提供的工具函数,方便实现目标功能。

宏 container_of

这个宏定义非常出名,好多文章对齐进行了解析,并且这个宏在内核和驱动中经常见到。

该宏的作用是通过结构体成员的地址和结构体类型推导出结构体的地址。

在 linux 源码的 tools\include\linux\kernel.h文件下,container_of()的定义如下:

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

#define container_of(ptr, type, member) ({   \
  const typeof(((type *)0)->member) * __mptr = (ptr); \
  (type *)((char *)__mptr - offsetof(type, member)); })

宏的参数分别为:type 是指结构体的类型,member 是成员在结构体中的名字,ptr 是该成员在 type 结构体中的地址。

宏container_of主要用在内核的通用容器中 。

该宏的详细介绍可以参考:对linux内核中container_of()宏的理解

链表

链表有两种类型:

  • 单向链表
  • 双向链表

内核中实现了循环双向链表,这个结构能够实现FIFO和LIFO。如果要使用内核提供的链表操作函数,代码中需要添加头文件

内核中链表的实现核心部分数据结构 struct list_head 定义为:

struct list_head
{
 struct list_head *next, *prev;
}

struct list_head数据结构不包含链表节点的数据区,通常是用在链表头或者嵌入到其他数据结构中。

创建和初始化链表有两种方法:动态创建和静态创建。

动态方法创建并初始化链表方法如下:

struct list_head mylist;
INIT_LIST_HEAD(&mylist);

INIT_LIST_HEAD() 展开如下:

static inline void INIT_LIST_HEAD(struct list_head *list)
{
    list->next = list;
    list->prev = list;
}

静态创建链表通过 LIST_HEAD 宏完成:

LIST_HEAD(mylist)

LIST_HEAD 的定义如下:

#define LIST_HEAD(name) \
   struct list_head name = LIST_HEAD_INIT(name)

其中LIST_HEAD_INIT 展开为:

#define LIST_HEAD_INIT(name) { &(name), &(name) }

把next和prev指针都初始化并指向自己,这样便初始化了一个带头节点的空链表。

添加节点到链表中,内核提供了几个接口函数,如list_add()是把一个节点添加到表头,list_add_tail()是插入表尾。

void list_add(struct list_head *new, struct list_head *head)
list_add_tail(struct list_head *new, struct list_head *head)

内核提供的list_add用于向链表添加新项,它是内部函数__list_add的包装。

static inline void __list_add(struct list_head *new, struct list_head *prev,struct list_head *next)
{
  next->prev = new;
  new->next = next;
  new->prev = prev;
  prev->next = new;
}

删除节点很简单:

void list_del(struct list_head *entry);

链表遍历

使用宏 list_for_each_entry(pos, head, member) 进行链表遍历。

参数解释 head:链表的头节点;member:数据结构中链表 struct list_head 的名称;pos:用于迭代。它是一个循环游标,就像 for(i=0; i 中的 i

#define list_for_each_entry(pos, head, member) \ 
for (pos = list_entry((head)->next,typeof(*pos), member); \
  &pos->member != (head); \
   pos = list_entry(pos->member.next,typeof(*pos), member))

#define list_entry(ptr, type, member)  container_of(ptr, type, member)

内核的睡眠机制

内核调度器管理要运行的任务列表,这被称作运行队列。睡眠进程不再被调度,因为已将它们从运行队列 中移除。除非其状态改变(唤醒),否则睡眠进程将永远不会被执行。

进程一旦进入等待状态,就可以释放处理器,一定要确保有条件或其他进程会唤醒它。Linux 内核通过提供一组函数和数据结构来简化睡眠机制的实现 。

等待队列

Linux内核提供了一个数据结构,用来记录等待执行的任务,那就是等待队列,主要用于处理被阻塞的 I/O 操作。其结构定义在 include/linux/wait.h 文件中:

struct wait_queue_entry {
 unsigned int  flags;
 void   *private;
 wait_queue_func_t func;
 struct list_head entry;
};

其中,entry  字段是一个链表,将进入睡眠的进程加入到这个链表中(在链表中排队),并进入睡眠状态。

处理等待队列也有两种方式:静态声明、动态声明,常用到的函数如下:

  • 静态声明
DECLARE_WAIT_QUEUE_HEAD(name)
  • 动态声明
wait_queue_head_t my_wait_queue;

init_waitqueue_head(&my_wait_queue);
  • 阻塞某个任务
/* 如果条件condition为真,则唤醒任务并执行。若为假,则阻塞 */
wait_event_interruptible(wq_head, condition)
  • 解除阻塞
void wake_up_interruptible(wait_queue_head_ts *q)

wait_event_interruptible 不会持续轮询,而只是在被调用时评估条件。如果条件为假,则进程将进入TASK_INTERRUPTIBLE 状态并从运行队列中删除。

当每次在等待队列中调用 wake_up_interruptible 时,都会重新检查条件。如果 wake_up_interruptible 运行时发现条件为真,则等待队列中的进程将被唤醒,并将其状态设置为 TASK_RUNNING

进程按照它们进入睡眠的顺序唤醒。要唤醒在队列中等待的所有进程,应该使用 wake_up_interruptible_all

如果调用了 wake_upwake_up_interruptible,并且条件仍然是 FALSE,则什么都不会发生。如果没有调 用 wake_up(或 wake_up_interuptible ),进程将永远不会被唤醒 。

工作队列

等待队列有了,Linux 内核提供了工作队列,其中的 work 结构定义如下

struct work_struct {
 atomic_long_t data;
 struct list_head entry;
 work_func_t func;
#ifdef CONFIG_LOCKDEP
 struct lockdep_map lockdep_map;
#endif
};

其中,func 为工作 work 的处理函数,其类型定义为:

typedef void (*work_func_t)(struct work_struct *work);

Linux 内核一直运行着 worker 线程,他会对工作队列中的 work 进行处理。

定义并初始化一个 work 操作如下:

struct work_struct wrk;

INIT_WORK(_work, _func)

将work 添加进内核的全局工作队列中,即让 work 参与调度

schedule_work(struct work_struct *work)

在驱动中,工作队列和等待队列可以配合使用。

定时器

Linux 内核提供了两种定时器:

  • 标准定时器
  • 高精度定时器

下边分别进行介绍。

标准定时器

标准定时器是以 jffies  为基本单位计数。jiffy 是在 中声明的内核时间单位。

jffies 是记录着从电脑开机到现在总共的时钟中断次数。取决于系统的时钟频率,单位是 Hz,一般是一秒钟中断产生的次数,每个增量被称为一个 Tick(时钟节拍)。

内核中定时器的结构定义为,在文件 中:

struct timer_list {
 struct hlist_node entry;
 unsigned long  expires;
 void   (*function)(struct timer_list *);
 u32   flags;

#ifdef CONFIG_LOCKDEP
 struct lockdep_map lockdep_map;
#endif
};

expires 是以 jiffies 为单位的绝对值。entry 是双向链表,function 为定时器的回调函数;flags 是可选的,被传递给回调函数。

设置定时器,提供用户定义的回调函数和标志变量值:

timer_setup(timer, callback, flags)

设置定时器的超时时间

mod_timer(struct timer_list *timer, unsigned long expires)

删除定时器:

void del_timer(struct timer_list *timer)

高精度定时器

内核 V2.6.16 引入了高精度定时器,通过配置内核 CONFIG_HIGH_RES_TIMERS 选项启用,其精度取决于平台,最高可达纳秒精度。标准定时器的精度为毫秒。

在系统上使用HRT时,要确认内核和硬件支持它。换句话说,必须用与平台相关的代码来访问硬件HRT。

若要使用高精度定时器,需要包含头文件

Linux内核源码HRT结构定义如下:

struct hrtimer {
 struct timerqueue_node  node;
 ktime_t    _softexpires;
 enum hrtimer_restart  (*function)(struct hrtimer *);
 struct hrtimer_clock_base *base;
 u8    state;
 u8    is_rel;
 u8    is_soft;
 u8    is_hard;
};

HRT初始化操作

void hrtimer_init(struct hrtimer *timer, clockid_t which_clock, enum hrtimer_mode mode);

启动 hrtimer

void hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode)

其中,mode 代表到期模式。对于绝对时间值,它应该是 HRTIMER_MODE_ABS,对于相对于现在的时间值,应该是HRTIMER_MODE_REL

取消 hrtimer

int hrtimer_cancel( struct hrtimer *timer);
int hrtimer_try_to_cancel(struct hrtimer *timer)

这两个函数当定时器没被激活时都返回 0,激活时 返回1。这两个函数之间的区别是,如果定时器处于激活状态或其回调函数正在运行,则 hrtimer_try_to_cancel 会失败,返回-1,而 hrtimer_cancel 将等待回调完成。

内核内部维护着一个任务超时列表(它知道什么时候要睡眠以及睡眠多久)。

在空闲状态下,如果下一个Tick比任务列表超时中的最小超时更远,内核则使用该超时值对定时器进行编程。当定时器到期时,内核重新启用周期Tick并调用调度器,它调度与超时相关的任务 。

内核锁机制

设备驱动程序常用的锁有两种:

  • 互斥锁
  • 自旋锁

下面分别进行介绍。

互斥锁

互斥锁 mutex 是较常用的锁机制。他的结构在文件 include/linux/mutex.h 定义

struct mutex 
{
 atomic_long_t  owner;
 raw_spinlock_t  wait_lock;
 struct list_head wait_list;
 ...
};

wait_list 为等待互斥锁的任务链表。

静态声明互斥锁:

DEFINE_MUTEX(mutexname)

动态声明:

struct mutex my_mutex;
mutex_init(&my_mutex);

获取互斥锁:

void mutex_lock(struct mutex *lock);
int mutex_lock_interruptible(struct mutex *lock);
int mutex_lock_killable(struct mutex *lock);

释放互斥锁:

void mutex_unlock(struct mutex *lock);

调用 mutex_lock() 时要非常小心,只有能够保证无论在什么情况下互斥锁都会释放时才可以使用它。

在用户上下文中,建议始终使用 mutex_lock_interruptible() 来获取互斥锁,因为 mutex_lock()即使收到信号(甚至是Ctrl+C组合键),也不会返回。

互斥锁使用规则

使用互斥锁需要遵守一些规则:

  • 一次只能有一个任务持有互斥锁;
  • 多次解锁是不允许的。
  • 它们必须通过API初始化。
  • 持有互斥锁的任务不可能退出,因为互斥锁将保持锁定,可能的竞争者会永远等待(将睡眠)。
  • 不能释放锁定的内存区域。
  • 持有的互斥锁不得重新初始化。
  • 由于它们涉及重新调度,因此互斥锁不能用在原子上下文中,如 Tasklet 和定时器。

自旋锁

自旋锁,顾名思义,就是CPU一直在循环等待锁可以获取。因此,线程在获取自旋锁的过程中会大量消耗CPU。

因此在可以快速获取时再使用它,尤其是当持有自旋锁的时间比重新调度时间少时 。一旦关键任务完成,自旋 锁就应该被释放。

在一个处理器上,自旋意味着在该处理器上不能再运行其他任何任务;因此,在单核机器上使用自旋锁是没有任何意义的。最佳情况下,系统可能会变慢,最糟情况下,和互斥锁一样会造成死锁。

在单个处理器(核)系统上,应该使用下边两种接口函数

spin_lock_irqsave(lock, flags)
spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)

它们分别禁用处理器上中断,防止中断并发。

由于事先并不知道所写驱动程序运行在什么系统上,因此建议使用 spin_lock_irqsave () 获取自旋锁,该函数会 在获取自旋锁之前,禁止当前处理器(调用该函数的处理器)上中断。

然后,应该用 spin_unlock_irqrestore() 释放锁,它执行的操作与获取自旋锁操作相反。

自旋锁与互斥锁

自旋锁和互斥锁都用用于处理内核中并发访问,它们有各自的使用场景。

两种锁有以下几点区别:

  • 互斥锁保护进程的关键资源,而自旋锁保护IRQ处理程序的关键部分 。
  • 互斥锁让竞争者在获得锁之前睡眠,而自旋锁在获得锁之前一直自旋循环(消耗CPU)。
  • 自旋锁不能长时间持有,因为等待者在等待取锁期间会浪费CPU时间;而互斥锁则可以长时间持有,因为竞争者被放入等待队列中进入睡眠状态。

好了,今天就这些内容。

感谢阅读,加油~


扫码,拉你进高质量嵌入式交流群


关注我【一起学嵌入式】,一起学习,一起成长。


觉得文章不错,点击“分享”、“”、“在看” 呗!

一起学嵌入式 公众号【一起学嵌入式】,RTOS、Linux编程、C/C++,以及经验分享、行业资讯、物联网等技术知
评论
  •  在全球能源结构加速向清洁、可再生方向转型的今天,风力发电作为一种绿色能源,已成为各国新能源发展的重要组成部分。然而,风力发电系统在复杂的环境中长时间运行,对系统的安全性、稳定性和抗干扰能力提出了极高要求。光耦(光电耦合器)作为一种电气隔离与信号传输器件,凭借其优秀的隔离保护性能和信号传输能力,已成为风力发电系统中不可或缺的关键组件。 风力发电系统对隔离与控制的需求风力发电系统中,包括发电机、变流器、变压器和控制系统等多个部分,通常工作在高压、大功率的环境中。光耦在这里扮演了
    晶台光耦 2025-01-08 16:03 84浏览
  • 在当前人工智能(AI)与物联网(IoT)的快速发展趋势下,各行各业的数字转型与自动化进程正以惊人的速度持续进行。如今企业在设计与营运技术系统时所面临的挑战不仅是技术本身,更包含硬件设施、第三方软件及配件等复杂的外部因素。然而这些系统往往讲究更精密的设计与高稳定性,哪怕是任何一个小小的问题,都可能对整体业务运作造成严重影响。 POS应用环境与客户需求以本次分享的客户个案为例,该客户是一家全球领先的信息技术服务与数字解决方案提供商,遭遇到一个由他们所开发的POS机(Point of Sal
    百佳泰测试实验室 2025-01-09 17:35 46浏览
  • 职场是人生的重要战场,既是谋生之地,也是实现个人价值的平台。然而,有些思维方式却会悄无声息地拖住你的后腿,让你原地踏步甚至退步。今天,我们就来聊聊职场中最忌讳的五种思维方式,看看自己有没有中招。1. 固步自封的思维在职场中,最可怕的事情莫过于自满于现状,拒绝学习和改变。世界在不断变化,行业的趋势、技术的革新都在要求我们与时俱进。如果你总觉得自己的方法最优,或者害怕尝试新事物,那就很容易被淘汰。与其等待机会找上门,不如主动出击,保持学习和探索的心态。加入优思学院,可以帮助你快速提升自己,与行业前沿
    优思学院 2025-01-09 15:48 42浏览
  • 在过去十年中,自动驾驶和高级驾驶辅助系统(AD/ADAS)软件与硬件的快速发展对多传感器数据采集的设计需求提出了更高的要求。然而,目前仍缺乏能够高质量集成多传感器数据采集的解决方案。康谋ADTF正是应运而生,它提供了一个广受认可和广泛引用的软件框架,包含模块化的标准化应用程序和工具,旨在为ADAS功能的开发提供一站式体验。一、ADTF的关键之处!无论是奥迪、大众、宝马还是梅赛德斯-奔驰:他们都依赖我们不断发展的ADTF来开发智能驾驶辅助解决方案,直至实现自动驾驶的目标。从新功能的最初构思到批量生
    康谋 2025-01-09 10:04 54浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2025-01-09 09:58 38浏览
  • HDMI 2.2 规格将至,开启视听新境界2025年1月6日,HDMI Forum, Inc. 宣布即将发布HDMI规范2.2版本。新HDMI规范为规模庞大的 HDMI 生态系统带来更多选择,为创建、分发和体验理想的终端用户效果提供更先进的解决方案。新技术为电视、电影和游戏工作室等内容制作商在当前和未来提供更高质量的选择,同时实现多种分发平台。96Gbps的更高带宽和新一代 HDMI 固定比率速率传输(Fixed Rate Link)技术为各种设备应用提供更优质的音频和视频。终端用户显示器能以最
    百佳泰测试实验室 2025-01-09 17:33 43浏览
  • 「他明明跟我同梯进来,为什么就是升得比我快?」许多人都有这样的疑问:明明就战绩也不比隔壁同事差,升迁之路却比别人苦。其实,之间的差异就在于「领导力」。並非必须当管理者才需要「领导力」,而是散发领导力特质的人,才更容易被晓明。许多领导力和特质,都可以通过努力和学习获得,因此就算不是天生的领导者,也能成为一个具备领导魅力的人,进而被老板看见,向你伸出升迁的橘子枝。领导力是什么?领导力是一种能力或特质,甚至可以说是一种「影响力」。好的领导者通常具备影响和鼓励他人的能力,并导引他们朝着共同的目标和愿景前
    优思学院 2025-01-08 14:54 93浏览
  • 村田是目前全球量产硅电容的领先企业,其在2016年收购了法国IPDiA头部硅电容器公司,并于2023年6月宣布投资约100亿日元将硅电容产能提升两倍。以下内容主要来自村田官网信息整理,村田高密度硅电容器采用半导体MOS工艺开发,并使用3D结构来大幅增加电极表面,因此在给定的占位面积内增加了静电容量。村田的硅技术以嵌入非结晶基板的单片结构为基础(单层MIM和多层MIM—MIM是指金属 / 绝缘体/ 金属) 村田硅电容采用先进3D拓扑结构在100um内,使开发的有效静电容量面积相当于80个
    知白 2025-01-07 15:02 155浏览
  • 在智能网联汽车中,各种通信技术如2G/3G/4G/5G、GNSS(全球导航卫星系统)、V2X(车联网通信)等在行业内被广泛使用。这些技术让汽车能够实现紧急呼叫、在线娱乐、导航等多种功能。EMC测试就是为了确保在复杂电磁环境下,汽车的通信系统仍然可以正常工作,保护驾乘者的安全。参考《QCT-基于LTE-V2X直连通信的车载信息交互系统技术要求及试验方法-1》标准10.5电磁兼容试验方法,下面将会从整车功能层面为大家解读V2X整车电磁兼容试验的过程。测试过程揭秘1. 设备准备为了进行电磁兼容试验,技
    北汇信息 2025-01-09 11:24 60浏览
  • 1月7日-10日,2025年国际消费电子产品展览会(CES 2025)盛大举行,广和通发布Fibocom AI Stack,赋智千行百业端侧应用。Fibocom AI Stack提供集高性能模组、AI工具链、高性能推理引擎、海量模型、支持与服务一体化的端侧AI解决方案,帮助智能设备快速实现AI能力商用。为适应不同端侧场景的应用,AI Stack具备海量端侧AI模型及行业端侧模型,基于不同等级算力的芯片平台或模组,Fibocom AI Stack可将TensorFlow、PyTorch、ONNX、
    物吾悟小通 2025-01-08 18:17 49浏览
  • 一个真正的质量工程师(QE)必须将一件产品设计的“意图”与系统的可制造性、可服务性以及资源在现实中实现设计和产品的能力结合起来。所以,可以说,这确实是一种工程学科。我们常开玩笑说,质量工程师是工程领域里的「侦探」、「警察」或「律师」,守护神是"墨菲”,信奉的哲学就是「墨菲定律」。(注:墨菲定律是一种启发性原则,常被表述为:任何可能出错的事情最终都会出错。)做质量工程师的,有时会不受欢迎,也会被忽视,甚至可能遭遇主动或被动的阻碍,而一旦出了问题,责任往往就落在质量工程师的头上。虽然质量工程师并不负
    优思学院 2025-01-09 11:48 75浏览
  • 故障现象一辆2017款东风风神AX7车,搭载DFMA14T发动机,累计行驶里程约为13.7万km。该车冷起动后怠速运转正常,热机后怠速运转不稳,组合仪表上的发动机转速表指针上下轻微抖动。 故障诊断 用故障检测仪检测,发动机控制单元中无故障代码存储;读取发动机数据流,发现进气歧管绝对压力波动明显,有时能达到69 kPa,明显偏高,推断可能的原因有:进气系统漏气;进气歧管绝对压力传感器信号失真;发动机机械故障。首先从节气门处打烟雾,没有发现进气管周围有漏气的地方;接着拔下进气管上的两个真空
    虹科Pico汽车示波器 2025-01-08 16:51 107浏览
  • 本文介绍编译Android13 ROOT权限固件的方法,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。关闭selinux修改此文件("+"号为修改内容)device/rockchip/common/BoardConfig.mkBOARD_BOOT_HEADER_VERSION ?= 2BOARD_MKBOOTIMG_ARGS :=BOARD_PREBUILT_DTB
    Industio_触觉智能 2025-01-08 00:06 109浏览
  • 根据环洋市场咨询(Global Info Research)项目团队最新调研,预计2030年全球中空长航时无人机产值达到9009百万美元,2024-2030年期间年复合增长率CAGR为8.0%。 环洋市场咨询机构出版了的【全球中空长航时无人机行业总体规模、主要厂商及IPO上市调研报告,2025-2031】研究全球中空长航时无人机总体规模,包括产量、产值、消费量、主要生产地区、主要生产商及市场份额,同时分析中空长航时无人机市场主要驱动因素、阻碍因素、市场机遇、挑战、新产品发布等。报告从中空长航时
    GIRtina 2025-01-09 10:35 54浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦