中断编程

0. 终端处理框架

1. 中断号--就是一个号码,需要通过一定的方式去获取到

在5.4.79内核中,从设备树中获取

获取中断号到方法:
	1, 宏定义
			IRQ_EINT(号码)
	2,设备树文件中
		arch/arm/boot/dts/exynos4412-origen.dts

硬件连接:
		key ---- gpx1_2--- EINT10



设备树文件:arch/arm/boot/dts/exynos4412-pinctrl.dtsi
gpx1: gpx1 {
	gpio-controller;
	#gpio-cells = <2>;

	interrupt-controller;
	interrupt-parent = <&gic>;
	interrupts = <GIC_SPI 24 IRQ_TYPE_LEVEL_HIGH>,
		     <GIC_SPI 25 IRQ_TYPE_LEVEL_HIGH>,
		     <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>,
		     <GIC_SPI 27 IRQ_TYPE_LEVEL_HIGH>,
		     <GIC_SPI 28 IRQ_TYPE_LEVEL_HIGH>,
		     <GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>,
		     <GIC_SPI 30 IRQ_TYPE_LEVEL_HIGH>,
		     <GIC_SPI 31 IRQ_TYPE_LEVEL_HIGH>;
	#interrupt-cells = <2>;
};


在编程过程中,需要定义自己的节点--描述当前设备用的中断号
	 arch/arm/boot/dts/exynos4412-origen.dts  +51
		
		key_int_node{
            compatible = "test_key";
            interrupt-parent = <&gpx1>;
            interrupts = <2 4>;
		};
编译设备树文件:
	make dtbs -j6 ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
更新dtbs文件:
	复制 exynos4412-origen.dtb 到NAS

上图中gpx1设备树代码是3.14内核中的,在5.4版本中 代码略有不同,具体如下:

    linux@ubuntu:~/Level10/day6/linux-5.4.79/arch/arm/boot/dts$ vim exynos4412-pinctrl.dtsi 

设备树中可以查到该设备节点

[root@farsight ]# cd proc/device-tree/key_int_node/
[root@farsight key_int_node]# ls
compatible        interrupt-parent  interrupts        name

2. 在驱动中去通过代码获取到中断号,并且申请中断(实现中断处理方法)

a,获取到中断号码:
	int get_irqno_from_node(void)
	{
		// 获取到设备树中的节点
		struct device_node *np = of_find_node_by_path("/key_int_node");
		if(np){
			printk("find node ok\n");
		}else{
			printk("find node failed\n");
		}

		// 通过节点去获取到中断号码
		int irqno = irq_of_parse_and_map(np, 0);
		printk("irqno = %d\n", irqno);
		
		return irqno;
	}
b,申请中断
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char * name, void * dev)
	参数1: 设备对应的中断号
	参数2: 中断的处理函数
			typedef irqreturn_t (*irq_handler_t)(int, void *);
	参数3:触发方式
			#define IRQF_TRIGGER_NONE	0x00000000  //内部控制器触发中断的时候的标志
			#define IRQF_TRIGGER_RISING	0x00000001 //上升沿
			#define IRQF_TRIGGER_FALLING	0x00000002 //下降沿
			#define IRQF_TRIGGER_HIGH	0x00000004  // 高点平
			#define IRQF_TRIGGER_LOW	0x00000008 //低电平触发
	参数4:中断的描述,自定义,主要是给用户查看的
			/proc/interrupts
	参数5:传递给参数2中函数指针的值
	返回值: 正确为0,错误非0


	参数2的赋值:参数1:irqno 和参数2:devid 都是由上面的request_irq函数传递的分别是该函数的参数1和参数5.
	irqreturn_t key_irq_handler(int irqno, void *devid)
	{
		return IRQ_HANDLED;
	}

	释放中断:
		void free_irq(unsigned int irq, void *dev_id)
		参数1: 设备对应的中断号
		参数2:与request_irq中第5个参数保持一致

效果展示

[root@farsight ]# insmod *.ko
[   17.570120] key_drv: loading out-of-tree module taints kernel.
[   17.575433] find node ok
[   17.577120] irqno = 107
[root@farsight ]# cat /proc/interrupts
           CPU0       CPU1       CPU2       CPU3
 36:          0          0          0          0     GIC-0  89 Level     mct_comp_irq
 37:      20031       3406       1122       6958     GIC-0  28 Level     MCT
 38:          0          0          0          0  COMBINER  18 Edge      arm-pmu
 39:          0          0          0          0  COMBINER  26 Edge      arm-pmu
