前言
前面講了linux中斷程式設計:頂半部、底半部
中斷頂半部:做緊急的,耗時短的事情,同時還啟動中斷底半部(如果有)。
中斷底半部:做耗時的事情,這個事件可以被中斷
實作方法:tasklet、工作隊列、軟中斷等機制。實際上是把耗時的事件推後執行,不在中斷程式中執行。
核心tasklet機制
tasklet
機制是一種比較特殊的軟中斷,
tasklet
一詞原意是“小片任務”的意思,這裡是指一小段可執行的代碼,且通常以函數的形式出現。這個
tasklet
綁定的函數在一個時刻隻能在一個
CPU
上運作,在
SMP
系統上不會出現并發問題。
tasklet
(小任務)機制是中斷處理下半部分最常用的一種方法,其使用也是非常簡單的。正如在前文中你所知道的那樣,一個使用
tasklet
的中斷程式首先會通過執行中斷處理程式來快速完成上半部分的工作,接着會通過調用
tasklet
使得下半部分的工作得以完成。可以看到,下半部分被上半部分所調用,至于下半部分何時執行則屬于核心工作。對應到我們此刻所說的
tasklet
就是,在中斷處理程式中,除了完成對中斷的響應等工作,還要調用
tasklet
,如下圖所示:
tasklet核心資料結構
路徑:
interrupt.h linux-3.5\include\linux
struct tasklet_struct
{
struct tasklet_struct *next; //用來實作多個 tasklet_struct 結構連結清單
unsigned long state; //目前這個tasklet是否已經被排程
atomic_t count; //值為0時候使用者才可以排程
void (*func)(unsigned long); //指向tasklet綁定的函數的指針
unsigned long data; //傳遞給tasklet綁定函數的參數
};
使用
tasklet
機制就是定義一個這個結構體變量,然後填充必須的元素:
count
、
func
、
data
。
tasklet機制核心API
tasklet定義及初始化函數
頭檔案
interrupt.h linux-3.5\include\linux
名稱 | 宏、函數 | 作用 |
---|---|---|
靜态初始化 | | 初始化相關成員預設被調用 |
靜态初始化 | | 初始化相關成員預設不能被調用 |
動态初始化 | | 初始化相關成員,處于激活狀态 |
; | 預設可以被調用 |
補充tasklet_init函數源碼:
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data)
{
t->next = NULL;
t->state = ;
atomic_set(&t->count, ); //預設設定為0 可以被調用
t->func = func;
t->data = data;
}
tasklet機制使能、失能函數
前面提到結構中
atomic_t count
成員值為
時使用者才可以排程,如果使用者想去排程一個沒有激活的
tasklet
或者禁止排程一個已經激活的
tasklet
可以使用以下兩個函數
名稱 | 函數 |
---|---|
取消激活函數 | |
激活函數 | |
說明:
如果對一個
tasklet_struct
結構變量調用了兩次
tasklet_disable
函數,則
count
會增加為
2
,這時你在調用一次
tasklet_enable
,那實際
count
值還是
1
,還是處于非激活狀态。
enable
與
disable
隻是相對而言。
tasklet_disable源碼補充:
static inline void tasklet_disable_nosync(struct tasklet_struct *t)
{
atomic_inc(&t->count); //單純的+1
smp_mb__after_atomic_inc();
}
static inline void tasklet_disable(struct tasklet_struct *t)
{
tasklet_disable_nosync(t);
tasklet_unlock_wait(t);
smp_mb();
}
tasklet_enable源碼補充:
void tasklet_enable(struct tasklet_struct *t)
{
smp_mb__before_atomic_dec();
atomic_dec(&t->count); //單純-1
}
tasklet排程函數 tasklet_schedule(struct tasklet_struct*t)
tasklet_schedule(struct tasklet_struct*t)
tasklet_struct
結構體中綁定了一個函數,當使用者需要執行這個函數的時候,需要自己調用
tasklet_schedule
函數去通知核心幫我們排程所綁定的函數
static inline void tasklet_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
__tasklet_schedule(t);
}
tasklet取消排程函數tasklet_kill(struct tasklet_struct*t)
void tasklet_kill(struct tasklet_struct *t);
void tasklet_kill(struct tasklet_struct *t)
{
if (in_interrupt())
printk("Attempt to kill tasklet from interrupt\n");
while (test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
do {
yield();
} while (test_bit(TASKLET_STATE_SCHED, &t->state));
}
tasklet_unlock_wait(t);
clear_bit(TASKLET_STATE_SCHED, &t->state);
}
tasklet機制程式設計步驟
- 第一步:寫tasklet結構中的綁定函數
void tasklet_func(unsigned long data)
{
······
}
- 第二步:定義tasklet結構
第一種使用靜态定義:(定義出的
能被排程)tasklet
第二種使用靜态定義:(定義出的DECLARE_TASKLET(mttasklet,tasklet_func,123);
不能被排程)tasklet
DECLARE_TASKLET_DISABLED(mttasklet,tasklet_func,123);
第三種動态定義:一般在子產品初始化時候
1)先定義一個變量
2)對這個變量進行初始化,如果是靜态定義結構體,略過這一步struct tasklet_struct mytasklet;
tasklet_init(&mytasklet,tasklet_func,123)
-
第三步:初始化tasklet結構
根據你想實作的功能是什麼,在什麼情況要調用到你綁定的函數,就在那裡調用
函數進行排程。tasklet_schedule
tasklet_schedule(&mytasklet)
-
第四步:在适當的地方進行排程
如果中途不想使用tasklet,則可以删除它。
一般情況在子產品解除安裝函數中編寫。tasklet_kill(&mytasklet)
- 第五步:如果想取消排程,則在适當的地方取消
tasklet機制程式設計示例
簡單的示例代碼
#include<linux/module.h>
#include<linux/init.h>
//添加頭檔案
#include<linux/interrupt.h>
//實作一個tasklet工作函數
void tasklet_func(unsigned long data)
{
printk("%s is call!! data:%d\r\n",__FUNCTION__,data);
}
//定義一個tasklet_struct結構變量,并且進行初始化
DECLARE_TASKLET(mytasklet, tasklet_func, );
static int __init tasklet_init(void)
{
//一安裝子產品就進行排程
tasklet_schedule(&mytasklet);
printk("%s is call!!",__FUNCTION__);
return ;
}
static void __exit tasklet_exit(void)
{
//删除tasklet
tasklet_kill(&mytasklet);
printk("tasklet is kill!\r\n");
}
MODULE_INIT(tasklet_init);
MODULE_EXIT(tasklet_exit);
MODULE_LICENSE("GPL");
開發闆運作效果
[[email protected]/zhangchao]#insmod tasklet.ko
[ 34.940000] tasklett_init is call!![ 34.940000] tasklet_func is call!! data:123
[[email protected]/zhangchao]#
循環排程綁定函數
在tasklet工作函數中再排程即可
#include<linux/module.h>
#include<linux/init.h>
//添加頭檔案
#include<linux/interrupt.h>
void tasklet_func(unsigned long data);
//定義一個tasklet_struct結構變量,并且進行初始化
DECLARE_TASKLET(mytasklet, tasklet_func, );
//實作一個tasklet工作函數
void tasklet_func(unsigned long data)
{
static unsigned long cnt=;
printk("cnt:%d %s is call!! data:%d\r\n",cnt++,__FUNCTION__,data);
tasklet_schedule(&mytasklet);
}
static int __init tasklett_init(void)
{
//一安裝子產品就進行排程
tasklet_schedule(&mytasklet);
printk("%s is call!!",__FUNCTION__);
return ;
}
static void __exit tasklett_exit(void)
{
//删除tasklet
tasklet_kill(&mytasklet);
printk("tasklet is kill!\r\n");
}
module_init(tasklett_init);
module_exit(tasklett_exit);
MODULE_LICENSE("GPL");
開發闆運作效果
[ 4010.785000] cnt:1573 tasklet_func is call!! data:123
[ 4010.790000] cnt:1574 tasklet_func is call!! data:123
[ 4010.795000] cnt:1575 tasklet_func is call!! data:123
[ 4010.800000] cnt:1576 tasklet_func is call!! data:123
[ 4010.805000] cnt:1577 tasklet_func is call!! data:123
[ 4010.810000] cnt:1578 tasklet_func is call!! data:123
[ 4010.815000] cnt:1579 tasklet_func is call!! data:123
[ 4010.820000] cnt:1580 tasklet_func is call!! data:123
[ 4010.825000] cnt:1581 tasklet_func is call!! data:123
[ 4010.830000] cnt:1582 tasklet_func is call!! data:123
[ 4010.835000] cnt:1583 tasklet_func is call!! data:123
[ 4010.840000] cnt:1584 tasklet_func is call!! data:123
[ 4010.845000] cnt:1585 tasklet_func is call!! data:123
[ 4010.850000] cnt:1586 tasklet_func is call!! data:123
[ 4010.855000] cnt:1587 tasklet_func is call!! data:123
[ 4010.860000] cnt:1588 tasklet_func is call!! data:123
失能函數
在初始化函數内先失能tasklet結構,再排程,将不會執行工作函數,但是`tasklet_kill` 一個沒有使能但是排程了的`tasklet`核心會異常,造成子產品解除安裝時核心會卡住。
static int __init tasklet_init(void)
{
//故意測試 失能tasklet再次調用看是否還能執行
tasklet_disable(&mytasklet);
//一安裝子產品就進行排程
tasklet_schedule(&mytasklet);
printk("%s is call!!",__FUNCTION__);
return 0;
}
//tasklet_kill 一個沒有使能但是排程了的tasklet核心會異常
static void __exit tasklet_exit(void)
{
//删除tasklet
tasklet_kill(&mytasklet);
printk("tasklet is kill!\r\n");
}
先失能後使能功能正常,解除安裝效果也正常
static int __init tasklet_init(void)
{
//故意測試 失能tasklet再次調用看是否還能執行
tasklet_disable(&mytasklet);
tasklet_enable(&mytasklet);
//一安裝子產品就進行排程
tasklet_schedule(&mytasklet);
printk("%s is call!!",__FUNCTION__);
return 0;
}
重複排程會出現什麼狀況呢?
**注意:先把工作函數中排程函數注釋掉否則會重複排程,否則不能達成測試目的**
static int __init tasklet_init(void)
{
//一安裝子產品就進行排程
tasklet_schedule(&mytasklet);
tasklet_schedule(&mytasklet);
tasklet_schedule(&mytasklet);
tasklet_schedule(&mytasklet);
printk("%s is call!!",__FUNCTION__);
return 0;
}
**運作效果表明:雖重複排程,但隻執行一次工作函數**
linux使用tasklet機制實作中斷底半部
思路:把以前寫的按鍵驅動程式中的服務函數分為中斷頂半部和中斷底半部,下面是按鍵中斷服務函數中所做的事
irqreturn_t key_handler(int irq, void * dev_id)
{
int dn; //存放按鍵狀态
//注冊的時候傳遞進來的是每個案件元素的首位址,類型是struct key_info*
//這裡進行還原,還原後可以取得這個結構的所有元素資訊
struct key_info *pdev_data=(struct key_info*)dev_id;
//判斷按下還是松開按鍵;可通過讀取按鍵IO電平狀态來判斷
//硬體上,按下按鍵傳回電平0 松開傳回1
dn=gpio_get_value(pdev_data->key_gpio);
if(!dn)
printk("key%d down!!\r\n",pdev_data->key_num+);
else
printk("key%d up!!\r\n",pdev_data->key_num+);
//存儲按鍵狀态到按鍵緩沖區
//這裡我們想實作正邏輯,'0'表示沒有按下,'1'表示按下。這裡取反一次
kbuf[pdev_data->key_num]= '0'+!dn;
press=;
wake_up_interruptible(&wq);
return IRQ_HANDLED;
}
軟體思路
把上面的代碼分為兩部分
頂半部:
隻是單純寫排程底半部代碼,因為目前代碼沒有什麼非常緊急的事情需要完成。
底半部:
把原來中斷程式中的代碼原樣複制過去,寫在tasklet服務函數中去
修改部分
- 添加頭檔案
#include<linux/interrupt.h>
-
修改按鍵結構體
因為有四個按鍵,每個按鍵對應一個
tasklet
struct key_info{
int key_gpio; //按鍵IO
int key_num; //按鍵編号
const char* name; //按鍵名稱
struct tasklet_struct keytasklet; //按鍵對應tasklet結構體
};
- 構造tasklet服務函數
//實作 tasklet函數
void key_tasklet_func(unsigned long data)
{
int dn; //存放按鍵狀态
//注冊的時候傳遞進來的是每個案件元素的首位址,類型是struct key_info*
//這裡進行還原,還原後可以取得這個結構的所有元素資訊
struct key_info *pdev_data=(struct key_info*)data;
//判斷按下還是松開按鍵;可通過讀取按鍵IO電平狀态來判斷
//硬體上,按下按鍵傳回電平0 松開傳回1
dn=gpio_get_value(pdev_data->key_gpio);
if(!dn)
printk("key%d down!!\r\n",pdev_data->key_num+);
else
printk("key%d up!!\r\n",pdev_data->key_num+);
//存儲按鍵狀态到按鍵緩沖區
//這裡我們想實作正邏輯,'0'表示沒有按下,'1'表示按下。這裡取反一次
kbuf[pdev_data->key_num]= '0'+!dn;
press=;
wake_up_interruptible(&wq);
}
- 修改中斷函數
irqreturn_t key_handler(int irq, void * dev_id)
{
//注冊的時候傳遞進來的是每個按鍵元素的首位址,類型是struct key_info*
//這裡進行還原,還原後可以取得這個結構的所有元素資訊
struct key_info *pdev_data=(struct key_info*)dev_id;
//啟動中斷底半部
tasklet_schedule(&pdev_data->keytasklet);
return IRQ_HANDLED;
}
- 初始化中注冊tasklet這裡不使用靜态初始化,使用動态初始化,在子產品初始化部分動态初始化
for(i=;i<;i++)
{
//初始化每個按鍵 tasklet結構
key_info[i].keytasklet;
tasklet_init(&key_info[i].keytasklet,key_tasklet_func,&key[i]);
irq=gpio_to_irq(keys[i].key_gpio);
err=request_irq(irq, key_handler,flags,keys[i].name,&keys[i]); //注冊key1中斷
//如果有注冊失敗的 跳出循環
if(err<)
{
break;
}
}
- 在子產品解除安裝中 删除tasklet
for(i=;i<;i++)
{
irq=gpio_to_irq(keys[i].key_gpio);
free_irq(irq,&keys[i]);
tasklet_kill(&key_info[i].keytasklet); //删除tasklet
}
驅動源碼
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<asm/uaccess.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<linux/kdev_t.h>
#include<linux/slab.h>
#include<linux/device.h> //增加自動建立裝置頭檔案
#include<linux/uaccess.h>
#include<linux/interrupt.h> //中斷注冊登出頭檔案
#include<linux/gpio.h> //gpio相關的頭檔案
#include<linux/delay.h>
#include<linux/sched.h>
//定義等待隊列 頭 wq,要是一個全局變量
DECLARE_WAIT_QUEUE_HEAD(wq);
//按鍵數量
#define KEY_SIZE (4)
//按鍵緩沖區
unsigned char kbuf[KEY_SIZE]={"0000"}; //儲存按鍵狀态 初始化為字元 0,0表示沒有按下,1表示按下
//定義全局變量是否有按鍵辨別
static int press=;
struct key_info{
int key_gpio; //按鍵IO
int key_num; //按鍵編号
const char* name; //按鍵名稱
struct tasklet_struct keytasklet; //按鍵對應tasklet結構體
};
struct key_info keys[]={
{EXYNOS4_GPX3(),,"key1"},
{EXYNOS4_GPX3(),,"key2"},
{EXYNOS4_GPX3(),,"key3"},
{EXYNOS4_GPX3(),,"key4"}
};
//定義字元裝置結構體
static struct cdev *xxxdriver_cdev;
//定義裝置号(包含主次)
static dev_t xxxdriver_num=;
//定義裝置類
static struct class *xxxdriver_class;
//定義裝置結構體
static struct device *xxxdriver_device;
//定義裝置名稱
#define XXXDRIVER_NAME "mydevice"
ssize_t XXX_read(struct file *file, char __user *usr, size_t size, loff_t *loft)
{
if(size>KEY_SIZE)
{
size=KEY_SIZE;
}
//如果按鍵沒有動作
if(press==)
{
//如果是非阻塞方式打開
if(file->f_flags & O_NONBLOCK)
{
//傳回錯誤碼
return -EAGAIN;
}else
{
//休眠
wait_event(wq,press);
}
}
//清除按鍵處理标志,執行到這裡表示發生了中斷有了按鍵動作
//一定清0,否則下次調用會出問題
press=;
if(copy_to_user(usr,kbuf,size))
{
printk("copy to user faild\r\n");
return -EFAULT;
}
return size;
}
int XXX_open (struct inode *node, struct file *pfile)
{
printk("files open success!!\r\n");
return ;
}
int XXX_release (struct inode *node, struct file *file)
{
printk("file close success!!\r\n");
return ;
}
/*key中斷服務函數:
按鍵按下、松開都會進入中斷服務函數
程式要把按鍵的狀态,儲存起來,讓使用者通過使用者變成API接口來讀取按鍵狀态
是以,函數中完成1):判斷按鍵按下還是松開;2)把按鍵狀态存儲起來
*/
irqreturn_t key_handler(int irq, void * dev_id)
{
//注冊的時候傳遞進來的是每個案件元素的首位址,類型是struct key_info*
//這裡進行還原,還原後可以取得這個結構的所有元素資訊
struct key_info *pdev_data=(struct key_info*)dev_id;
//啟動中斷底半部
tasklet_schedule(&pdev_data->keytasklet);
return IRQ_HANDLED;
}
//實作 tasklet函數
void key_tasklet_func(unsigned long data)
{
int dn; //存放按鍵狀态
//注冊的時候傳遞進來的是每個案件元素的首位址,類型是struct key_info*
//這裡進行還原,還原後可以取得這個結構的所有元素資訊
struct key_info *pdev_data=(struct key_info*)data;
//判斷按下還是松開按鍵;可通過讀取按鍵IO電平狀态來判斷
//硬體上,按下按鍵傳回電平0 松開傳回1
dn=gpio_get_value(pdev_data->key_gpio);
if(!dn)
printk("key%d down!!\r\n",pdev_data->key_num+);
else
printk("key%d up!!\r\n",pdev_data->key_num+);
//存儲按鍵狀态到按鍵緩沖區
//這裡我們想實作正邏輯,'0'表示沒有按下,'1'表示按下。這裡取反一次
kbuf[pdev_data->key_num]= '0'+!dn;
press=;
wake_up(&wq);
}
//檔案操作函數結構體
static struct file_operations xxxdriver_fops={
.owner=THIS_MODULE,
.open=XXX_open,
.release=XXX_release,
.read=XXX_read,
};
static __init int xxxdriver_init(void)
{
//定義錯誤傳回類型
int err;
//定義中斷編号
int irq,i;
//設定中斷屬性 非共享中斷 上升下降沿觸發
unsigned long flags=IRQF_DISABLED |
IRQF_TRIGGER_FALLING |
IRQF_TRIGGER_RISING;
for(i=;i<;i++)
{
//初始化每個按鍵 tasklet結構
tasklet_init(&keys[i].keytasklet,key_tasklet_func,&keys[i]);
irq=gpio_to_irq(keys[i].key_gpio);
err=request_irq(irq, key_handler,flags,keys[i].name,&keys[i]); //注冊key1中斷
//如果有注冊失敗的 跳出循環
if(err<)
{
break;
}
}
//有注冊失敗的 将前面注冊成功的釋放掉
if(err<){
for(--i;i>;i--)
{
irq=gpio_to_irq(keys[i].key_gpio);
free_irq(irq, &keys[i]);
}
}
//配置設定字元裝置結構體,前面隻是定義沒有配置設定空間
xxxdriver_cdev=cdev_alloc();
//判斷配置設定成功與否
if(xxxdriver_cdev==NULL)
{
err=-ENOMEM;
printk("xxxdriver alloc is err\r\n");
goto err_xxxdriver_alloc;
}
//動态配置設定裝置号
err=alloc_chrdev_region(&xxxdriver_num, , , XXXDRIVER_NAME);
//錯誤判斷
if(err<)
{
printk("alloc xxxdriver num is err\r\n");
goto err_alloc_chrdev_region;
}
//初始化結構體
cdev_init(xxxdriver_cdev,&xxxdriver_fops);
//驅動注冊
err=cdev_add(xxxdriver_cdev,xxxdriver_num,);
if(err<)
{
printk("cdev add is err\r\n");
goto err_cdev_add;
}
//建立裝置類
xxxdriver_class=class_create(THIS_MODULE,"xxx_class");
err=PTR_ERR(xxxdriver_class);
if(IS_ERR(xxxdriver_class))
{
printk("xxxdriver creat class is err\r\n");
goto err_class_create;
}
//建立裝置
xxxdriver_device=device_create(xxxdriver_class,NULL, xxxdriver_num,NULL, "xxxdevice");
err=PTR_ERR(xxxdriver_device);
if(IS_ERR(xxxdriver_device))
{
printk("xxxdriver device creat is err \r\n");
goto err_device_create;
}
printk("xxxdriver init is success\r\n");
return ;
//硬體初始化部分
err_device_create:
class_destroy(xxxdriver_class);
err_class_create:
cdev_del(xxxdriver_cdev);
err_cdev_add:
unregister_chrdev_region(xxxdriver_num, );
err_alloc_chrdev_region:
kfree(xxxdriver_cdev);
err_xxxdriver_alloc:
return err;
}
static __exit void xxxdriver_exit(void)
{
int irq,i;
for(i=;i<;i++)
{
irq=gpio_to_irq(keys[i].key_gpio);
free_irq(irq,&keys[i]);
tasklet_kill(&keys[i].keytasklet); //删除tasklet
}
device_destroy(xxxdriver_class,xxxdriver_num);
class_destroy(xxxdriver_class);
cdev_del(xxxdriver_cdev);
unregister_chrdev_region(xxxdriver_num, );
printk("xxxdriver is exit\r\n");
}
module_init(xxxdriver_init);
module_exit(xxxdriver_exit);
MODULE_LICENSE("GPL");
app函數
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
int i,file_fp;
unsigned char btn[]={"0000"},cur[]={"0000"};
file_fp = open(argv[],O_RDWR);
while()
{
read(file_fp,cur,);
for(i=;i<;i++)
{
if(cur[i]!=btn[i])
{
btn[i]=cur[i];
if(cur[i]=='1')
printf("按鍵%d 按下\r\n",i+);
else
printf("按鍵%d 彈起\r\n",i+);
}
}
}
close(file_fp);
}
Makefile
KERN_DIR = /zhangchao/linux3/linux-
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
prog:
arm-linux-gcc btn_app.c -o app
obj-m += btn.o
開發闆運作效果
[root@ZC/zhangchao]#insmod btn.ko
[ ] xxxdriver init is success
[root@ZC/zhangchao]#./app /dev/xxxdevice
[ ] files open success!!
[ ] key1 down!!
按鍵 按下
[ ] key1 up!!
按鍵 彈起
[ ] key2 down!!
按鍵 按下
[ ] key2 up!!
按鍵 彈起
開發闆運作效果與單純運作按鍵中斷代碼效果相同,當然這裡隻是一個簡單的例子沒有實際意義。