讲师中心 微信公众号

工具推荐

视频效率加速
剧云
剧云

中文短剧剧本制作平台

updream

一键生成高质量推广成片

Laper
Laper

剧本策划到成片协作

Seko
Seko

AI短剧一键生成成片

Linux 内核辅助工具函数盘点:container_of 宏的解析与应用

梦明吖_6595

梦明吖_6595

发布时间:2024-07-07 15:34:01

|

1073人浏览过

|

来源于ITcool

转载

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

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

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

宏container_of

这个宏定义十分出名,很多文章对齐进行了解析,但是这个宏在内核和驱动中常常看见。

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

在linux源码的toolsincludelinuxkernel.h文件下linux 驱动 内核,container_of()的定义如下:

<span style="color: #5c6370;font-style: italic;line-height: 26px">#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)</span><br /><br /><span style="color: #5c6370;font-style: italic;line-height: 26px">#define container_of(ptr, type, member) ({   </span><br />  const typeof(((<span style="color: #e6c07b;line-height: 26px">type</span> *)0)->member) * __mptr = (ptr); <br />  (<span style="color: #e6c07b;line-height: 26px">type</span> *)((char *)__mptr - offsetof(<span style="color: #e6c07b;line-height: 26px">type</span>, member)); })<br />

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

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

该宏的详尽介绍可以参考:

数组

数组有两种类型:

内核中实现了循环单向数组,这个结构就能实现FIFO和LIFO。假如要使用内核提供的数组操作函数,代码中须要添加头文件。

内核中数组的实现核心部份数据结构structlist_head定义为:

struct list_head<br />{<br /> struct list_head *next, *prev;<br />}<br />

structlist_head数据结构不包含数组节点的数据区,一般是用在数组头或则嵌入到其他数据结构中。

内核驱动下载官网_linux 驱动 内核_内核驱动灵魂

创建和初始化数组有两种方式:动态创建和静态创建。

动态方式创建并初始化数组方式如下:

struct list_head mylist;<br />INIT_LIST_HEAD(&mylist);<br />

INIT_LIST_HEAD()展开如下:

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

静态创建数组通过LIST_HEAD宏完成:

LIST_HEAD(mylist)<br />

LIST_HEAD的定义如下:

<span style="color: #5c6370;font-style: italic;line-height: 26px">#define LIST_HEAD(name) </span><br />   struct list_head name = LIST_HEAD_INIT(name)<br />

其中LIST_HEAD_INIT展开为:

<span style="color: #5c6370;font-style: italic;line-height: 26px">#define LIST_HEAD_INIT(name) { &(name), &(name) }</span><br />

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

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

void list_add(struct list_head *new, struct list_head *head)<br />list_add_tail(struct list_head *new, struct list_head *head)<br />

内核提供的list_add用于向数组添加新项linux命令行,它是内部函数__list_add的包装。

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

删掉节点很简单:

内核驱动灵魂_linux 驱动 内核_内核驱动下载官网

void list_del(struct list_head *entry);<br />

数组遍历

使用宏list_for_each_entry(pos,head,member)进行数组遍历。

