1. 程式人生 > >linux驅動開發學習--對中斷和核心定時器的學習筆記

linux驅動開發學習--對中斷和核心定時器的學習筆記

一 中斷理解                                
中斷的含義                                
所謂中斷是指 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;
}