本系列文章主要講Linux中的中斷和時間管理,文章機構如下:
01 - 驅動中的中斷處理
02 - 中斷下半部 tasklet
03 - 中斷的下半部 workqueue
04 - Linux中的延時操作函數
05 - Linux硬件定時 jiffies
06 - Linux 低分辨率定時器
07 - Linux高分辨率定時器
文章目錄
1. 驅動中定義的中斷處理相關的API
1.1 註冊中斷函數
驅動中註冊中斷處理函數的原型如下:
原 型: static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char * name, void * dev)
功 能: 向內核中註冊中斷處理函數
@param1: 設備上所用中斷號,這個中斷號不是硬件手冊上查到的號,而是內核中的中斷號,這個號將會用於決定構造的 struct irqaction 對象被插入到哪個鏈表,並用於初始化 struct irqaction 對象中的irq成員
@param2: 指向中斷處理函數的指針
@param3: 與中斷相關的標誌,用於初始化 struct irqaction 對象中的flags成員,這些中斷標誌可以用位或的方式來設置多個,常用的標誌如下:
#define IRQF_TRIGGER_RISING 0x00000001 // 上升沿觸發 在 include\linux\interrupt.h 中定義
#define IRQF_TRIGGER_FALLING 0x00000002 // 下降沿觸發
#define IRQF_TRIGGER_HIGH 0x00000004 // 高電平觸發
#define IRQF_TRIGGER_LOW 0x00000008 // 低電平觸發
#define IRQF_SHARED 0x00000080 // 共享中斷標誌,多個設備共享一箇中斷線,共享的所有中斷都必須指定此標誌。如果使用共享中斷的話,request_irq 函數的 dev 參數就是唯一區分他們的標誌。
#define IRQF_TIMER (__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD) // 定時器專用中斷標誌
#define __IRQF_TIMER 0x00000200
#define IRQF_NO_SUSPEND 0x00004000
#define IRQF_NO_THREAD 0x00010000
@param4: 該中斷在 /proc 中的名字,用於初始化 struct irqaction 對象中的name成員
@param5: 區別不同設備所對應的 struct irqaction 對象,在 struct irqaction 對象從鏈表中移除時需要,dev用於初始化 struct irqaction 對象中的dev_id成員。共享中斷必須傳遞一個非NULL實參,非共享中斷可以傳NULL。中斷髮生後調用中斷處理函數時也會把該參數傳遞給中斷處理函數。
@retval: 成功返回0,失敗返回負的錯誤碼。
需要注意的是:request_irq 函數根據傳入的參數構造好一個 struct irqaction 對象並加入到對應的鏈表後,還將對應的中斷使能了,因此我們不需要再使能中斷。
其中 struct irqaction 結構體的定義如下:
struct irqaction {
irq_handler_t handler; // 中斷處理函數的指針
void *dev_id; // 區別共享中斷中不同設備的ID
void __percpu *percpu_dev_id;
struct irqaction *next; // 將共享同一IRQ號的 struct irqaction 對象鏈接在一起的指針
irq_handler_t thread_fn;
struct task_struct *thread;
struct irqaction *secondary;
unsigned int irq; // IRQ號
unsigned int flags;
unsigned long thread_flags; // 以IRQF_爲開頭的一組標誌
unsigned long thread_mask;
const char *name;
struct proc_dir_entry *dir;
} ____cacheline_internodealigned_in_smp;
1.2 中斷處理函數
中斷處理函數原型如下:
原 型: irqreturn_t (*irq_handler_t)(int, void *); include\linux\irqreturn.h
功 能: 中斷處理函數,中斷髮生後中斷處理函數會自動調用
@param1: IRQ號
@param2: 對應的設備ID,也是 struct irqaction 對象中的dev_id成員
@retval: 返回值是一個枚舉類型,在 include\linux\irqreturn.h 中定義,詳細信息如下
enum irqreturn {
IRQ_NONE = (0 << 0), // 不是驅動所管理的設備產生的中斷,用於共享中斷
IRQ_HANDLED = (1 << 0), // interrupt was handled by this device
IRQ_WAKE_THREAD = (1 << 1), // handler requests to wake the handler thread
};
一般中斷處理函數返回值使用如下形式:
return IRQ_RETVAL(IRQ_HANDLED)
IRQ_RETVAL宏的定義如下:
#define IRQ_RETVAL(x) ((x) ? IRQ_HANDLED : IRQ_NONE)
1.3 註銷中斷的函數
註銷中斷處理函數的原型如下:
原 型: void free_irq(unsigned int, void *)
功 能: 註銷一箇中斷處理函數
@param1: IRQ號
@param2: 對應的設備ID,也是 struct irqaction 對象中的dev_id成員,共享中斷必須要傳遞一個非NULL的實參,和request_irq中的dev_id保持一致
@retval: 無返回值
1.4 使能及禁止使能相關函數
除此之外,還有一些中斷常用的使能和禁止的函數如下:
void enable_irq(unsigned int irq) // 使能指定的中斷
void disable_irq(unsigned int irq) // 禁止指定的中斷
disable_irq函數要等到當前正在執行的中斷處理函數執行完才返回,因此使用者需要保證不會產生新的中斷,並且確保所有已經開始執行的中斷處理程序已經全部退出。在這種情況下,可以使用另外一箇中斷禁止函數:
void disable_irq_nosync(unsigned int irq)
disable_irq_nosync 函數調用以後立即返回,不會等待當前中斷處理程序執行完畢。
上面三個函數都是使能或者禁止某一箇中斷,有時候我們需要關閉當前處理器的整個中斷系統,這個時候可以使用如下兩個函數:
local_irq_enable() // 使能當前處理器中斷系統
local_irq_disable() // 禁止當前處理器中斷系統
// 函數原型如下
#define local_irq_enable() do { } while (0)
#define local_irq_disable() do { } while (0)
local_irq_save(flags) // 用於禁止中斷,並且將中斷狀態保存在 flags 中
local_irq_restore(flags) // 用於恢復中斷,將中斷恢復到 flags 狀態
中斷處理函數應該快速完成,不能消耗太長時間。因爲處理進入中斷後相應的中斷被屏蔽,在之後的代碼中也沒有重新開啓,因此整個中斷處理過程中中斷是禁止的,如果中斷處理函數執行的過長,其他的中斷將會被掛起,從而對其他中斷的相應造成嚴重的影響。
必須記住的一條準則是:在中斷處理函數中一定不能調用可能引起進程切換的函數,因爲一旦中斷處理程序被切換將不能再次被調度,這是內核對終端處理的一個嚴格限制。學過的函數有 copy_from_user 、copy_to_user
2. 示例代碼
2.1 demo.c
在代碼的112行添加了註冊中斷的函數,中斷處理函數是my_irq_handler,因爲定義了共享中斷所以必須傳遞dev_id。產生中斷事件之後會自動的調用50行的中斷處理函數,在裏面可以做中斷相關的內容,用宏 IRQ_RETVAL(IRQ_HANDLED) 作爲中斷處理函數的返回值。
本實例中只是搭建了中斷處理的框架,並沒有進行實際的操作,可以利用按鍵來模擬外部中斷。
#include <linux/module.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/atomic.h>
#include <linux/slab.h> // kzalloc和kfree的頭文件
#include <linux/interrupt.h> // 中斷相關的頭文件
typedef struct
{
dev_t dev_no;
char devname[20];
char classname[20];
struct cdev demo_cdev;
struct class *cls;
struct device *dev;
struct mutex my_mutex; // 定義一個互斥體,在init函數中進行初始化
}demo_struct;
demo_struct *my_demo_dev = NULL; // 定義一個設備結構體指針,指向NULL
static int demo_open(struct inode *inode, struct file *filp)
{
/* 首先判斷設備是否可用 */
if( mutex_lock_interruptible(&my_demo_dev->my_mutex) ) // 訪問共享資源之前獲取互斥體,成功獲取返回0
{
return -ERESTARTSYS; // 不能獲取返回錯誤碼
}
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
return 0;
}
static int demo_release(struct inode *inode, struct file *filp)
{
/* 釋放互斥量 */
mutex_unlock(&my_demo_dev->my_mutex);
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
return 0;
}
struct file_operations demo_ops = {
.open = demo_open,
.release= demo_release,
};
irqreturn_t my_irq_handler(int irq, void *dev_id)
{
/*
中斷處理函數執行內容
*/
return IRQ_RETVAL(IRQ_HANDLED); // 中斷處理函數常用的返回值
}
static int __init demo_init(void)
{
int ret;
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
/* 開闢空間 */
my_demo_dev = kzalloc(sizeof(demo_struct), GFP_KERNEL);
if ( IS_ERR(my_demo_dev) )
{
printk("kzalloc failed.\n");
ret = PTR_ERR(my_demo_dev);
goto kzalloc_err;
}
strcpy(my_demo_dev->devname, "demo_chrdev"); // 給設備名字賦值
strcpy(my_demo_dev->classname, "demo_class"); // 給設備類的名字賦值
mutex_init(&my_demo_dev->my_mutex); // 初始化互斥體
ret = alloc_chrdev_region(&my_demo_dev->dev_no, 0, 0, my_demo_dev->devname);
if (ret)
{
printk("alloc_chrdev_region failed.\n");
goto region_err;
}
cdev_init(&my_demo_dev->demo_cdev, &demo_ops);
ret = cdev_add(&my_demo_dev->demo_cdev, my_demo_dev->dev_no, 1);
if (ret < 0)
{
printk("cdev_add failed.\n");
goto add_err;
}
my_demo_dev->cls = class_create(THIS_MODULE, my_demo_dev->classname); /* 在目錄/sys/class/.. */
if ( IS_ERR(my_demo_dev->cls) )
{
ret = PTR_ERR(my_demo_dev->cls);
printk("class_create failed.\n");
goto cls_err;
}
my_demo_dev->dev = device_create(my_demo_dev->cls, NULL, my_demo_dev->dev_no, NULL, "chrdev%d", 0); /* 在目錄/dev/.. */
if ( IS_ERR(my_demo_dev->dev) )
{
ret = PTR_ERR(my_demo_dev->dev);
printk("device_create failed.\n");
goto dev_err;
}
// 註冊中斷,因爲是共享中斷所以必須傳遞dev_id
ret = request_irq(123, my_irq_handler, IRQF_TRIGGER_RISING | IRQF_SHARED, "irq_test", my_demo_dev);
if ( ret ) // 成功返回0,失敗返回錯誤碼
{
printk("request_irq failed.\n");
goto irq_err;
}
return 0;
irq_err:
device_destroy(my_demo_dev->cls, my_demo_dev->dev_no);
dev_err:
class_destroy(my_demo_dev->cls);
cls_err:
cdev_del(&my_demo_dev->demo_cdev);
add_err:
unregister_chrdev_region(my_demo_dev->dev_no, 1);
region_err:
kfree(my_demo_dev); // 釋放空間,避免內存泄漏
kzalloc_err:
return ret;
}
static void __exit demo_exit(void)
{
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
free_irq(123, my_demo_dev); // 註銷中斷
device_destroy(my_demo_dev->cls, my_demo_dev->dev_no);
class_destroy(my_demo_dev->cls);
cdev_del(&my_demo_dev->demo_cdev);
unregister_chrdev_region(my_demo_dev->dev_no, 1);
kfree(my_demo_dev); // 釋放空間,避免內存泄漏
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
2.2 test.c
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd;
int i, count = 1;
fd = open("/dev/chrdev0", O_RDWR, 0666);
if (fd < 0)
{
perror("open");
return -1;
}
while(1);
close(fd);
return 0;
}
2.3 Makefile
KERNELDIR ?= /home/linux/ti-processor-sdk-linux-am335x-evm-04.00.00.04/board-support/linux-4.9.28/
PWD := $(shell pwd)
all:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) modules
arm-linux-gnueabihf-gcc test.c -o app
install:
sudo cp *.ko app /tftpboot
clean:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
rm app
obj-m += demo.o