口腔溃疡一直不好是什么原因| 打升白针有什么副作用| 三湖慈鲷可以和什么鱼混养| 重要是什么意思| 血液粘稠会有什么症状| 晚上睡觉阴部外面为什么会痒| 孕妇喝什么汤| 什么的落日| 仙姑是什么意思| 巨蟹座女生喜欢什么样的男生| 左耳耳鸣是什么原因| 末那识是什么意思| 鱼胶是鱼的什么部位| 身上为什么会起湿疹| hpv用什么药| 1月10日什么星座| 脑出血有什么后遗症| 体寒是什么原因引起的| 一什么云彩| 口气重是什么原因| 阴道口长什么样| 建卡需要带什么证件| 拉肚子应该吃什么| 什么汤补气血效果最好| 积聚病什么意思| 胃部间质瘤是什么性质的瘤| 雷尼替丁主要治什么胃病| 谷丙转氨酶是检查什么的| dennis什么意思| 7月18号是什么日子| 为道日损什么意思| 遨游是什么意思| 秋葵不适宜什么人吃| 上厕所出血是什么原因| 欺山莫欺水是什么意思| 什么是痔疮| 女人喝什么补气养血| 吃什么能升血小板| 揽子是什么意思| 传票是什么意思| naps是什么意思| 柠檬和什么不能一起吃| 蛇的天敌是什么动物| 什么车不能开| 光是什么意思| 蛇鼠一窝是什么生肖| 茶禅一味是什么意思| 吃什么长内膜| 吃汉堡为什么要配可乐| 1964属什么| 左肩后背疼是什么原因| 人活着意义是什么| 坏垣是什么意思| 马天尼是什么酒| 蔗去掉草字头读什么| 雪菜是什么菜| 交接是什么意思| 什么工作最赚钱| 男性生殖系统感染吃什么药| 织女是什么意思| 什么有什么造句| 什么方法可以让月经快点来| 双下肢静脉彩超主要检查什么| 晚上两点是什么时辰| 一阴一阳是什么数字| 半夜脚抽筋是什么原因| 五险都有什么| 子宫内膜回声不均匀是什么意思| 淋巴结钙化是什么意思| 什么惊什么怪| 什么样的孙悟空| 屈服是什么意思| 男人蛋蛋疼是什么原因| 切尔斯什么意思| 文房四宝指什么| 268数字代表什么意思| 喝啤酒吃什么菜最好| 胸口中间疼是什么原因| 补气血吃什么药| 梦见婆婆是什么意思| 梦见下大雨是什么征兆| 甲状腺钙化是什么意思| 天堂是什么意思| 维酶素片搭配什么药治萎缩性胃炎| 右眼皮跳是什么预兆女| 寓言故事有什么特点| 欺世盗名是什么意思| 尿里带血是什么原因女性| 打嗝不停是什么病前兆| 百香果是什么季节的| 吃什么排便最快| 冰醋酸是什么| 月牙消失了是什么原因| 脑梗用什么药效果好| 单发房早是什么意思| 什么之心路人皆知| 泌乳素高是什么意思| 小孩子上火吃什么能降火| 下海是什么意思| 西米是什么东西| 扒是什么意思| 吃皮蛋不能和什么一起吃| 画龙点睛是什么生肖| 风言风语是什么意思| 血脂高有什么表现| 月牙是什么| 梦见下大雪是什么预兆| 老实是什么意思| 黄体不足吃什么| 书香门第的书香指什么| 鸡飞狗跳的意思是什么| 打2个喷嚏代表什么| 九月二十号是什么星座| 藿香正气水不能和什么药一起吃| 什么的贾宝玉| 老鼠跟什么属相最配| 画蛇添足告诉我们什么道理| 高血压能吃什么水果| 眼睛挂什么科| 维生素c是补什么的| 静脉曲张不治疗会有什么后果| 保护嗓子长期喝什么茶| 乳房头疼是什么原因| 为什么用| 8月5日是什么星座| 半夜12点是什么时辰| 为什么会水肿| 10万个为什么| 水生木是什么意思| 胃烂了是什么病严重吗| 白细胞偏低吃什么药| 甲状腺4a是什么意思| 心肺气虚吃什么中成药| 属龙的今年要注意什么| 晚上睡觉脚酸痛什么原因| vj是什么意思| 脱臼是什么感觉| 肠胃胀气是什么原因| 瑾字属于五行属什么| 蹦蹦跳跳的动物是什么生肖| 沏茶是什么意思| 江苏有什么山| 痔疮为什么不建议手术| bayer是什么药| 为什么湿气重| 吃什么药不能献血| 孽缘是什么意思| 秦始皇的母亲叫什么名字| 什么是马上风| 虾头部黄黄的是什么| 甲亢是什么回事| 睡着了流口水是什么原因| 什么烟最好抽| 心绞痛吃什么药最管用| 射频消融是什么手术| 李连杰为什么不娶丁岚| bu什么颜色| 手莫名其妙的肿了因为什么| 口干口苦是什么病| 才高八斗代表什么生肖| 心仪的人是什么意思| 排卵是什么| 10.30什么星座| 五月二十二是什么星座| 藿香正气水什么牌子的好| 信口雌黄是什么意思| 肾错构瘤是什么病| 未亡人什么意思| 溜溜是什么意思| 什么时候最容易受孕| 土字旁有什么字| 吃什么东西能变白| 吃什么快速排便| spf是什么意思| 什么荔枝最贵| 左下腹是什么部位| 什么的形状| 头晕吃什么药效果好| 奶奶的妈妈叫什么| 特药是什么意思| 流鼻涕咳嗽吃什么药| pc是什么材质| 圣字五行属什么| 细菌感染引起的发烧吃什么药| 梦见别人给我介绍对象是什么意思| dr是什么意思| 男士检查精子挂什么科| 感冒吃什么药好得快| 什么东西越洗越脏答案| 偏头疼是什么原因引起| 怀孕是什么意思| 心悸是什么原因造成的呢| 梦见别人给自己剪头发是什么意思| 1月1日是什么日子| 为什么要文化大革命| 总胆红素高是什么病| 女性尿液发黄是什么原因| 人为什么做梦| 老是干咳嗽是什么原因| 混圈是什么意思| 6月23号什么星座| 血糖偏高会有什么症状| 呼吸不顺畅是什么原因| 外阴瘙痒是什么情况| 血液由什么组成| 农历六月十一是什么星座| 蔻驰包属于什么档次| 头发全白是什么病| 内裤发霉是什么原因| 被蟑螂咬了擦什么药| 面霜是什么| 卯时五行属什么| 钙化点是什么意思| 脖子落枕挂什么科| 火龙果有什么功效| 被螨虫咬了非常痒用什么药膏好| 八一是什么节| 嘴唇发白什么原因| 想什么| 痈肿疮疖是什么意思| 补液盐是什么| 龙飞凤舞是什么意思| 看见双彩虹有什么征兆| 身上长白色的斑点是什么原因| 低密度脂蛋白偏高吃什么药| 这是什么鱼| 7月15日是什么节日| 清真什么意思| 36年属什么生肖| 孕妇梦见自己出轨是什么意思| 王玉是什么字| 糖尿病人能吃什么水果| acl医学上是什么意思| 眼睛干涩吃什么食物好| 白癜风早期症状是什么| 月经一个月来两次什么原因| 无利不起早是什么意思| 正局级什么级别| 葡萄糖有什么作用| 腊月二十三是什么星座| 打喷嚏是什么原因| 99属什么| 滴水观音叶子发黄是什么原因| 什么的兵马俑| 88年属什么| 焦虑症是什么原因引起的| 跟班是什么意思| 恐龙蛋是什么水果| 2006年出生的是什么命| ab和o型血生的孩子是什么血型| 右眉上方有痣代表什么| 葡萄糖为什么叫葡萄糖| 澳大利亚有什么动物| 贪嗔痴是什么意思| 芝士和奶酪有什么区别| 碱性磷酸酶偏低是什么原因| 乳腺结节应该挂什么科| 食道炎用什么药最好| 8月6日什么星座| 万条垂下绿丝绦是什么季节| 梦见卖衣服是什么意思| 芒果像什么比喻句| 入党有什么好处| 植物神经紊乱中医叫什么病| 百度
这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 ? 论坛首页 ? 嵌入式开发 ? 软件与操作系统 ? Linux驱动程序可用的内核辅助工具(一)

