蛻變成蝶~Linux設備驅動之中斷與定時器

  「我叮嚀你的 你說 不會遺忘 你告訴個人 我也所有珍藏 對於咱們來講 記憶是飄不落的日子 永遠不會發黃 相聚的時候 老是很短 期待的時候 老是很長 歲月的溪水邊 撿拾起多少閃亮的詩行 若是你要想念我 就望一望天上那 閃爍的繁星 有我尋覓你的 目光」 謝謝你,曾經來過~
  中斷與定時器是咱們再熟悉不過的問題了,咱們在進行裸機開發學習的 時候,這幾乎就是重難點,也是每一個程序必要的模塊信息,那麼在Linux中,咱們又怎麼實現延時、計數,和中斷呢?html

1、中斷node

1.概述linux

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

二、中斷編程數據結構

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.ui

(2) 釋放irqatom

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

2.二、使能屏蔽中斷spa

 (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說明中斷已被處理

}

...

  

2、定時器/時鐘

一、概述

  軟件意義上的定時器最終依賴硬件定時器來實現,內核在時鐘中斷髮生後檢測個定時器釋放到期,到期後的定時器處理函數將做爲軟中斷底半部執行。驅動編程中,能夠利用一組函數和數據結構來完成定時器觸發工做或者某些週期性任務。

(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.二、睡着延遲

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

 

二、內核定時器使用模板

//設備結構體

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

三、實例--秒字符設備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

 

3、總結

  Linux中斷處理分爲兩個半部,上述都講得很清楚了,這裏強調如下,爲了充分利用CPU資源,在對延時使用不是很精確的狀況下,睡眠等待值得推薦。對於上述的幾個例子,須要你們本身在Linux的操做中敲出來,而且編譯,看輸出的結果才能徹底理解~

  版權全部,轉載請註明轉載地址:http://www.cnblogs.com/lihuidashen/p/4462220.html

相關文章
相關標籤/搜索