一、中斷
1.概述
所謂中斷是指cpu在執行程序的過程中,出現了某些突發事件急待處理,cpu必需暫停執行當前執行的程序,轉去處理突發事件,處理完之后cpu又返回原程序位置並繼續執行,根據中斷來源,中斷分為內部中斷和外部中斷,軟中斷指令等屬於內部中斷,中斷還可以分為可屏蔽中斷和不可以屏蔽中斷。Linux 的中斷處理分為頂半部和底半部,頂半部完成盡可能少得的比較緊急的功能,往往只是簡單的完成“登記中斷”的工作,就是將底半部處理程序掛到該設備的底半部處理隊列中去,中斷處理機制如下圖:
2、中斷編程
2.1 申請和釋放中斷
(1) 申請irq
1
|
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
1
|
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
1
2
3
4
5
6
7
8
|
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)工作隊列
1
2
3
4
5
6
7
8
9
|
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參數判斷是不是本設備的中斷
共享中斷模塊
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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 結構體的實例對應一個定時器,其定義如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
struct
timer_list {
struct
list_head entry,
/*定時器列表*/
unsigned
long
expires,
/*定時器到期時間*/
void
(*function) (unsigned
long
),
/*定時器處理函數*/
unsigned
long
data,
/*作為參數被傳入定時器處理函數*/
struct
timer_base_s *base,
...
};
|
如定義一個名為my_timer 的定時器:
1
|
struct
timer_list my_timer;
|
(2) 初始化定時器
1
2
3
4
5
6
7
|
void
init_timer (
struct
timer_list *timer);
TIMER_INITIALIZER (_function, _expires, _data)
DEFINE_TIMER (_name, _function, _expires, _data)
setup_timer ();
|
(3) 增加定時器
1
|
void
add_timer (
struct
timer_list *timer);
|
(4) 刪除定時器
1
|
int
del_timer (
struct
timer_list *timer);
|
(5) 修改定時器的expire
1
|
int
mod_timer (
struct
timer_list *timer, unsigned
long
expires);
|
(6) 對於周期性的任務,linux內核還提供了一種delayed_work機制來完成,本質上用工作隊列和定時器實現。
6.1,內核延時
linux內核中提供了如下3個函數分別進行納秒,微妙和毫秒延時
1
2
3
4
5
|
void
ndelay(unsigned
long
nsecs);
void
udelay(unsigned
long
usecs);
void
mdelay(unsigned
long
msecs);
|
上述延時實現的原理實質上是忙等待,毫秒延時比較cpu耗資源,對於毫秒級以上時延,內核提供了如下函數
1
2
3
4
5
|
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、內核定時器使用模板
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
//設備結構體
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,代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
|
#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”設備文件以來經歷的秒數。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
#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的操作中敲出來,並且編譯,看輸出的結果才能完全理解~