Maison > Article > Tutoriel système > Inventaire des fonctions des outils auxiliaires du noyau Linux : analyse et application de la macro containers_of
Scannez et suivez-nous pour apprendre intégré, apprendre ensemble et grandir ensemble
Le noyau Linux est un logiciel indépendant. Il n'utilise aucune bibliothèque de langage C. Il a implémenté lui-même de nombreux outils et outils auxiliaires.
Cette série d'articles passera en revue certaines fonctions d'outils auxiliaires fournies par le noyau. Lors de la compilation d'un programme pilote noyau de pilote Linux, nous pouvons utiliser les fonctions d'outils fournies par le noyau pour atteindre facilement la fonction cible.
Macro conteneur_de
Cette définition de macro est très connue, et de nombreux articles l'ont analysée, mais cette macro est souvent vue dans le noyau et les pilotes.
La fonction de cette macro est de dériver l'adresse de la structure via l'adresse du membre de la structure et le type de structure.
Sous le fichier toolsincludelinuxkernel.h du code source Linuxnoyau du pilote Linux, containers_of() est défini comme suit :
<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>
Les paramètres de la macro sont : type fait référence au type de structure, member est le nom du membre dans la structure et ptr est l'adresse du membre dans la structure de type.
La macro containers_of est principalement utilisée dans le conteneur général du noyau.
Pour une introduction détaillée à cette macro, veuillez vous référer à :
Tableau
Il existe deux types de tableaux :
Le noyau implémente un tableau unidirectionnel cyclique, et cette structure peut implémenter FIFO et LIFO. Si vous souhaitez utiliser les fonctions d'opération de tableau fournies par le noyau, vous devez ajouter un fichier d'en-tête au code.
La structure de données structlist_head, la partie centrale de l'implémentation des tableaux dans le noyau, est définie comme :
<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>
La structure de données structlist_head ne contient pas la zone de données du nœud du tableau. Elle est généralement utilisée dans l'en-tête du tableau ou intégrée dans d'autres structures de données.
Il existe deux manières de créer et d'initialiser des tableaux : la création dynamique et la création statique.
Créez et initialisez des tableaux dynamiquement comme suit :
<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() est développé comme suit :
<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>
La création statique des tableaux se fait via la macro 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 est défini comme suit :
<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>
où LIST_HEAD_INIT s'étend à :
<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为等待互斥锁的任务数组。
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!