(八)linux中断实现

一、linux中断的实现

#include <linux/interrupt.h>

linux系统针对底层的中断处理过程做了封装,使用linux提供函数,可以实现中断的响应和处理。
1、申请中断

int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
	    const char *name, void *dev)

参数说明:
unsigned int irq ---->中断号,每个中断源有一个唯一的中断号
irq_handler_t handler ---->中断服务程序,中断响应的时候,执行的函数。
unsigned long flags ---->中断的标志,外部中断:触发方式
const char *name ---->中断的名称,自定义
void *dev ---->向中断服务程序发送的参数
返回值:
成功,返回0;失败,返回复数的错误码。

注意:
1)查看中断的信息:

[root@GEC6818 /]#cat /proc/interrupts 
           CPU0       CPU1       CPU2       CPU3       CPU4       CPU5       CPU6       CPU7       
 33:          0          0          0          0          0          0          0          0       GIC  pl08xdmac
 34:          0          0          0          0          0          0          0          0       GIC  pl08xdmac
 37:          0          0          0          0          0          0          0          0       GIC  rtc 1hz
 39:         11          5          4         12         18         12         10         18       GIC  nxp-uart
 48:          1          0          0          0          0          0          0          0       GIC  s3c2440-i2c.1
 49:       1096         48       1960       1223       1589       1246       1132       1533       GIC  s3c2440-i2c.2
中断号                 各个CPU上中断发生的次数                                                  中断控制器   中断的名称

2)一个中断已经申请过了,再次申请,就申请不到了。
3)中断的优先级是高于所有的进程的。

2、中断服务程序
注意问题:
裸机:中断服务程序是没有返回值和参数的。
linux:中断服务程序是底层的中断处理过程调用的函数,所以有参数和返回值。

irqreturn_t (*irq_handler_t)(int irq, void *dev);

参数说明:
int irq ----> 中断号
void *dev —>申请中断的时候,传递的参数
返回值:

/**
 * enum irqreturn
 * @IRQ_NONE		interrupt was not from this device
 * @IRQ_HANDLED		interrupt was handled by this device
 * @IRQ_WAKE_THREAD	handler requests to wake the handler thread
 */
enum irqreturn {
	IRQ_NONE		= (0 << 0),
	IRQ_HANDLED		= (1 << 0),   ---->中断处理结束,正常返回
	IRQ_WAKE_THREAD		= (1 << 1),
};

3、释放中断

void free_irq(unsigned int, void *);

参数说明:
int irq ----> 中断号
void *dev —>申请中断的时候,传递的参数

 

二、中断号

每个中断源都有一个唯一的中断号,中断号和中断源是对应的,中断源和硬件是相关的,不同的处理器,中断源是不同的。

中断号的定义:
/arch/arm/mach-s5p6818/include/mach/s5p6818_irq.h

1、串口的中断号

#define IRQ_PHY_UART1					(6	+ 32) // pl01115_Uart_modem
#define IRQ_PHY_UART0					(7	+ 32) // UART0_MODULE
#define IRQ_PHY_UART2					(8	+ 32) // UART1_MODULE
#define IRQ_PHY_UART3					(9	+ 32) // pl01115_Uart_nodma0
#define IRQ_PHY_UART4					(10 + 32)	// pl01115_Uart_nodma1
#define IRQ_PHY_UART5					(11 + 32)	// pl01115_Uart_nodma2

2、IIC的中断号

#define IRQ_PHY_I2C0					(15 + 32)
#define IRQ_PHY_I2C1					(16 + 32)
#define IRQ_PHY_I2C2					(17 + 32)

3、定时器的中断号

#define IRQ_PHY_TIMER_INT0				(23 + 32)
#define IRQ_PHY_TIMER_INT1				(24 + 32)
#define IRQ_PHY_TIMER_INT2				(25 + 32)
#define IRQ_PHY_TIMER_INT3				(26 + 32)

4、其他

#define IRQ_PHY_ADC						(41 + 32)
#define IRQ_PHY_WDT						(31 + 32)

5、外部中断的中断号

#define IRQ_PHY_MAX_COUNT       	(74 + 32) // ADD GIC IRQ
#define IRQ_GPIO_START			IRQ_PHY_MAX_COUNT
enum {
    PAD_GPIO_A      = (0 * 32),
    PAD_GPIO_B      = (1 * 32),
    PAD_GPIO_C      = (2 * 32),
    PAD_GPIO_D      = (3 * 32),
    PAD_GPIO_E      = (4 * 32),
    PAD_GPIO_ALV    = (5 * 32),
};
#define IRQ_GPIO_A_START		(IRQ_GPIO_START + PAD_GPIO_A)
#define IRQ_GPIO_B_START		(IRQ_GPIO_START + PAD_GPIO_B)
#define IRQ_GPIO_C_START		(IRQ_GPIO_START + PAD_GPIO_C)
#define IRQ_GPIO_D_START		(IRQ_GPIO_START + PAD_GPIO_D)
#define IRQ_GPIO_E_START		(IRQ_GPIO_START + PAD_GPIO_E)

