T13 中斷與時鐘

1 中斷概念

  • 參考T6
  • 一句話,中斷上半部要求快進快出,耗時操做則可放在中斷下半部執行
  • 下半部實現方式:
  • 軟中斷
  • tasklet
  • 工做隊列
  • 內核定時器,也能夠將工做退後一段時間(精準的)執行

2 tasklet

tasklet是中斷處理下半部最多見的一種方式,驅動程序通常先申請中斷,在中斷處理函數內完成中斷上半部分的工做以後再調用tasklet,tasklet有以下優勢:linux

  • 一個tasklet只能夠在一個CPU上同步執行,不一樣的tasklet能夠在不一樣的CPU上同步執行
  • tasklet的實現是創建在兩個軟件中斷的基礎之上的,即HI_SOFTIRQACKLET_SOFTIRQ,本質上沒什麼區別,只不過HI_SOFTIRQ的優先級更高一些
  • 因爲tasklet是在軟中斷上實現的,因此像軟中斷同樣不能睡眠、不能阻塞、處理函數內不能含有致使睡眠的動做,如:減少信號量、從用戶空間拷貝數據或者手動分配內存等
  • 一個tasklet可以被禁止而且以後被從新使能,它不會執行直到它被使能的次數與被禁止的次數相同
  • tasklet的串行化使tasklet函數沒必要是可重入的(不須要考慮併發),所以簡化了設備驅動程序開發者的工做
  • 每一個cpu擁有一個tasklet_vec鏈表,具體是哪一個cpu的tasklet_vec鏈表,是根據當前線程是運行在哪一個cpu來決定的

2.1 tasklet API

/* tasklet結構體 */
struct tasklet_struct
{
	struct tasklet_struct *next;	//鏈表節點
	unsigned long state;			//屬性
	atomic_t count;					//計數,用於禁止使能,每禁止一次計數加1,每使能一次計數減1,只有禁止次數和使能次數同樣(count=0)的時候tasklet纔會執行調用函數
	void (*func)(unsigned long);	//中斷下半部實現方法,不能阻塞,不能睡眠
	unsigned long data;				//中斷服務函數的參數
};

/* tasklet結構體變量是tasklet_vec鏈表下的一個節點,next是鏈表下一個節點,status使用了2個位,以下:*/
enum
{
    TASKLET_STATE_SCHED,		//1表示已經被調度,0表示還沒調度
    TASKLET_STATE_RUN			//1表示tasklet正在執行,0表示還沒有執行,只針對SMP有效,單處理器無心義
};

/* tasklet初始化 */
/* 1.定義時初始化:定義變量名爲name的tasklet_struct結構體變量,並初始化調用函數func,參數爲data,使能tasklet */
DECLARE_TASKLET(name, func, data);
#define DECLARE_TASKLET(name, func, data) struct tasklet_struct name = {NULL, 0, ATOMIC_INIT(0), func, data}
/* 2.定義時初始化:定義變量名爲name的tasklet_struct結構體變量,並初始化調用函數func,參數爲data,禁止tasklet */
DECLARE_TASKLET_DISABLED(name, func, data);
#define DECLARE_TASKLET_DISABLED(name, func, data) struct tasklet_struct name = {NULL, 0, ATOMIC_INIT(1), func, data}
/* 3.運行中初始化,先定義struct tasklet_struct name,後初始化 */
void tasklet_init(struct tasklet_struct * t, void(* func)(unsigned long), unsigned long data)
{
    t->next = NULL;
    t->state = 0;				//設置爲未調度,未執行
    atomic_set(&t->count, 0);	 //默認使能
    t->func = func;
    t->data = data;
}

/* 啓動中斷下半部 */
static inline void tasklet_schedule(struct tasklet_struct * t);
static inline int tasklet_trylock(struct tasklet_struct * t);
static inline void tasklet_unlock(struct tasklet_struct * t);
static inline void tasklet_unlock_wait(struct tasklet_struct * t);

