日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

linux中断处理汇编入口,Linux中断处理体系结构分析(一)

發布時間:2023/12/4 linux 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 linux中断处理汇编入口,Linux中断处理体系结构分析(一) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

中斷也是一種異常,之所以把它單獨的列出來,是因為中斷的處理與具體的開發板密切相關,除一些必須、共用的中斷(比如系統時鐘中斷、片內外設UART中斷)外,必須由驅動開發者提供處理函數。內核提煉出中斷處理的共性,搭建一個非常容易擴充的中斷處理體系。

init_IRQ函數(代碼在arch/arm/kernel/irq.c中)被用來初始化中斷和處理框架,設置各種中斷的默認處理函數。當發生中斷時,中斷總入口函數asm_do_IRQ就可以調用這些函數進行下一步處理。

Linux中斷處理體系結構分析(二)

(2010-06-10 21:08)

1.中斷處理的體系結構

我們知道編寫設備驅動程序一定要用到中斷處理函數,這在驅動程序的編寫中,占據很重要的一部分。在響應一個特定的中斷的時候,內核會執行一個函數,該函數

叫做中斷處理程序(interrupt handler)或中斷服務例程(interrupt service routine

,ISP).產生中斷的每個設備都有一個相應的中斷處理程序,中斷處理程序通常不和特定的設備關聯,而是和特定的中斷關聯的,也就是說,如果一個設備可以

產生多種不同的中斷,那么該就可以對應多個中斷處理程序,相應的,該設備的驅動程序也就要準備多個這樣的函數。在Linux內核中處理中斷是分為上半部

(top half),和下半部(bottom

half)之分的。上半部只做有嚴格時限的工作,例如對接收到的中斷進行應答或復位硬件,這些工作是在所有的中斷被禁止的

情況下完成的,能夠被允許稍后完成的工作會推遲到下半部去。要想了解上半部和下半部的機制可以閱讀一下《Linux內核設計與實現》的第七章的內容。

Linux內核將所有的中斷統一編號,使用一個irq_desc結構數組來描述這些中斷;每個數組項對應一個中斷,也可能是一組中斷,它們共用相同的中斷號,里面記錄了中斷的名稱、中斷狀態、中斷標記(比如中斷類型、是否共享中斷等),并提供了中斷的低層硬件訪問函數(清除、屏蔽、使能中斷),提供了這個中斷的處理函數入口,通過它可以調用用戶注冊的中斷處理函數。

通過irq_desc結構數組就可以了解中斷處理體系結構,irq_desc結構的數據類型include/linux/irq.h

中定義,

struct irq_desc {

unsigned int????????irq;

struct timer_rand_state *timer_rand_state;

unsigned int *kstat_irqs;

#ifdef CONFIG_INTR_REMAP

struct irq_2_iommu *irq_2_iommu;

#endif

irq_flow_handler_t????handle_irq; // 當前中斷的處理函數入口

struct irq_chip????????*chip; //低層的硬件訪問

struct msi_desc????????*msi_desc;

void????????????*handler_data;

void????????????*chip_data;

struct irqaction????*action;????// 用戶提供的中斷處理函數鏈表

unsigned int????????status;????????//IRQ狀態

........

const char????????*name; //中斷的名稱

} ____cacheline_internodealigned_in_smp;

handle_irq是這個或這組中斷的處理函數入口。發生中斷時,總入口函數asm_do_IRQ將根據中斷號調用相應irq_desc數組項中

handle_irq.handle_irq使用chip結構中的函數清除、屏蔽或者重新使能中斷,還要調用用戶在action鏈表中注冊的中斷處理函

數。

irq_chip結構類型也是在include/linux/irq.h中定義,其中的成員大多用于操作底層硬件,比如設置寄存器以屏蔽中斷,使能中斷,清除中斷等。

struct irq_chip {

const char????*name;

unsigned int????(*startup)(unsigned int irq);//啟動中斷,如果不設置,缺省為“enable

void????????(*shutdown)(unsigned int irq);/*關閉中斷,如果不設置,缺省為"disable"*/

void????????(*enable)(unsigned int irq);// 使用中斷,如果不設置,缺省為"unmask"

void????????(*disable)(unsigned int irq);//禁止中斷,如果不設置,缺省為“mask”

void????????(*ack)(unsigned int irq);/*響應中斷,通常是清除當前中斷使得可以接收下一個中斷*/

void????????(*mask)(unsigned int irq); //屏蔽中斷源

void????????(*mask_ack)(unsigned int irq);//屏蔽和響應中斷

void????????(*unmask)(unsigned int irq);//開啟中斷源

void????????(*eoi)(unsigned int irq);

........

const char????*typename;

};irq_desc結構中的irqaction結構類型在include/linux/iterrupt.h中定義。用戶注冊的每個中斷