...
106:      19237          0          0          0  exynos4210_wkup_irq_chip   6 Level     eth0
107:          0          0          0          0  exynos4210_wkup_irq_chip   2 Edge      IRQ_EINT10
IPI0:          0          1          1          1  CPU wakeup interrupts
...
按下按键K3
[root@farsight ]# [ 2979.408843] --------------key_irq_handler------------------
[ 2983.202843] --------------key_irq_handler------------------

[root@farsight ]#

3,实现字符设备驱动的框架

// 1,设定一个全局的设备对象
key_dev = kzalloc(sizeof(struct key_desc),  GFP_KERNEL);

// 2,申请主设备号
key_dev->dev_major = register_chrdev(0, "key_drv", &key_fops);

// 3,创建设备节点文件
key_dev->cls = class_create(THIS_MODULE, "key_cls");
key_dev->dev = device_create(key_dev->cls, NULL, 
								MKDEV(key_dev->dev_major,0), NULL, "key0");

4,驱动中将硬件所产生的数据传递给用户

a,硬件如何获取数据
	key: 按下和抬起: 1/0
	读取key对应的gpio的状态,可以判断按下还是抬起
	
	读取key对应gpio的寄存器--数据寄存器
	
	//读取数据寄存器
	int value = readl(key_dev->reg_base + 4) & (1<<2);

b,驱动如何传递给用户
	在中断处理中填充数据:
		key_dev->event.code = KEY_ENTER;
		key_dev->event.value = 0;


	在xxx_read中将数据传递给用户
		ret = copy_to_user(buf, &key_dev->event,  count);

c,用户如何拿到--编写应用程序
	while(1)
	{
		read(fd, &event, sizeof(struct key_event));

		if(event.code == KEY_ENTER)
		{
			if(event.value)
			{
				printf("APP__ key enter pressed\n");
			}else{
				printf("APP__ key enter up\n");
			}
		}
	}

5,实现文件IO模型之一阻塞,等同于休眠

文件io模型:
		1,非阻塞
		2,阻塞
		3,多路复用--select/poll
		4, 异步信号通知faync

阻塞: 当进程在读取外部设备的资源(数据),资源没有准备好,进程就会休眠
	linux应用中,大部分的函数接口都是阻塞
		scanf();
		read();
		write();
		accept();
驱动中需要调用
	1,将当前进程加入到等待队列头中
		add_wait_queue(wait_queue_head_t * q, wait_queue_t * wait)
	2,将当前进程状态设置成TASK_INTERRUPTIBLE
		set_current_state(TASK_INTERRUPTIBLE)
	3,让出调度--休眠
		schedule(void)

更加智能的接口,等同于上面的三个接口:
	wait_event_interruptible(wq, condition)

驱动如何去写代码
	1,等待队列头
			wait_queue_head_t

			init_waitqueue_head(wait_queue_head_t *q);
	
	2,在需要等待(没有数据)的时候,进行休眠
		wait_event_interruptible(wait_queue_head_t wq, condition) // 内部会构建一个等待队列项/节点wait_queue_t
		参数1: 等待队列头
		参数2: 条件,如果是为假,就会等待,如果为真,就不会等待
				可以用一标志位,来表示是否有数据


	3,在一个合适的时候(有数据),会将进程唤醒
		wake_up_interruptible(wait_queue_head_t *q)

		用法:
			wake_up_interruptible(&key_dev->wq_head);
			//同时设置标志位
			key_dev->key_state  = 1;

6, 非阻塞: 在读写的时候,如果没有数据,立刻返回,并且返回一个出错码

	用的会比较少,因为比较耗资源

open("/dev/key0", O_RDWR|O_NONBLOCK);
------------------------------------
驱动中需要去区分,当前模式是阻塞还是非阻塞
//如果当前是非阻塞模式,并且没有数据,立马返回一个出错码
if(filp->f_flags & O_NONBLOCK && !key_dev->key_state)
	return -EAGAIN;

7,多路复用--select和poll