/* 使能以前一個被disable的tasklet;若這個tasklet已經被調度,他會很快運行。tasklet_enable和task_disable必須匹配使用,由於內核跟蹤每一個tasklet的「禁止次數」 */
static inline void tasklet_enable(struct tasklet_struct * t);
    
/* 與tasklet_schedule相似,只是在更高優先級執行。當軟中斷處理運行時,它處理高優先級tasklet,在其餘軟中斷以前,只有具備低響應週期要求的驅動(實時性不強)才應使用這個函數,可避免其餘軟件中斷處理引入的附加週期 */
void tasklet_hi_schedule(struct tasklet_struct * t);

/* 確保了tasklet不會被再次調度運行,即執行此函數以後若再次調用tasklet_schedule函數,中斷下半部也不會執行。一般當一個設備正在被關閉或者模塊卸載時被調用。若是tasklet正在運行,那麼這個函數需等待直到tasklet執行完畢。若tasklet從新調度它本身,則必須阻止在調用tasklet_kill以前它從新調度本身,如同使用del_timer_sync。注意:該函數不是真的去殺掉被調度的tasklet,而是保證tasklet再也不調用 */
void tasklet_kill(struct tasklet_struct *t);

2.2 tasklet使用示例

  • 驅動代碼,因爲目前沒有開發板,故而缺乏具體的硬件中斷觸發源,因此將會沒法觸發中斷下半部份的執行。在本示例中直接將中斷下半部份觸發放在了模塊初始化函數裏面,也就是說當模塊一旦被安裝,那麼就會觸發中斷下半部分的執行
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/tasklet$ cat tasklet.c 
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/interrupt.h>

struct message {
    int id;
    int val;
};

struct tasklet_struct mytask;

void mytask_func(unsigned long data)
{
    struct message *msg = (struct message *)data;
    printk("id = %d, val = %d\n", msg->id, msg->val);
    kfree(msg);
    msg = NULL;
}
static int __init task_init(void)
{
    struct message *msg;
    printk(KERN_INFO "%s start\n", __func__);
    msg = (struct message *)kzalloc(sizeof(struct message), GFP_KERNEL);
    if (msg == NULL) {
        printk(KERN_ERR "failed to kmalloc\n");
        return -ENOMEM;
    }
    msg->id = 7;
    msg->val = 77;
    tasklet_init(&mytask, mytask_func, (unsigned long)msg);
    tasklet_schedule(&mytask);
    printk(KERN_INFO "%s end\n", __func__);
    return 0;
}

static void __exit task_exit(void)
{
    printk(KERN_INFO "%s start\n", __func__);
    tasklet_kill(&mytask);
    printk(KERN_INFO "%s end\n", __func__);
}

module_init(task_init);
module_exit(task_exit);
MODULE_LICENSE("GPL");
  • 調試過程,能夠看到,當模塊安裝以後就會觸發中斷下半部分,進而去執行了中斷回調函數
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/tasklet$ sudo insmod tasklet.ko 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/tasklet$ sudo rmmod tasklet.ko 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/tasklet$ dmesg
[  583.990890] task_init start
[  583.990893] task_init end
[  583.990897] id = 7, val = 77
[  593.107581] task_exit start
[  593.107582] task_exit end

3 工做隊列

工做隊列是一種將工做退後執行的形式,交由一個內核線程去執行進在程上下文執行,其不能訪問用戶空間。最重要的特色是工做隊列容許從新調度甚至是睡眠。工做隊列子系統提供了一個默認的工做線程來處理這些工做。默認的工做者線程叫作/events/n,這裏的n是處理器編號,每一個處理器對應一個線程,也能夠本身建立工做者線程編程

3.1 工做隊列 API

  • 系統默認的工做隊列
/* 工做隊列work_struct結構體 */
struct work_struct {
/*	atomic_long_t data; */
	unsigned long data;
	struct list_head entry;
	work_func_t func;
#ifdef CONFIG_LOCKDEP
	struct lockdep_map lockdep_map;
#endif
};
typedef void (*work_func_t)(struct work_struct *work);