處理函數用一個irqaction結構來表示,一個中斷比如共享中斷可以有多個處理函數,它們的irqaction結

構鏈接成一個鏈表,以action為表頭。irqation結構定義如下:

struct irqaction {

irq_handler_t handler; //用戶注冊的中斷處理函數

unsigned long flags; //中斷標志,比如是否共享中斷,電平觸發還是邊沿觸發

const char *name; //用戶注冊的中斷名字

void *dev_id; //用戶傳給上面的handler的參數,還可以用來區分共享中斷

struct irqaction *next; //指向下一個用戶注冊函數的指針

int irq; //中斷號

struct proc_dir_entry *dir;

irq_handler_t thread_fn;

struct task_struct *thread;

unsigned long thread_flags;

};?? irq_desc結構數組、它的成員“struct irq_chip *chip” "struct irqaction *action",這3種數據結構構成了中斷處理體系的框架。下圖中描述了Linxu中斷處理體系結構的關系圖:

中斷處理流程如下

(1)發生中斷時,CPU執行異常向量vector_irq的代碼

(2)在vector_irq里面,最終會調用中斷處理的總入口函數asm_do_IRQ

(3)asm_do_IRQ根據中斷號調用irq_desc數組項中的handle_irq。

(4)handle_irq會使用chip成員中的函數來設置硬件,比如清除中斷、禁止中斷、重新使能中斷等

(5)handle_irq逐個調用用戶在aciton鏈表中注冊的處理函數

中斷體系結構的初始化就是構造這些數據結構,比如irq_desc數組項中的handle_irq、chip等成員;用戶注冊中斷時就是構造action鏈表;用戶卸載中斷時就是從action鏈表中去除不需要的項。

2.中斷處理體系結構的初始化

init_IRQ函數被用來初始化中斷處理體系結構,代碼在arch/arm/kernel/irq.c中

153 void __init init_IRQ(void)

154 {

155 int irq;

156

157 for (irq = 0; irq < NR_IRQS; irq++)

158 irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;

159

160 init_arch_irq();

161 }157~~158行 初始化irq_desc結構數組中每一項的中斷狀態

160行調用架構相關的中斷初始化函數。對于S3C2440開發板,這個函數就是s3c24xx_init_irq,移植machine_desc結構中

的init_irq成員就指向這個函數s3c24xx_init_irq函數在arch/arm/plat-s3c24xx/irq.c中定義,它為所有

中斷設置了芯片相關的數據結構(irq_desc[irq].chip),設置了處理函數入口(irq_desc[irq].handle_irq)。以

外部中斷EINT4-EINT23為例,用來設置它們的代碼如下:

void __init s3c24xx_init_irq(void)

534 {

535 unsigned long pend;

536 unsigned long last;

537 int irqno;

538 int i;

........

637 for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {

638 irqdbf("registering irq %d (extended s3c irq)\n", irqno);

639 set_irq_chip(irqno, &s3c_irqext_chip);

640 set_irq_handler(irqno, handle_edge_irq);

641 set_irq_flags(irqno, IRQF_VALID);

...............655 for (irqno = IRQ_S3CUART_RX1; irqno <= IRQ_S3CUART_ERR1; irqno++) {

656 irqdbf("registering irq %d (s3c uart1 irq)\n", irqno);

657 set_irq_chip(irqno, &s3c_irq_uart1);

658 set_irq_handler(irqno, handle_level_irq);

659 set_irq_flags(irqno, IRQF_VALID);

660 }

..........

676 irqdbf("s3c2410: registered interrupt handlers\n");

677 }

678

在639行set_irq_chip函數的作用就是“irq_desc[irno].chip =

&s3c_irqext_chip”,以后就可能通過irq_desc[irqno].chip結構中的函數指針設置這些外部中斷的觸發方式(電

平觸發,邊沿觸發),使能中斷,禁止中斷。

在640行設置這些中斷的處理函數入口為handle_edge_irq,即“irq_desc[irqno].handle_irq

=handle_edge_irq”.發生中斷時,handle_edge_irq函數會調用用戶注冊的具體處理函數;?在641行設置中斷標志為

“IRQF_VALID”,表示可以使用它們。init_IRQ函數執行完后,irq_desc數組項的chip,handl_irq成員都被設置

2.2 用戶注冊中斷處理函數的過程

用戶驅動程序通過request_irq函數向內核注冊中斷處理函數,request_irq函數根據中斷號找到irq_desc數組項,然后在它的

action鏈表添加一個表項。原先的內核中requset_irq函數在kernel/irq/manage.c中定義,而現在2.6.32版本中,進

行了改變,可以看這篇文章http://eeek.borgchat.net/lists/newbies/msg39146.html,這里解釋了,在2.6.32內核中我們可以看到找不到了request_irq函數的實現,而是用request_threaded_irq()函數給替換了。我們可以在inclue/linux/interrupt.h中找到這個函數的原型。

110 #ifdef CONFIG_GENERIC_HARDIRQS

111 extern int __must_check

112 request_threaded_irq(unsigned int irq, irq_handler_t handler,

113 irq_handler_t thread_fn,

114 unsigned long flags, const char *name, void *dev);

115

116 static inline int __must_check

117 request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,

118 const char *name, void *dev)