poll的应用:
0, 存在的意义:单进程单线程下,解决同时监听多个设备的难题。(当然解决此问题也可以使用多线程或多进程来处理)
1, 需要打开多个文件(多个设备)

2, 利用poll来实现监控fd的读,写,出错
	#include <poll.h>

   int poll(struct pollfd *fds, nfds_t nfds, int timeout);
   参数1: 表示多个文件描述符集合
		struct pollfd描述的是文件描述符到信息
		struct pollfd {
           int   fd;  //文件描述符
           short events;   //希望监控fd的什么事件:读,写,出错
						POLLIN 读,
						POLLOUT 写,
						POLLERR出错
           short revents;    //结果描述,表示当前的fd是否有读,写,出错
						//用于判断,是内核自动赋值
						POLLIN 读,
						POLLOUT 写,
						POLLERR出错
		};
	参数2:被监控到fd的个数
	参数3: 监控的时间:
				正: 表示监控多少ms
				负数: 无限的时间去监控
				0: 等待0ms,类似于非阻赛
	返回值: 负数:出错
			大于0,表示fd中有数据
			等于0: 时间到

8,如果应用中使用poll对设备文件进行了监控,那么设备驱动就必须实现poll接口

unsigned int key_drv_poll(struct file *filp, struct poll_table_struct *pts)
{
	
	// 返回一个mask值
	unsigned int mask;
	// 调用poll_wait,将当前到等待队列注册系统中
	poll_wait(filp, &key_dev->wq_head, pts);
	
	// 1,当没有数据到时候返回一个0
	if(!key_dev->key_state)
		mask = 0;

	// 2,有数据返回一个POLLIN
	if(key_dev->key_state)
		mask |= POLLIN;

	return mask;
	
}

const struct file_operations key_fops = {
	.poll = key_drv_poll,
	
};

9, 异步信号通知: 当有数据到时候,驱动会发送信号(SIGIO)给应用,就可以异步去读写数据,不用主动去读写

	a,应用--处理信号,主要是读写数据
	 void catch_signale(int signo)
	{
		if(signo == SIGIO)
		{
			printf("we got sigal SIGIO");
			// 读取数据
			read(fd, &event, sizeof(struct key_event));
			if(event.code == KEY_ENTER)
			{
				if(event.value)
				{
					printf("APP__ key enter pressed\n");
				}else
				{
					printf("APP__ key enter up\n");
				}
			}
		}

	}

	// 1,设置信号处理方法
	signal(SIGIO,catch_signale);
	// 2,将当前进程设置成SIGIO的属主进程
	fcntl(fd, F_SETOWN, getpid());

	// 3,将io模式设置成异步模式
	int flags  = fcntl(fd, F_GETFL);
	fcntl(fd, F_SETFL, flags | FASYNC );

fcntl函数 根据文件描述词来操作文件的特性 文件控制函数 fcntl -- file control

头文件:
	#include <unistd.h>

	#include <fcntl.h>