/* 初始化工做隊列 */
INIT_WORK(struct work_struct *work, work_func_t func);
DECLARE_WORK(n, f);

/* 開啓中斷下半部,即將給定工做的處理函數提交給缺省的工做隊列和工做線程 */
int schedule_work(struct work_struct *work);

/* 確保沒有工做隊列入口在系統中任何地方運行 */
void flush_scheduled_word(void);

/* 延時執行一個任務 */
int schedule_delayed_work(struct delayed_struct *work, unsigned long delay);

/* 從一個工做隊列中去除入口 */
int cancel_dalayed_work(struct delayed_struct *work);
  • 使用自定義工做隊列:建立工做隊列使用3個宏,成功後返回workqueue_struct *指針,並建立了工做線程。三個宏主要區別在於後面兩個參數singlethreadfreezablesinglethread爲0時會爲每一個CPU上建立一個工做者線程,爲1時只在當前運行的cpu上建立一個工做者線程。freezable會影響內核線程結構體thread_infoPF_NOFREEZE標記
/* 多處理器時會爲每一個cpu建立一個工做線程 */
#define create_workqueue(name)	__create_workqueue((name), 0, 0)

/* 只建立一個工做線程,系統掛起時線程也掛起 */
#define create_freezeable_workqueue(name)	__create_workqueue((name), 1, 1)

/* 只建立一個工做線程,系統掛起時線程不會掛起 */
#define create_singlethread_workqueue(name)		__create_workqueue((name), 1, 0)

/* 釋放建立的工做隊列資源 */
void destroy_workqueue(struct workqueue_struct *wq);

/* 延時調用指定工做隊列的工做 */
queue_delayed_work(struct workqueue_struct *wq, struct delay_struct *work, unsigned long delay);

/* 取消指定工做隊列的延時工做 */
cancel_delayed_work(struct delay_struct *work);

/* 將工做加入到工做隊列中進行調度 */
queue_work(struct workqueue_struct *wq, struct work_struct *work);

/* 等待隊列中的任務所有執行完畢 */
void flush_workqueue(struct workqueue_struct *wq);

3.2 工做隊列使用示例

  • 使用系統的工做隊列
#include <linux/init.h>
#include <linux/module.h>
#include <linux/workqueue.h>

void my_func(struct work_struct *ws);
DECLARE_WORK(my_work, my_func);

void my_func(struct work_struct *ws)
{
    printk(KERN_INFO "doing work\n");
}

static int __init work_queue_init(void)
{
    schedule_work(&my_work);
    return 0;
}

static void __exit work_queue_exit(void)
{
    
}

module_init(work_queue_init);
module_exit(work_queue_exit);
MODULE_LICENSE("GPL");
  • 調試
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/workqueue$ sudo insmod work_queue.ko 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/workqueue$ dmesg
[ 4097.029387] doing work
  • 使用本身的工做隊列
#include <linux/init.h>
#include <linux/module.h>
#include <linux/workqueue.h>

struct work_struct my_work;
struct workqueue_struct *my_wq;

void my_func(struct work_struct *ws)
{
    printk(KERN_INFO "doing work\n");
}

static int __init work_queue_init(void)
{
    /* create my work queue */
    my_wq = create_workqueue("my_wq");
    if (!my_wq) {
        printk(KERN_ERR "failed to create workqueue\n");
        return -1;
    }
    /* init my work */
    INIT_WORK(&my_work, my_func);
    /* schedule my work to my work queue */
    queue_work(my_wq, &my_work);
    return 0;
}

static void __exit work_queue_exit(void)
{
    /* destroy my work queue */
    destroy_workqueue(my_wq);
    my_wq = NULL;
}