119 {

120 return request_threaded_irq(irq, handler, NULL, flags, name, dev);

121 }

123 extern void exit_irq_thread(void);

124 #else

126 extern int __must_check

127 request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,

128 const char *name, void *dev);

136 static inline int __must_check

137 request_threaded_irq(unsigned int irq, irq_handler_t handler,

138 irq_handler_t thread_fn,

139 unsigned long flags, const char *name, void *dev)

140 {

141 return request_irq(irq, handler, flags, name, dev);

142 }

143

144 static inline void exit_irq_thread(void) { }

145 #endif其

實具體的實現在request_threaded_irq函數中,也是在/kernel/irq/manage.c中定

義,requset_threaded_irq函數首先使用這4個參數構造一個irqaction結構,然后調用setup_irq函數將它鏈入鏈表中,

1003 int request_threaded_irq(unsigned int irq, irq_handler_t handler,

1004 irq_handler_t thread_fn, unsigned long irqflags,

1005????????????????????????? const char *devname, void *dev_id)

.............

1056 action->handler = handler;

1057 action->thread_fn = thread_fn;

1058 action->flags = irqflags;

1059 action->name = devname;

1060 action->dev_id = dev_id;

1061

1062 chip_bus_lock(irq, desc);

1084 local_irq_restore(flags);

1085 enable_irq(irq);

...........

1088 return retval;

1089 }

1090 EXPORT_SYMBOL(request_threaded_irq);

setup_irq函數也是在kernel/irq.manage.c中定義,它完成如下3個主要功能

(1)將新建的irqaction結構鏈入irq_desc[irq]結構的action鏈表中,這有兩種可能。

果action鏈表為空,則直接鏈入,否則先判斷新建的irqaction結構和鏈表中的irqaction結構所表示的中斷類型是否一致,即是否都聲明

為"可共享的"(IRQF_SHARED)、是否都使用相同的觸發方式,如果一致,則將新建的irqation結構鏈入

(2)設置irq_desc[irq]結構中chip成員的還沒設置的指針,讓它們指向一些默認函數

chip成員在init_IRQ函數初始化中斷體系結構的時候已經設置了,這里只是設置其中還沒設置的指針這通過irq_chip_set_defaults函數來完成,它在kernel/irq/chip.c中定義

296 void irq_chip_set_defaults(struct irq_chip *chip)

297 {

298 if (!chip->enable)

299 chip->enable = default_enable;//調用chip->unmask

300 if (!chip->disable)

301 chip->disable = default_disable;//此函數為空

302 if (!chip->startup)

303 chip->startup = default_startup;//調用chip->enable

310 if (!chip->shutdown)

311 chip->shutdown = chip->disable != default_disable ?

312 chip->disable : default_shutdown;

313 if (!chip->name)

314 chip->name = chip->typename;

315 if (!chip->end)

316 chip->end = dummy_irq_chip.end;

317 }

(4)啟動中斷

如果irq_desc[irq]結構中status成員沒有被指明IRQ_NOAUTOEN(表示注冊中斷時不要使用中

斷),還要調用chip->startup或chip->enable來啟動中斷,所謂啟動中斷通常就是使用中斷。一般情況下,只有那些“可

以自動使能的”中斷對應的irq_desc[irq].status才會被指明為IRQ_NOAUTOEN,所以,無論哪種情況,執行

