Linux裝置驅動之中斷與定時器

時光漫步LH發表於2015-04-29

“我叮嚀你的 你說 不會遺忘 你告訴我的 我也全部珍藏 對於我們來說 記憶是飄不落的日子 永遠不會發黃 相聚的時候 總是很短 期待的時候 總是很長 歲月的溪水邊 撿拾起多少閃亮的詩行 如果你要想念我 就望一望天上那 閃爍的繁星 有我尋覓你的 目光” 謝謝你,曾經來過~

中斷與定時器是我們再熟悉不過的問題了,我們在進行裸機開發學習的 時候,這幾乎就是重難點,也是每個程式必要的模組資訊,那麼在Linux中,我們又怎麼實現延時、計數,和中斷呢?

一、中斷

1.概述

所謂中斷是指cpu在執行程式的過程中,出現了某些突發事件急待處理,cpu必需暫停執行當前執行的程式,轉去處理突發事件,處理完之後cpu又返回原程式位置並繼續執行,根據中斷來源,中斷分為內部中斷和外部中斷,軟中斷指令等屬於內部中斷,中斷還可以分為可遮蔽中斷和不可以遮蔽中斷。Linux 的中斷處理分為頂半部和底半部,頂半部完成儘可能少得的比較緊急的功能,往往只是簡單的完成“登記中斷”的工作,就是將底半部處理程式掛到該裝置的底半部處理佇列中去,中斷處理機制如下圖:

2、中斷程式設計

2.1 申請和釋放中斷

(1) 申請irq

int request_irq (unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id)

irq 是要申請的中斷號,handler是向系統登記的中斷處理函式,irq_flags是中斷處理的屬性,可以指定中斷的觸發方式機處理方式,在處理方式方面,IRQF_DISABLED,表明中斷處理程式是快速處理程式,快速處理程式被呼叫時遮蔽所有中斷,IRQF_SHARED,表示多個裝置共享中斷(中斷處理程式)。dev_id 在中斷共享時會用到,一般設定為這個裝置的結構體或者NULL.

(2) 釋放irq

void free_irq (unsigned int irq, void *dev_id); 引數定義與request_irq()相同

2.2、使能遮蔽中斷

(1) 遮蔽(3個)

void disable_irq (int irq);

void disable_irq_nosync (ing irq);//立即返回

void enable_irq (int irq);

void disable_irq_nosync(int irq)與void disable_irq(int irg)的區別是前者立即返回,後者等待目前中斷處理完。

(2) 遮蔽所有中斷

#define local_irq_save (flags)//遮蔽本cpu所有

void local_irq_disable (void) //遮蔽本cpu所有中斷

前者會保留中斷狀態儲存在flags中(flags為unsigned long型別)。

(3) 恢復中斷

#define local_irq_restore (flags)

void local_irq_enable (void);

以local開頭的方法作用範圍是本cpu內。

2.3 底半部機制–實現機制主要有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_schedule (&my_tasklet);
 /*使系統在適當的時候排程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) 軟中斷(與通常說的軟中斷(軟體指令引發的中斷),比如arm的swi是完全不同的概念)

在linux核心中,用softirq_action結構體表徵一個軟中斷,這個結構體包含軟中斷處理函式指標和傳遞給函式的引數。使用open_softirq()函式可以註冊軟中斷對應的處理函式,而raise_softirq()函式可以觸發一個軟中斷。軟中斷和tasklet 執行與軟中斷上下文,仍屬於原子上下文的一種,而工作佇列則執行與程式上下文。因此,軟中斷和tasklet處理函式中不能睡眠,而工作佇列處理函式中允許睡眠。local_bh_disable() 和 local_bh_enable()是核心中用於禁止和使能軟中斷和tasklet底半部機制的函式。

2.4 中斷共享

多個裝置共享一根中斷線的情況在硬體系統中廣泛存在,共享中斷的多個裝置在申請中斷時,都應該使用IRQF_SHARED標誌,而且一個裝置以IRQF_SHARED標誌申請中斷成功的前提是該中斷未被申請或該中斷雖然被申請了,但它之前申請該中斷的裝置都以IRQF_SHARED標誌申請中斷,儘管核心模組可以訪問全域性地址都可以作為request_irq(…,void *dev_id)的最後一個引數,但是社結構體被指標顯然是可傳入的最佳引數.

在中端到來時,會遍歷共享此中斷的所有中斷處理程式,在中斷處理程式頂半部中,應該根據硬體暫存器中的資訊比照傳入的dev_id引數判斷是不是本裝置的中斷

共享中斷模組

irqreturn_t xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs)
{
...
int status = read_int_status();//獲知中斷源
if(!is_myint(dev_id,status))//判斷是否為本裝置
return IRQ_NONE;//不是本裝置中斷立即返回
//是本裝置中斷進行處理
...
return IRQ_HANDLED;//返回IRQ_HANDLED說明中斷已被處理
}
...

二、定時器/時鐘

1、概述

軟體意義上的定時器最終依賴硬體定時器來實現,核心在時鐘中斷髮生後檢測個定時器釋放到期,到期後的定時器處理函式將作為軟中斷底半部執行。驅動程式設計中,可以利用一組函式和資料結構來完成定時器觸發工作或者某些週期性任務。

(1) 一個timer_list 結構體的例項對應一個定時器,其定義如下:

struct timer_list {
struct list_head entry, /*定時器列表*/
unsigned long expires, /*定時器到期時間*/
void (*function) (unsigned long), /*定時器處理函式*/
unsigned long data,/*作為引數被傳入定時器處理函式*/
struct timer_base_s *base,
...
};

