T15 內核工具輔助函數

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

  • 在linux中等待隊列的結構以下:
struct __wait_queue_head {
    spinlock_t lock;			//自旋鎖,用來對task_list鏈表起保護做用,實現了等待隊列的互斥訪問
    struct list_head task_list;	//用來存放等待的進程,即上述的鏈表
};
typedef struct __wait_queue_head wait_queue_head_t;
  • API
#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

相關文章
相關標籤/搜索