request_irq注冊中斷之后,這個中斷就已經被使能了。

總結一下request_irq函數注冊

(1)irq_des[irq]結構中的action鏈表中已經鏈入了用戶注冊的中斷處理函數

(2)中斷的觸發方式已經被設好

(3)中斷已經被使能

2.3 中斷的處理過程

asm_do_IRQ是中斷的C語言總入口函數,它在/arch/arm/kernel/irq.c中定義,

106 asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)

107 {

108 struct pt_regs *old_regs = set_irq_regs(regs);

109

110 irq_enter();

111

112 /*

113 * Some hardware gives randomly wrong interrupts. Rather

114 * than crashing, do something sensible.

115 */

116 if (unlikely(irq >= NR_IRQS)) {

117 if (printk_ratelimit())

118 printk(KERN_WARNING "Bad IRQ%u\n", irq);

119 ack_bad_irq(irq);

120 } else {

121 generic_handle_irq(irq);

122 }

123

124 /* AT91 specific workaround */

125 irq_finish(irq);

126

127 irq_exit();

128 set_irq_regs(old_regs);

129 }

desc_hand_irq函數直接調用desc結構中的hand_irq成員函數,它就是irq_desc[irq].handle.irq

asm_do_IRQ函數中參數irq的取值范圍為IRQ_EINT0~(IRQ_EINT0 + 31),只有32個取值。它可能是一個實際的中斷號,也可能是一組中斷的中斷號。這里有S3C2440的芯片特性決定的:發生中斷時,INTPND寄存器的某一位被置1,INTOFFSET寄存器中記錄了是哪一位(0--31),中斷向量調用asm_do_IRQ之前要把INTOFFSET寄存器的值確定irq參數。每一個實際的中斷在irq_desc數組中都有一項與它對應,它們的數目不止32.當asm_do_IRQ函數參數irq表示的是“一組”中斷時,irq_desc[irq].handle_irq成員函數還需要先分辨出是哪一個中斷,然后調用irq_desc[irqno].handle_irq來進一步處理。

以外部中斷EINT8—EINT23為例,它們通常是邊沿觸發

(1)它們被觸發里,INTOFFSET寄存器中的值都是5,asm_do_IRQ函數中參數irq的值為(IRQ_EINTO+5),即IRQ_EINT8t23,

(2)irq_desc[IRQ_EINT8t23].handle_irq在前面init_IRQ函數初始化中斷體系結構的時候被設為s3c_irq_demux_extint8.

(3)s3c_irq_demux_extint8函數的代碼在arch/arm/plat-s3c24xx/irq.c中,它首先讀取EINTPEND、EINTMASK寄存器,確定發生了哪些中斷,重新計算它們的中斷號,然后調用irq_desc數組項中的handle_irq成員函數

453 s3c_irq_demux_extint8(unsigned int irq,

454 struct irq_desc *desc)

455 {

456 unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND); //EINT8-EINT23 發生時,相應位被置1

457 unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);//屏蔽寄存器

458

459 eintpnd &= ~eintmsk; //清除被屏蔽的位

460 eintpnd &= ~0xff; /* 清除低8位(EINT8對應位8)ignore lower irqs */

461

462 /* 循環處理所有子中斷*/

463

464 while (eintpnd) {

465 irq = __ffs(eintpnd); //確定eintpnd中為1的最高位

466 eintpnd &= ~(1<

467

468 irq += (IRQ_EINT4 - 4);//重新計算中斷號,前面計算出irq等于8時,中斷號為

IRQ_EINT8

469 generic_handle_irq(irq);//調用這中斷的真正的處理函數

470 }

471

472 }

void

(4)IRQ_EINT8--IRQ_EINT23這幾個中斷的處理函數入口,在init_IRQ函數初始化中斷體系結構的時候已經被設置為handle_edge_irq函數,desc_handle_irq(irq,irq_desc+irq)就是調用這個函數,它在kernel/irq/chip.c中定義,它用來處理邊沿觸發的中斷,

中斷發生的次數統計

531 handle_edge_irq(unsigned int irq, struct irq_desc *desc)

532 {

533 spin_lock(&desc->lock);

534

535 desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);

536

537 /*

538 * If we're currently running this IRQ, or its disabled,

539 * we shouldn't process the IRQ. Mark it pending, handle

540 * the necessary masking and go out

541 */

542 if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) ||