共3条 1/1 1 跳转至

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

高工
2025-08-06 09:57:09     打赏
百度 ”吴笛赴圣彼得堡大学访学时,为编撰翻译《世界诗库·俄罗斯卷》拜访了很多学者,也为主编《普希金全集》而遍访普希金生命的足迹,最终将8卷《普希金全集》带回国内。

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。如果要使用内核提供的链表操作函数,代码中需要添加头文件 <linux/lis.h>。

内核中链表的实现核心部分数据结构 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<foo; 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_up 或 wake_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 是在 <linux/jiffies.h> 中声明的内核时间单位。

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

内核中定时器的结构定义为,在文件<linux/timer.h> 中:

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/hrtimer.h>

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时间;而互斥锁则可以长时间持有,因为竞争者被放入等待队列中进入睡眠状态。

好了,今天就这些内容。





关键词: Linux     驱动程序     内核     辅助工具    

高工
2025-08-06 08:32:10     打赏
2楼

LINUX学习的东东很多


高工
2025-08-06 08:37:43     打赏
3楼

知识点密集且复杂


共3条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]
脚发热是什么原因 处女座跟什么星座最配 孺子可教什么意思 小月子可以吃什么水果 碱水是什么
什么是元气 全身发麻是什么原因 圣诞节吃什么 女人丹凤眼意味什么 心电图可以检查出什么
1998年属什么生肖 维生素d3什么时候吃最好 什么水果糖分最高 听吧新征程号角吹响是什么歌 汽球是什么生肖
左耳朵痒代表什么预兆 大头菜又叫什么菜 孕妇为什么不能参加婚礼 挂面是什么面 景页读什么
性冷淡吃什么药最好hcv9jop3ns4r.cn 94岁属什么hcv8jop0ns3r.cn 有什么有什么成语hcv8jop5ns2r.cn 哮喘不能吃什么sanhestory.com 什么梳子梳头最好hcv9jop8ns3r.cn
soleil是什么意思hcv9jop0ns9r.cn 人的舌头有什么作用hcv8jop1ns1r.cn 曲安奈德针治疗什么hcv8jop8ns4r.cn 拔完智齿吃什么食物好hcv8jop3ns6r.cn 肠道易激惹综合症是什么症状hcv8jop5ns2r.cn
种什么最赚钱hcv8jop3ns8r.cn 游离前列腺特异性抗原是什么意思hcv8jop3ns9r.cn 泡泡像什么hcv9jop0ns6r.cn 身上起小红点是什么原因hcv8jop4ns7r.cn 匹马棉是什么面料hcv8jop3ns5r.cn
怀孕做梦梦到蛇是什么意思onlinewuye.com 飞检是什么意思hcv9jop3ns6r.cn elle中文叫什么hcv7jop6ns5r.cn 生姜什么时候种植最合适hcv9jop3ns4r.cn ca199偏高是什么原因hcv8jop3ns3r.cn
百度