linux驱动开发学习--对中断和内核定时器的学习笔记

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Rocky_zhm/article/details/47447057
一 中断理解                                
中断的含义                                
所谓中断是指 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;
}


猜你喜欢

转载自blog.csdn.net/Rocky_zhm/article/details/47447057