543 !desc->action)) {

544 desc->status |= (IRQ_PENDING | IRQ_MASKED);

545 mask_ack_irq(desc, irq);

546 goto out_unlock;

547 }

548 kstat_incr_irqs_this_cpu(irq, desc);

549

550 /* Start handling the irq */

551 if (desc->chip->ack)

552 desc->chip->ack(irq);

553

554 /* Mark the IRQ currently in progress.*/

555 desc->status |= IRQ_INPROGRESS;

556

557 do {

558 struct irqaction *action = desc->action;

559 irqreturn_t action_ret;

560

561 if (unlikely(!action)) {

562 desc->chip->mask(irq);

563 goto out_unlock;

564 }

565

566 /*

567 * When another irq arrived while we were handling

568 * one, we could have masked the irq.

569 * Renable it, if it was not disabled in meantime.

570 */

571 if (unlikely((desc->status &

572 (IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) ==

573 (IRQ_PENDING | IRQ_MASKED))) {

574 desc->chip->unmask(irq);

575 desc->status &= ~IRQ_MASKED;

576 }

577

578 desc->status &= ~IRQ_PENDING;

579 spin_unlock(&desc->lock);

580 action_ret = handle_IRQ_event(irq, action);

581 if (!noirqdebug)

582 note_interrupt(irq, desc, action_ret);

583 spin_lock(&desc->lock);

584

585 } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);

586

587 desc->status &= ~IRQ_INPROGRESS;

588 out_unlock:

589 spin_unlock(&desc->lock);

590 }

591

響應中斷,通常是清除當前中斷使得可以接收下一個中斷,對于IRQ_EINT8~IRQ_EINT23這幾個中斷,desc->chip在前面init_IRQ函數初始化中斷體系結構的時候被設為s3c_irqext_chip.desc->chip->ack就是s3c_irqext_ack函數,(arch/armplat-s3c24xx/irq.c)它用來清除中斷

handle_IRQ_event函數來逐個執行action鏈表中用戶注冊的中斷處理函數,它在kernel/irq/handle.c中定義。

do {

379 trace_irq_handler_entry(irq, action);

380 ret = action->handler(irq, action->dev_id);//執行用戶注冊的中斷處理函數

381 trace_irq_handler_exit(irq, action, ret);

382

383 switch (ret) {

384 case IRQ_WAKE_THREAD:

385 /*

386 * Set result to handled so the spurious check

387 * does not trigger.

388 */

389 ret = IRQ_HANDLED;

390

391 /*

392 * Catch drivers which return WAKE_THREAD but

393 * did not set up a thread function

394 */

395 if (unlikely(!action->thread_fn)) {

396 warn_no_thread(irq, action);

397 break;

398 }

399

400 /*

408 if (likely(!test_bit(IRQTF_DIED,

409 &action->thread_flags))) {

410 set_bit(IRQTF_RUNTHREAD, &action->thread_flags);

411 wake_up_process(action->thread);

412 }

413

414 /* Fall through to add to randomness */

415 case IRQ_HANDLED:

416 status |= action->flags;

417 break;

418

419 default:

420 break;

421 }

422

423 retval |= ret;

424 action = action->next; //下一個

425 } while (action);

用戶注冊的中斷處理函數的參數為中斷號irq,action->dev_id。后一個參數是通過request_irq函數注冊中斷時傳入的dev_id參數,它由用戶自己指定、自己使用,可以為空,當這個中斷是“共享中斷”時除外。

對于電平觸發的中斷,它們的irq_desc[irq].handle_irq通常是handle_level_irq函數。它也是在kernel/irq/chip.c中定義,其功能與上述handle_edge_irq函數相似,

對于handle_level_irq函數已經清除了中斷,但是它只限于清除SoC內部的的信號,如果外設輸入到SoC的中斷信號仍然有效,這就會導致當前中斷處理完成后,會誤認為再次發生了中斷,對于這種情況,需要用戶注冊的中斷處理函數中清除中斷,先清除外設的中斷,然后再清除SoC內部的中斷號。

中斷的處理流程可以總結如下

(1)中斷向量調用總入口函數asm_do_IRQ,傳入根據中斷號irq

(2)asm_do_IRQ函數根據中斷號irq調用irq_desc[irq].handle_irq,它是這個中斷的處理函數入口,對于電平觸發的中斷,這個入口函數通常為handle_level_irq,對于邊沿觸發的中斷,這個入口通常為handle_edge_irq

