1 container_of宏
1.1 簡介
- 已知某結構體成員的地址,根據成員的地址去找出結構體的地址
1.2 API
#include <linux/kernel.h>
/**
* container_of - cast a member of a structure out to the containing structure
*
* @ptr: 結構體成員地址
* @type: 結構體類型
* @member: 結構體成員名字
*
*/
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
1.3 示例
struct ipstore{
unsigned long time;
__u32 addr[4];
struct list_head list;
};
void container_of_test()
{
struct ipstore ip1;
struct ipstore *p1;
p1 = container_of(&ip1.list, struct ipstore, list);
printf("ip1's addr:0x%0x\n", &ip1);
printf("p1's addr:0x%0x\n", p1);
}
[root@xxx c_base]# ./a.out
ip1's addr:0xa5fe1fe0
p1's addr:0xa5fe1fe0
2 鏈表
2.1 簡介
- 假設某個驅動程序裏面一共管理了5個設備,所以該驅動程序須要實時監控這5個設備的執行狀態,故而須要引入鏈表
- 鏈表實際上有單鏈表與雙鏈表
- 然而linux內核裏面已經實現了雙鏈表,由於雙鏈表可以實現FIFO與LIFO,該鏈表結構保存在了
<linux/list.h>
中
- 內核中鏈表實現的核心部分數據結構以下
struct list_head {
struct list_head *next, *prev;
};
2.2 API
#include <linux/list.h>
/*
功能:動態建立初始化鏈表
參數:
@list:實例化的鏈表對象
*/
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
/* 初始化鏈表示例 */
struct list_head mylist;
INIT_LIST_HEAD(&mylist);
/* 靜態建立初始化鏈表 */
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
LIST_HEAD_INIT(name) (&(name), &(name))
- 建立鏈表節點:要新建節點,只須要建立數據結構實例,初始化嵌入在其中的list_head字段
/* 建立鏈表節點示例 */
struct car {
int door_number;
char *color;
char *module;
struct list_head list;
};
struct car *blackcar = kmalloc(sizeof(struct car), GFP_KERNEL);
/* 動態初始化鏈表 */
INIT_LIST_HEAD(&blackcar->list);
/*
功能:添加鏈表節點(頭插法)
參數:
@new:新建的節點
@head:要插入的鏈表頭節點地址
*/
void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
static inline void __list_add(struct list_head *new, struct list_head *prev, struct list_head *next)
{
next->prev = new;
new->next = next;
next->prev = prev;
next->next = new;
}
/*
功能:添加鏈表節點(尾插法)
參數:
@new:新建的節點
@head:要插入的鏈表頭節點地址
*/
void list_add_tail(struct list_head *new, struct list_head *head);
/*
功能:刪除鏈表中的一個節點
參數:
@entry:鏈表中的某個節點
*/
void list_del(struct list_head *entry);
/*
功能:鏈表遍歷
參數:
@pos:用於迭代,是一個循環遊標,相似於for(i = 0;;)中的i
@head:鏈表頭節點
@member:數據結構中鏈表struct list_head的名字
*/
#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)
/* 示例 */
struct car *acar;
int blue_car_num = 0;
list_for_each_entry(acar, carlist, list) {
if (acar->color == "blue")
blue_car_num++;
}
2.3 示例
/* 汽車類 */
struct car {
int door_number;
char *color;
char *module;
struct list_head list;
};
/* 汽車鏈表頭部 */
static LIST_HEAD(carlist);
/* new一些汽車 */
struct car *redcar = kmalloc(sizeof(struct car), GPF_KERNEL);
struct car *bluecar = kmalloc(sizeof(struct car), GPF_KERNEL);
/* 初始化每一個汽車節點 */
INIT_LIST_HEAD(&redcar->list);
INIT_LIST_HEAD(&bluecar->list);
/* 填充汽車屬性(忽略) */
/* 將汽車對象添加到鏈表中 */
list_add(&redcar->list, &carlist);
list_add(&bluecar->list, &carlist);
/* 將紅車從鏈表中移除 */
list_del(&redcar->list);
/* 釋放紅車對象內存 */
kfree(redcar);
/* 遍歷汽車鏈表 */
struct car *acar;
int blue_car_num = 0;
list_for_each_entry(acar, carlist, list) {
if (acar->color == "blue")
blue_car_num++;
}
3 內核休眠
3.1 簡介
- 即將內核進程阻塞直到進程獲取了資源
- 在linux設備驅動中阻塞進程可使用等待隊列來實現
- 在內核中,等待隊列是有不少用處的,尤爲是在中斷處理、進程同步、定時等場合,可使用等待隊列實現阻塞進程的喚醒。它以隊列爲基礎數據結構,與進程調度機制緊密結合,可以用於實現內核中的異步事件通知機制,同步對系統資源的訪問
3.2 API
struct __wait_queue_head {
spinlock_t lock; //自旋鎖,用來對task_list鏈表起保護做用,實現了等待隊列的互斥訪問
struct list_head task_list; //用來存放等待的進程,即上述的鏈表
};
typedef struct __wait_queue_head wait_queue_head_t;
#include <linux/wait.h>
#include <linux/sched.h>
/* 定義並初始化等待隊列(動態) */
wait_queue_head_t wait;
init_waitqueue_head(&wait);
/* 定義並初始化等待隊列(靜態) */
#define DECLARE_WAIT_QUEUE_HEAD(name) wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
/* 添加等待隊列,將等待隊列元素wait添加到等待隊列隊頭q所指向的等待隊列鏈表中 */
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
/* 移除等待隊列 */
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
/* 等待事件,在等待隊列中睡眠直到condition爲真 */
/* 注意:
@queue:做爲等待隊列頭的等待隊列被喚醒
@condition:必須爲真,不然就阻塞
@timeout:相較於condition,timeout有更高的優先級
*/
wait_event(wq, condition);
wait_evevt_timeout(wq, condition, timeout);
/* wait_event_interruptible不會持續去輪詢判斷條件是否知足,而只是在其被調用的時候纔會去判斷條件是否成立,若是條件爲假進程將進入TASK_INTERRUPTIBLE並從運行隊列中刪除。啥時候被調用呢?只有當調用wake_up_interruptible喚醒函數的時候纔會去檢查條件,當條件爲真的時候,進程狀態將會被設置爲TASK_RUNNING */
wait_event_interruptible(wq, condition);
wait_event_interruptible_timeout(wq, condition, timeout);
/* 睡眠,其中sleep_on做用是將目前進程的狀態設置爲TASK_UNINTERRUPTIBLE,直到資源可用,q引導的等待隊列被喚醒;
interruptible_sleep_on是將進程狀態設置爲TASK_INTERRUPTIBLE
*/
sleep_on(wait_queue_head_t *q);
interruptible_sleep_on(wait_queue_head_t *q);
/* 喚醒等待隊列,可喚醒處於TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE狀態的進程 */
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)
/* 只能喚醒處於TASK_INTERRUPTIBLE狀態的進程 */
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
3.3 示例
- 驅動示例,首先定義一個條件變量,以後初始化一個工做隊列。在模塊加載的時候回調函數
work_handler
添加到工做隊列中,以後會執行wait_event_interruptible
函數,顯然此刻條件爲假,故而進程進入睡眠。在回調函數中睡眠5秒後將條件變量置爲1,以後使用wake_up_interruptible
喚醒進程,進程再去執行wait_event_interruptible
函數,此刻條件成立,故主進程將被喚醒
#include <linux/init.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/workqueue.h>
#include <linux/wait.h>
#include <linux/time.h>
static DECLARE_WAIT_QUEUE_HEAD(my_wq);
static int condition = 0;
/* declare a work queue */
static struct work_struct wrk;
static void work_handler(struct work_struct *work)
{
printk(KERN_INFO "enter %s\n", __func__);
msleep(5000);
printk(KERN_INFO "wake up the sleeping moudle");
condition = 1;
wake_up_interruptible(&my_wq);
}
static int __init my_init(void)
{
printk(KERN_INFO "wait queue example init\n");
/* init work queue */
INIT_WORK(&wrk, work_handler);
/* commit our handler function to work queue */
schedule_work(&wrk);
printk(KERN_INFO "going to sleep %s\n", __func__);
/* wait for wake up job*/
wait_event_interruptible(my_wq, condition != 0);
printk(KERN_INFO "worken up by the work job\n");
return 0;
}
static void __exit my_exit(void)
{
printk(KERN_INFO "wait queue example exit\n");
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Qi Han <15023820769@163.com");
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/wait_qu
eue$ sudo insmod wait_queue.ko
[sudo] hq 的密碼:
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/wait_qu
eue$ sudo rmmod wait_queue
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/wait_queue$ dmesg -wH
[ +0.000625] wait queue example init
[ +0.000003] going to sleep my_init
[ +0.000004] enter work_handler
[ +5.228811] wake up the sleeping moudle
[ +0.000059] worken up by the work job
[ +13.778234] wait queue example exit
4 內核定時器
4.1 簡介
- 標準定時器:標準定時器是內核定時器,它以Jiffy爲粒度運行
- Jiffy是在
<linux/jiffies.h>
中聲明的內核時間單位,其表明系統自啓動以來的滴答數
HZ
表示系統在1秒鐘以內的滴答數
expires = jiffies + n * HZ
,其中expires
是咱們想要的滴答數
- 舉個例子:假設當前系統在1秒鐘以內的滴答數爲100,當前總的滴答數爲50,那麼在2秒鐘以後系統總的滴答數將會是
50 + 2 * 100 = 250
- 那麼此刻聰明的同窗就會問道,這個jiffy的值是否會溢出麼?答案是可能會溢出。假設在一個32位系統中,jiffy的值能夠從0增長到4294967295,假設1秒鐘產生1000個jiffy,那麼系統能夠運行
4294967296 / 1000 = 4294967.296
秒,即大概49.7天左右。假如在一個64位的系統中,按照一樣方法計算,系統能夠運行6億年
- 那麼在32位系統裏面是如何保存jiffy的值呢?在
<linux/jiffies.h>
中引入了另一個變量,將jiffies指向低32位,jiffies_64指向高位。在64位系統中jiffies = jiffies_64
extern u64 jiffies_64;
4.2 API