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++,以及经验分享、行业资讯、物联网等技术知
评论 (0)
  •   无人装备作战协同仿真系统软件:科技的关键支撑   无人装备作战协同仿真系统软件,作为一款综合性仿真平台,主要用于模拟无人机、无人车、无人艇等无人装备在复杂作战环境中的协同作战能力、任务规划、指挥控制以及性能评估。该系统通过搭建虚拟战场环境,支持多种无人装备协同作战仿真,为作战指挥、装备研发、战术训练和作战效能评估,提供科学依据。   应用案例   系统软件供应可以来这里,这个首肌开始是幺伍扒,中间是幺幺叁叁,最后一个是泗柒泗泗,按照数字顺序组合就可以找到。   核心功能   虚拟战
    华盛恒辉l58ll334744 2025-04-14 17:24 90浏览
  • 一、引言:智能化趋势下的学爬玩具开发挑战随着早教理念的普及,学爬玩具作为婴幼儿早期运动能力开发的重要工具,市场需求持续增长。然而,传统学爬玩具开发面临多重挑战:需集成红外遥控、语音交互、电机控制等多模块,开发周期长、硬件成本高;复杂的红外编解码与语音功能实现依赖工程师深度参与,技术门槛陡增。如何以更低成本、更快速度打造差异化产品,成为行业亟待解决的痛点。二、传统开发模式痛点分析硬件冗余红外接收模块、语音芯片、主控MCU分立设计,导致PCB面积增加,BOM成本攀升。开发周期长需工程师独立完成红外协
    广州唯创电子 2025-04-16 08:40 53浏览
  • 一、芯片的发展历程总结:1、晶体管的诞生(1)电子管时代 20世纪40年代,电子管体积庞大、功耗高、可靠性差,无法满足计算机小型化需求。(2)晶体管时代 1947年,贝尔实验室的肖克利、巴丁和布拉顿发明点接触晶体管,实现电子信号放大与开关功能,标志着固态电子时代的开端。 1956年,肖克利发明晶体管。(3)硅基晶体管时代 早期晶体管采用锗材料,但硅更耐高温、成本低,成为主流材料。2、集成电路的诞生与发展 1958年,德州仪器工程师基尔比用锗材料制成世界上第一块含多个晶体管的集成电路,同年仙童半导
    碧海长空 2025-04-15 09:30 129浏览
  • 一、智能语音播报技术演进与市场需求随着人工智能技术的快速发展,TTS(Text-to-Speech)技术在商业场景中的应用呈现爆发式增长。在零售领域,智能收款机的语音播报功能已成为提升服务效率和用户体验的关键模块。WT3000T8作为新一代高性能语音合成芯片,凭借其优异的处理能力和灵活的功能配置,正在为收款机智能化升级提供核心技术支持。二、WT3000T8芯片技术特性解析硬件架构优势采用32位高性能处理器(主频240MHz),支持实时语音合成与多任务处理QFN32封装(4x4mm)实现小型化设计
    广州唯创电子 2025-04-15 08:53 112浏览
  • 展会名称:2025成都国际工业博览会(简称:成都工博会)展会日期:4月23 -25日展会地址:西部国际博览城展位号:15H-E010科士威传动将展示智能制造较新技术及全套解决方案。 2025年4月23-25日,中国西部国际博览城将迎来一场工业领域的年度盛会——2025成都国际工业博览会。这场以“创链新工业,共碳新未来”为主题的展会上,来自全球的600+ 家参展企业将齐聚一堂,共同展示智能制造产业链中的关键产品及解决方案,助力制造业向数字化、网络化、智能化转型。科士威传动将受邀参展。&n
    科士威传动 2025-04-14 17:55 90浏览
  • 三、芯片的制造1、制造核心流程 (1)晶圆制备:以高纯度硅为基底,通过拉晶、切片、抛光制成晶圆。 (2)光刻:光刻、离子注入、薄膜沉积、化学机械抛光。 (3)刻蚀与沉积:使用干法刻蚀(等离子体)精准切割图形,避免侧壁损伤。 (4)掺杂:注入离子形成PN结特性,实现晶体管开关功能。2、材料与工艺创新 (1)新材料应用: 高迁移率材料(FinFET中的应变硅、GaN在射频芯片中的应用); 新型封装技术(3D IC、TSV硅通孔)提升集成度。 (2)工艺创新: 制程从7nm到3nm,设计架构由F
    碧海长空 2025-04-15 11:33 217浏览
  • 2025年4月13日(中国武汉)——在全球经济分化与地缘政治不确定性加剧的背景下,科技与金融的深度融合已成为推动创新与繁荣的关键动力。为实现科技创新、产业进步和金融发展有机结合,发挥金融对科技创新和产业进步的支持作用,国际金融论坛(IFF)科技金融委员会启动大会暨首届科技金融圆桌会议于4月13日在湖北省武汉市武汉产业创新发展研究院成功举行。同时,IFF科技金融委员会由国际金融论坛IFF与武创院联合成立。本次大会汇聚了来自政府、产业与学术研究机构及金融等多领域的精英,共同探讨科技金融如何更好地服务
    华尔街科技眼 2025-04-15 20:53 51浏览
  • 在当今汽车电子化和智能化快速发展的时代,车规级电子元器件的质量直接关系到汽车安全性能。三星作为全球领先的电子元器件制造商,其车规电容备受青睐。然而,选择一个靠谱的三星车规电容代理商至关重要。本文以行业领军企业北京贞光科技有限公司为例,深入剖析如何选择优质代理商。选择靠谱代理商的关键标准1. 授权资质与行业地位选择三星车规电容代理商首先要验证其授权资质及行业地位。北京贞光科技作为中国电子元器件行业的领军者,长期走在行业前沿,拥有完备的授权资质。公司专注于市场分销和整体布局,在电子元器件领域建立了卓
    贞光科技 2025-04-14 16:18 152浏览
  • 四、芯片封测技术及应用场景1、封装技术的发展历程 (1)DIP封装:早期分立元件封装,体积大、引脚少; (2)QFP封装:引脚密度提升,适用于早期集成电路。 (3)BGA封装:高密度互连,散热与信号传输优化; (4)3D封装:通过TSV(硅通孔)实现垂直堆叠,提升集成度(如HBM内存堆叠); (5)Chiplet封装:异质集成,将不同工艺节点的模块组合(如AMD的Zen3+架构)。 (6)SiP封装:集成多种功能芯片(如iPhone的A系列SoC整合CPU、GPU、射频模块)。2、芯片测试 (1
    碧海长空 2025-04-15 11:45 218浏览
  • 一、智能门锁市场痛点与技术革新随着智能家居的快速发展,电子门锁正从“密码解锁”向“无感交互”进化。然而,传统人体感应技术普遍面临三大挑战:功耗高导致续航短、静态人体检测能力弱、环境适应性差。WTL580微波雷达解决方案,以5.8GHz高精度雷达感知技术为核心,突破行业瓶颈,为智能门锁带来“精准感知-高效触发-超低功耗”的全新交互范式。二、WTL580方案核心技术优势1. 5.8GHz毫米波雷达:精准感知的革命全状态人体检测:支持运动、微动(如呼吸)、静态(坐卧)多模态感知,检测灵敏度达0.1m/
    广州唯创电子 2025-04-15 09:20 98浏览
  • 二、芯片的设计1、芯片设计的基本流程 (1)需求定义: 明确芯片功能(如处理器、存储、通信)、性能指标(速度、功耗、面积)及目标应用场景(消费电子、汽车、工业)。 (2)架构设计: 确定芯片整体框架,包括核心模块(如CPU、GPU、存储单元)的协同方式和数据流路径。 (3)逻辑设计: 通过硬件描述语言(如Verilog、VHDL)将架构转化为电路逻辑,生成RTL(寄存器传输级)代码。 (4)物理设计: 将逻辑代码映射到物理布局,涉及布局布线、时序优化、功耗分析等,需借助EDA工具(如Ca
    碧海长空 2025-04-15 11:30 175浏览
  • 你知道精益管理中的“看板”真正的意思吗?在很多人眼中,它不过是车间墙上的一块卡片、一张单子,甚至只是个用来控制物料的工具。但如果你读过大野耐一的《丰田生产方式》,你就会发现,看板的意义远不止于此。它其实是丰田精益思想的核心之一,是让工厂动起来的“神经系统”。这篇文章,我们就带你一起从这本书出发,重新认识“看板”的深层含义。一、使“看板”和台车结合使用  所谓“看板”就是指纸卡片。“看板”的重要作用之一,就是连接生产现场上道工序和下道工序的信息工具。  “看板”是“准时化”生产的重要手段,它总是要
    优思学院 2025-04-14 15:02 118浏览
  •   高空 SAR 目标智能成像系统软件:多领域应用的前沿利器   高空 SAR(合成孔径雷达)目标智能成像系统软件,专门针对卫星、无人机等高空平台搭载的 SAR传感器数据,融合人工智能与图像处理技术,打造出的高效目标检测、识别及成像系统。此软件借助智能算法,显著提升 SAR图像分辨率、目标特征提取能力以及实时处理效率,为军事侦察、灾害监测、资源勘探等领域,提供关键技术支撑。   应用案例系统软件供应可以来这里,这个首肌开始是幺伍扒,中间是幺幺叁叁,最后一个是泗柒泗泗,按照数字顺序组合
    华盛恒辉l58ll334744 2025-04-14 16:09 155浏览
  • 一、引言:健康管理数字化浪潮下的血压监测转型在慢性病高发与老龄化加剧的双重压力下,家庭健康监测设备正从“被动测量工具”向“主动健康管家”演进。传统血压计虽能提供基础数值,却无法解决用户的核心痛点:数据如何解读?异常如何干预?风险如何预防?WT2605C芯片方案的诞生,通过“AI对话+云端互联+个性化服务”三重技术突破,重新定义了血压计的价值边界——它不仅是一台测量仪器,更是一个全天候在线的健康管理生态系统。二、传统血压计的局限与用户需求升级1. 功能单一性困境数据孤岛:仅显示收缩压/舒张压数值,
    广州唯创电子 2025-04-16 08:55 55浏览
我要评论
0
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