(3)入口函數首先清除中斷,入口函數是handle_level_irq時還要屏蔽中斷

(4)逐個調用用戶在irq_desc[irq].aciton鏈表中注冊的中斷處理函數

(5)入口函數是handle_level_irq時還要重新開啟中斷

卸載中斷處理函數這通過free_irq函數來實現,它與request_irq一樣,也是在kernel/irq/mangage.c中定義。

它需要用到兩個參數:irq和dev_id,它們與通過request_irq注冊中斷函數時使用的參數一樣,使用中斷號irq定位action鏈表,再使用dev_id在action鏈表中找到要卸載的表項。同一個中斷的不同中斷處理函數必須使用不同的dev_id來區分,在注冊共享中斷時參數dev_id必惟一。

free_irq函數的處理過程與request_irq函數相反

(1)根據中斷號irq,dev_id從action鏈表中找到表項,將它移除

(2)如果它是惟一的表項,還要調用IRQ_DESC[IRQ].CHIP->SHUTDOWN或IRQ_DESC[IRQ].CHIP->DISABLW來關閉中斷。

在響應一個特定的中斷的時候,內核會執行一個函數,該函數叫做中斷處理程序(interrupt handler)或中斷服務例程(interrupt service routine ,ISP).產生中斷的每個設備都有一個相應的中斷處理程序,中斷處理程序通常不和特定的設備關聯,而是和特定的中斷關聯的,也就是說,如果一個設備可以產生多種不同的中斷,那么該就可以對應多個中斷處理程序,相應的,該設備的驅動程序也就要準備多個這樣的函數。在Linux內核中處理中斷是分為上半部(top

half),和下半部(bottom

half)之分的。上半部只做有嚴格時限的工作,例如對接收到的中斷進行應答或復位硬件,這些工作是在所有的中斷被禁止的情況下完成的,能夠被允許稍后完成的工作會推遲到下半部去。要想了解上半部和下半部的機制可以閱讀一下《Linux內核設計與實現》

這里主要是仿照《嵌入式Linux開發完全手冊》上的例子寫的,只是增加了別外兩個按按鍵。在我的mini2440開發板上有6個按鍵。在上兩篇文章中,主要分析了驅動中的整體的流程,現在來看一個具體的例子,是如何使用中斷的。

1. 模塊的初始化函數和卸載函數

/* 執行"insmod mini2440_buttons.ko"命令時就會調用這個函數*/

static int __init mini2440_buttons_init (void)

{

int ret;

/*

這里主要是注冊設備驅動程序,參數為主設備號,如果BUTTON_MAJOR設為0,表示由內核自動分配主設備號,設備的名

字,file_operations結構,操作主調和號為BUTTON_MAJOR的設備文件時,就會調用mini2440_buttons_fops中

的相關成員函數*/

ret = register_chrdev(BUTTON_MAJOR,DEVICE_NAME,&mini2440_buttons_fops);

if(ret < 0)

{

printk(DEVICE_NAME "can't register major number\n");

return ret ;

}

printk(DEVICE_NAME"initialized\n");

return 0;

}

/* 執行 rmmod mini2440_buttons.ko0 命令時就會調用這個函數 */

static void __exit mini2440_buttons_exit(void)

{//卸載驅動程序

unregister_chrdev(BUTTON_MAJOR,DEVICE_NAME);

}

//指定驅動程序的初始化函數和卸載函數

module_init(mini2440_buttons_init);

module_exit(mini2440_buttons_exit);下面這個結構體是每一個字符驅動程序都是要用到的。這里定義了應用程序可以使用的設備操作函數,只有在這個結構體中的函數,在應用程序中才可以使用,在下面的驅動程序中要實現下面的函數。

/* 這個結構是字符設備驅動程序的核心,當應用程序操作設備文件時所調用的open,read,write等函數,最終會調用這個結構中的對應函數*/

static struct file_operations mini2440_buttons_fops =

{

.owner = THIS_MODULE, //這是 個宏,指向編譯模塊時自動創建的_this_module變量

.open = mini2440_buttons_open,

.release = mini2440_buttons_close,

.read = mini2440_buttons_read,

};2. mini2440_buttons_open函數

在應用程序執行“open("/dev/buttons",..)"系統調用時,mini2440_buttons_open函數將被調用。這用來注冊6個按鍵的中斷處理程序

static int mini2440_buttons_open(struct inode *inode,struct file *file)

