完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
中断是操作系统最常见的事件之一,无论是系统层的“软中断”还是CPU底层的“硬中断”都是编程时常用的。中断的作用之一是充分利用CPU资源,正常情况下,CPU执行用户任务,当外设触发中断产生时,CPU停止当前任务,转而去处理中断信息。处理完中断再返回任务处继续执行。
对于硬中断,顾名思义,由硬件产生,CPU定时器、各类总线、GPIO以及外设键盘、磁盘、鼠标等。对于嵌入式来说,触摸屏、传感器等都可以产生中断信号。硬中断处理要实时和高效率,一般由驱动层处理。 对于软中断,是由操作系统实现,不会直接中断CPU。操作系统在任务管理、调度过程会有软中断过程。对于驱动层面,软中断往往结合硬中断一起使用。 1. linux中断 1.1 中断上半部和下半部 中断的基本原则是“快速执行完并退出”,但一些设备中往往需要处理大量的耗时事务。不同于裸机编程,操作系统是多个进程和多个线程执行,宏观上达到并行运行的状态,外设中断则会打断内核中任务调度和运行,及屏蔽其外设的中断响应,中断函数耗时过长会使得系统实时性和并发性降低。为了提高系统的实时性和并发性,linux内核将中断处理程序分为上半部(top half)和下半部(bottom half)。上半部分任务比较少,处理一些寄存器操作、时间敏感任务,以及“登记中断”通知内核及时处理下半部的任务。下半部分,则负责处理中断任务中的大部分任务,特别是耗时任务必需放在下半部。 以触摸屏外设为例:中断上下部区别:
1.2 中断设计 一个完整的中断程序由上半部和下半部分共同构成,在编写设备驱动程序前,就需考虑好上半部和下半部的分配。很多时候上半部与下半部并没有严格的区分界限,主要由程序员根据实际设计,如某些外设中断可以没有下半部。关于上下半部的划分原则,就是主要事务、耗时事务划分在下半部处理。 可以参考以下原则:
1.3 中断上半部实现 上半部中断一般包括几个步骤
1.3.1 中断回调函数 该部分为真正的中断上半部,处理时间敏感任务和中断状态清除,同时触发内核调度下半部。 中断回调函数类型,是一个函数指针,位于“kernel/include/linux/interrupt.h”中。 typedef irqreturn_t (*irq_handler_t)(int, void *);
/** * enum irqreturn * @IRQ_NONE interrupt was not from this device or was not handled * @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), }; typedef enum irqreturn irqreturn_t; 注1.3.2 中断号 系统中断源是多元的,linux内核会给每个中断源分配一个唯一的中断号,用以区分不同设备的中断。终端号为一个int类型的整数。引入设备树后,中断号一般在会设备树中设备节点描述了,驱动程序通过指定函数获取。
unsigned int irq_of_parse_and_map(struct device_node *node, int index); [tr]参数含义[/tr]
int gpio_to_irq(unsigned gpio) /* 把gpio序号转换为中断号 */ 1.3.3 中断注册 int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev) request_ir用于中断注册,同时函数内部会使能中断,不需手动再去使能。 [tr]参数含义[/tr]
关于中断类型(flags),“interrupt.h”有相关定义 #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 /* 电平触发 */ #define IRQF_SHARED 0x00000080 /* 多个设备共享中断 */ #define IRQF_PROBE_SHARED 0x00000100 #define __IRQF_TIMER 0x00000200 #define IRQF_PERCPU 0x00000400 #define IRQF_NOBALANCING 0x00000800 #define IRQF_IRQPOLL 0x00001000 #define IRQF_ONESHOT 0x00002000 #define IRQF_NO_SUSPEND 0x00004000 #define IRQF_FORCE_RESUME 0x00008000 #define IRQF_NO_THREAD 0x00010000 #define IRQF_EARLY_RESUME 0x00020000 #define IRQF_COND_SUSPEND 0x00040000 1.3.4 中断使能和失能 void disable_irq_nosync(unsigned int irq); /* 失能中断,立即返回 */ bool disable_hardirq(unsigned int irq); void disable_irq(unsigned int irq); /* 失能中断,需等待中断执行完才返回 */ void disable_percpu_irq(unsigned int irq); void enable_irq(unsigned int irq); /* 使能中断 */ 注:1.3.5 中断释放 void free_irq(unsigned int, void *); 设备退出时,必须释放中断。“free_irq”函数释放设备中断后,并会禁止设备中断,无需手动禁止。如果是共享中断,只有释放完最后一个设备才会禁止中断。 1.4 中断下半部实现 linux内核提供了3种下半部实现方式,分别是soft tirq(软中断)、tasklet、work queue(工作队列),三种方式应用在不同的场合下。
1.4.1 软中断 linux内核软中断用结构体“struct softirq_action”描述,位于“kernel/include/linux/interrupt.h”中,从原型看就是一个软中断处理回调函数指针,函数实体就是“下半部”处理的任务,由驱动工程师实现。 /* softirq mask and active fields moved to irq_cpustat_t in * asm/hardirq.h to get better cache usage. KAO */ struct softirq_action { void (*action)(struct softirq_action *); }; “interrupt.h”枚举了常用软中断类型。 /* PLEASE, avoid to allocate new softirqs, if you need not _really_ high frequency threaded job scheduling. For almost all the purposes tasklets are more than enough. F.e. all serial device BHs et al. should be converted to tasklets, not to softirqs. */ enum { HI_SOFTIRQ=0, /* 最高优先级软中断 */ TIMER_SOFTIRQ, /* 定时器软中断 */ NET_TX_SOFTIRQ, /* 网络发送软中断 */ NET_RX_SOFTIRQ, /* 网络接收软中断 */ BLOCK_SOFTIRQ, /* 块操作软中断 */ BLOCK_IOPOLL_SOFTIRQ, /* 块IO轮询软中断 */ TASKLET_SOFTIRQ, /* tasklet软中断 */ SCHED_SOFTIRQ, /* 调度软中断 */ HRTIMER_SOFTIRQ, /* Unused, but kept as tools rely on the numbering. Sigh! */ /* 高精度定时器软中断 */ RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */ /* RCU软中断 */ NR_SOFTIRQS /* 软中断总数 */ }; 软中断使用步骤 1)注册软中断 void open_softirq(int nr, void (*action)(struct softirq_action *)); [tr]参数含义[/tr]
软中断注册,必须采用“静态”方式,因为内核起来后会调用“softirq_init”初始化软中断。 2)触发软中断 在上半部调用“raise_softirq”函数通知内核执行下半部。 void raise_softirq(unsigned int nr); [tr]参数含义[/tr]
1.4.2 tasklet tasklet本质是软中断,基于软中断封装实现的一种方式,tasklet的描述“struct tasklet_struct”结构体同样位于“kernel/include/linux/interrupt.h”中。 struct tasklet_struct { struct tasklet_struct *next; /* 链式存储,表示下一tasklet节点 */ unsigned long state; /* tasklet状态,TASKLET_STATE_SCHED表示被调度过程,TASKLET_STATE_RUN表示tasklet正在某个CPU上执行 */ atomic_t count; /* tasklet引用数,原子操作 */ void (*func)(unsigned long); /* 回调处理函数,下半部处理任务置于此 */ unsigned long data; /* 传递给回调函数fun的参数 */ }; count成员与tasklet状态相关,如果count等于0,tasklet处于enable状态,大于0则处于disable状态。 static inline void tasklet_disable_nosync(struct tasklet_struct *t) { atomic_inc(&t->count); smp_mb__after_atomic(); } static inline void tasklet_disable(struct tasklet_struct *t) { tasklet_disable_nosync(t); /* 自减 */ tasklet_unlock_wait(t); smp_mb(); } static inline void tasklet_enable(struct tasklet_struct *t) { smp_mb__before_atomic(); atomic_dec(&t->count); /* 自加 */ } tasklet使用步骤 1)注册tasklet 使用tasklet机制,首先需定义一个“struct tasklet_struct”,可以动态和静态定义,然后调用“tasklet_init”函数初始化。 void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data); [tr]参数含义[/tr]
linux内核“interrupt.h”中封装了一个初始化的宏“DECLARE_TASKLET”,也可以直接调用该宏初始化,传入参数与“tasklet_init”一致。 #define DECLARE_TASKLET(name, func, data) struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data } 2)触发调度 在上半部调用“tasklet_schedule”函数通知内核执行下半部调度。 void tasklet_schedule(struct tasklet_struct *t) [tr]参数含义[/tr]
1.4.3 工作队列 工作队列与前两种方式最大不同是,下半部任务由内核创建一个线程进行处理。内核线程有可能被其他线程抢占,因此工作队列允许睡眠或者重新调度。如果下半部任务实时性要求不高,允许睡眠,则选择工作队列;否则选择软中断或者tasklet。工作队列是没有优先级的,多个工作队列时,是按照FIFO的方式进行处理。 工作队列的方式下,把下半部任务封装为工作项(work),linux内核用“struct work_strcut”结构体描述,位于“kernel/include/workqueue.h”中。 struct work_struct { atomic_long_t data; /* 传递给回调函数fun的参数 */ struct list_head entry; /* 指针入口 */ work_func_t func; /* 回调处理函数,下半部处理任务置于此 */ #ifdef CONFIG_LOCKDEP struct lockdep_map lockdep_map; #endif }; typedef void (*work_func_t)(struct work_struct *work); 一系列工作项(work)组成工作队列(work queue),“workqueue_struct”结构体描述,原型位于“kernel/kernel/workqueue.c”中。 /* * The externally visible workqueue. It relays the issued work items to * the appropriate worker_pool through its pool_workqueues. */ struct workqueue_struct { struct list_head pwqs; /* WR: all pwqs of this wq */ struct list_head list; /* PR: list of all workqueues */ struct mutex mutex; /* protects this wq */ int work_color; /* WQ: current work color */ int flush_color; /* WQ: current flush color */ atomic_t nr_pwqs_to_flush; /* flush in progress */ struct wq_flusher *first_flusher; /* WQ: first flusher */ struct list_head flusher_queue; /* WQ: flush waiters */ struct list_head flusher_overflow; /* WQ: flush overflow list */ struct list_head maydays; /* MD: pwqs requesting rescue */ struct worker *rescuer; /* I: rescue worker */ int nr_drainers; /* WQ: drain in progress */ int saved_max_active; /* WQ: saved pwq max_active */ struct workqueue_attrs *unbound_attrs; /* PW: only for unbound wqs */ struct pool_workqueue *dfl_pwq; /* PW: only for unbound wqs */ #ifdef CONFIG_SYSFS struct wq_device *wq_dev; /* I: for sysfs interface */ #endif #ifdef CONFIG_LOCKDEP struct lockdep_map lockdep_map; #endif char name[WQ_NAME_LEN]; /* I: workqueue name */ ...... }; |
|
|
|
工作队列使用步骤
工作队列可以使用linux系统创建的队列和用户自定义队列,队列可以设定为延迟执行和非延迟执行。 1)注册工作项 使用工作队列机制,首先需定义一个“struct work_struct”工作项,可以动态和静态定义。
#define INIT_WORK(_work, _func) __INIT_WORK((_work), (_func), 0) #define DECLARE_WORK(n, f) struct work_struct n = __WORK_INITIALIZER(n, f)
#define INIT_DELAYED_WORK(_work, _func) __INIT_DELAYED_WORK(_work, _func, 0) #define DECLARE_DELAYED_WORK(n, f) struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f, 0) [tr]参数含义[/tr]
注:2)使用自定义队列
如果使用用户自定义的工作队列,则首先需创建一个工作队列。创建工作队列,首先是定义一个“struct workqueue_struct”工作队列指针,调用“create_singlethread_workqueue”宏创建工作队列。 #define create_singlethread_workqueue(name) alloc_ordered_workqueue("%s", WQ_MEM_RECLAIM, name) [tr]参数含义[/tr]
例子: struct workqueue_struct *pworkqueue = create_singlethread_workqueue("wq0");
/* 非延迟工作队列绑定 */ bool queue_work(struct workqueue_struct *wq, struct work_struct *work) { return queue_work_on(WORK_CPU_UNBOUND, wq, work); } /* 延迟工作队列绑定 */ bool queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *dwork, unsigned long delay) { return queue_delayed_work_on(WORK_CPU_UNBOUND, wq, dwork, delay); } [tr]参数含义[/tr]
例子: struct work_struct work; struct workqueue_struct *pwrokqueue; void work_handle(struct work_struct *pw) { /* todo */ } INIT_WORK(work, work_handle); pworkqueue = create_singlethread_workqueue("wq0"); queue_work(pwrokqueue, &work); /* 绑定自定义工作队列 */
void destroy_workqueue(struct workqueue_struct *wq) [tr]参数含义[/tr]
3)触发调度
bool schedule_work(struct work_struct *work)
bool schedule_delayed_work(struct delayed_work *dwork, unsigned long delay) [tr]参数含义[/tr]
gq0{ compatible = "gq0"; gpios = <&gpio1, 10, GPIO_ACTIVE_LOW>; pinctrl-names = "default"; pinctrl-0 = <&spi1_cs0_gpio>; /* gpio模式 */ interrupt-parent = <&gpio1>; interrupts = <10, IRQ_TYPE_EDGE_BOTH>; /* 上升沿和下降沿触发 */ status = "okay"; }; spi1_cs0_gpio: spi1_cs0_gpio { rockchip,pins = <1 10 RK_FUNC_GPIO &pcfg_pull_none>, }; 2. 中断驱动编写 以gpio为例,编写一个gpio触发的中断驱动,并获取gpio状态值。使用的是GPIO1_B2端口。 2.1 实现方式
2.2 添加设备树 GPIO1_B2引脚是复用引脚,可以复用为sp1的片选引脚(SP1_CSn0)。原设备树文件已添加 SPI1_CSn0的pin节点描述,在其他后增加GPIO属性描述。同时增加一个“gpioirq”的驱动节点信息。
/* 在rk3399.dtsi 中添加 */ spi1 { ...... spi1_cs0: spi1-cs0 { rockchip,pins = <1 10 RK_FUNC_2 &pcfg_pull_up>; }; spi1_cs0_gpio: spi1_cs0_gpio { /* 添加GPIO pin描述 */ rockchip,pins = <1 10 RK_FUNC_GPIO &pcfg_pull_none>, }; ...... }
/* 在rk3399-firefly-port.dtsi 中添加 */ gq0{ compatible = "gq0"; gpios = <&gpio1 10 GPIO_ACTIVE_LOW>; pinctrl-names = "default"; pinctrl-0 = <&spi1_cs0_gpio>; /* gpio模式 */ interrupt-parent = <&gpio1>; interrupts = <10 IRQ_TYPE_EDGE_BOTH>; /* 上升沿和下降沿触发 */ status = "okay"; }; 2.3 设备数据结构 struct gpioirq_dev { struct cdev dev; /* 字符驱动 */ dev_t dev_id; /* 设备ID */ struct class *dev_class; /* 设备类 */ int gpio; /* GPIO序号 */ int irq; /* 中断序号 */ wait_queue_head_t r_queue; /* 等待队列 */ bool r_en; /* 可读标识 */ struct fasync_struct *r_sync;/* 内核通知应用信号 */ }; 2.4 中断函数 static irqreturn_t gq0_irq_handle(int irq, void *dev_id) { struct gpioirq_dev *p; p = (struct gpioirq_dev *)dev_id; p->r_en = true; wake_up_interruptible(&(p->r_queue)); /* 唤醒休眠进程 */ /* 通知应用进程数据可读 * SIGIO:信号类型 * POLL_IN:普通数据可读 */ kill_fasync(&p->r_sync, SIGIO, POLL_IN); return IRQ_HANDLED; } 2.5 状态读取函数 static ssize_t gq0_read(struct file *pfile, char __user *buf, size_t size, loff_t * offset) { int ret = 0; struct gpioirq_dev *p; char level = 0; p = pfile->private_data; wait_event_interruptible(p->r_queue, p->r_en); /* 进程休眠,等待中断 */ level = gpio_get_value(p->gpio); ret = copy_to_user(buf, &level, 1); return ret; } 2.6 中断注册 static int gq0_probe(struct platform_device *pdev) { struct device *dev; int ret = -1; dev_t id = 0; struct device_node *nd; nd = pdev->dev.of_node; /* 设备树节点 */ if(nd == NULL) { printk("get node faileedn"); return -1; } gq0.gpio = of_get_named_gpio(nd, "gpios", 0); /* 获取GPIO */ if(gq0.gpio < 0) { printk("get gpio failedn"); return -1; } if (!gpio_is_valid(gq0.gpio)) { printk("gpio [%d] is invalidn", gq0.gpio); return -1; } ret = gpio_request(gq0.gpio, "gq0"); /* 申请GPIO */ if(ret < 0) { printk("gpio request failedn"); return ret; } ret = gpio_direction_input(gq0.gpio); //gq0.irq = gpio_to_irq(gq0.gpio); /* 中断号映射 */ gq0.irq = irq_of_parse_and_map(nd, 0); ret = request_irq(gq0.irq, gq0_irq_handle, gq0.irq_mode, "gq0", &gq0);/* 注册中断 */ if(ret<0) { printk("request gq0 irq failedn"); free_irq(gq0.irq, &gq0); gpio_free(gq0.gpio); return ret; } ...... } 2.7 platform 驱动 static struct of_device_id of_gq0_ids[] = { {.compatible = "gpioirq"}, /* 与节点设备树“compatible ”属性一致 */ { } }; static struct platform_driver gq0_driver = { .driver = { .owner = THIS_MODULE, .name = DEV_NAME, .of_match_table = of_gq0_ids, }, .probe = gq0_probe, .remove = gq0_remove, }; module_platform_driver(gq0_driver); /* platform 驱动注册和注销 */ |
|
|
|
你正在撰写答案
如果你是对答案或其他答案精选点评或询问,请使用“评论”功能。
1496 浏览 1 评论
synopsys 的design ware:DW_fpv_div,浮点数除法器,默认32位下,想提升覆盖率(TMAX),如果用功能case去提升覆盖率呢?
1682 浏览 1 评论
RK3588 GStreamer调试四路鱼眼摄像头四宫格显示报错
3963 浏览 1 评论
【飞凌嵌入式OK3576-C开发板体验】RKNN神经网络-YOLO图像识别
254 浏览 0 评论
【飞凌嵌入式OK3576-C开发板体验】SSH远程登录网络配置及CAN通讯
1336 浏览 0 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-8 18:44 , Processed in 0.665181 second(s), Total 74, Slave 56 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号