参数解释head:数组的头节点;member:数据结构中数组structlist_head的名称;pos:用于迭代。它是一个循环游标,如同for(i=0;i中的i。

<span style="color: #5c6370;font-style: italic;line-height: 26px">#define list_for_each_entry(pos, head, member)  </span><br /><span style="color: #c678dd;line-height: 26px">for</span> (pos = list_entry((head)->next,typeof(*pos), member); <br />  &pos->member != (head); <br />   pos = list_entry(pos->member.next,typeof(*pos), member))<br /><br /><span style="color: #5c6370;font-style: italic;line-height: 26px">#define list_entry(ptr, type, member)  container_of(ptr, type, member)</span><br />

内核的睡眠机制

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

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

等待队列

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

struct wait_queue_entry {<br /> unsigned int  flags;<br /> void   *private;<br /> wait_queue_func_t func;<br /> struct list_head entry;<br />};<br />

其中,entry数组是一个数组,将步入睡眠的进程加入到这个数组中(在数组中排队),并步入睡眠状态。

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

DECLARE_WAIT_QUEUE_HEAD(name)<br />

wait_queue_head_t my_wait_queue;<br /><br />init_waitqueue_head(&my_wait_queue);<br />

/* 如果条件condition为真,则唤醒任务并执行。若为假,则阻塞 */<br />wait_event_interruptible(wq_head, condition)<br />

void wake_up_interruptible(wait_queue_head_ts *q)<br />

内核驱动下载官网_linux 驱动 内核_内核驱动灵魂

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

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

进程根据它们步入睡眠的次序唤起。要唤起在队列中等待的所有进程,应当使用wake_up_interruptible_all。

假如调用了wake_up或wake_up_interruptible,而且条件依然是FALSE,则哪些都不会发生。若果没有调用wake_up(或wake_up_interuptible),进程将永远不会被唤起。

工作队列

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

struct work_struct {<br /> atomic_long_t data;<br /> struct list_head entry;<br /> work_func_t func;<br /><span style="color: #5c6370;font-style: italic;line-height: 26px">#ifdef CONFIG_LOCKDEP</span><br /> struct lockdep_map lockdep_map;<br /><span style="color: #5c6370;font-style: italic;line-height: 26px">#endif</span><br />};<br />

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

typedef void (*work_func_t)(struct work_struct *work);<br />

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

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

struct work_struct wrk;<br /><br />INIT_WORK(_work, _func)<br />

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

schedule_work(struct work_struct *work)<br />

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

定时器

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

内核驱动下载官网_内核驱动灵魂_linux 驱动 内核

下面分别进行介绍。

标准定时器

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

jffies是记录着从笔记本开机到现今总共的时钟中断次数。取决于系统的时钟频度,单位是Hz,通常是一秒钟中断形成的次数linux 发邮件,每位增量被称为一个Tick(时钟节拍)。

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

struct timer_list {<br /> struct hlist_node entry;<br /> unsigned long  expires;<br /> void   (*<span style="color: #c678dd;line-height: 26px">function</span>)(struct timer_list *);<br /> u32   flags;<br /><br /><span style="color: #5c6370;font-style: italic;line-height: 26px">#ifdef CONFIG_LOCKDEP</span><br /> struct lockdep_map lockdep_map;<br /><span style="color: #5c6370;font-style: italic;line-height: 26px">#endif</span><br />};<br />

expires是以jiffies为单位的绝对值。entry是单向数组,function为定时器的反弹函数;flags是可选的,被传递给反弹函数。

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

timer_setup(timer, callback, flags)<br />

设置定时器的超时时间

mod_timer(struct timer_list *timer, unsigned long expires)<br />

删掉定时器:

void del_timer(struct timer_list *timer)<br />

高精度定时器

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

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

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

linux 驱动 内核_内核驱动灵魂_内核驱动下载官网

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

struct hrtimer {<br /> struct timerqueue_node  node;<br /> ktime_t    _softexpires;<br /> enum hrtimer_restart  (*<span style="color: #c678dd;line-height: 26px">function</span>)(struct hrtimer *);<br /> struct hrtimer_clock_base *base;<br /> u8    state;<br /> u8    is_rel;<br /> u8    is_soft;<br /> u8    is_hard;<br />};<br />

HRT初始化操作

void hrtimer_init(struct hrtimer *timer, clockid_t which_clock, enum hrtimer_mode mode);<br />

启动hrtimer

void hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode)<br />

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

取消hrtimer

int hrtimer_cancel( struct hrtimer *timer);<br />int hrtimer_try_to_cancel(struct hrtimer *timer)<br />

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

内核内部维护着一个任务超时列表(它晓得哪些时侯要睡眠以及睡眠多久)。

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

内核锁机制

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

下边分别进行介绍。

互斥锁

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

struct mutex <br />{<br /> atomic_long_t  owner;<br /> raw_spinlock_t  wait_lock;<br /> struct list_head wait_list;<br /> ...<br />};<br />

wait_list为等待互斥锁的任务数组。

热门AI工具

更多
WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

讯飞绘文

讯飞绘文是一款由科大讯飞推出的一站式 AIGC 内容运营平台。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

PixPix
PixPix Hot

PixPix是一款面向电商视觉生产的AI商品图生成工具。

咔片AIPPT

一款在线AI演示文稿制作工具,可根据主题和内容需求辅助生成PPT结构与页面,提高演示材料制作效率。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

花生AI
花生AI Hot

一款AI视频创作工具,主要用于B站推出的AI视频创作工具,适合需要提升相关任务效率的用户。

Laper
Laper Hot

Laper是专为编剧、导演和制片人推出的 AI 原生剧本创作工具。

相关专题

更多
golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

5454

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

5631

2025.07.04

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2921

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

416

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

437

2026.01.06

C++ 数据结构与算法实现教程合集
C++ 数据结构与算法实现教程合集

以 C++ 为实现语言,系统讲解核心数据结构与算法,涵盖链表(单链表/双链表/环检测)、栈与队列(单调栈/优先队列)、二叉树(遍历/BST/AVL/红黑树)、哈希表(开地址法/链地址法)、图(邻接表/BFS/DFS/Dijkstra/拓扑排序)、常见排序算法(快排/归并/堆排/计数排序)的实现与复杂度分析,同时分享 LeetCode 刷题技巧、竞赛编程常用模板(二分/前缀和/滑动窗口/动态规划),帮助开发者夯实算法基础。

512

2026.05.09

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

4858

2023.08.10

function是什么
function是什么

function是函数的意思,是一段具有特定功能的可重复使用的代码块,是程序的基本组成单元之一,可以接受输入参数,执行特定的操作,并返回结果。本专题为大家提供function是什么的相关的文章、下载、课程内容,供大家免费下载体验。

3700

2023.08.04

Go-Zero API开发使用教程
Go-Zero API开发使用教程

本专题聚焦Go-Zero的API开发,拆解.api描述文件的语法结构、路由与分组定义、参数绑定与校验规则,说明统一响应体、JWT鉴权、跨域与接口文档生成的落地方式,并给出网关聚合与接口测试的实践建议。

40

2026.11.12

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Python uv官方安装与入门文档
Python uv官方安装与入门文档

共0课时 | 0人学习

Buffalo框架快速入门指南
Buffalo框架快速入门指南

共0课时 | 0人学习

Valgrind支持平台说明
Valgrind支持平台说明

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn