스캔하고 팔로우하여 임베디드 학습, 함께 학습, 함께 성장하세요
Linux 커널은 C 언어 라이브러리를 사용하지 않고 자체적으로 많은 도구와 보조 도구를 구현한 독립 소프트웨어입니다.
이 기사 시리즈에서는 커널에서 제공하는 일부 보조 도구 기능을 검토합니다. 드라이버 프로그램 linux 드라이버 커널을 컴파일할 때 커널에서 제공하는 도구 기능을 사용하여 쉽게 목표 기능을 달성할 수 있습니다.
매크로 컨테이너_of
이 매크로 정의는 매우 유명하고 많은 기사에서 이를 분석한 바 있지만, 이 매크로는 커널이나 드라이버에서 자주 볼 수 있습니다.
이 매크로의 기능은 구조체 멤버의 주소와 구조체 유형을 통해 구조체의 주소를 파생시키는 것입니다.
리눅스 소스 코드리눅스 드라이버 커널의 toolsincludelinuxkernel.h 파일 아래에,container_of()는 다음과 같이 정의됩니다.
으아악
매크로의 매개변수는 다음과 같습니다. type은 구조의 유형을 나타내며, member는 구조에 있는 멤버의 이름, ptr은 유형 구조에 있는 멤버의 주소입니다.
container_of 매크로는 커널의 일반 컨테이너에서 주로 사용됩니다.
이 매크로에 대한 자세한 소개는 다음을 참조하세요.
배열
배열에는 두 가지 유형이 있습니다.
커널은 순환 단방향 배열을 구현하며 이 구조는 FIFO 및 LIFO를 구현할 수 있습니다. 커널에서 제공하는 배열 연산 함수를 사용하려면 코드에 헤더 파일을 추가해야 합니다.
커널 배열 구현의 핵심 부분인 structlist_head 데이터 구조는 다음과 같이 정의됩니다.
으아악
structlist_head 데이터 구조에는 배열 노드의 데이터 영역이 포함되지 않습니다. 일반적으로 배열 헤더에 사용되거나 다른 데이터 구조에 포함됩니다.
배열을 만들고 초기화하는 방법에는 동적 생성과 정적 생성이라는 두 가지 방법이 있습니다.
다음과 같이 배열을 동적으로 생성하고 초기화합니다.
으아악
INIT_LIST_HEAD()는 다음과 같이 확장됩니다.
으아악
정적 배열 생성은 LIST_HEAD 매크로를 통해 수행됩니다.
으아악
LIST_HEAD는 다음과 같이 정의됩니다.
으아악
LIST_HEAD_INIT는 다음으로 확장됩니다.
으아악
把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为等待互斥锁的任务数组。
위 내용은 Linux 커널 보조 도구 기능 인벤토리: Container_of 매크로 분석 및 적용의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!