如定義一個名為my_timer 的定時器:

struct timer_list my_timer;

(2) 初始化定時器

void init_timer (struct timer_list *timer);
TIMER_INITIALIZER (_function, _expires, _data)
DEFINE_TIMER (_name, _function, _expires, _data)
setup_timer ();

(3) 增加定時器

void add_timer (struct timer_list *timer);

(4) 刪除定時器

int del_timer (struct timer_list *timer);

(5) 修改定時器的expire

int mod_timer (struct timer_list *timer, unsigned long expires);

(6) 對於週期性的任務,linux核心還提供了一種delayed_work機制來完成,本質上用工作佇列和定時器實現。

6.1,核心延時

linux核心中提供瞭如下3個函式分別進行納秒,微妙和毫秒延時

void ndelay(unsigned long nsecs);
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);

上述延時實現的原理實質上是忙等待,毫秒延時比較cpu耗資源,對於毫秒級以上時延,核心提供瞭如下函式

void msleep(unsigned int millisecs);
unsigned long msleep_interruptible(unsigned int millisecs);
void ssleep(unsigned int seconds);

上述函式將使得呼叫它的程式,睡眠引數指定的時間,unsigned long msleep_interruptible()可以被訊號打斷,另兩個不行

6.2、睡著延遲

睡著延遲在等待的時間到來之間程式處於睡眠狀態,schedule_timeout()可以使當前任務睡眠指定的jiffies之後重新被排程,msleep()和msleep_interruptible()就包含了schedule_timeout()實質上schedule_timeout()的實現原理是向系統新增一個定時器,在定時器處理函式中喚醒引數對應的程式,其中結合了sleep_on()和__set_current_state(TASK_INTERRUPTIBLE)等函式。

2、核心定時器使用模板

//裝置結構體
struct xxx_dev{
struct cdev cdev;
...
struct timer_list xxx_timer;//定義定時器
}
//驅動中某函式
xxx_funcl(...)
{
struct xxx_dev *dev = filp->private_data;
...
//初始化定時器
init_timer(&dev->xxx_time);
dev->xxx_timer.function = &xxx_do_timer;//定義定時器處理函式
dev->xxx_timer.data = (unsigned long)dev;//裝置結構體指標作為定時器處理引數
dev->xxx_timer.expires = jiffies + delay;//定義到期時間
add_timer(&dev->xxx_timer);//註冊定時器
...
}
//驅動中某函式
xxx_func2(...)
{
...
//刪除中斷
del_timer(&dev->xxx_timer);
...
}
//定時器處理函式
static void xxx_do_timer(unsigned long arg)
{
struct xxx_dev *dev = filp->private_data;
...
dev->xxx_timer.expires = jiffies + delay;//重新設定定時時間
add_timer(&dev->xxx_timer);
...
}

HZ表示延時1s

3、例項–秒字元裝置second_drv.c ,它在被開啟時將初始化的定時器加到核心定時器連結串列中,每秒輸出一次當前的jiffes,程式碼如下:

#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 <asm/system.h>
 #include <asm/uaccess.h>
 #include <linux/slab.h>
#define SECOND_MAJOR 248
static int second_major = SECOND_MAJOR;
struct second_dev {
 struct cdev cdev;
 atomic_t counter;
 struct timer_list s_timer;
 };
struct second_dev *second_devp;
 static void second_timer_handle (unsigned long arg)
 {
 mod_timer (&second_devp->s_timer, jiffies + HZ);
 atomic_inc (&second_devp->counter);
 printk (KERN_NOTICE "current jiffies is %ld\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;
 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;
 }
 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,
 };
 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);
 kfree (second_devp);
 unregister_chrdev_region (MKDEV (second_major, 0), 1);
 }
 MODULE_AUTHOR ("Ljia-----Ljia");
 MODULE_LICENSE ("Dual BSD/GPL");
 module_param (second_major, int, S_IRUGO);
 module_init (second_init);
 module_exit (second_exit);

在second的open()函式中,將啟動定時器,此後每秒會再次執行定時器處理函式,且在release()函式中刪除,編譯驅動,載入並建立“/dev/second”裝置檔案節點之後,用以下程式開啟,second_test會不斷讀取來自“/dev/second”裝置檔案以來經歷的秒數。

#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;
 }

執行second_test後,不斷輸出jiffes的值,如下

current jiffes is 17216
 current jiffes is 17316
 current jiffes is 17416
 current jiffes is 17516
 current jiffes is 17616
 current jiffes is 17716
 current jiffes is 17816
 current jiffes is 17916
 current jiffes is 17016
 current jiffes is 17116
 current jiffes is 17216
 current jiffes is 17316

而應用程式將不斷輸出來自開啟的“/dev/second”如下:

seconds after open /dev/second :1
 seconds after open /dev/second :2
 seconds after open /dev/second :3
 seconds after open /dev/second :4
 seconds after open /dev/second :5
 seconds after open /dev/second :6
 seconds after open /dev/second :7
 seconds after open /dev/second :8
 seconds after open /dev/second :9
 seconds after open /dev/second :10

三、總結

Linux中斷處理分為兩個半部,上述都講得很清楚了,這裡強調以下,為了充分利用CPU資源,在對延時使用不是很精確的情況下,睡眠等待值得推薦。對於上述的幾個例子,需要大家自己在Linux的操作中敲出來,並且編譯,看輸出的結果才能完全理解~

相關文章