主机参考:VPS测评参考推荐/专注分享VPS服务器优惠信息!若您是商家可以在本站进行投稿,查看详情!此外我们还提供软文收录、PayPal代付、广告赞助等服务,查看详情! |
我们发布的部分优惠活动文章可能存在时效性,购买时建议在本站搜索商家名称可查看相关文章充分了解该商家!若非中文页面可使用Edge浏览器同步翻译!PayPal代付/收录合作 |
本教程运行环境:linux7.3系统,Dell G3电脑。
1.Linux中断1.1 Linux中断API函数是在Linux内核中申请中断所必需的。request_irq函数用于申请中断,可能会导致睡眠,所以在中断上下文或者其他禁止睡眠的代码段中不能使用request_irq函数。request_irq函数将激活(启用)中断,因此我们不需要手动启用中断。request_irq函数的原型如下:irq:要申请的中断号。Handler:中断处理函数,将在中断发生后执行。标志:中断标志。可以查看文件include/Linux/interrupt . hname:中断名中的所有中断标志。设置后,可以在文件/proc/interrupts中看到相应的中断名。Dev:如果flags设置为IRQF_SHARED,dev用于区分不同的中断。一般当dev设置为设备结构时,dev会传递给中断处理函数IRQ _ handler _ t的第二个参数,返回值:0中断请求成功,其他负中断请求失败。如果返回-EBUSY,则中断已被请求。
使用中断时,需要通过request_irq函数申请。用完后需要通过free_irq函数释放相应的中断。如果中断不是共享的,那么free_irq将删除中断处理程序并禁用它。free_irq函数的原型如下:函数参数和返回值的含义如下:irq:要释放的中断。Dev:如果中断设置为shared (IRQF_SHARED),这个参数用来区分具体的中断。只有当最后一个中断处理程序被释放时,共享才被禁止。返回值:无。
使用request_irq函数申请中断时,需要设置中断处理函数。中断处理函数的格式如下:
的常用中断使用和禁用功能如下:enable_irq和disable_irq用于启用和禁用指定的中断,irq是要禁用的中断号。disable_irq函数在当前执行的中断处理程序完成之前不会返回,因此用户需要确保不会产生新的中断,并且已经开始执行的所有中断处理程序都已退出。在这种情况下,可以使用另一个中断抑制函数:disable_irq_nosync函数,调用后会立即返回,不需要等待当前中断处理程序执行完毕。以上三个功能是启用或禁用某个中断。有时候我们需要关闭当前处理器的整个中断系统,也就是说在学习STM32的时候关闭全局中断。此时可以使用以下两个函数:local_irq_enable用于启用当前处理器中断系统,local_irq_disable用于禁用当前处理器中断系统。如果任务A调用local_irq_disable关闭全局中断10S,任务B在关闭2S时开始运行,任务B也调用local_irq_disable关闭全局中断3S。3秒钟后,任务B调用local_irq_enable函数打开全局中断。此时只过了zhujicankao3=5秒,然后开启全局中断。这时候任务A关闭全局中断10秒的愿望破灭了,然后任务A就“生气”了。结果很严重,系统可能会被任务a彻底崩溃,要解决这个问题,任务B不能简单粗暴的通过local_irq_enable函数打开全局中断,而是把中断状态恢复到之前的状态。考虑到其他任务的感受,此时应该使用以下两个函数:
1.2上半部和下半部在某些材料中也称为上半部和下半部,意思都一样。我们用request_irq申请中断时注册的中断服务函数属于中断处理的上层部分,只要触发中断就会执行中断处理函数。我们都知道中断处理功能必须尽快完成,越短越好,但现实往往是残酷的。有些中断处理过程只是比较耗时,所以我们要处理它们来缩短中断处理函数的执行时间。比如电容式触摸屏通过中断通知SOC触摸事件,SOC响应中断,然后通过IIC接口读取触摸坐标值上报给系统。但是我们都知道IIC的最高速度只有400Kbit/S,所以在中断期间通过IIC读取数据会浪费时间。我们可以暂时执行通过IIC读取触摸数据的操作,中断处理函数只相应中断,然后清除中断标志位。这时候中断处理进程分为两部分:上半部分:上半部分是中断处理函数,那些比较快的,不会花很长时间的进程都可以在上半部分完成。第二部分:如果中断处理过程比较耗时,那么就把这些耗时的代码前移,交给第二部分执行,这样中断处理函数就会快进快出。所以在Linux内核中把中断分成上半部分和下半部分的主要目的是实现中断处理函数的快进快出,那些时间敏感、执行速度快的操作可以放到中断处理函数中,也就是上半部分。剩下的工作都可以在下半部分进行,比如把数据复制到上半部分的内存中,数据的具体处理可以在下半部分进行。至于哪些码属于上半部分,哪些码属于下半部分,没有明确的规定。一切根据实际使用情况来判断,这将考验司机们的基本功。以下是一些参考点,供参考:①如果要处理的内容不希望被其他中断打断,可以放在上半部分。②如果要处理的任务有时效性,可以放在上半部分。③如果要处理的任务与硬件有关,可以放在上半部分。④除以上三点外的其他工作,下半部分优先考虑。下部机构:
起初,Linux内核提供了“下半部分”机制来实现后半部分,简称“BH”。后来引入了软中断和小任务来代替“BH”机制,完全可以代替BH。从2.5版本的Linux内核开始,BH就被抛弃了。Linux内核使用结构softirq_action来表示软中断。在include/linux/interrupt.h文件中定义了softirq_action结构,内容如下:在kernel/softirq.c文件中定义了10个软中断。如下图:NR_SOFTIRQS是一个枚举类型,在文件include/linux/interrupt.h中定义,定义如下:可以看出有10个软中断,所以NR_SOFTIRQS是10,所以数组softirq_vec有10个元素。softirq_action结构中的action成员变量是软中断的服务函数。数组softirq_vec是一个全局数组,因此所有CPU(对于SMP系统)都可以访问它。每个CPU都有自己的触发和控制机制,只执行自己触发的软中断。但是每个CPU执行的软中断服务函数是一样的,都是数组softirq_vec中定义的动作函数。要使用软中断,必须先用open_softirq函数注册相应的软中断处理函数。open_softirq函数的原型如下:nr:要开启的软中断,从样本代码51.1.2.3中选择一个。动作:软中断对应的处理功能。返回值:没有返回值。注册软中断后,需要通过raise_softirq函数触发。raise_softirq函数的原型如下:编译时必须静态注册软中断!Linux内核使用softirq_init函数来初始化软中断。在kernel/softirq.c文件中定义了softirq_init函数,其内容如下:
Tasklet是另一种由软中断实现的下半部分机制。在软中断和微线程之间,推荐你用微线程。Linux内核使用structure-17——第489行的func函数是要由tasklet执行的处理函数。用户定义功能的内容,相当于中断处理功能。如果要使用tasklet,首先要定义一个tasklet,然后用tasklet_init函数初始化它。tasklet LED _ init函数的原型如下:1函数参数和返回值的含义如下:T:Tasklet func:Tasklet initialized:Tasklet的处理函数。数据:要传递给func函数的参数返回值:无返回值。您也可以使用宏DECLARE_TASKLET来定义和初始化TASKLET。DECLARE_tasklet是在include/linux/interrupt.h文件中定义的,定义如下:其中name是要定义的TASKLET的名称,是tasklet_struct类型的时间变量,func是TASKLET的处理函数,data是传递给func函数的参数。调用上部的tasklet_schedule函数,也就是中断处理函数,可以让tasklet在合适的时间运行。tasklet_schedule函数的原型如下:关于tasklet的参考使用示例如下:2
工作队列是执行模式的另一个较低的部分。工作队列在进程上下文中执行,工作队列将延迟的工作交给内核线程执行。因为工作队列在进程上下文中工作,所以允许工作队列休眠或重新调度。所以,如果想推迟上班睡觉,可以选择上班排队;否则只能选择软中断或者小任务。Linux内核用work_struct结构表示一个作业,内容如下(条件编译略):这些作业被组织成工作队列,用workqueue_struct结构表示。内容如下(省略条件编译):Linux内核使用worker线程处理工作队列中的所有作业,Linux内核使用worker结构表示worker线程。worker结构的内容如下:从示例代码51.1.2.10可以看出,每个worker都有一个工作队列,worker线程处理自己工作队列中的所有作业。在实际的驱动开发中,我们只需要定义work (work_struct),基本不用担心工作队列和工作线程。创建一个简单的作业非常简单。直接定义一个work_struct结构变量,然后用INIT_WORK宏初始化作业就可以了。INIT_WORK宏的定义如下:28
1.3设备树中断信息节点如果使用设备树,需要在设备树中设置中断属性信息。Linux内核通过读取设备树中的中断属性信息来配置中断。对于中断控制器,设备树绑定信息参考文档documentation/device tree/bindings/arm/gic . txt,打开文件imx6ull.dtsi,其中intc节点为I.MX6ULL的中断控制器节点,节点内容如下: line 2,兼容属性的值为“arm,cortex-aGIC”。在Linux内核源代码中搜索“arm,cortex-agic”,找到gic中断控制器驱动文件。第3行,#中断单元与#地址单元和#大小单元相同。指示该中断控制器下设备的单元大小。对于设备,中断信息将通过使用中断属性来描述。#interrupt-cells描述中断属性的信元大小,即一条消息中有多少个信元。每个单元格都是一个32位整数值。对于由ARM处理的GIC,总共有三个单元。这三个单元的含义如下:第一个单元:中断类型,0表示SPI中断,1表示PPI中断。第二个单元:中断号,SPI中断从0到987,PPI中断从0到15。第三个单元:标志,位线4,中断描述了中断源信息。对于gpio5,有两条信息,中断类型为SPI,触发电平为IRQ_TYPE_LEVEL_HIGH。区别在于中断源,一个是74,一个是75。打开IMX6ULL参考手册第三章中断和DMA事件,找到表1,其内容如图50.1.3.1: 从图50.1.3.1可以看出GPIO5使用了两个中断号,一个是74,一个是75。其中74对应GPIO5_IO00~GPIO5_IO15的低16 IO,75对应GPIO 5 _ IO16 ~ GPIO 5 _ IO31的高16 IO。在第8行,中断控制器指示gpio5节点也是一个中断控制器,用于控制gpio5所有io的中断。第9行,将#interrupt-cells改为2。打开imx6ull-alientek-emmc.dts文件,找到以下内容:34
1.4获取中断号。写驱动的时候,需要用到中断号。我们使用中断号,中断信息已经写入设备树。因此,可以通过irq_of_parse_and_map函数从interupts属性中提取相应的设备号。函数的原型如下:函数参数和返回值的含义如下:dev:设备节点。索引:索引号。中断属性可能包含多条中断信息。指定要通过索引获取的信息。返回值:中断号。如果使用gpio,gpio_to_irq函数可用于获取gpio对应的中断号。函数的原型如下:
2.驱动代码# include ;curkeynum = 0;/*对于传递给定时器的参数,注意强转,然后在中断处理函数*/dev-->中转回;timer . data =(volatile long)dev _ id;/* mod_timer会启动定时器,第二个参数是要修改的超时*/mod _ timer(& dev-& gt;timer,jiffies+msecs _ to _ jiffies(10));返回IRQ _ RETVAL(IRQ _ HANDLED);}/* @描述:定时服务功能,用来摇键。定时器到期后,再次读取*密钥值。如果该键仍被按下,则表示该键有效。* @ param–arg:设备结构变量* @ return:none */void timer _ function(unsigned long arg){ unsigned char value;无符号字符数;struct irq _ keydesc * keydescstruct imx 6 uirq _ dev * dev =(struct imx 6 uirq _ dev *)arg;/*因为只有一个键,所以这里是0 */num = dev->;curkeynumkeydesc = & ampdev-& gt;irqkeydesc[num];value = gpio _ get _ value(key desc-& gt;gpio);/*读取IO值*/ if(value == 0){ /*按键*/atomic _ set(& dev-& gt;keyvalue,key desc-& gt;值);} else{ /* key release */*这种情况下是按下后释放。如果使用keyValue加releaseKey */ /*,则releaseKey始终为0 */atomic _ set(& dev-& gt;keyvalue,0x 80 | key desc-& gt;值);原子集(& ampdev-& gt;releasekey,1);/* mark release key */} }/* @ descriptIOn:key io初始化* @ param:none * @ return:none */static int key io _ init(void){ unsigned char I = 0;int ret = 0;/* 1.获取key node */irqdev . nd = of _ find _ node _ by _ path("/key & quot;);if(irqdev . nd = = NULL){ printk(& quot;找不到关键节点!\ r \ n & quot);return-EINVAL;}/*为每个键选择GPIO */(I = 0;我& ltKEY _ NUMi++) { irqDev.irqkeydesc[i]。gpio = of _ get _ named _ gpio(irqdev . nd,& quotkey-gpio & quot;,我);if (irqDev.irqkeydesc[i])。gpio & lt0){ printk(& quot;能& # 39;t获取密钥% d \ r \ n & quot,我);}}/*初始化key使用的IO并设置为中断模式*/for(I = 0;我& ltKEY _ NUM++) {/*首先命名每个IO */*通过0 */memset (irqdev)清除名称。irqkeydesc [i]。名称,0,sizeof (irqdev。irqkeydesc [i]。姓名));/* name IO */sprintf(irqdev . irqkeydesc[I].名称,"密钥% d & quot,我);/*请求gpio */gpio _ request(irqdev . irqkeydesc[I])。gpio,irqdev.irqkeydesc [i]。姓名);/*设置GPIO为输入*/GPIO _ Direction _ Input (irqdev。irqkeydesc [I]。GPIO);/*获取中断号,下面是两个方法,都可以获取*//*从interrupts属性中获取*/*注意I和它在设备树中设置的一样多,就会获取*//*下面的方法是获取中断号的通用函数*/irqdev.irqkeydesc [i]。IRQ num = IRQ _ of _ parse _ and _ map(irqdev . nd,I);#if 0 /*这个方法就是gpio获取中断号的方式*/irqdev.irqkeydesc [i]。IRQ num = gpio _ to _ IRQ(irqdev . irqkeydesc[I])。gpio);# endif printk(& quot;密钥%d:gpio=%d,irqnum = % d \ r \ n & quot,I,irqDev.irqkeydesc[i]。gpio,irqDev.irqkeydesc[i]。IRQ num);} /* 2.Key中断初始化*/*设置中断处理函数和key初始值*/*因为只有一个key0。,这里没有循环*/irqdev.irqkeydesc [0]。handler = key0 _ handlerirqDev.irqkeydesc[0]。value = KEY0VALUE/*申请中断*/for(I = 0;我& ltKEY _ NUM++) {/* request _ IRQ参数*中断号、中断函数、中断触发类型、中断名称、传递给中断处理函数的参数(第二个)、传递的结构* */ret = request _ IRQ (irqdev。irqkeydesc [i]。irqnum,irqdev。irqkeydesc [i]。处理程序,IRQF _ TRIGGER _ FALLING | IRQF _ TRIGGER _ RISING,irqDev.irqkeydesc[i]。姓名& ampirqDev);if(ret & lt;0){ printk(& quot;irq %d请求失败!\ r \ n & quot,irqDev.irqkeydesc[i]。IRQ num);返回-默认;}} /* 3.创建timer */init _ timer(& irqdev . timer);irqdev . timer . function = timer _ function;/*注意不能让下面的计时器运行,因为运行之前要按键*/*启动计时器。定时器由mod_timer启动,通常初始化阶段的定时器使用add _ timer */return 0;} static int imx 6 uirq _ open(struct inode * inode,struct file * filp){ filp-& gt;私有数据= & ampirqDev返回0;} static int imx 6 uirq _ release(struct inode * inode,struct file * filp){//struct imx 6 uirq _ dev * dev =(struct imx 6 uirq _ dev *)filp-& gt;private _ data返回0;} /** @description:从设备中读取数据* @ param–filp:要打开的设备文件(文件描述符)* @ param–buf:返回到用户空间的数据缓冲区* @param-cnt:要读取的数据长度* @ param–offt:从文件首地址的偏移量* @return:读取的字节数,如果为负数,则表示读取失败*/static size _ timx 6 uirq _ read(struct file * filp,char _ _ user * buf,size _ tcnt,loft _ t * off)无符号字符key value = 0;/* key值*/unsigned char release key = 0;/*标记是否一次完成*/structimx 6 uirq _ dev * dev =(structimx 6 uirq _ dev *)filp->;private _ datakey value = atomic _ read(& amp;dev-& gt;key value);releasekey = atomic_read。dev-& gt;release key);If (releasekey) {/*有一个键被按下*/if(key value & 0x 80){ key value & amp;= ~ 0x80/*因为中断中有一个0x80,所以从其中去掉0x 80 */ret = copy _ to _ user(BUF,& keyvalue,sizeof(key value));} else { goto data _ error} atomic _ set(& amp;dev-& gt;releasekey,0);/*按标志清除*/}否则{/*未按*/goto data _ error;}返回0;data _ error:return-EINVAL;}/*字符设备操作集*/static const struct file _ operations imx 6 uirq _ fops = {。owner = this _ module,。open = imx6uirq _ open,。release = imx6uirq _ release,。read = imx 6 uirq _ read };/*模块入口函数*/static int _ _ initimx 6 uirq _ init(void){/*定义了一些必需的变量*/int ret = 0;/* 1.注册字符设备驱动*/irqdev . major = 0;if(irqdev . major){ irqdev . devid = MKDEV(irqdev . major,0);ret = register _ chr dev _ region(irqdev . devid,IMX6UIRQ_CNT,imx 6 uirq _ NAME);} else { alloc _ chr dev _ region(& amp;irqDev.devid,0,IMX6UIRQ_CNT,imx 6 uirq _ NAME);irqdev . MAJOR = MAJOR(irqdev . devid);irqdev . MINOR = MINOR(irqdev . devid);} if(ret & lt;0){ goto fail _ devid;} printk(& quot;让devid成功!\ r \ n & quot);printk(& quot;主要= %d,次要= % d \ r \ n & quot、irqDev.major、irqdev . minor);/* 2.初始化cdev */irqdev . cdev . owner = this _ module;cdev _ init(& amp;irqdev . cdev & amp;imx 6 uirq _ fops);ret = cdev _ add(& amp;irqDev.cdev,irqDev.devid,imx 6 uirq _ CNT);if(ret & lt;0){ goto fail _ cdev;} else { printk(& quot;Cdev添加成功!\ r \ n & quot);} /* 3.自动创建一个设备节点*/irqdev . class = class _ create(this _ module,imx 6 uirq _ name);if(IS _ ERR(irqdev . class)){ ret = PTR _ ERR(irqdev . class);goto fail _ class} else { printk(& quot;类创建成功!\ r \ n & quot);} irqdev . device = device _ create(irqdev . class,NULL,irqDev.devid,NULL,imx 6 uirq _ NAME);if(IS _ ERR(irqdev . device)){ ret = PTR _ ERR(irqdev . device);goto fail _ device} else { printk(& quot;设备创建成功!\ r \ n & quot);} /* 4.初始化key */atomic _ set(& irqdev . key value,inva key);原子集(& ampirqDev.releasekey,0);keyio _ init();printk(& quot;irqDev初始化!\ r \ n & quot);返回0;/*错误处理*/fail _ device:class _ destroy(irqdev . class);cdev _ del(& amp;irqdev . cdev);fail _ cdev:unregister _ chr dev _ region(irqdev . devid,imx 6 uirq _ CNT);fail _ devid:ret ret;}/*模块出口函数*/static void _ _ exit imx 6 uirq _ exit(void){ unsigned int I = 0;/*删除计时器*/del _ timer _ sync(& irqdev . timer);/*释放中断*/for(I = 0;我& ltKEY _ NUMi++){ free _ IRQ(irqdev . irqkeydesc[I].IRQ num & amp;irqDev);} /* 1.释放设备号*/cdev _ del(& irqdev . cdev);/* 2.注销设备号*/unregister _ chr dev _ region(irqdev . devid,imx 6 uirq _ CNT);/* 3.销毁设备*/device _ destroy (irqdev.class,irqdev . devid);/* 4.销毁类*/class _ destroy(irqdev . class);printk(& quot;irqDev出口!\ r \ n & quot);}/*模块进出注册*/Module _ init(imx 6 uirq _ init);module _ exit(imx 6 uirq _ exit);模块许可证(& quotGPL & quot);模块作者(& quot邵哲明& quot);3.应用程序代码# include < sys/types . h & gt;# include & ltsys/stat . h & gt;# include & ltfcntl.h & gt# include & ltstdio.h & gt# include & ltunistd.h & gt# include & ltstdlib.h & gt# include & ltstring.h & gt#包含& quotLinux/ioctl . h & quot;/* * argc:应用参数个数* argv[]:参数有哪些?具体参数,表示参数是字符串*的形式。chrdevbaseapp & lt0:1 & gt;0表示关灯,1表示开灯*。chrdevbaseApp /dev/led 0关灯*chrdevbaseApp /dev/led 1开灯* */int main (intargc,char * argv []) {if (argc!= 2){ printf(& quot;错误用法!\ r \ n & quot);return-1;} int fd,retchar *文件名;无符号字符数据;filename = argv[1];fd = open(文件名,O _ RDWR);if(FD & lt;0){ printf(& quot;文件%s打开失败!\ r \ n & quot,文件名);return-1;} while (1) { ret = read(fd,& ampdata,sizeof(data));if(ret & lt;0) {/*错误或无效的数据读取*/}否则{/*正确的数据读取*/ if (data) /*读取数据*/printf(& quot;键值= % # X \ r \ n & quot,数据);} }关闭(FD);返回0;}4.使用tasklet处理中断# include的后半部分< sys/types . h & gt;# include & ltsys/stat . h & gt;# include & ltfcntl.h & gt# include & ltstdio.h & gt# include & ltunistd.h & gt# include & ltstdlib.h & gt# include & ltstring.h & gt#包含& quotLinux/ioctl . h & quot;/* * argc:应用参数个数* argv[]:参数有哪些?具体参数,表示参数是字符串*的形式。chrdevbaseapp & lt0:1 & gt;0表示关灯,1表示开灯*。chrdevbaseApp /dev/led 0关灯*chrdevbaseApp /dev/led 1开灯* */int main (intargc,char * argv []) {if (argc!= 2){ printf(& quot;错误用法!\ r \ n & quot);return-1;} int fd,retchar *文件名;无符号字符数据;filename = argv[1];fd = open(文件名,O _ RDWR);if(FD & lt;0){ printf(& quot;文件%s打开失败!\ r \ n & quot,文件名);return-1;} while (1) { ret = read(fd,& ampdata,sizeof(data));if(ret & lt;0) {/*错误或无效的数据读取*/}否则{/*正确的数据读取*/ if (data) /*读取数据*/printf(& quot;键值= % # X \ r \ n & quot,数据);} }关闭(FD);返回0;}5.工作队列处理后半部分的开发模式与tasklet相同。注意工作可以推导出设备的dev结构,所以工作一般放在dev结构里。
推荐:《linux视频教程》以上是Linux内核是否有中断功能的详细内容。请多关注主机参考其他相关文章!
这几篇文章你可能也喜欢:
- 在Linux上操作vi编辑器(Linux上的vi编辑器命令)
- Mondoze:住宅IP/原生IP/IDC IP,VPS低至$8.33/马来西亚服务器/AS152742/11.11促销
- torchbyte 罗马尼亚 VPS 起价为 20 美元/年,AMD Ryzen9+ NVMe 硬盘,免费 DDoS 防护
- zlidc(智联IDC):韩国原生IP云服务器,35.9美元/季度,4核/4G内存/50G SSD/300M优质网络@2.5T月流量
- 椰草云双11活动:香港云服务器81元/年,香港实体服务器199元/月(香港云服务商)
本文由主机参考刊发,转载请注明:linux内核有中断功能吗(linux中断原理) https://zhujicankao.com/87438.html
评论前必须登录!
注册