linux驅動開發學習--對中斷和核心定時器的學習筆記
阿新 • • 發佈:2018-10-31
一 中斷理解
中斷的含義
所謂中斷是指 CPU 在執行程式的過程中,出現了某些突發事件時 CPU 必須暫停
執行當前的程式,轉去處理突發事件,處理完畢後 CPU 又返回原程式被中斷的位置
並繼續執行
中斷的分類
內部中斷 內部中斷的中斷源來自 CPU內部(軟體中斷指令、溢位、除法錯誤等,例如,作業系統從使用者態切換到核心態需藉助 CPU 內部的軟體中斷)
外部中斷 外部中斷的中斷源來自 CPU 外部,由外設提出請求
可遮蔽中斷 可遮蔽中斷可以通過遮蔽字被遮蔽,遮蔽後,該中斷不再得到響應
不可遮蔽中斷
向量中斷 採用向量中斷的 CPU 通常為不同的中斷分配不同的中斷號,當檢測到某中斷號的中斷到來後,就自動跳轉到與該中斷號對應的地址執行。不同中斷號的中斷有不同的入口地址
非向量中斷 非向量中斷的多箇中斷共享一個入口地址,進入該入口地址後再通過軟體判斷中斷標誌來識別具體是哪個中斷
總結來說:
向量中斷由硬體提供中斷服務程式入口地址,
非向量中斷由軟體提供中斷服務程式入口地址。
二 使用中斷的裝置需要申請和釋放對應的中斷,分別使用核心提供的 request_irq()和 free_irq()函式。
1.申請 IRQ
int request_irq(unsigned int irq, irq 是要申請的硬體中斷號
void (*handler)(int irq, void *dev_id, struct pt_regs *regs), handler 是向系統登記的中斷處理函式,是一個回撥函式,中斷髮生時,系統呼叫這個函式
unsigned long irqflags, irqflags 是中斷處理的屬性 SA_INTERRUPT則表示中斷處理程式是快速處理程式,快速處理程式被呼叫時遮蔽所有中斷,慢速處理程式不遮蔽
const char * devname, SA_SHIRQ,則表示多個裝置共享中斷
void *dev_id); dev_id 是handler的引數
返回值
0 表示申請成功
-INVAL 中斷號無效或處理函式指標為NULL
-EBUSY 中斷已經被佔用且不能共享
2.釋放 IRQ
void free_irq(unsigned int irq,void *dev_id);
3.使能和遮蔽中斷
遮蔽中斷 void disable_irq(int irq); 此函式品遮蔽中斷時,立即返回
void disable_irq_nosync(int irq); 而此函式會等待目前的中斷處理完成
開啟中斷 void enable_irq(int irq);
遮蔽本CPU內的所有中斷 void local_irq_save(unsigned long flags); 以上各 local_開頭的方法的作用範圍是本 CPU 內
void local_irq_disable(void);
恢復中斷 void local_irq_restore(unsigned long flags);
void local_irq_enable(void);
三 中斷處理 機制
Linux 系統實現底半部的機制主要有 tasklet、工作佇列和軟中斷
1. tasklet
void my_tasklet_func(unsigned long); /*定義一個處理函式*/
DECLARE_TASKLET(my_tasklet, my_tasklet_func, data); /*定義一個 tasklet 結構 my_tasklet,與 my_tasklet_func(data)函式相關聯*/
在需要排程 tasklet 的時候引用一個 tasklet_schedule()函式就能使系統在適當的時候進行排程執行
tasklet_schedule(&my_tasklet);
2. 工作佇列
struct work_struct my_wq; /*定義一個工作佇列*/
void my_wq_func(unsigned long); /*定義一個處理函式*/
INIT_WORK(&my_wq, (void (*)(void *)) my_wq_func, NULL); /*初始化工作佇列並將其與處理函式繫結*/
schedule_work(&my_wq); /*排程工作佇列執行*/
3. 軟中斷
軟中斷是用軟體方式模擬硬體中斷的概念,實現巨集觀上的非同步執行效果,tasklet也是基於軟中斷實現的。
硬中斷是外部裝置對 CPU 的中斷
軟中斷通常是硬中斷服務程式對核心的中斷
訊號則是由核心(或其他程序)對某個程序的中斷
四 核心定時器
1. 核心定時器介紹
struct timer_list {
struct list_head entry;
unsigned long expires;//定時時間,jiffies+delayn
void (*function)(unsigned long);//處理函式
unsigned long data;
struct tvec_base *base;
...
};//核心定時器結構體
2. 基本操作函式
struct timer_list my_timer; //宣告一個定時器
void init_timer(struct timer_list *timer); //初始化定時器,在該定時器成員賦值前,先執行初始化
void add_timer(struct timer_list *timer); //向核心新增一個定時器,啟動該定時器
int mod_timer(struct timer_list *timer,unsigned long expires);//修改定時的expire
int del_timer(struct timer_list *timer); //刪除一個定時器
核心定時使用簡介:
宣告一個定時器,然後初始化,然後新增定時器到核心,定時器開始啟動,時間到後,定時器處理程式會自動執行,在中斷程式裡需要重新設定定時值,並重新使用add_timer來開啟定時
器。
3. 短延時
void ndelay(unsigned long nsecs); //忙等待延時nsecs納秒
void udelay(unsigned long usecs); //忙等待延時usecs微秒
void mdelay(unsigned long msecs); //忙等待延時msecs毫秒
4. 長延時
例子1:延時100個jiffies
unsigned long delay = jiffes+100;
while(time_before( jiffes,delay) );
例子2: 延時2秒
unsigned long delay = jiffes+2*Hz;
while(time_before( jiffes,delay) );
此外還有time_after(a,b),用法類似。
5. 睡著延時
void msleep( unsigned int millisecs ); //睡眠millisecs個毫秒的延時
void ssleep( unsigned int seconds ); //睡眠seconds秒的延時
unsigned long msleep_interruptible( unsigned int millisecs );//睡眠millisecs個毫秒的延時,並且睡眠期間可被中斷
中斷的含義
所謂中斷是指 CPU 在執行程式的過程中,出現了某些突發事件時 CPU 必須暫停
執行當前的程式,轉去處理突發事件,處理完畢後 CPU 又返回原程式被中斷的位置
並繼續執行
中斷的分類
內部中斷 內部中斷的中斷源來自 CPU內部(軟體中斷指令、溢位、除法錯誤等,例如,作業系統從使用者態切換到核心態需藉助 CPU 內部的軟體中斷)
外部中斷 外部中斷的中斷源來自 CPU 外部,由外設提出請求
可遮蔽中斷 可遮蔽中斷可以通過遮蔽字被遮蔽,遮蔽後,該中斷不再得到響應
不可遮蔽中斷
向量中斷 採用向量中斷的 CPU 通常為不同的中斷分配不同的中斷號,當檢測到某中斷號的中斷到來後,就自動跳轉到與該中斷號對應的地址執行。不同中斷號的中斷有不同的入口地址
非向量中斷 非向量中斷的多箇中斷共享一個入口地址,進入該入口地址後再通過軟體判斷中斷標誌來識別具體是哪個中斷
總結來說:
向量中斷由硬體提供中斷服務程式入口地址,
非向量中斷由軟體提供中斷服務程式入口地址。
二 使用中斷的裝置需要申請和釋放對應的中斷,分別使用核心提供的 request_irq()和 free_irq()函式。
1.申請 IRQ
int request_irq(unsigned int irq, irq 是要申請的硬體中斷號
void (*handler)(int irq, void *dev_id, struct pt_regs *regs), handler 是向系統登記的中斷處理函式,是一個回撥函式,中斷髮生時,系統呼叫這個函式
unsigned long irqflags, irqflags 是中斷處理的屬性 SA_INTERRUPT則表示中斷處理程式是快速處理程式,快速處理程式被呼叫時遮蔽所有中斷,慢速處理程式不遮蔽
const char * devname, SA_SHIRQ,則表示多個裝置共享中斷
void *dev_id); dev_id 是handler的引數
返回值
0 表示申請成功
-INVAL 中斷號無效或處理函式指標為NULL
-EBUSY 中斷已經被佔用且不能共享
2.釋放 IRQ
void free_irq(unsigned int irq,void *dev_id);
3.使能和遮蔽中斷
遮蔽中斷 void disable_irq(int irq); 此函式品遮蔽中斷時,立即返回
void disable_irq_nosync(int irq); 而此函式會等待目前的中斷處理完成
開啟中斷 void enable_irq(int irq);
遮蔽本CPU內的所有中斷 void local_irq_save(unsigned long flags); 以上各 local_開頭的方法的作用範圍是本 CPU 內
void local_irq_disable(void);
恢復中斷 void local_irq_restore(unsigned long flags);
void local_irq_enable(void);
三 中斷處理
Linux 系統實現底半部的機制主要有 tasklet、工作佇列和軟中斷
1. tasklet
void my_tasklet_func(unsigned long); /*定義一個處理函式*/
DECLARE_TASKLET(my_tasklet, my_tasklet_func, data); /*定義一個 tasklet 結構 my_tasklet,與 my_tasklet_func(data)函式相關聯*/
在需要排程 tasklet 的時候引用一個 tasklet_schedule()函式就能使系統在適當的時候進行排程執行
tasklet_schedule(&my_tasklet);
2. 工作佇列
struct work_struct my_wq; /*定義一個工作佇列*/
void my_wq_func(unsigned long); /*定義一個處理函式*/
INIT_WORK(&my_wq, (void (*)(void *)) my_wq_func, NULL); /*初始化工作佇列並將其與處理函式繫結*/
schedule_work(&my_wq); /*排程工作佇列執行*/
3. 軟中斷
軟中斷是用軟體方式模擬硬體中斷的概念,實現巨集觀上的非同步執行效果,tasklet也是基於軟中斷實現的。
硬中斷是外部裝置對 CPU 的中斷
軟中斷通常是硬中斷服務程式對核心的中斷
訊號則是由核心(或其他程序)對某個程序的中斷
四 核心定時器
1. 核心定時器介紹
struct timer_list {
struct list_head entry;
unsigned long expires;//定時時間,jiffies+delayn
void (*function)(unsigned long);//處理函式
unsigned long data;
struct tvec_base *base;
...
};//核心定時器結構體
2. 基本操作函式
struct timer_list my_timer; //宣告一個定時器
void init_timer(struct timer_list *timer); //初始化定時器,在該定時器成員賦值前,先執行初始化
void add_timer(struct timer_list *timer); //向核心新增一個定時器,啟動該定時器
int mod_timer(struct timer_list *timer,unsigned long expires);//修改定時的expire
int del_timer(struct timer_list *timer); //刪除一個定時器
核心定時使用簡介:
宣告一個定時器,然後初始化,然後新增定時器到核心,定時器開始啟動,時間到後,定時器處理程式會自動執行,在中斷程式裡需要重新設定定時值,並重新使用add_timer來開啟定時
器。
3. 短延時
void ndelay(unsigned long nsecs); //忙等待延時nsecs納秒
void udelay(unsigned long usecs); //忙等待延時usecs微秒
void mdelay(unsigned long msecs); //忙等待延時msecs毫秒
4. 長延時
例子1:延時100個jiffies
unsigned long delay = jiffes+100;
while(time_before( jiffes,delay) );
例子2: 延時2秒
unsigned long delay = jiffes+2*Hz;
while(time_before( jiffes,delay) );
此外還有time_after(a,b),用法類似。
5. 睡著延時
void msleep( unsigned int millisecs ); //睡眠millisecs個毫秒的延時
void ssleep( unsigned int seconds ); //睡眠seconds秒的延時
unsigned long msleep_interruptible( unsigned int millisecs );//睡眠millisecs個毫秒的延時,並且睡眠期間可被中斷
五 定時器例程
#include <linux/module.h> #include <linux/types.h> #include <linux/fs.h> #include <linux/errno.h> #include <linux/mm.h> #include <linux/sched.h> #include <linux/init.h> #include <linux/cdev.h> #include <asm/io.h> #include <linux/version.h> #if LINUX_VERSION_CODE > KERNEL_VERSION(3, 3, 0) #include <asm/switch_to.h> #else #include <asm/system.h> //在2.6.x的核心版本中,檔案操作結構體中,才會有ioctl的欄位,高版本中使用unlocked_ioctl, #endif #include <asm/uaccess.h> #include <linux/slab.h> #define SECOND_MAJOR 248 /*預設的 second 的主裝置號*/ static int second_major = SECOND_MAJOR; /*second 裝置結構體*/ struct second_dev { struct cdev cdev; /*cdev 結構體*/ atomic_t counter; /* 一共經歷了多少秒?*/ struct timer_list s_timer; /*裝置要使用的定時器*/ }; struct second_dev *second_devp; /*定時器處理函式*/ static void second_timer_handle (unsigned long arg) { //修改定時的expire,重新設定定時時間 mod_timer (&second_devp->s_timer, jiffies + HZ); //原子計數,對當前記錄時間更新 atomic_inc (&second_devp->counter); printk(KERN_NOTICE "current jiffies is %lld\n", jiffies); } /*檔案開啟函式*/ int second_open (struct inode *inode, struct file *filp) { /*初始化定時器*/ init_timer (&second_devp->s_timer); second_devp->s_timer.function = &second_timer_handle; //當定時器達到時,執行定時處理函式。 second_devp->s_timer.expires = jiffies + HZ; //定時時間,jiffies + HZ(HZ預設值為1000) add_timer (&second_devp->s_timer);/*新增(註冊)定時器*/ atomic_set (&second_devp->counter, 0); //計數清零 return 0; } /*檔案釋放函式*/ int second_release (struct inode *inode, struct file *filp) { //刪除一個定時器 del_timer (&second_devp->s_timer); return 0; } /*Second 讀函式*/ static ssize_t second_read (struct file *filp, char __user *buf, size_t count, loff_t *ppos) { int counter; counter = atomic_read (&second_devp->counter); if (put_user (counter, (int *)buf)) return -EFAULT; else return sizeof (unsigned int); } /*檔案操作結構體*/ static const struct file_operations second_fops = { .owner = THIS_MODULE, .open = second_open, .release = second_release, .read = second_read, }; /*初始化並註冊 cdev*/ static void second_setup_cdev (struct second_dev *dev, int index) { int err, devno = MKDEV (second_major, index); cdev_init (&dev->cdev, &second_fops); dev->cdev.owner = THIS_MODULE; err = cdev_add (&dev->cdev, devno, 1); if (err) printk (KERN_NOTICE "Error %d adding CDEV %d", err, index); } /*裝置驅動模組載入函式*/ int second_init (void) { int ret; dev_t devno = MKDEV (second_major, 0); /* 申請裝置號*/ if (second_major) ret = register_chrdev_region (devno, 1, "second"); else { /* 動態申請裝置號 */ return alloc_chrdev_region (&devno, 0, 1, "second"); second_major = MAJOR (devno); } if (ret < 0) return ret; /* 動態申請裝置結構體的記憶體*/ second_devp = kmalloc (sizeof (struct second_dev), GFP_KERNEL); if (!second_devp) { /*申請失敗*/ ret = -ENOMEM; goto fail_malloc; } memset (second_devp, 0, sizeof (struct second_dev)); second_setup_cdev (second_devp, 0); return 0; fail_malloc: unregister_chrdev_region (devno, 1); return ret; } /*模組解除安裝函式*/ void second_exit (void) { cdev_del (&second_devp->cdev); /*登出 cdev*/ kfree (second_devp); /*釋放裝置結構體記憶體*/ unregister_chrdev_region (MKDEV (second_major, 0), 1); /* 釋放裝置號 */ } MODULE_AUTHOR ("zhongming"); MODULE_LICENSE ("Dual BSD/GPL"); module_param (second_major, int, S_IRUGO); module_init (second_init); module_exit (second_exit);
Makefile檔案
obj-m := Second.o
KERNEL_VERSION ?= $(shell uname -r)
PWD := $(shell pwd)
all:
make -C /lib/modules/$(KERNEL_VERSION)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(KERNEL_VERSION)/build M=$(PWD) clean
測試程式碼
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
int main (void)
{
int fd;
int counter = 0;
int old_counter = 0;
fd = open ("/dev/Second", O_RDONLY);
if (fd != -1) {
while (1) {
read (fd, &counter, sizeof (unsigned int));
if (counter != old_counter) {
printf ("seconds after open /dev/second: %d\n", counter);
old_counter = counter;
}
}
} else {
printf ("Device open failure\n");
}
return 0;
}