如何做电商网站首页,go 是做网站的吗,邯郸景区网站制作,制作和淘宝商城一样网站1、中断基础概念
1.1 什么是中断
CPU在正常运行期间#xff0c;由外部或者内部引起的时间#xff0c;让CPU停下当前正在运行的程序#xff0c;转而去执行触发他的中断所对应的程序#xff0c;这就是中断。
响应中断的过程#xff1a;
1中断请求
2中断…1、中断基础概念
1.1 什么是中断
CPU在正常运行期间由外部或者内部引起的时间让CPU停下当前正在运行的程序转而去执行触发他的中断所对应的程序这就是中断。
响应中断的过程
1中断请求
2中断响应
3保护现场
4中断处理
5恢复现场
6中断返回
如果不响应中断就是中断屏蔽。
1.2 什么是中断上下文为什么会有中断上下文
中断的存在可以极大的提高CPU的运行效率但是中断会打断内核进程中的正常调度和运行所以为保证系统实时性中断服务程序必须足够简短但实际应用中某些时候发生中断时需要处理大量的事务这时候如果都在中断服务程序中完成则会严重降低中断的实时性基于这个原因Linux系统提出一个概念把中断服务程序分为两部分中断上文和中断下文。
中断上文完成尽可能少却比较急的任务中断上文的特点就是响应速度快。
中断下文处理中断剩余的大量比较耗时间的任务而且可以被中断打断。
总之中断上文越快也好中断下文可以做比较耗时间的事情但是不能死循环。
1.3 Linux中断可以嵌套吗
以前可以现在不可以。
2、设备树上的中断节点以及相关函数
2.1 设备树中的中断节点
如果一个设备需要用到中断功能开发人员就需要在设备树中配置好中断属性信息因为设备树是用来描述硬件信息的然后Linux内核通过设备树配置的中断属性来配置中断功能。
设备树中断的参考绑定文档
Documentation\devicetree\bindings\arm\gic.txt
中断实际上是非常复杂但是作为开发人员只需要关心怎么在设备树中指定中断怎么在代码中获得中断就可以。其他的事情比如设备树中的中断控制器这些都是由原厂的BSP工程师帮我们写好了不需要我们修改。
比如在imx6ull,dtsi文件其中的inc节点就是imx6ull的中断控制器节点如下
intc: interrupt-controller00a01000 {compatible arm,cortex-a7-gic;#interrupt-cells 3;interrupt-controller;reg 0x00a01000 0x1000,0x00a02000 0x100;
};
比如对于GPIO来说GPIO的节点也可以作为中断控制器在imx6ull.dtsi中GPIO1是节点如下所示
gpio1: gpio0209c000 {compatible fsl,imx6ul-gpio, fsl,imx35-gpio;reg 0x0209c000 0x4000;interrupts GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH,GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH;gpio-controller;#gpio-cells 2;interrupt-controller;#interrupt-cells 2;
};
这些工作都是由原厂的BSP工程师来帮我我们写好的并不需要我们来写除非将来你有机会去原厂工作否则不会从头开始写一个设备树文件的分工是非常明确的我们需要关注的点是怎么在设备树里面 描述一个外设的中断节点下面请看一下例子。
key {#address-cells 1,#size-cells 1,compatible key;pinctrl-name default;pinctrl-0 pinctrl_key;key-gpio gpio1 18 GPIO_ACTIVE_LOW;interrupt-parent gpio1;interrupts 18 IRQ_TYPE_EDOGE_BOTH;status okay;
}
在上面这个例子中先使用pinctrl和gpio子系统把这个引脚设置为gpio功能因为在使用中断功能的时候需要把引脚设置为输入然后使用interrupt-parent和interrupts属性来描述中断。 interrupt-parent属性值为gpio1也就是要使用gpio1这个中断控制器为什么是gpio1呢因为我们引脚使用的是gpio1里面的io18所以我们使用的是gpio1这个中断控制器。 interrupts属性设置的是中断源为啥里面有两个中断源呢因为gpio1这个中断控制器里面#interrupts-cells的值为2。例子中的第一个cells的18表示GPIO1组的18号IRQ_TYPE_EDOGE_BOTH表示上升沿和下降沿都有效。IRQ_TYPE_EDOGE_BOTH定义在include/linux/irq.h中。
所以在设备树里面配置中断的时候只需要两个步骤即可第一个就是把管脚设置为功能。第一个步骤就是使用 interrupt-parent 和 interrupts 属性来描述中断。
2.2 中断相关函数
1获取中断号的相关函数
编写驱动的时候需要用到中断号每一个中断都有一个中断号我们用到中断号中断信息已经写到了设备树里面因此可以通过irq_of_parse_and_map函数从interupts属性中提取到对应的设备号函数原型在include/linux/of_irq.h如下
unsigned int irq_of_parse_and_map(struct device_node *node, int index);
参数
node:设备节点。
index:索引号interrupts属性可能包含多条中毒啊您西通过index指定要获取的信息。
返回值中断号。
如果使用GPIO的话可以使用gpio_to_irq函数来获取gpio对应的中断号函数原型在include/linux/gpio.h如下
int gpio_to_irq(unsigned int gpio)
参数
gpio要获取的GPIO编号。
返回值GPIO对应的中断号。
2申请中断函数
同GPIO一样在Linux内核里面如果我们要使用某个中断也是需要申请的申请中断我们使用的函数是requst_irq,其在include/linux/interrupt.h中如下
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev);
参数
irq:要申请的中断号。
handler:中断处理函数当中断发生以后酒后执行此中断处理函数。
flags:中断标志。中断标志可以在include/linux/interrupt.h里面查看所有中断标志这里我们介绍几个常用的中断标志如下图所示
name:中断名字设置以后可以在/proc/interrupts文件中看到对应的中断名字。
dev:如果将flags设置为IRQ_SHARED的话dev用来区分不同的中断一般情况下将dev设置为设备结构体dev将传递给中断函数irq_handler_t的第二个人参数。
返回值0中断申请成功其他负值申请失败如果返回-EBUSY的话表示中断已经被申请了。 3中断处理函数
使用给request_irq函数申请中断的时候需要设置中断处理函数中断处理函数格式如下所示
typedef irqreturn_t (*irq_handler_t)(int, void *);
//第一个参数是要申请的中断处理函数要对应的中断号。第二个参数是一个指向void的指针也就是个通用指针需要与request_irq函数的dev参数保持一致。用于区分共享中断的不同设备dev也可以指向设备数据结构。中断 处理函数的返回值比为irqreturn_t类型irqreturn_t类型如下所示
其在include\linux\irqreturn.h
enum irqreturn {IRQ_NONE (0 0),IRQ_HANDLED (1 0),IRQ_WAKE_THREAD (1 1),
};
可以看出irqreturn_t是个枚举类型一共有三种返回值。一般中断服务函数返回值如下形式
return IRQ_RETVAL(IRQ_HANDLED);
4 free_irq函数
中断使用完成以后就要通过free_irq函数释放掉相应的中断。如果中断不是共享的那么free_irq会删除中断处理函数并且禁止中断。free_irq函数原型在include/linux/interrupt.h
void free_irq(unsigned int, void *);
函数
irq要释放的中断号。
dev:如果中断设置为共享的话此参数用来区分具体的中断共享中断只有在释放最后中断处理函数的时候才会被禁止掉。
返回值:无。
3、按键中断实验
3.1 设备树中不加intrrupt-parent和interrupts属性
由于使用的是GPIO中断所以可以不用intrrupt-parent和interrupts属性这两个属性设备树添加节点如下
test_key{compatible keys;pinctrl-names default;pinctrl-0 pinctrl_gpio_keys;gpios gpio1 18 GPIO_ACTIVE_LOW;
}
pinctrl-gpio_keys:gpio-keys{fsl,pins MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0x80000000;
}
注意下面 这两个地方的值要一致因为匹配成功才会进入probe函数。 将这个改动的设备树文件编译然后烧写到开发板上在/proc/devive-tree/上查询到设备树添加的节点 #include linux/init.h
#include linux/module.h
#include linux/platform_device.h
#include linux/of.h
#include linux/of_address.h
#include linux/gpio.h
#include linux/of_gpio.h
#include linux/interrupt.h
struct device_node *test_device_node;
struct property *test_node_property;
int gpio_num;
int irq 0;
static const of_device_id of_match_table_test[] {//匹配表{.compatible keys},
};
static const platform_device_id beep_id_table {.name beep_test,
};
irqreturn_t test_key(int irq, void *args)
{printk(test_key\n);return IRQ_HANDLED;
}
/*设备树节点compatible属性与of_match_table_test的compatible相匹配就会进入该函数,pdev是匹配成功后传入的设备树节点*/
int beep_probe(struct platform_device *pdev)
{int ret 0;printk(beep_probe\n);//查找要查找的节点test_device_node of_find_node_by_path(/test_key);if (test_device_node NULL) {printk(test_device_node find error\n);return -1;}printk(test_device_node name is %s\n,test_device_node-name);//test_keygpio_num of_get_named_gpio(test_device_node, gpios, 0);if (gpio_num 0) {printk(of_get_named_gpio error\n);return -1;}printk(gpio_num name is %d\n,gpio_num);gpio_direction_input(gpio_num);irq gpio_to_irq(gpio_num);printk(irq is %d\n,irq);ret request_irq(irq, test_key, IRQF_TRIGGER_RISING, test_key, NULL);//if (ret 0) {printk(request_irq error\n);return -1;}return 0;
}
int beep_remove(struct platform_device *pdev)
{pritnk(beep_remove \n);return 0;
}
strcut platform_driver beep_device {.probe beep_probe,.remove beep_remove,.driver {.owner THIS_MODULE,.name 123,.of_match_table of_match_table_test,//匹配表 },.id_table beep_id_table,
};
static int beep_driver_init(void)
{int ret -1;ret platform_driver_register(beep_device);if(ret 0) {printk(platform_driver_register error \n);}printk(platform_driver_register ok\n);return 0;
}
static void beep_driver_exit(void)
{free_irq(irq, NULL);platform_driver_unregister(beep_device);printk(beep_driver_exit \n);
}
module_init(beep_driver_init);
module_exit(beep_driver_exit);
MODULE_LICENSE(GPL);
编译加载驱动
、
怎么看成功申请了中断呢下图可以看到申请成功了中断号是48. 按下开发板的按键,会打印对应的信息 怎么看中断发生了几次呢 上图也打印了五次信息.
3.2 设备树中加intrrupt-parent和interrupts属性
设备树中加intrrupt-parent和interrupts属性下面修改dts改成边沿触发都可以即上升沿和下降沿都可以 修改驱动
主要区别就是获取中断号的函数你变了改成irq_of_parse_and_map获取。
#include linux/init.h
#include linux/module.h
#include linux/platform_device.h
#include linux/of.h
#include linux/of_address.h
#include linux/gpio.h
#include linux/of_gpio.h
#include linux/interrupt.h
#include linux/of_irq.h
struct device_node *test_device_node;
struct property *test_node_property;
int gpio_num;
int irq 0;
static const of_device_id of_match_table_test[] {//匹配表{.compatible keys},
};
static const platform_device_id beep_id_table {.name beep_test,
};
irqreturn_t test_key(int irq, void *args)
{printk(test_key\n);return IRQ_HANDLED;
}
/*设备树节点compatible属性与of_match_table_test的compatible相匹配就会进入该函数,pdev是匹配成功后传入的设备树节点*/
int beep_probe(struct platform_device *pdev)
{int ret 0;printk(beep_probe\n);//查找要查找的节点test_device_node of_find_node_by_path(/test_key);if (test_device_node NULL) {printk(test_device_node find error\n);return -1;}printk(test_device_node name is %s\n,test_device_node-name);//test_keygpio_num of_get_named_gpio(test_device_node, gpios, 0);if (gpio_num 0) {printk(of_get_named_gpio error\n);return -1;}printk(gpio_num name is %d\n,gpio_num);gpio_direction_input(gpio_num);//获取中断号//irq gpio_to_irq(gpio_num);irq irq_of_parse_and_map(test_device_node, 0);printk(irq is %d\n,irq);ret request_irq(irq, test_key, IRQF_TRIGGER_RISING, test_key, NULL);//if (ret 0) {printk(request_irq error\n);return -1;}return 0;
}
int beep_remove(struct platform_device *pdev)
{pritnk(beep_remove \n);return 0;
}
strcut platform_driver beep_device {.probe beep_probe,.remove beep_remove,.driver {.owner THIS_MODULE,.name 123,.of_match_table of_match_table_test,//匹配表 },.id_table beep_id_table,
};
static int beep_driver_init(void)
{int ret -1;ret platform_driver_register(beep_device);if(ret 0) {printk(platform_driver_register error \n);}printk(platform_driver_register ok\n);return 0;
}
static void beep_driver_exit(void)
{free_irq(irq, NULL);platform_driver_unregister(beep_device);printk(beep_driver_exit \n);
}
module_init(beep_driver_init);
module_exit(beep_driver_exit);
MODULE_LICENSE(GPL);
编译加载驱动 按下按键 4、中断上下文之tasklet
4.1 什么是tasklet
tasklet是中断处理中断下文的常用的一种方法tasklet是一种特殊的软中断。处理中断下文的机制还有工作队列和软中断。
4.2怎么使用tasklet来设计中断下文
框图如下 Linux把中断分成两个部分一个是上半部分一个是下半部分在上半部分我们只处理紧急的事情同时我们可以调用tasklet来启动中断下文比较耗时间的就要放到下文来处理调用tasklet以后tasklet绑定的函数并不会立马执行而是出中断以后经过一个很短的不确定时间来执行。
4.3 tasklet定义
tasklet由tasklet_struct结构表示每个结构体单独代表一个tasklet。在linux/intrrupt.h中定义为
struct tasklet_struct
{struct tasklet_struct *next;//链表中的下一个tasklet,方便管理和设置tasklet;unsigned long state;//tasklet的状态atomic_t count;//表示tasklet是否在激活状态如果是0就处在激活状态如果非0就处在非激活状态。void (*func)(unsigned long);//结构体中的func成员是tasklet的绑定哈桑农户data是它唯一的参数。unsigned long data;//函数执行的时候传递的参数第五个参数data可以区分不同结构体之间传过来的参数。
};
4.4 tasklet相关函数
1 tasklet_schdule函数
作用调度tasklet.
void __tasklet_schedule(struct tasklet_struct *t);
参数指向tasklet_struct结构的指针。
2tasklet_init函数
作用动态初始化tasklet
tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data);
参数
*t:指向tasklet_struct结构的指针。
functasklet绑定的函数。
data:函数执行的时候传递的函数。
3tasklet_kill函数
作用删除一个tasklet
void tasklet_kill(struct tasklet_struct *t);
参数指向tasklet_struct结构的指针
4.5 使用tasklet设备及中断下文步骤
1步骤一定义一个tasklet结构体
2步骤二动态初始化tasklet
3步骤三编写tasklet绑定的函数
4步骤四在中断上文调用tasklet
5步骤五卸载模块的时候删除tasklet
4.6 实验代码
在3.2的基础上修改。
#include linux/init.h
#include linux/module.h
#include linux/platform_device.h
#include linux/of.h
#include linux/of_address.h
#include linux/gpio.h
#include linux/of_gpio.h
#include linux/interrupt.h
#include linux/of_irq.h
struct device_node *test_device_node;
struct property *test_node_property;
int gpio_num;
int irq 0;
struct tasklet_struct key_test;
static const of_device_id of_match_table_test[] {//匹配表{.compatible keys},
};
static const platform_device_id beep_id_table {.name beep_test,
};
irqreturn_t test_key(int irq, void *args)
{printk(start\n);tasklet_schedule(key_test);printk(end\n);return IRQ_HANDLED;
}
void test(void)
{int i 100;while (i--) {printk(teat_key is %d\n,i);//中断下文打印一百次}
}
/*设备树节点compatible属性与of_match_table_test的compatible相匹配就会进入该函数,pdev是匹配成功后传入的设备树节点*/
int beep_probe(struct platform_device *pdev)
{int ret 0;printk(beep_probe\n);//查找要查找的节点test_device_node of_find_node_by_path(/test_key);if (test_device_node NULL) {printk(test_device_node find error\n);return -1;}printk(test_device_node name is %s\n,test_device_node-name);//test_keygpio_num of_get_named_gpio(test_device_node, gpios, 0);if (gpio_num 0) {printk(of_get_named_gpio error\n);return -1;}printk(gpio_num name is %d\n,gpio_num);gpio_direction_input(gpio_num);//获取中断号//irq gpio_to_irq(gpio_num);irq irq_of_parse_and_map(test_device_node, 0);printk(irq is %d\n,irq);ret request_irq(irq, test_key, IRQF_TRIGGER_RISING, test_key, NULL);//if (ret 0) {printk(request_irq error\n);return -1;}tasklet_init(kety_test, test, 0);return 0;
}
int beep_remove(struct platform_device *pdev)
{pritnk(beep_remove \n);return 0;
}
strcut platform_driver beep_device {.probe beep_probe,.remove beep_remove,.driver {.owner THIS_MODULE,.name 123,.of_match_table of_match_table_test,//匹配表 },.id_table beep_id_table,
};
static int beep_driver_init(void)
{int ret -1;ret platform_driver_register(beep_device);if(ret 0) {printk(platform_driver_register error \n);}printk(platform_driver_register ok\n);return 0;
}
static void beep_driver_exit(void)
{free_irq(irq, NULL);testlet_kill(key_test);platform_driver_unregister(beep_device);printk(beep_driver_exit \n);
}
module_init(beep_driver_init);
module_exit(beep_driver_exit);
MODULE_LICENSE(GPL);
加载驱动按下按键 4.6.1 修改代码看看第tasklet结构体五个参数data的值能不能传进来 编译加载驱动按下按键 5、等待队列
5.1 阻塞和非阻塞的概念
阻塞当前设备如果不可读或不可写时也就是不能获得资源的时候那么当前进程会被挂起。只有当设备满足条件的时候才可以返回。默认情况下文件都是以这种方式打开。
非阻塞当前设备不可读或不可写时该函数不糊阻塞当前进程要么放弃要么不停的查询直到可以操作为止。
读写函数是否阻塞可以通过参数来指定
fd open(filepath, O_RDWR); //默认阻塞打开
fd open(filepath, O_RDWR|O_NONBLOCK); //非阻塞方式打开
5.2 等待队列基础知识
当我们进程去访问设备的时候经常需要等待有特定事件发生以后在继续往下运行这个时候就需要在驱动里面实现当条件不满足的时候进程休眠当条件满足的时候在由内核唤醒进程。那么等待队列就实现了在事件上的条件等待。
1等待队列头
等待队列头就是一个等待队列的头部每个访问设备的进程都是一个队列项当设备不可用的时候就要将这些进程对应的等待队列添加到等待队列里面。
等待队列头使用结构体wait_queue_head_t来表示这个结构体定义在文件include/linux/wait.h里面结构体内容如下
struct __wait_queue_head {spinlock_t lock;//自旋锁struct list_head task_list;//链表头
};
typedef struct __wait_queue_head wait_queue_head_t;
类型名是wait_queue_head_t只要记住这个即可。
定义一个等待队列头wait_queue_head_t test_wq
定义等待队列头以后需要初始话可以使用init_wait_queue_head宏初始化等待队列头函数原型如下
extern void __init_waitqueue_head(wait_queue_head_t *q, const char *name, struct lock_class_key *);
#define init_waitqueue_head(q) \do { \static struct lock_class_key __key; \\__init_waitqueue_head((q), #q, __key); \} while (0)
也可以使用宏DECLARE_WAIT_QUEUE_HEAD来一次性完成等待队列头的定义和初始化。
extern void __init_waitqueue_head(wait_queue_head_t *q, const char *name, struct lock_class_key *);
#define DECLARE_WAIT_QUEUE_HEAD(name) \wait_queue_head_t name __WAIT_QUEUE_HEAD_INITIALIZER(name)
5.3 等待队列相关函数
1init_waitqueue_head宏
作用动态初始化等待队列头结构。
extern void __init_waitqueue_head(wait_queue_head_t *q, const char *name, struct lock_class_key *);
#define init_waitqueue_head(q) \do { \static struct lock_class_key __key; \\__init_waitqueue_head((q), #q, __key); \} while (0)
2wait_event宏
作用不可中断的阻塞等待让调用进程不可中断的睡眠状态在等待队列里面睡眠知道condition变成真被内核唤醒。
long prepare_to_wait_event(wait_queue_head_t *q, wait_queue_t *wait, int state);
#define ___wait_event(wq, condition, state, exclusive, ret, cmd) \
({ \__label__ __out; \wait_queue_t __wait; \long __ret ret; /* explicit shadow */ \\init_wait_entry(__wait, exclusive ? WQ_FLAG_EXCLUSIVE : 0); \for (;;) { \long __int prepare_to_wait_event(wq, __wait, state);\\if (condition) \break; \\if (___wait_is_interruptible(state) __int) { \__ret __int; \goto __out; \} \\cmd; \} \finish_wait(wq, __wait); \
__out: __ret; \
})
参数
wq:wait_queue_head_t 指针
condition为等待的条件为假时才可以进入休眠。
注意调用的时候要确认condition的值是真还是假如果调用condition为真则不会休眠。
3 wait_event_interruptible宏
功能可中断的阻塞等待让调用的 进程进入可中断的睡眠状态知道condition变成真被内核唤醒或信号打断唤醒。
#define wait_event_interruptible(wq, condition) \
({ \int __ret 0; \might_sleep(); \if (!(condition)) \__ret __wait_event_interruptible(wq, condition); \__ret; \
})
参数
wq:wait_queue_head_t 指针
condition为等待的条件为假时才可以进入休眠。
返回判断condition是否为真如果为真则返回反则检查如果进程是被信号唤醒会返回-ERESTARTSYS错误码。如果是condition为真则返回0。
4wake_up宏
功能唤醒所有休眠进程。
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)
参数
x等待队列头结构指针
5wake_up_interruptible宏
功能唤醒可中断 的休眠进程
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
参数
x等待队列头结构指针
5.4 实验为什么需要等待队列
5.4.1 不使用等待队列
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#incldue unistd.h
int main(int argc, char *argv[])
{int fd;int value;fd open(/dev/test_wq,O_RDWD);if (fd 0) {perror(open error);return fd;}while (1) {read(fd, value, sizeof(value));printf(value is %d\n,value);}return 0;
}
#include linux/init.h
#include linux/module.h
#include linux/platform_device.h
#include linux/of.h
#include linux/of_address.h
#include linux/gpio.h
#include linux/of_gpio.h
#include linux/interrupt.h
#include linux/of_irq.h
struct device_node *test_device_node;
struct property *test_node_property;
int value 0;//用来模拟管脚的状态
//struct tasklet_struct key_test;
static const of_device_id of_match_table_test[] {//匹配表{.compatible keys},
};
static const platform_device_id beep_id_table {.name beep_test,
};
irqreturn_t test_key(int irq, void *args)
{value ! value;return IRQ_HANDLED;
}
int misc_open(struct inode *inode, struct file *file)
{printk(misc_open\n);return 0;
}
int misc_release(struct inode *inode, struct file *file)
{printk(misc_relese\n);return 0;
}
ssize_t misc_read(struct file *file,char __user *ubuf, size_t size, loff_t *loff_t)
{if(copy_to_user(ubuf, value, sizeof(value)) ! 0) {printk(copy_to_user error\n);return -1;}return 0;
}
ssize_t misc_wirie(struct file *file,char __user *ubuf, size_t size, loff_t *loff_t)
{char kbuf[64] {0};if(copy_form_user(kbuf, ubuf, strlen(kbuf)) ! 0) {printk(copy_form_user error\n);return -1;}printk(kbuf is %s\n,kbuf);if(kbuf[0] 1)get_set_value(beep_gpio, 1);else if(kbuf[0] 0)get_set_value(beep_gpio, 0);return 0;
}
struct file_operations misc_fops {.owner THIS_MODULE,.open misc_open,.release misc_release,.write misc_wirie,.read misc_read
};
struct miscdevice misc_dev {.minor MISC_DYNAMIC_MINOR,.name test_wq,.fops misc_fops
};
/*设备树节点compatible属性与of_match_table_test的compatible相匹配就会进入该函数,pdev是匹配成功后传入的设备树节点*/
int beep_probe(struct platform_device *pdev)
{int ret 0;printk(beep_probe\n);//查找要查找的节点test_device_node of_find_node_by_path(/test_key);if (test_device_node NULL) {printk(test_device_node find error\n);return -1;}printk(test_device_node name is %s\n,test_device_node-name);//test_keygpio_num of_get_named_gpio(test_device_node, gpios, 0);if (gpio_num 0) {printk(of_get_named_gpio error\n);return -1;}printk(gpio_num name is %d\n,gpio_num);gpio_direction_input(gpio_num);//获取中断号//irq gpio_to_irq(gpio_num);irq irq_of_parse_and_map(test_device_node, 0);printk(irq is %d\n,irq);ret request_irq(irq, test_key, IRQF_TRIGGER_RISING, test_key, NULL);//if (ret 0) {printk(request_irq error\n);return -1;}//tasklet_init(key_test, test, 0);ret misc_register(misc_dev);return 0;
}
int beep_remove(struct platform_device *pdev)
{pritnk(beep_remove \n);return 0;
}
strcut platform_driver beep_device {.probe beep_probe,.remove beep_remove,.driver {.owner THIS_MODULE,.name 123,.of_match_table of_match_table_test,//匹配表 },.id_table beep_id_table,
};
static int beep_driver_init(void)
{int ret -1;ret platform_driver_register(beep_device);if(ret 0) {printk(platform_driver_register error \n);}printk(platform_driver_register ok\n);return 0;
}
static void beep_driver_exit(void)
{free_irq(irq, NULL);misc_deregister(misc_dev);platform_driver_unregister(beep_device);printk(beep_driver_exit \n);
}
module_init(beep_driver_init);
module_exit(beep_driver_exit);
MODULE_LICENSE(GPL);
后台运行app可以使用top命令查看cpu占用率 5.4.2 使用等待队列
对上面的驱动进行修改
#include linux/init.h
#include linux/module.h
#include linux/platform_device.h
#include linux/of.h
#include linux/of_address.h
#include linux/gpio.h
#include linux/of_gpio.h
#include linux/interrupt.h
#include linux/of_irq.h
#include linux/wait.h
#inlcude linux/sched.h
struct device_node *test_device_node;
struct property *test_node_property;
DECLARE_WAIT_QUEUE_HEAD(key_wq);//这种方法是一次性的初始化等待队列头的定义和初始化还有一种方法是下面这种
//wait_queue_head_t key_wq
//#define init_waitqueue_head(key_wq);
int wq_flags 0;
int value 0;//用来模拟管脚的状态
//struct tasklet_struct key_test;
static const of_device_id of_match_table_test[] {//匹配表{.compatible keys},
};
static const platform_device_id beep_id_table {.name beep_test,
};
irqreturn_t test_key(int irq, void *args)
{value ! value;wq_flags 1;wake_up(key_wq);return IRQ_HANDLED;
}
int misc_open(struct inode *inode, struct file *file)
{printk(misc_open\n);return 0;
}
int misc_release(struct inode *inode, struct file *file)
{printk(misc_relese\n);return 0;
}
ssize_t misc_read(struct file *file,char __user *ubuf, size_t size, loff_t *loff_t)
{wait_event_interruptible(key_wq, wq_flags);if(copy_to_user(ubuf, value, sizeof(value)) ! 0) {printk(copy_to_user error\n);return -1;}wq_flags 0;return 0;
}
ssize_t misc_wirie(struct file *file,char __user *ubuf, size_t size, loff_t *loff_t)
{char kbuf[64] {0};if(copy_form_user(kbuf, ubuf, strlen(kbuf)) ! 0) {printk(copy_form_user error\n);return -1;}printk(kbuf is %s\n,kbuf);if(kbuf[0] 1)get_set_value(beep_gpio, 1);else if(kbuf[0] 0)get_set_value(beep_gpio, 0);return 0;
}
struct file_operations misc_fops {.owner THIS_MODULE,.open misc_open,.release misc_release,.write misc_wirie,.read misc_read
};
struct miscdevice misc_dev {.minor MISC_DYNAMIC_MINOR,.name test_wq,.fops misc_fops
};
/*设备树节点compatible属性与of_match_table_test的compatible相匹配就会进入该函数,pdev是匹配成功后传入的设备树节点*/
int beep_probe(struct platform_device *pdev)
{int ret 0;printk(beep_probe\n);
//查找要查找的节点test_device_node of_find_node_by_path(/test_key);if (test_device_node NULL) {printk(test_device_node find error\n);return -1;}printk(test_device_node name is %s\n,test_device_node-name);//test_keygpio_num of_get_named_gpio(test_device_node, gpios, 0);if (gpio_num 0) {printk(of_get_named_gpio error\n);return -1;}printk(gpio_num name is %d\n,gpio_num);gpio_direction_input(gpio_num);//获取中断号//irq gpio_to_irq(gpio_num);irq irq_of_parse_and_map(test_device_node, 0);printk(irq is %d\n,irq);ret request_irq(irq, test_key, IRQF_TRIGGER_RISING, test_key, NULL);//if (ret 0) {printk(request_irq error\n);return -1;}//tasklet_init(key_test, test, 0);ret misc_register(misc_dev);return 0;
}
int beep_remove(struct platform_device *pdev)
{pritnk(beep_remove \n);return 0;
}
strcut platform_driver beep_device {.probe beep_probe,.remove beep_remove,.driver {.owner THIS_MODULE,.name 123,.of_match_table of_match_table_test,//匹配表 },.id_table beep_id_table,
};
static int beep_driver_init(void)
{int ret -1;ret platform_driver_register(beep_device);if(ret 0) {printk(platform_driver_register error \n);}printk(platform_driver_register ok\n);return 0;
}
static void beep_driver_exit(void)
{free_irq(irq, NULL);misc_deregister(misc_dev);platform_driver_unregister(beep_device);printk(beep_driver_exit \n);
}
module_init(beep_driver_init);
module_exit(beep_driver_exit);
MODULE_LICENSE(GPL);
主要改动如下 编译代码并加载驱动 再一次后台运行看看cpu运行率咋样 6、工作队列
6.1 什么是工作队列
工作队列workqueue)是实现中断下文的机制之一是一种将工作退后执行的形式。那工作队列和我们之前学习的tasklet有什么不同呢tasklet也是实现中断下文的机制。他们两个最主要的区别是tasklet不能休眠二工作队列可以休眠。所以tasklet可以用来处理比较耗时间的事情二工作队列可以处理非常复杂的并且更耗时间的事情。
6.2 工作队列workqueue)的工作原理
LInux系统在启动期间会创建内核线程该线程创建以后就处于sleep状态然后这个线程会一直去队列里面读看看有没有任务如果有就执行如果没有就休眠。工作队列的实现机制实际上是非常复杂的初学阶段只需要了解这些基本概念接口。可以类比理解为 流水线的机械Linux系统自动会创建一个。多种不同的无聊使用同一个流水线机械那么这个就是共享工作队列的概念。
如果当前的流水线机械不能满足我们加工的无聊我们是不是就需要重新定制一台流水线机器呢这个就是自定义工作队列的概念。
共享工作队列有什么缺点呢
不需要自己 创建但是如果前面的工作比较耗时间就会影响后面的工作。
自定义工作队列有什么优缺点呢
需要自己创建系统开销大。一抔点是不会受到其他工作的影响。因为这个流水线就是专门加工这一种零件的。
6.3 工作队列相关API
尽管工作队列的实现机制非常复杂但是我们使用工作队列其实就是在这个流水线上添加自己的无聊然后等待自己执行即可。
一个具体的工作类比就是流水线上的物料我们使用work_struct来描述的定义在include\linux\workqueue.h里面。所以我们使用工作队列的第一步就是先定义个工作队列。
struct work_struct {atomic_long_t data;struct list_head entry;work_func_t func;
#ifdef CONFIG_LOCKDEPstruct lockdep_map lockdep_map;
#endif
};
在这个结构体里面只需要关注func这个成员就可以了他是一个函数指针以为我们要把我们需要完成的工作写在这个函数里面。
1宏DECLARE_WORK
作用静态定义并且初始化工作队列。
#define DECLARE_WORK(n, f) \struct work_struct n __WORK_INITIALIZER(n, f)
2宏INIT_WORK
作用动态定义并初始化工作结构。
#define INIT_WORK(_work, _func) \__INIT_WORK((_work), (_func), 0)
参数
work工作队列地址。
func:工作函数
举例
struct work_struct test;
在模块初始化函数中INIT_WORK(test, func);
相当于:DECLARE_WORK(test,func);
3schedule_work
作用调度工作把work_struct挂到CPU相关的工作结构队列链表上等待工作者线程处理。
static inline bool schedule_work(struct work_struct *work);
参数
work工作队列结构体地址
需要注意的是如果调度完工作并不会马上执行只是加到了共享的工作队列里面去等轮到他才会执行。
如果我们多次调用相同的任务假如上一次的任务还没有处理完成那么多次调度相同的任务是无效的。
6.4实验代码
例程在tasklet实验代码上改
#include linux/init.h
#include linux/module.h
#include linux/platform_device.h
#include linux/of.h
#include linux/of_address.h
#include linux/gpio.h
#include linux/of_gpio.h
#include linux/interrupt.h
#include linux/of_irq.h
#include linux/workqueue.h
struct device_node *test_device_node;
struct property *test_node_property;
int gpio_num;
int irq 0;
//struct tasklet_struct key_test;
struct work_struct key_test;
static const of_device_id of_match_table_test[] {//匹配表{.compatible keys},
};
static const platform_device_id beep_id_table {.name beep_test,
};
irqreturn_t test_key(int irq, void *args)
{printk(start\n);tasklet_schedule(key_test);printk(end\n);return IRQ_HANDLED;
}
void test(void)
{int i 100;while (i--) {printk(teat_key is %d\n,i);//中断下文打印一百次}
}
/*设备树节点compatible属性与of_match_table_test的compatible相匹配就会进入该函数,pdev是匹配成功后传入的设备树节点*/
int beep_probe(struct platform_device *pdev)
{int ret 0;printk(beep_probe\n);//查找要查找的节点test_device_node of_find_node_by_path(/test_key);if (test_device_node NULL) {printk(test_device_node find error\n);return -1;}printk(test_device_node name is %s\n,test_device_node-name);//test_keygpio_num of_get_named_gpio(test_device_node, gpios, 0);if (gpio_num 0) {printk(of_get_named_gpio error\n);return -1;}printk(gpio_num name is %d\n,gpio_num);gpio_direction_input(gpio_num);//获取中断号//irq gpio_to_irq(gpio_num);irq irq_of_parse_and_map(test_device_node, 0);printk(irq is %d\n,irq);ret request_irq(irq, test_key, IRQF_TRIGGER_RISING, test_key, NULL);//if (ret 0) {printk(request_irq error\n);return -1;}//tasklet_init(kety_test, test, 0);INIT_WORK(key_test, test);return 0;
}
int beep_remove(struct platform_device *pdev)
{pritnk(beep_remove \n);return 0;
}
strcut platform_driver beep_device {.probe beep_probe,.remove beep_remove,.driver {.owner THIS_MODULE,.name 123,.of_match_table of_match_table_test,//匹配表 },.id_table beep_id_table,
};
static int beep_driver_init(void)
{int ret -1;ret platform_driver_register(beep_device);if(ret 0) {printk(platform_driver_register error \n);}printk(platform_driver_register ok\n);return 0;
}
static void beep_driver_exit(void)
{free_irq(irq, NULL);//testlet_kill(key_test);platform_driver_unregister(beep_device);printk(beep_driver_exit \n);
}
module_init(beep_driver_init);
module_exit(beep_driver_exit);
MODULE_LICENSE(GPL);
加载驱动按下按键