函数原型:    
	int fcntl(int fd, int cmd);

	int fcntl(int fd, int cmd, long arg);         

	int fcntl(int fd, int cmd, struct flock *lock);`

描述:

    fcntl()针对(文件)描述符提供控制.参数fd是被参数cmd操作(如下面的描述)的描述符.            

针对cmd的值,fcntl能够接受第三个参数(arg)

fcntl函数有5种功能:

	1.复制一个现有的描述符(cmd=F_DUPFD).

	2.获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).

	3.获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).	------本次实验使用的参数

	4.获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).------本次实验使用的参数

	5.获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW).

cmd 选项:

		 F_GETFL     取得fd的文件状态标志,如同下面的描述一样(arg被忽略)

         F_SETFL     设置给arg描述符状态标志,可以更改的几个标志是:O_APPEND, O_NONBLOCK,O_SYNC和O_ASYNC。

         F_GETOWN 取得当前正在接收SIGIO或者SIGURG信号的进程id或进程组id,进程组id返回成负值(arg被忽略)                    

命令字(cmd)F_GETFL和F_SETFL的标志如下面的描述:

         O_NONBLOCK        非阻塞I/O;如果read(2)调用没有可读取的数据,或者如果write(2)操作将阻塞,read或write调用返回-1和EAGAIN错误
		                             
		 O_APPEND             强制每次写(write)操作都添加在文件大的末尾,相当于open(2)的O_APPEND标志         

         O_DIRECT             最小化或去掉reading和writing的缓存影响.系统将企图避免缓存你的读或写的数据.

                         如果不能够避免缓存,那么它将最小化已经被缓存了的数 据造成的影响.如果这个标志用的不够好,将大大的降低性能                      

         FASYNC               当I/O可用的时候,允许SIGIO信号发送到进程组,例如:当有数据可以读的时候

注意: 在修改文件描述符标志或文件状态标志时必须谨慎,先要取得现在的标志值,然后按照希望修改它,最后设置新标志值。不能只是执行F_SETFD或F_SETFL命令,这样会关闭以前设置的标志位。

fcntl的返回值: 与命令有关。如果出错,所有命令都返回-1,如果成功则返回某个其他值。下列三个命令有特定返回值:F_DUPFD,F_GETFD,F_GETFL以及F_GETOWN。第一个返回新的文件描述符,第二个返回相应标志,最后一个返回一个正的进程ID或负的进程组ID。

	b,驱动--发送信号
			1,需要和进程进行关联--记录信号该发送给谁
				实现一个fasync的接口

				int key_drv_fasync(int fd, struct file *filp, int on)
				{
					//只需要调用一个函数记录信号该发送给谁
					return fasync_helper(fd, filp, on,  &key_dev->faysnc);

				}
			2,在某个特定的时候去发送信号,在有数据的时候
				//发送信号
				kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);
struct fasync_struct {                                    
        spinlock_t              fa_lock;
        int                     magic;
        int                     fa_fd;
        struct fasync_struct    *fa_next; /* singly linked list */
        struct file             *fa_file;
        struct rcu_head         fa_rcu;
};

/**
 *fasync_helper - 将一个fasync_struct对象注册进内核
 *@fd:文件描述符,由fasync传入
 *@filp:file指针,由fasync传入
 *@sig:信号类型,通常使用的就是SIGIO
 *@dev_fasync:事前准备的fasync_struct对象指针的指针
 */
int fasync_helper(int fd, struct file * filp, int sig, struct fasync_struct ** dev_fasync);  

/**
 *kill_fasync - 释放一个信号
 *@dev_fasync:事前使用fasync_helper注册进内核的fasync_struct对象指针的指针
 *@filp:file指针,由fasync传入
 *@sig:信号类型,通常使用的就是SIGIO
 *@flag:标志,通常,如果资源可读用POLLIN,如果资源可写用POLLOUT
 */
void kill_fasync(struct fasync_struct **dev_fasync, int sig, int flag);

示例代码

应用代码

//---------------key_test.c----------------------
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h>

#define KEY_ENTER 28
#define EAGAIN 35

int fd;

struct key_value_dsc{
	int code;		//表示按键的类型:home, esc, Q, W, E, R, T...
	int date;		//表示按下还是抬起 1/0
};

struct key_value_dsc key_value;

void catch_signal(int signo)
{
	int ret;
	
	if(signo == SIGIO)
	{
		printf("__APP__:catch_signal\n");
		
		//可以直接去读数据, 一定可以读到数据
		ret = read(fd, &key_value, sizeof(struct key_value_dsc));
		
		if(ret < 0){
			perror("read:");
			exit(1);
		}
		
		if(key_value.code == KEY_ENTER)
		{
			if(key_value.date == 1)
			{
				printf("__APP__key enter pressed\n",key_value.date);
			}
			else if(key_value.date == 0) 
			{
				printf("__APP__key enter up\n",key_value.date);
			}
		}
	}
	
}

int main(int argc, char **argv)
{
	int ret, flag;
	char in_buf[128] = {0};
	
	fd = open("/dev/INTRRUPT KEY3",O_RDWR);

	if(fd < 0){
		perror("open:");
		exit(1);
	}

	//1.设置信号处理方法
	signal(SIGIO, catch_signal);

	//2.将当前进程设置成SIGIO的属主进程
	fcntl(fd,F_SETOWN,getpid());

	//3. 将IO模式设置成异步模式
	flag = fcntl(fd,F_GETFL); 
	fcntl(fd,F_SETFL,flag | FASYNC);

	while(1)
	{
		printf("__APP__: wating\n");
		sleep(1);
	}
	
	close(fd);
	return 0;
}

驱动代码

//--------key_drv.c--------------
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/input-event-codes.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <asm/io.h>


#define GPX1CON_ADDR 0x11000c20
#define GPX1_SIZE 8

//设计一个全局设备对象--描述按键信息
struct key_irq_str{
	int irqno;
	unsigned int dev_major;
	unsigned int dev_minor;
	struct class* pkey_class;	
	struct device* pkey_device;
	void* reg_virt_base;
	wait_queue_head_t wq;
	int key_state;	//表示是否有数据
	struct fasync_struct* fasync;
};
struct key_irq_str *pkey_irq_str;

//设计一个描述按键的数据的对象
struct key_value_dsc{
	int code;		//表示按键的类型:home, esc, Q, W, E, R, T...
	int date;		//表示按下还是抬起 1/0
};
struct key_value_dsc key3_value_dsc = {
	.code = 0,
	.date = 0,
};

int key_drv_fasync(int fd, struct file * filp, int on)
{
	//只需要调用一个函数,记录信号该发送给谁
	return fasync_helper(fd, filp, on, &pkey_irq_str->fasync);
}

irqreturn_t key_irq_handler(int irqno, void *devid)
{
	unsigned int GPX1DAT;
	
	printk("--------------%s------------------\n",__FUNCTION__);
	
	GPX1DAT = readl(pkey_irq_str->reg_virt_base + 4);
	
	if( GPX1DAT &= 0x1<<2 ){
		printk("__KERN__:Enter up\n");	 
		key3_value_dsc.code = KEY_ENTER;
		key3_value_dsc.date = 0;
	}
	else{
		printk("__KERN__:Enter down\n");
		key3_value_dsc.code = KEY_ENTER;
		key3_value_dsc.date = 1;
	}

	//表示有数据,需要唤醒整个等待队列
	wake_up_interruptible(&(pkey_irq_str->wq));
	
	//同时设置标志位
	pkey_irq_str->key_state = 1;
	
	//发送信号
	kill_fasync(&(pkey_irq_str->fasync), SIGIO, POLLIN);
	
	return IRQ_HANDLED;
}

int key_drv_open (struct inode* inode, struct file *filp){
	printk("__KERN__:open\n");
	return 0;
}

ssize_t key_drv_read (struct file *filp, char __user *buf, size_t count, loff_t *fpos){
	int ret;

	//兼容阻塞和非阻塞模式。如果当前是非阻塞模式,并且没有数据,立马返回一个出错码
	if(filp->f_flags & O_NONBLOCK && !pkey_irq_str->key_state)
	{
		return EAGAIN;
	}
	
	wait_event_interruptible(pkey_irq_str->wq, pkey_irq_str->key_state);

	//表示有数据
	ret = copy_to_user(buf, &key3_value_dsc, count);

	if(ret == 0)
	{
		//printk("__KERN__:read\n");
	}
	else
	{
		printk("__KERN__:Err to read\n");
	}
	
	key3_value_dsc.code = 0;
	key3_value_dsc.date = 0;

	pkey_irq_str->key_state = 0;
	
	return count;
}

ssize_t key_drv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fpos){
	

	return 0;
}


int key_drv_release (struct inode *inod, struct file *filp){
	printk("__KERN__:release\n");
	return 0;
}


const struct file_operations key_drv_fops = {
	.open = key_drv_open,
	.read = key_drv_read,
	.write = key_drv_write,
	.release = key_drv_release,
	.fasync = key_drv_fasync,
};

int get_irqno_from_node(void)
{
	//获取到设备树中的节点

	struct device_node *np = of_find_node_by_path("/key_int_node");
	
	if(np){
		printk("find node ok\n");
	}
	else{
		printk("find node failed\n");
	}
	//通过节点去获取到中断号码
	pkey_irq_str->irqno = irq_of_parse_and_map(np,0);
	
	printk("irqno = %d\n",pkey_irq_str->irqno);

	return pkey_irq_str->irqno;
}

static int __init key_drv_init(void)
{
	
	int ret;
	
	//1.设定一个全局的设备对象
	pkey_irq_str = kzalloc(sizeof(struct key_irq_str), 1);

	//2.申请主设备号,设置次设备号
	pkey_irq_str->dev_major = register_chrdev(0, "key_drv_test", &key_drv_fops);

	pkey_irq_str->dev_minor = 0;

	//3.创建设备节点
	pkey_irq_str->pkey_class = class_create(THIS_MODULE, "key_interrupt_dev");

	pkey_irq_str->pkey_device = device_create(pkey_irq_str->pkey_class,NULL,MKDEV(pkey_irq_str->dev_major, pkey_irq_str->dev_minor),NULL,"INTRRUPT KEY%d",3);
	
	//4.硬件初始化--地址映射或中断申请
	
	ret = get_irqno_from_node();

	ret = request_irq(pkey_irq_str->irqno, key_irq_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, "IRQ_EINT10", NULL);

	if(ret != 0)
	{
		printk("request_irq ERROR!\n");
		return ret;
	}

	pkey_irq_str->reg_virt_base = ioremap(GPX1CON_ADDR, GPX1_SIZE);

	//5. 初始化等待队列头
	init_waitqueue_head(&(pkey_irq_str->wq));
	pkey_irq_str->key_state = 0;

	
	
	return 0;
}

static void __exit key_drv_exit(void)
{
	iounmap(pkey_irq_str->reg_virt_base);
	free_irq(pkey_irq_str->irqno,NULL);
	device_destroy(pkey_irq_str->pkey_class, MKDEV(pkey_irq_str->dev_major, pkey_irq_str->dev_minor));
	class_destroy(pkey_irq_str->pkey_class);
	unregister_chrdev(pkey_irq_str->dev_major, "key_drv_test");
	kfree(pkey_irq_str);
}


module_init(key_drv_init);
module_exit(key_drv_exit);
MODULE_LICENSE("GPL");

Makefie代码

ROOTFS_DIR = /home/linux/Level11

APP_NAME = key_test

CROSS_COMPILE = arm-none-linux-gnueabi-
CC = $(CROSS_COMPILE)gcc

ifeq ($(KERNELRELEASE), )

KERNEL_DIR = /home/linux/Level10/day6/linux-5.4.79
CUR_DIR = $(shell pwd)

all :
		make -C  $(KERNEL_DIR) M=$(CUR_DIR) modules -j6 ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
		$(CC) $(APP_NAME).c -o $(APP_NAME)

clean :
		make -C  $(KERNEL_DIR) M=$(CUR_DIR) clean
		rm $(APP_NAME) ./1/$(APP_NAME)
		clear

install:
		cp -raf  *.ko  $(APP_NAME)   ./1/
		
else
obj-m += key_drv.o

endif

10, 中断的下半部

0,存在的意义:有的时候中断处理程序耗时较长,会影响用户体验甚至影响到其他进程的正常执行,此时可以将中断一分为二,关键部分优先执行,
剩下的部分切分出去,不再占用中断。关键部分执行完毕后,调用剩余部分,让其在内核线程中继续执行,而不是继续占用原线程。
延申阅读:https://developer.aliyun.com/article/379819

1,softirq: 处理比较快,但是内核级别的机制,需要修改整个内核源码,不推荐也不常用
2,tasklet: 内部实现实际调用了softirq
3, workqueue: 工作队列


1,tasklet:
	struct tasklet_struct
	{
		struct tasklet_struct *next;
		unsigned long state;
		atomic_t count;
		void (*func)(unsigned long); // 下半部的实现逻辑
		unsigned long data; // 传递给func
	};

	a, 初始化,不必直接对其初始化,使用下面的tasklet_init函数即可。
		struct tasklet_struct mytasklet;

		tasklet_init(struct tasklet_struct * t, void(* func)(unsigned long), unsigned long data)

		参数1:结构体地址,
		参数2:下半部实现函数
		参数3:传递给 下半部实现函数 的参数

		例子:
		void key_tasklet_half_irq(unsigned long data)
		{
			// 表示有数据,需要去唤醒整个进程/等待队列
			wake_up_interruptible(&key_dev->wq_head);
			//同时设置标志位
			key_dev->key_state  = 1;

			//发送信号
			kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);
		}

		tasklet_init(&key_dev->mytasklet, key_tasklet_half_irq, 45);


	b,在上半部中放入到内核线程中--启动
		// 启动下半步
		tasklet_schedule(&key_dev->mytasklet);

	c,模块卸载的时候:
		tasklet_kill(&key_dev->mytasklet);