{

int i;

int err;

for (i=0;i

{ //注冊中斷處理函數 一共六個

err = request_irq(button_irqs[i].irq,buttons_interrupt,button_irqs[i].flags,button_irqs[i].name,(void *)&press_cnt[i]);

if (err)

break;

}

if(err) //出錯處理函數,如果出錯釋放已經注冊的中斷

{

i--;

for(;i>=0;i--)

free_irq(button_irqs[i].irq,(void *)&press_cnt[i]);

return -EBUSY;

}

return 0;

}requst_irq

函數執行成功后,這6個按鍵所用的GPIO引腳的功能被設為外部中斷,觸發方式為下降沿觸發,中斷處理函數為buttons_interrupt.最后一

個參數“(void *)&press_cnt[i]”將在buttons_interrupt函數中用到,它用來

存儲按鍵被按下的次數。參數button_irqs的定義如下:

struct button_irq_desc

{

int irq;//中斷號

unsigned long flags; //中斷標志,用來定義中斷的觸發方式

char *name; //中斷名稱

};

static struct button_irq_desc button_irqs[] =

{? //下面是按鍵對應的外部的中斷號,觸發方式,名稱

{IRQ_EINT8,IRQF_TRIGGER_FALLING,"KEY0"},

{IRQ_EINT11,IRQF_TRIGGER_FALLING,"KEY1"},

{IRQ_EINT13,IRQF_TRIGGER_FALLING,"KEY2"},

{IRQ_EINT14,IRQF_TRIGGER_FALLING,"KEY3"},

{IRQ_EINT15,IRQF_TRIGGER_FALLING,"KEY4"},

{IRQ_EINT19,IRQF_TRIGGER_FALLING,"KEY5"},

};3. mini2440_buttons_close函數

mini2440_buttons_close函數的作用是用來卸載6個按鍵的中斷處理函數代碼如下:

/* 應用程序對設備文件/dev/buttons執行close(...)時。就會調用mini2440_buttons_close函數*/

static int mini2440_buttons_close(struct inode *inode,struct file *file)

{

int i;

for(i=0;i

{ //釋放已注冊的函數

free_irq(button_irqs[i].irq,(void *)&press_cnt[i]);

}

return 0;

}4. mini2440_buttons_read函數

中斷處理函數會在press_cnt數組中記錄按鍵被按下的次數。mini_buttons_read函數,首先判斷是否按鍵再次按下,如果沒有則休眠;否則讀取press_cnt數組的數據,

/*等待隊列:

當沒有按鍵被按下時,如果有進程調用mini2440_buttons_read函數,它將休眠*/

static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

/*中斷事件標志,中斷服務程序將它置1,mini2440_buttons_read將它清0*/

static volatile int ev_press = 0;

/*應用程序對設備文件/dev/buttons執行read(...)時,就會調用mini2440_buttons_read函數*/

static int mini2440_buttons_read(struct file *filp,char __user *buff,size_t count,loff_t *offp)

{

unsigned long err;

//如果ev_press等于0,休眠

wait_event_interruptible(button_waitq,ev_press);

ev_press = 0;// 執行到這里是ev_press肯定是1,將它清0

//將按鍵狀態復制給用戶,并清0

err = copy_to_user(buff,(const void *)press_cnt,min(sizeof(press_cnt),count));

memset((void *)press_cnt,0,sizeof(press_cnt));

return err ? -EFAULT:0;

}???wait_event_interruptible首先會判斷ev_press是否為0,如果為0才會令當前進程進入休眠,否則向下繼續執行,它的第一個

參數,button_waitq是一個等待的隊列,在前面定義過,第二個參數ev_press用來表示中斷是否已經發生,中斷服務程序將它置1,如果

ev_press為0,當前進程進入休眠,中斷發生時中斷處理函數buttons_interrupt會把它喚醒。將press_cnt數組的內容復制到

用戶空間,buff參數表示的緩沖區位于用戶空間,使用copy_to_user向它賦值。

5.中斷處理函數buttons_interrupt

static irqreturn_t buttons_interrupt(int irq,void *dev_id)

{

volatile int *press_cnt = (volatile int *)dev_id;

*press_cnt = *press_cnt + 1; //按鍵計數加1

ev_press = 1; //表示中斷發生

wake_up_interruptible(&button_waitq); //喚醒休眠的進程

return IRQ_RETVAL (IRQ_HANDLED);

}

buttons_interrupt函數被調用時,第一個參數irq表示發生的中斷號,第二個參數dev_id就是前面使用request_irq注冊中斷時傳入的“&pres_cnt[i]”.

將mini2440_buttons.c放到內核源碼目錄drivers/char下,在drivers/char目錄下生成可加載模塊

mini2440_buttons.ko,把它放開開發板根文件系統的/lib/modules/2.6.22.6目錄下,就可以使用"insmod

mini2440_buttons"、“rmmod mini2440_buttons.ko”命令進行加載,卸載了。

6.測試程序

編寫的測試程序buttons_test.c,編譯后生成可執行文件,然后把它放到開發板根文件系統/usr/bin目錄下。在開發板根文件系統中建立設備文件。

#mknod /dev/buttons c 232 0然后使用“insmod mini2440_buttons”命令加載模塊。執行完這條命令后可以看到在控制臺中執行“cat /proc/devices”

[root@Frankzfz 2.6.32.2-FriendlyARM]$cat /proc/devices

Character devices:

1 mem

2 pty

3 ttyp

4 /dev/vc/0

4 tty

5 /dev/tty

5 /dev/console

5 /dev/ptmx

7 vcs

10 misc

13 input

14 sound

29 fb

89 i2c

90 mtd

116 alsa

128 ptm

136 pts

153 spi

180 usb

189 usb_device

204 s3c2410_serial

232 buttons? //主設備號 剛注冊的設備名

252 hidraw

253 ttySDIO

254 rtc

Block devices:

1 ramdisk

256 rfd

259 blkext

31 mtdblock

44 ftl

93 nftl

96 inftl

179 mmc運行測試程序button_test后,/dev/buttons設備就會被打開,可以使用“cat /proc/interrupts”命令看到注冊的6個中斷了。

[root@Frankzfz /mnt]$cat /proc/interrupts

CPU0

30: 157894 s3c S3C2410 Timer Tick

42: 0 s3c ohci_hcd:usb1

43: 8 s3c s3c2440-i2c

51: 9751 s3c-ext eth0

52: 3 s3c-ext KEY0

55: 1 s3c-ext KEY1

57: 2 s3c-ext KEY2

58: 15 s3c-ext KEY3

59: 75 s3c-ext KEY4

63: 10 s3c-ext KEY5?70: 848 s3c-uart0 s3c2440-uart

71: 1476 s3c-uart0 s3c2440-uart

83: 0 - s3c2410-wdt

Err: 0第一列表示中斷號,第二列表示這個中斷發生的次數,第三列的文字表示這個中斷的硬件訪問結構“struct irq_chip *chip”的名字。它在初始化中斷體系結構時指定,第四列文字表示中斷的名稱,它在request_irq中指定。

測試程序buttons_test.c如下

#include

#include

#include

int main(int argc, char **argv)

{

int i;

int ret;

int fd;

int press_cnt[4];

fd = open("/dev/buttons",0); // 打開設備

if (fd < 0) {

printf("Can't open /dev/buttons\n");

return -1;

}

// 這是個無限循環,進程有可能在read函數中休眠,當有按鍵被按下時,它才返回

while (1) {

// 讀出按鍵被按下的次數

ret = read(fd, press_cnt, sizeof(press_cnt));

if (ret < 0) {

printf("read err!\n");

continue;

}

for (i = 0; i < sizeof(press_cnt)/sizeof(press_cnt[0]); i++) {

// 如果被按下的次數不為0,打印出來

if (press_cnt[i])

}

}

close(fd);

return 0;

}在運行button_test時,可以以后臺運行在button_test &。在開發板上按下不同的按鍵可以在串口上看到以下的信息。

$K1 has been pressed 1

K2 has been pressed 1

K2 has been pressed 1

K3 has been pressed 1

K3 has been pressed 5

K3 has been pressed 2

K6 has been pressed 1

K4 has been pressed 1

K4 has been pressed 1

K4 has been pressed 1

K4 has been pressed 3

K4 has been pressed 7

K4 has been pressed 2

K4 has been pressed 5

K4 has been pressed 10

K4 has been pressed 5

K5 has been pressed 1

K5 has been pressed 1

K5 has been pressed 1

K5 has been pressed 1

[3] + Done(255) ./button_test?? 這只是一處簡單的測試程序,當有時按下一次時,也可能出現說是按下了10次,沒有很精確。如果以前沒有按鍵被按下則進行休眠狀態。

總結

以上是生活随笔為你收集整理的linux中断处理汇编入口,Linux中断处理体系结构分析(一)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。