Heim > Artikel > System-Tutorial > Funktionsinventur des Linux-Kernel-Hilfstools: Analyse und Anwendung des Makros „container_of“.
Scannen Sie und folgen Sie uns, um eingebettet zu lernen, gemeinsam zu lernen und gemeinsam zu wachsen
Der Linux-Kernel ist eine unabhängige Software. Er verwendet keine C-Sprachbibliothek und hat viele Tools und Hilfstools selbst implementiert.
In dieser Artikelserie werden einige vom Kernel bereitgestellte Hilfstoolfunktionen besprochen. Beim Kompilieren eines Treiberprogramms (Linux-Treiberkernel) können wir die vom Kernel bereitgestellten Toolfunktionen verwenden, um die Zielfunktion einfach zu erreichen. Makro-Container_von
Diese Makrodefinition ist sehr bekannt und wurde in vielen Artikeln analysiert, aber dieses Makro wird häufig im Kernel und in den Treibern gefunden.
Die Funktion dieses Makros besteht darin, die Adresse der Struktur aus der Adresse des Strukturmitglieds und dem Strukturtyp abzuleiten.
Unter der Datei toolsincludelinuxkernel.h des Linux-Quellcodes
Linux-Treiberkernelist container_of() wie folgt definiert:
<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>
Die Parameter des Makros sind: Typ bezieht sich auf den Typ der Struktur, Mitglied ist der Name des Mitglieds in der Struktur und ptr ist die Adresse des Mitglieds in der Typstruktur.
Das Makro container_of wird hauptsächlich im allgemeinen Container des Kernels verwendet.
Eine ausführliche Einführung in dieses Makro finden Sie unter:
Array
Es gibt zwei Arten von Arrays:
Der Kernel implementiert ein zyklisches Einwegarray, und diese Struktur kann FIFO und LIFO implementieren. Wenn Sie die vom Kernel bereitgestellten Array-Operationsfunktionen verwenden möchten, müssen Sie dem Code eine Header-Datei hinzufügen.
Die Datenstruktur structlist_head, der Kernbestandteil der Array-Implementierung im Kernel, ist definiert als:
<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>
Die Datenstruktur structlist_head enthält nicht den Datenbereich des Array-Knotens. Sie wird im Allgemeinen im Array-Header verwendet oder in andere Datenstrukturen eingebettet.
Es gibt zwei Möglichkeiten, Arrays zu erstellen und zu initialisieren: dynamische Erstellung und statische Erstellung.
Erstellen und initialisieren Sie Arrays dynamisch wie folgt:
<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() wird wie folgt erweitert:
<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>
Die statische Erstellung von Arrays erfolgt über das LIST_HEAD-Makro:
<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 ist wie folgt definiert:
<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>
wobei LIST_HEAD_INIT erweitert wird zu:
<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>
删掉节点很简单:
<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>
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内核提供了两种定时器:
下面分别进行介绍。
标准定时器
标准定时器是以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内核源码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为等待互斥锁的任务数组。
Das obige ist der detaillierte Inhalt vonFunktionsinventur des Linux-Kernel-Hilfstools: Analyse und Anwendung des Makros „container_of“.. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!