分析:
S5P6818一共32*5=160个GPIO。每个GPIO都可以作为外部中断使用:

按键 GPIO口 GPIO口号 中断号
K2 —> GPIOA28 ---->PAD_GPIO_A+28 ----->IRQ_GPIO_A_START + 28
K3 —> GPIOB30 ---->PAD_GPIO_B+30 ----->IRQ_GPIO_B_START + 30
K4 —> GPIOB31 ---->PAD_GPIO_B+31 ----->IRQ_GPIO_B_START + 31
K6 —> GPIOB9 ---->PAD_GPIO_B+9 ----->IRQ_GPIO_B_START + 9

GPIO头文件:
#include <cfg_type.h>

 

三、中断的标志

注意:
中断一安装成功(request_irq()),中断默认是打开的。

1、外部中断的中断标志
#define IRQF_TRIGGER_RISING 0x00000001
#define IRQF_TRIGGER_FALLING 0x00000002
#define IRQF_TRIGGER_HIGH 0x00000004
#define IRQF_TRIGGER_LOW 0x00000008

双边沿触发:
IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING

2、普通中断
IRQF_DISABLED - keep irqs disabled when calling the action handler.
当响应当前中断的时候,关闭其他的中断

IRQF_SHARED - allow sharing the irq among several devices
当一个中断源,注册多次,也就是一个中断源对应多个中断服务程序时

IRQF_TIMER - Flag to mark this interrupt as timer interrupt
当注册定时器的中断时,使用的标志。

 

四、中断源对应的中断服务程序

通常情况下,一个中断源对应一个中断服务程序。
也可以多个中断源对应一个中断服务程序,但是在中断服务程序中,需要区分中断源。

1、使用中断号来区分中断源

2、使用申请中断的时候,传递给中断服务程序的参数来区分中断源

 

五、中断服务程序与原子上下文

#include <linux/delay.h>
void ssleep(unsigned int seconds);  --->内核中的秒级睡眠

1、中断服务程序是一个原子过程:
例:

irqreturn_t key2_isr(int irq, void *dev)
{
	printk("key2 is pressing, irq = %d\n",irq);
	ssleep(1); //阻塞--->调度--->出错
	printk("after irq\n");
	return 	IRQ_HANDLED;
}

2、出错的信息:

[root@GEC6818 /test]#insmod key_drv.ko 
[   14.663000] gec6818 key driver init 
[root@GEC6818 /test]#[   17.544000] key2 is pressing, irq = 134   ---->已经进入到了中断服务程序
[   17.545000] BUG: scheduling while atomic: swapper/3/0/0x00010002   ----->出错,在一个原子过程中,进行进程的调度。
[   17.548000] Modules linked in: key_drv(O)
[   17.553000] [<c001517c>] (unwind_backtrace+0x0/0x134) from [<c0731340>] (__schedule+0x720/0x7d4)
[   17.561000] [<c0731340>] (__schedule+0x720/0x7d4) from [<c072f564>] (schedule_timeout+0x198/0x324)
[   17.570000] [<c072f564>] (schedule_timeout+0x198/0x324) from [<c0052e80>] (msleep+0x2c/0x38)
[   17.579000] [<c0052e80>] (msleep+0x2c/0x38) from [<bf00001c>] (key2_isr+0x1c/0x30 [key_drv])
[   17.587000] [<bf00001c>] (key2_isr+0x1c/0x30 [key_drv]) from [<c00b7344>] (handle_irq_event_percpu+0x80/0x284)
[   17.597000] [<c00b7344>] (handle_irq_event_percpu+0x80/0x284) from [<c00b7584>] (handle_irq_event+0x3c/0x5c)
[   17.607000] [<c00b7584>] (handle_irq_event+0x3c/0x5c) from [<c00b9b70>] (handle_level_irq+0xb4/0x108)
[   17.616000] [<c00b9b70>] (handle_level_irq+0xb4/0x108) from [<c001ef1c>] (gpio_handler+0x9c/0x17c)
[   17.625000] [<c001ef1c>] (gpio_handler+0x9c/0x17c) from [<c00b6b5c>] (generic_handle_irq+0x28/0x38)
[   17.634000] [<c00b6b5c>] (generic_handle_irq+0x28/0x38) from [<c000f4e4>] (handle_IRQ+0x58/0xb0)
[   17.643000] [<c000f4e4>] (handle_IRQ+0x58/0xb0) from [<c000854c>] (gic_handle_irq+0x28/0x58)
[   17.651000] [<c000854c>] (gic_handle_irq+0x28/0x58) from [<c000e840>] (__irq_svc+0x40/0x70)
[   17.660000] Exception stack(0xe30a9f88 to 0xe30a9fd0)
[   17.665000] 9f80:                   ffffffed 00000003 00000000 00000000 e30a8000 c0b298c8
[   17.673000] 9fa0: c0737290 e30a8000 e30a8000 c0aae128 00000000 00000000 00000019 e30a9fd0
[   17.681000] 9fc0: c000f83c c000f840 600b0013 ffffffff

