首页 > 系统教程 > LINUX > 正文

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

王林
发布: 2024-07-07 15:34:01
转载
1048人浏览过

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

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

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

宏container_of

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

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

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

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px"><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 /></code>
登录后复制

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

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

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

数组

数组有两种类型:

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

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

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px">struct list_head<br />{<br /> struct list_head *next, *prev;<br />}<br /></code>
登录后复制

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

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

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

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

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px">struct list_head mylist;<br />INIT_LIST_HEAD(&mylist);<br /></code>
登录后复制

INIT_LIST_HEAD()展开如下:

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px">static inline void INIT_LIST_HEAD(struct list_head *list)<br />{<br />    list->next = list;<br />    list->prev = list;<br />}<br /></code>
登录后复制

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

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px">LIST_HEAD(mylist)<br /></code>
登录后复制

LIST_HEAD的定义如下:

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px"><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 /></code>
登录后复制

其中LIST_HEAD_INIT展开为:

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px"><span style="color: #5c6370;font-style: italic;line-height: 26px">#define LIST_HEAD_INIT(name) { &(name), &(name) }</span><br /></code>
登录后复制

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

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

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px">void list_add(struct list_head *new, struct list_head *head)<br />list_add_tail(struct list_head *new, struct list_head *head)<br /></code>
登录后复制

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

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px">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 /></code>
登录后复制

删掉节点很简单:

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

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px">void list_del(struct list_head *entry);<br /></code>
登录后复制

数组遍历

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

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

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px"><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 /></code>
登录后复制

内核的睡眠机制

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

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

等待队列

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

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px">struct wait_queue_entry {<br /> unsigned int  flags;<br /> void   *private;<br /> wait_queue_func_t func;<br /> struct list_head entry;<br />};<br /></code>
登录后复制

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

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

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px">DECLARE_WAIT_QUEUE_HEAD(name)<br /></code>
登录后复制

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px">wait_queue_head_t my_wait_queue;<br /><br />init_waitqueue_head(&my_wait_queue);<br /></code>
登录后复制

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px">/* 如果条件condition为真,则唤醒任务并执行。若为假,则阻塞 */<br />wait_event_interruptible(wq_head, condition)<br /></code>
登录后复制

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px">void wake_up_interruptible(wait_queue_head_ts *q)<br /></code>
登录后复制

内核驱动下载官网_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结构定义如下

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px">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 /></code>
登录后复制

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

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px">typedef void (*work_func_t)(struct work_struct *work);<br /></code>
登录后复制

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

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

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px">struct work_struct wrk;<br /><br />INIT_WORK(_work, _func)<br /></code>
登录后复制

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

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px">schedule_work(struct work_struct *work)<br /></code>
登录后复制

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

定时器

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

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

下面分别进行介绍。

标准定时器

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

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

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

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px">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 /></code>
登录后复制

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

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

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px">timer_setup(timer, callback, flags)<br /></code>
登录后复制

设置定时器的超时时间

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px">mod_timer(struct timer_list *timer, unsigned long expires)<br /></code>
登录后复制

删掉定时器:

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px">void del_timer(struct timer_list *timer)<br /></code>
登录后复制

高精度定时器

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

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

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

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

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

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px">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 /></code>
登录后复制

HRT初始化操作

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px">void hrtimer_init(struct hrtimer *timer, clockid_t which_clock, enum hrtimer_mode mode);<br /></code>
登录后复制

启动hrtimer

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px">void hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode)<br /></code>
登录后复制

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

取消hrtimer

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px">int hrtimer_cancel( struct hrtimer *timer);<br />int hrtimer_try_to_cancel(struct hrtimer *timer)<br /></code>
登录后复制

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

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

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

内核锁机制

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

下边分别进行介绍。

互斥锁

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

<code style="padding: 16px;color: #abb2bf;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;padding-top: 15px;background: #282c34;border-radius: 5px">struct mutex <br />{<br /> atomic_long_t  owner;<br /> raw_spinlock_t  wait_lock;<br /> struct list_head wait_list;<br /> ...<br />};<br /></code>
登录后复制

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

以上就是Linux 内核辅助工具函数盘点:container_of 宏的解析与应用的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:ITcool网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号