module_init(work_queue_init);
module_exit(work_queue_exit);
MODULE_LICENSE("GPL");
  • 調試
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/workqueue$ sudo insmod work_queue.ko 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/workqueue$ dmesg \
[ 5472.402008] doing work
  • 查找命令
find -name "*.c" | xargs grep "create_workqueue"

4 內核定時器

  • 軟件意義上的定時器最終依賴硬件定時器來實現,內核在時鐘中斷髮生後檢測檢測一個定時器釋放到期,到期後的定時器處理函數將做爲軟中斷底半部執行。驅動編程中,能夠利用一組函數和數據結構來完成定時器觸發工做或某些週期性任務
  • 定時器在linux內核中主要是採用一個數據結構來實現的,可是須要注意的是定時器是一個只運行一次的對象,即當一個定時器結束之後,還須要從新添加定時器。可是能夠採用mod_timer()函數動態的改變定時器到達時間

4.1 原理

  • 這個驅動主要實現內核定時器的基本操做,內核定時器主要是經過下面結構體struct timer_list實現,須要的頭文件包括#include <linux/timer.h>,可是實際開發過程當中不須要包含該頭文件,由於在sched.h中包含了該頭文件
struct timer_list {
    struct list_head entry;
    unsigned long expires;
    
    void (*function)(unsigned long);
    unsigned long data;
}
  • 定時器的實現主要是該數據結構的填充和部分函數的配合便可完成。其中上述部分元素意義以下:
  • expires:定義定時器到期時間,一般採用jiffiesHZ這個兩個全局變量來配合設置該參數的值,如expires = jiffies + n * HZ,其中jiffies是自啓動以來的滴答數,HZ是一秒鐘的滴答數。假設系統1秒的滴答數爲1,咱們但願定時10秒鐘,並且咱們在啓動定時器的時候jiffies數值爲5,那麼10秒後滴答總數將爲5 + 10 * 1 = 15
  • function:即一個函數指針,就是定時器處理函數,當定時時間到達以後就會觸發該函數的執行
  • data:一般實現參數的傳遞,從function的參數類型值咱們能夠看到,data能夠做爲定時器處理函數的參數,其餘的元素可經過內核的函數來初始化

4.2 定時器API

/* 定時器初始化 */
init_timer(struct timer_list *timer);
/* 定時器初始化 */
#define DEFINE_TIMER(_name, _function, _expires, _data)	struct timer_list_name = TIMER_INITIALIZER(_function, _expires, _data)

/* 添加定時器到內核 */
void add_timer(struct timer_list *timer)
{
    BUG_ON(timer_pending(timer));
    mod_timer(timer, timer->expires;
}

/* 刪除定時器,若是定時器的定時時間尚未到達,那麼才能夠刪除定時器 */
int del_timer(struct timer_list *timer);

/* 修改定時器到達時間,該函數特色是:無論定時器是否到達定時時間,都會從新添加一個定時器到內核,因此能夠在定時器出路函數中調用該函數去修改新的定時時間 */
int mode_timer(struct timer_list *timer, unsigned long expires);

4.3 定時器示例(老版本內核)

#include <linux/module.h>
#include <linux/init.h>
#include <linux/timer.h>

struct timer_list g_timer;

void timer_func(unsigned long data)
{
    printk(KERN_INFO "%s->current jiffies = %ld\n", __func__, jiffies);
    mod_timer(&g_timer, jiffies + HZ);
}

static int __init timer_init(void)
{
    /* init timer old kernel can use init_timer(), but new kernel can not use this function */
    init_timer(&g_timer);
    // DEFINE_TIMER(g_timer, timer_func);
    /* set timer parameters */
    printk(KERN_INFO "%s->current jiffies = %ld\n", __func__, jiffies);
    g_timer.expires = jiffies + 2 * HZ;
    g_timer.function = timer_func;
    /* add timer */
    add_timer(&g_timer);
    return 0;
}

static void __exit timer_exit(void)
{
    del_timer(&g_timer);
}

module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
相關文章
相關標籤/搜索