3、原子过程
原子过程—>这个过程是一个连续的过程,该过程不能产生阻塞,不能被打断。

中断服务程序的执行过程就是一个原子过程,中断服务程序执行的过程中,使用的就是原子上下文。

上下文—>程序执行过程中,使用的环境:代码、stack、heap

哪些函数可能会产生阻塞:
ssleep()/copy_to_user()/copy_from_user()/获取信号量的函数。

不能把中断服务程序挂起。

 

六、等待队列

设置一个等待的条件,条件满足,进程就继续向下执行;条件不满足,进程就阻塞在等待队列上。当条件满足后,中断会唤醒等待队列中的进程,进程再继续向下执行。

等待队列也是一种同步的方法。

1、定义一个等待的条件

static int key_press_flag = 0;

2、定义一个等待队列

static wait_queue_head_t gec6818_key_wq;

3、初始化队列头

void init_waitqueue_head(wait_queue_head_t *q)

4、进程访问等待队列

void wait_event(wait_queue_head_t q, int condition) 
void wait_event_interruptible(wait_queue_head_t q, int condition)	

参数说明:
wait_queue_head_t *q —>定义并初始化好的等待队列
int condition—>等待条件

注意:
进程访问等待队列的时候,判断等待条件,条件满足,进程继续执行,条件不满足,进程就产生阻塞,进入睡眠状态,睡眠状态有两种。

1)进程进入睡眠的两种不同的状态:
可中断睡眠:进程在睡眠状态的时候,给该进程发信号,这个进程可以接收信号,并立即处理信号。
不可中断睡眠:进程在睡眠状态的时候,给该进程发信号,这个进程可以接收信号,但是不会立即响应这个信号,当进程进入执行状态,再去处理这个信号。

2)如何查看进程的状态:

[root@GEC6818 /]#top
Mem: 21092K used, 806356K free, 0K shrd, 892K buff, 2628K cached
CPU:  0.0% usr  1.1% sys  0.0% nic 98.8% idle  0.0% io  0.0% irq  0.0% sirq
Load average: 0.00 0.00 0.00 1/85 138
  PID  PPID USER     STAT   VSZ %VSZ CPU %CPU COMMAND
  138   125 root     R     3336  0.4   1  1.1 top
    1     0 root     S     3336  0.4   2  0.0 init
  125     1 root     S     3336  0.4   2  0.0 -/bin/sh
   44     2 root     SW       0  0.0   0  0.0 [kworker/0:1]
  102     2 root     SW       0  0.0   4  0.0 [mmcqd/0]
    2     0 root     SW       0  0.0   0  0.0 [kthreadd]
    3     2 root     SW       0  0.0   0  0.0 [ksoftirqd/0]
    4     2 root     SW       0  0.0   0  0.0 [kworker/0:0]
    5     2 root     SW       0  0.0   3  0.0 [kworker/u:0]
    6     2 root     SW       0  0.0   0  0.0 [migration/0]
    7     2 root     SW       0  0.0   0  0.0 [watchdog/0]
    8     2 root     SW       0  0.0   1  0.0 [migration/1]
    9     2 root     SW       0  0.0   1  0.0 [kworker/1:0]
   10     2 root     SW       0  0.0   1  0.0 [ksoftirqd/1]
   11     2 root     SW       0  0.0   1  0.0 [watchdog/1]
   12     2 root     SW       0  0.0   2  0.0 [migration/2]
   13     2 root     SW       0  0.0   2  0.0 [kworker/2:0]
   14     2 root     SW       0  0.0   2  0.0 [ksoftirqd/2]
   15     2 root     SW       0  0.0   2  0.0 [watchdog/2]
   16     2 root     SW       0  0.0   3  0.0 [migration/3]

可中断睡眠 — S; 不可中断睡眠 — D。

3)如何想一个进程发信号
#kill 信号 PID
#killall 信号 进程的名字

4)有哪些信号

[root@GEC6818 /]#kill -l
 1) HUP
 2) INT
 3) QUIT
 4) ILL
 5) TRAP
 6) ABRT
 7) BUS
 8) FPE
 9) KILL
10) USR1
11) SEGV
12) USR2
13) PIPE
14) ALRM
15) TERM
16) STKFLT
17) CHLD
18) CONT
19) STOP
20) TSTP
21) TTIN
22) TTOU
23) URG
24) XCPU
25) XFSZ
26) VTALRM
27) PROF
28) WINCH
29) POLL
30) PWR
31) SYS
32) RTMIN
64) RTMAX

5、唤醒等待队列的进程

void wake_up(wait_queue_head_t *q)

 

七、附录

中断示例代码:key_drv.c
按键状态获取-1
按键状态获取-2
按键状态获取-3

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值