日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

irq_desc操作

發布時間:2023/12/20 编程问答 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 irq_desc操作 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本博客轉載自:http://rock3.info/blog/2013/11/17/irq_desc數組的初始化過程/
這篇博客解析非常精辟,再次表達對原作者的敬意。

irq_desc[]數組是linux內核中用于維護IRQ資源的管理單元,它存儲了某IRQ號對應的哪些處理函數,屬于哪個PIC管理、來自哪個設備、IRQ自身的屬性、資源等,是內核中斷子系統的一個核心數組,習慣上稱其為“irq數組”(個人愛好,下標就irq號)。本篇博客著重學習irq_desc[]數組的一些操作的過程和方法,如初始化、中斷處理、中斷號申請、中斷線程等,而對于輔助性的8259A和APIC等設備的初始化過程,不詳細討論,對于某些圖片或代碼,也將其省略掉了。

本文中出現的irq_desc->和desc->均表示具體的irq數組變量,稱其中的一個個體為irq_desc[]數組元素,描述個體時也直接時用字符串desc。為了區別PIC的handle和driver的handle,將前者稱為中斷處理函數(對應desc->handle_irq,實際上對應handle_xxx_irq()),而將后者稱為中斷處理操作(對應desc->action)。本文中將以irq_descp[]數組為操作對象的層稱為irq層。本文使用的內核代碼版本為3.10.9。一篇好的博客應該是盡量多的說明,配少量的核心代碼,這里偷懶了,很多部分實際是代碼分析的過程,也沒有省略掉。本篇博客耗時48小時。

一、irq_desc結構和irq_desc[]數組
irq_desc[]數組,在kernel/irq/irqdesc.c中聲明,用于內核管理中斷請求,例如中斷請求來自哪個設備,使用什么函數處理,同步資源等:

struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
[0 … NR_IRQS-1] = {
.handle_irq = handle_bad_irq,
.depth = 1,
.lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
}
};
整體上,關于irq_desc結構體,如下圖所示:irq_desc

struct irq_desc結構體(以前的版本結構體的名字是irq_desc_t)定義如下所示(簡化過,include/linux/irqdesc.h)。大部分成員都是輔助性的,關鍵的成員是irq_data、handle_irqs、action、depth、lock、istat,所謂irq_desc[]數組的初始化,看其主要成員的初始化的過程,在這里做簡單的說明:

action指針指向具體的設備驅動提供的中斷處理操作,就是所為的ISR,action本身是一個單向鏈表結構體,由next指針指向下一個操作,因此action實際上是一個操作鏈,可以用于共享IRQ線的情況。
handle_irq是irq_desc結構中與PIC相關的中斷處理函數的接口,通常稱作”hard irq handler“。此函數對應了PIC中的handle_xxx_irq()系列函數(xxx代表觸發方式),do_IRQ()就會調用該函數,此函數最終會執行desc->action。
irq_data用于描述PIC方法使用的數據,irq_data下面有兩個比較重要的結構:chip和state_use_accessors,前者表示此irq_desc[]元素時用的PIC芯片類型,其中包含對該芯片的基本操作方法的指針;后者表示該chip的狀態和屬性,其中有些用于判斷irq_desc本身應該所處的狀態。
lock用于SMP下不同core下的同步。
depth表示中斷嵌套深度,也即一個中斷打斷了幾個其他中斷。
istate表示該desc目前的狀態,將在“六、istate狀態”中描述。

struct irq_desc {
struct irq_data irq_data;
irq_flow_handler_t handle_irq;

struct irqaction action; / IRQ action list /
unsigned int status_use_accessors;
unsigned int core_internal_state__do_not_mess_with_it;
unsigned int depth; / nested irq disables */
raw_spinlock_t lock;

struct module *owner;
const char *name;
} ____cacheline_internodealigned_in_smp;
這里還是看一下irqaction結構體,action的handler是具體的中斷服務程序,next指針用于指向同一個鏈上的后一個的irqaction,thread_fn用于描述軟中斷處理函數。

typedef irqreturn_t (*irq_handler_t)(int, void *);

struct irqaction {
irq_handler_t handler;
void *dev_id;
void __percpu *percpu_dev_id;
struct irqaction *next;
irq_handler_t thread_fn;
struct task_struct *thread;
unsigned int irq;
unsigned int flags;
unsigned long thread_flags;
unsigned long thread_mask;
const char *name;
struct proc_dir_entry *dir;
} ____cacheline_internodealigned_in_smp;
這意味著所有的驅動在寫中斷處理函數時,必須以irqreturn_t為類型:

// intel e1000
static irqreturn_t e1000_intr(int irq, void *data);
// acpi
static irqreturn_t acpi_irq(int irq, void *dev_id)
// hd
static irqreturn_t hd_interrupt(int irq, void *dev_id)
// ac97
static irqreturn_t atmel_ac97c_interrupt(int irq, void *dev)
在這里,很容易產生一個問題,就是驅動程序處理的數據在哪?總要有些數據要處理,是從void參數嗎?那么這個數據怎么獲取的?handle_irq_event_percpu()函數里有具體的action的調用方式:

1
res = action->handler(irq, action->dev_id);
那么,void *參數來自action->dev_id,而dev_id是驅動程序注冊時,調用request_irq()函數傳遞給內核的。而這個dev_id通常指向一個device設備,驅動程序就通過該device設備將需要的數據接收上來,并進行處理。

二、irq_desc[]的初始化——8259A
irq_desc[]數組是內核維護中斷請求資源的核心數組,它必須在合適的時機予以初始化。內核起動后,有步驟的初始化內核各個子系統,init_IRQ()函數主要負責完成內核中斷子系統的主要初始化。irq_desc[]數組伴隨著init_IRQ()函數的執行而完成其一部分的初始化。
init_IRQ()函數的調用路徑為main()->…->start_kernel()->init_IRQ()->native_init_IRQ()。init_IRQ()函數與irq_desc[]數組初始化或者IDT、interrupt[]數組的設置有關的函數或過程,關于init_IRQ的內部調用關系,如下圖所示:

init_IRQ

下面是具體的代碼分析過程:從init_IRQ()函數開始分析,init_IRQ在arch/x86/kernel/irqinit.c中定義:

void __init init_IRQ(void)
{
int i;

/* * We probably need a better place for this, but it works for* now ...*/ x86_add_irq_domains();/* * On cpu 0, Assign IRQ0_VECTOR..IRQ15_VECTOR's to IRQ 0..15.* If these IRQ's are handled by legacy interrupt-controllers like PIC,* then this configuration will likely be static after the boot. If* these IRQ's are handled by more mordern controllers like IO-APIC,* then this vector space can be freed and re-used dynamically as the* irq's migrate etc.*/ for (i = 0; i < legacy_pic->nr_legacy_irqs; i++)per_cpu(vector_irq, 0)[IRQ0_VECTOR + i] = i;x86_init.irqs.intr_init();

}
x86_add_irq_domains()直接略過。這里的注釋還時很有用的,這里說開始時使用8259A注冊這些中斷向量號,如果系統使用IO APIC,將覆蓋這些中斷向量號,并且能夠動態的重新使用。vector_irq為在arch/x86/include/asm/hw_irq.h中定義的per_cpu整形數組,長度為256,用于描述每個CPU的中斷向量號,即vector_irq[](vector_irq[]元素初始化時被賦值為-1)中存儲著系統可以使用的中斷向量號。這里需要注意,vector_irq[]數組時PER_CPU的。

legacy_pic字面意思為“遺留的PIC”,就是指8259A,legacy_pic定義在arch/x86/kernel/i8259.c,其中NR_IRQS_LEGACY為16:

struct legacy_pic default_legacy_pic = {
.nr_legacy_irqs = NR_IRQS_LEGACY,
.chip = &i8259A_chip,
.mask = mask_8259A_irq,
.unmask = unmask_8259A_irq,
.mask_all = mask_8259A,
.restore_mask = unmask_8259A,
.init = init_8259A,
.irq_pending = i8259A_irq_pending,
.make_irq = make_8259A_irq,
};

struct legacy_pic *legacy_pic = &default_legacy_pic;
a) native_inti_IRQ()
init_IRQ()將vector_irq[]逐個賦值(就賦值了16個,從0x30到0x39)。x86_init為x86架構初始化時的一個全局變量,記錄了各個子系統(irq,paging,timer,iommu,pci等)初始化使用的具體函數。而實際的x86_init.irqs.intr_init指針指向native_init_IRQ()函數(arch/x86/kernel/irqinit.c):

void __init native_init_IRQ(void)
{
int i;

/* Execute any quirks before the call gates are initialised: */ x86_init.irqs.pre_vector_init();apic_intr_init();/* * Cover the whole vector space, no vector can escape* us. (some of these will be overridden and become* 'special' SMP interrupts)*/ i = FIRST_EXTERNAL_VECTOR; for_each_clear_bit_from(i, used_vectors, NR_VECTORS) {/* IA32_SYSCALL_VECTOR could be used in trap_init already. */set_intr_gate(i, interrupt[i - FIRST_EXTERNAL_VECTOR]); } if (!acpi_ioapic && !of_ioapic)setup_irq(2, &irq2);

#ifdef CONFIG_X86_32
irq_ctx_init(smp_processor_id());
#endif
}
x86_init.irqs.pre_vector_init指針指向init_ISA_irqs()函數,主要完成8259A/Local APIC的初始化,apic_intr_init()函數主要完成apic相關的中斷的初始化。接著,native_init_IRQ()函數將調用set_intr_gate()函數設置中斷門,將interrupt[]數組設置的地址設置到相應的中斷門。注意,這里只是對沒有used_vectors進行set_intr_gate()的賦值,并不是從FIRST_EXTERNAL_VECTOR到NR_VECTORS全部賦值,因為有些特殊情況會預留(關于used_vectors和vector_irq的關系,詳見“七、中斷向量、鎖和CPU”)。余下的兩個接口處理了一些特殊情況,這里不展開了。
實際上init_IRQ()主要調用了native_init_IRQ(),除了使用set_intr_gate()來初始化Interrupt describptor外,后者主要干了兩件事:init_ISA_irqs()和apic_intr_init()。先從簡單的看起,apic_intr_init()函數實際上是一系列的set_intr_gate,但不通過interrupt[]數組,也不通過irq_desc[](這就是native_init_IRQ()函數中所為的“特殊情況”,屬于used_vectors的范圍):

static void __init apic_intr_init(void)
{
smp_intr_init();

#ifdef CONFIG_X86_THERMAL_VECTOR
alloc_intr_gate(THERMAL_APIC_VECTOR, thermal_interrupt);
#endif

#ifdef CONFIG_HAVE_KVM
/* IPI for KVM to deliver posted interrupt */
alloc_intr_gate(POSTED_INTR_VECTOR, kvm_posted_intr_ipi);
#endif

}
而smp_intr_init()函數如下執行apic_intr_intr()函數類似的操作,也通過set_intr_gate()函數設置了一些中斷門。

這些中斷門沒有通過interrupt數組,也沒有irq_desc數組,而是直接使用set_intr_gate()接口將其IDT中的中斷門描述符初始化。而這些中斷在/proc/interrupt中顯示比較特殊,并不以中斷向量號的形式顯示,而是以名字的形式,比如NMI,本身也不連接任何的PIC(截取一部分):

[rock3@e4310 linux-stable]$ cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3

44: 66 80 77 72 PCI-MSI-edge snd_hda_intel
45: 14948296 0 0 0 PCI-MSI-edge iwlwifi
NMI: 1539 19912 17314 17232 Non-maskable interrupts
LOC: 45133746 42836772 33584448 33666542 Local timer interrupts
SPU: 0 0 0 0 Spurious interrupts
PMI: 1539 19912 17314 17232 Performance monitoring interrupts
IWI: 641572 409182 330064 302186 IRQ work interrupts
然后看比較復雜的init_ISA_irqs()函數,代碼如下:

void __init init_ISA_irqs(void)
{
struct irq_chip *chip = legacy_pic->chip;
const char *name = chip->name;
int i;

#if defined(CONFIG_X86_64) || defined(CONFIG_X86_LOCAL_APIC)
init_bsp_APIC();
#endif
legacy_pic->init(0);

for (i = 0; i < legacy_pic->nr_legacy_irqs; i++)irq_set_chip_and_handler_name(i, chip, handle_level_irq, name);

}
legacy_pic->init指針指向init_8259A()函數,因此init_ISA_irqs執行了init_8259A(0)。irq_set_chip_and_handler_name()函數用于設置irq_desc[]數組的handle_irq、name、chip等成員。因此init_ISA_irqs()函數做了三件事:init_bsp_APIC()、init_8259A()、irq_set_chip_and_handler_name()。此時legacy_pic->nr_legacy_irqs為16。

init_bsp_APIC()為對Local APIC的某種初始化操作,與irq_desc[]數組初始化無關,不討論了。

init_8259A(0)為對8259A的某種初始化操作,與Irq_desc[]數組的初始化無關,不討論了。

irq_set_chip_and_handler_name()函數如下(kernel/irq/chip.c):

void
irq_set_chip_and_handler_name(unsigned int irq, struct irq_chip *chip,
irq_flow_handler_t handle, const char name)
{
irq_set_chip(irq, chip);
__irq_set_handler(irq, handle, 0, name);
}
irq_set_chip()將irq_descp[]數組的action的chip成員,主要是__irq_set_handler()函數(kernel/irq/chip.c),看下__irq_set_handler()函數都設置了irq_desc[]數組的什么成員:

void
__irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
const char *name)
{
unsigned long flags;
struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, 0);

if (!desc)return;if (!handle) {handle = handle_bad_irq; } else {if (WARN_ON(desc->irq_data.chip == &no_irq_chip))goto out; }/* Uninstall? */ if (handle == handle_bad_irq) {if (desc->irq_data.chip != &no_irq_chip)mask_ack_irq(desc);irq_state_set_disabled(desc);desc->depth = 1; } desc->handle_irq = handle; desc->name = name;if (handle != handle_bad_irq && is_chained) {irq_settings_set_noprobe(desc);irq_settings_set_norequest(desc);irq_settings_set_nothread(desc);irq_startup(desc, true); }

out:
irq_put_desc_busunlock(desc, flags);
}
主要就設置了兩個成員:handle_irq全部設置為handle_level_irq,name設置為“XT-PIC”(8259A)。而irq_desc[]數組中的handle_irq成員在do_IRQ()中被調用來執行具體的ISA。這個位置使用了buslock,也即desc->irq_data.chip->irq_bus_lock,而不是desc->lock。buslock用于中斷控制器的操作,desc->lock用于IRQ中斷處理函數的操作。

三、irq_desc[]的初始化——IO APIC
IO APIC的handle_irq通過setup_IO_APIC_irqs()函數初始化。調用過程是start_kernel()->rest_init()->kernel_thread(kernel_init)->kernel_init_freeable()->smp_init()->APIC_init_uniprocessor()->setup_IO_APIC()->setup_IO_APIC_irqs()->__io_apic_setup_irqs()->io_apic_setup_irq_pin()->setup_ioapic_irq()->ioapic_register_intr()->irq_set_chip_and_handler_name()。中間經過了太多的過程,其中主要的入口有setup_IO_APIC()用于初始化IO APIC,在初始化IO APIC的過程中完成了對IO APIC的irq_desc[]數組的初始化(setup_IO_APIC_irqs()),最終調用了irq_set_chip_and_handler_name()函數,完成了對irq_desc[]數組的初始化,其初始化desc->handle默認為handle_fasteoi_irq()或handle_edge_irq(),desc->name分別對應fasteoi或edge。

當然,這個過程在8259A調用irq_set_chip_and_handler_name()之后,那么根據__irq_set_handler()的實現,handle_irq可以更新,因此后注冊的IO APIC替代了先前的8259A。

這里還有個IRQ個數的問題,8259A初始化了16個irq_desc[]數組(0x30到0x39),而APIC應該時224個,但是實際上在setup_IO_APIC_irqs()函數執行時,輪詢了系統偵測到的所有的IO APIC,對每個IO APIC在__io_apic_setup_irqs()函數中,又輪詢該IO APIC上注冊的所有的設備,對于每個注冊者,執行io_apic_setup_irq_pin()->setup_ioapic_irq()->ioapic_register_intr()->irq_set_chip_and_handler_name()的過程,而對于每個IO APIC上的每個注冊者,對應的irq號,就通過pin_2_irq()接口確認。這意味著,IO APIC要將哪些irq_desc[]數組初始化。

IO APIC的hanle_irq有兩種,分別是handle_fasteoi_irq()和handle_edge_irq(),最終也都調用了handle_irq_event()->handle_irq_event_percpu(),在irq_desc[]初始化上與8259A一致,在“四、desc->handle_irq”部分會詳細分析。

這樣就存在一個問題,因為IO APIC并非每個irq_desc[]數組元素都去初始化,而是只初始化那些連接有設備的,那么如何能保證這些irq號就是驅動申請的irq號那?

四、desc->handle_irq
經過“irq_desc[]的初始化”部分的描述desc->handle_irq已經初始化完畢,而desc->handle_irq接口實際上可以掛接幾個函數(8259A和IO APIC):

void handle_level_irq(unsigned int irq, struct irq_desc *desc);(8259A)
void handle_fasteoi_irq(unsigned int irq, struct irq_desc *desc);(IO APIC)
void handle_edge_irq(unsigned int irq, struct irq_desc *desc);(IO APIC)
? 字面意思說明三種處理函數分別代表電平觸發、xxx觸發、邊沿觸發,他們之間各有不同但最終均調用了handle_irq_event()。本文中將以以上函數稱為handle_xxx_irq()系列函數(還有其他幾個handle_xxx_irq()函數,也屬于此系列,但是不是x86平臺時用或者不是8259A或IO APIC時用)。他們三者之間在何時屏蔽IRQ線、是否要響應發出中斷信號的硬件等處有微小的區別。

下面以handle_level_irq()函數為例,看下它具體干了什么事情,其他handle_xxx_irq()函數做了基本相同的工作:

void
handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
raw_spin_lock(&desc->lock);
mask_ack_irq(desc);

if (unlikely(irqd_irq_inprogress(&desc->irq_data)))if (!irq_check_poll(desc))goto out_unlock;desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING); kstat_incr_irqs_this_cpu(irq, desc);/** If its disabled or no action available* keep it masked and get out of here*/ if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {desc->istate |= IRQS_PENDING;goto out_unlock; }handle_irq_event(desc);cond_unmask_irq(desc);

out_unlock:
raw_spin_unlock(&desc->lock);
}
首先,raw_spin_lock先獲取鎖(關于desc->lock將在“七、中斷向量、鎖和CPU“部分詳細介紹),退出前釋放鎖,然后按照下列次序進行處理:

用mask_irq_ack()函數向產生中斷的硬件發出ACK響應,并暫時屏蔽該中斷線(handle_level_irq()獨有操作)。
用irqd_irq_inprogress()判斷中斷是否處于inprogress階段,如果處于inprogress階段,則校驗并等待“偽中斷”輪詢完畢(關閉“偽中斷”輪詢,詳見“六、istate狀態”)。
去掉desc->istate中的IRQS_REPLAY和IRQS_WAITING標志(詳見“六、istate狀態”)。
用kstat_incr_irqs_this_cpu()函數更新desc關于cpu的統計數據。
如果desc->action為空或者desc->irq_data處于DISABLE狀態,則將該irq_desc[]元素掛起(desc->istate置位IRQS_PENDING)并返回。
執行handle_irq_event(desc),循環調用desc->action。
用cond_unmask_irq()函數恢復IRQ線。
handle_irq_event()代碼如下(kernel/irq/handle.c):

irqreturn_t handle_irq_event(struct irq_desc *desc)
{
struct irqaction *action = desc->action;
irqreturn_t ret;

desc->istate &= ~IRQS_PENDING; irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS); raw_spin_unlock(&desc->lock);ret = handle_irq_event_percpu(desc, action);raw_spin_lock(&desc->lock); irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS); return ret;

}
handle_irq_event()函數首先將irq_desc[]數組的istate清除IRQ_PENDING標志,然后設置desc->irq_data->state_use_accessors增加IRQD_IRQ_INPROGRESS,然后執行handle_irq_event_percpu()函數,逐個cpu執行action,執行完畢后,清除desc->irq_data->state_use_accessors的IRQD_IRQ_INPROGRESS標志,說明IRQD_IRQ_INPROGRESS標志表示正在執行某個具體中斷處理操作,也即正在執行action。注意此處鎖的位置,更新desc->irq_data->state_use_accessors的標志時,鎖,執行action的時候不鎖,進入handle_irq_event()時,就已經鎖住了。下面來看handle_irq_event_percpu()函數:

irqreturn_t
handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{
irqreturn_t retval = IRQ_NONE;
unsigned int flags = 0, irq = desc->irq_data.irq;

do {irqreturn_t res;trace_irq_handler_entry(irq, action);res = action->handler(irq, action->dev_id);trace_irq_handler_exit(irq, action, res);if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interrupts\n",irq, action->handler))local_irq_disable();switch (res) {case IRQ_WAKE_THREAD:/** Catch drivers which return WAKE_THREAD but* did not set up a thread function*/if (unlikely(!action->thread_fn)) {warn_no_thread(irq, action);break;}irq_wake_thread(desc, action);/* Fall through to add to randomness */case IRQ_HANDLED:flags |= action->flags;break;default:break;}retval |= res;action = action->next; } while (action);add_interrupt_randomness(irq, flags);if (!noirqdebug)note_interrupt(irq, desc, retval); return retval;

}
trace_irq_handler_entry()函數和trace_irq_handler_entry()函數用于找出action中那些不合法的(網上有人這么說,沒找到,具體不詳)并強行disble他們。具體中斷處理調用了action->handler(),反復執行將整個chain上的所有action都執行一遍,所有的返回值或起來作為總的返回值。針對IRQ_WAKE_THREAD,說明驅動程序使用了軟斷的方式(設置了thread_fn),那么就要調用irq_wake_thread(),嘗試在調度器中激活action->thread_fn。關于irq_wake_thread(),詳見“八、中斷線程”部分。最后,在默認情況下,通過note_interrupt()接口更新desc->action的結果,并決定是否觸發“偽中斷”輪詢函數poll_spurious_irq()。

從上述分析不難看處,Linux內核在實現內核中斷處理函數函數時層次分明:

desc->handle_irq,負責與PIC觸發方式相關的操作,并作desc->istate相關校驗、置位;
handle_irq_event(),負責設置desc->irq_data以及解鎖避免其他CPU忙等。
handle_irq_event_percpu(),負責具體的在CPU上運行desc->action以及轉向軟中斷、轉向“偽中斷”輪詢的過程。
五、中斷申請函數request_irq()
從上述描述可以看出,irq_desc[]數組的name,handle_irq,irq_data->chip伴隨著8259A和IO APIC的初始化而初始化,而irq_desc[]數組的其他部分,如actions還沒有注冊,actions是驅動程序通過request_irq()函數項內核的IRQ系統申請一個IRQ號,并初始化該號的irq_desc[]數組元素中的action(include/linux/interrupt.h):

static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
request_threaded_irq()函數代碼真不少,刪除參數校驗和調試的部分,主要完成了申請action內存,并初始化之,然后掛接action和irq_desc[]:

int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn, unsigned long irqflags,
const char *devname, void *dev_id)
{

action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!action)
return -ENOMEM;

action->handler = handler; action->thread_fn = thread_fn; action->flags = irqflags; action->name = devname; action->dev_id = dev_id;chip_bus_lock(desc); retval = __setup_irq(irq, desc, action); chip_bus_sync_unlock(desc);if (retval)kfree(action); ... return retval;

}
自此,irq_desc[]數組的初始化基本完成。

request_irq()函數需要攜帶irq號這個變量用于注冊,但是驅動程序本身并清楚哪個中斷號是可用的,也不清楚自己的IRQ線對應的哪個irq_desc[]元素,怎么解決這個問題?一方面,驅動程序一般都有自己的默認中斷向量號,但不止一個,調用request_irq()時,會調用irq_settings_can_request()函數(給省略掉了)來檢測該desc是否可以被申請,如果不能被申請,則返回-EINVAL,驅動程序會再重新申請;另一方面,內核提供了一種irq號偵測機制,auto probe,詳見“六、istate狀態”。

六、istate狀態
Linux內核中的中斷子系統有四種狀態、特性,分別是:

用于描述irq_desc的istate。
用于描述irq_desc->action->handler的flags。
用于描述irq_desc->action->thread_fn的thread_flags。
用于描述irq_desc->irq_data的state_use_accessors。
此處重點介紹istate,但首先還是irq_desc->action->flags。include/linux/interrupt.h文件中定義了handling routines的一些標志(并沒有完全列出,還有一些IRQF_TRIGGER_的表示觸發方式的標志):

/*

  • These flags used only by the kernel as part of the
  • irq handling routines.
  • IRQF_DISABLED - keep irqs disabled when calling the action handler.
  • DEPRECATED. This flag is a NOOP and scheduled to be removed
  • IRQF_SHARED - allow sharing the irq among several devices
  • IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur
  • IRQF_TIMER - Flag to mark this interrupt as timer interrupt
  • IRQF_PERCPU - Interrupt is per cpu
  • IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
  • IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
  • registered first in an shared interrupt is considered for
  • performance reasons)
  • IRQF_ONESHOT - Interrupt is not reenabled after the hardirq handler finished.
  • Used by threaded interrupts which need to keep the
  • irq line disabled until the threaded handler has been run.
  • IRQF_NO_SUSPEND - Do not disable this IRQ during suspend
  • IRQF_FORCE_RESUME - Force enable it on resume even if IRQF_NO_SUSPEND is set
  • IRQF_NO_THREAD - Interrupt cannot be threaded
  • IRQF_EARLY_RESUME - Resume IRQ early during syscore instead of at device
  • resume time.

*/
以上標志如果非得從“狀態”和“特性”中選擇一個的話,應該是“特性”,其前綴為IRQF_,表示IRQ Flags。在request_irq()函數執行時,需要填寫flags變量,也就是這些標志,這些標志用于說明你申請的handler的特性,request_irq的flags總是帶有IRQF_DISABLED的標志,其他特性讓其他IRQ操作函數按不同的路徑執行。如IRQ_SHARED標志表示可以共享IRQ號,IRQF_NO_THREAD標志表示中斷處理函數不能被線程化。

然后是istate狀態,kernel/irq/internals.h中定義了這些“狀態”,使用IRQS_前綴,他們可以同時存在,而并不一定相互轉化:

/*

  • Bit masks for desc->state

  • IRQS_AUTODETECT - autodetection in progress

  • IRQS_SPURIOUS_DISABLED - was disabled due to spurious interrupt

  • detection
  • IRQS_POLL_INPROGRESS - polling in progress

  • IRQS_ONESHOT - irq is not unmasked in primary handler

  • IRQS_REPLAY - irq is replayed

  • IRQS_WAITING - irq is waiting

  • IRQS_PENDING - irq is pending and replayed later

  • IRQS_SUSPENDED - irq is suspended
    */
    enum {
    IRQS_AUTODETECT = 0x00000001,
    IRQS_SPURIOUS_DISABLED = 0x00000002,
    IRQS_POLL_INPROGRESS = 0x00000008,
    IRQS_ONESHOT = 0x00000020,
    IRQS_REPLAY = 0x00000040,
    IRQS_WAITING = 0x00000080,
    IRQS_PENDING = 0x00000200,
    IRQS_SUSPENDED = 0x00000800,
    };
    這些狀態存儲在irq_desc->istate,也就是core_internal_state__do_not_mess_with_it。從字面意思看,有些狀態不好理解,通過他們在linux內核中的調用關系搞清楚來龍去脈:

    IRQS_AUTODETECT狀態和IRQS_WAITING狀態

? AUTODETECT狀態在函數probe_irq_on()中被開啟,在probe_ifq_off()函數或probe_ifq_mask()函數中被關閉。而probe_irq_on()和probe_irq_off()是驅動與內核申請可用的irq號的調用函數,一個驅動程序可以通過request_irq()向內核申請注冊中斷處理函數到desc->action,此時需要攜帶irq號作為參數,而irq號可以自是預先設定(Intel Enternet drvier),也可以通過一種叫自動偵測(auto probe)的過程來完成:

首先,其驅動程序調用probe_irq_on()接口,開始auto probe的過程。probe_irq_on()函數將返回一個32bits的irq_desc是否可用的掩碼mask(此處因為返回值類型為int,所以只能返回32位的掩碼)。probe_irq_on()接口首先選出所有可以被偵測(desc->irq_data->status_use_accessors是否包含IRQD_IRQ_NOPROBE標志)、并且沒有action函數的irq_desc[],并將這些irq_desc->istate設置IRQS_AUTODETECT標志和IRQS_WAITING標志;然后過濾掉所有的偽中斷irq_desc[],是否可用通過判斷desc->istate包含IRQS_WAITING狀態(偽中斷經過handle_xxx_irq()處理將清除IRQS_WAITING狀態)。上面的工作對于auto probe都是輔助性的,但最重要的是probe_irq_on()函數在開始時就調用了async_synchronize_full()接口,來同步內核中所有異步調用的函數(這樣就不存在發生了中斷,但沒有被記錄的情況)。
其次,驅動程序收到mask掩碼后(當然也可以利用mask做一些事情,但并非所有的驅動都這樣做),主動產生一次硬件中斷。
然后,驅動程序調用probe_irq_off(mask),后者偵測所有的irq_desc[],查看到底是哪些irq_desc[]元素發生了硬件中斷,如果只發生了一次硬件中斷,那么就返該irq_desc[]的下標,也即irq號;如果沒有偵測到中斷發生,則返回0;如果偵測到多次中斷發生,則返回一個負數(中斷發生次數的相反數)。probe_irq_off()函數通過desc->istate是否包含IRQS_WAITING標志來判斷是否發生了一次硬件中斷(包含就沒發生,不包含就發生了)。
最后,驅動程序通過probe_irq_off()的返回值,來判斷是否是一個可用的irq號,如果可用,則通過request_irq()來申請注冊中斷處理函數。
? 如果再扣的細一點,還有兩個問題:

問題1,async_synchronize_full()函數能夠使所有的irq_desc[]的istate都去掉IRQS_WAITING標志,這是為什么?
問題2,probe_irq_off()函數為什么可以使用帶有IRQS_WAITING標志來判斷哪個irq描述符發生了硬件中斷?
對問題1,涉及到IRQS_WAITING標志的含義,IRQS_WAITING標志在probe_irq_on()接口中被設置,在執行irq_desc->handle_irq,也即具體的handle_xxx_irq()接口時被清除,猜測async_synchronize_full()接口可能會等待所有的中斷處理函數執行完畢吧(此處還有問題,如何等待源源不斷的中斷處理執行完成?那得看async_synchronize_full的細節了。)

對問題2,在init_8259A()函數或者setup_IO_APIC_irqs()函數中,都調用了irq_set_chip_and_handler_name()函數,后者將desc->irq_data.chip和desc->handle_irq以及desc->name給予初始化,這意味著與irq_desc[]關聯的PIC已經完全驅動起來,并且與irq_desc[]建立了關聯關系,當硬件產生中斷信號時,PIC可以接收到該信號,并通知CPU,CPU查找到IDT里的中斷處理程序后,就執行了do_IRQ()函數,然后調用了handle_IRQ_event()接口,然后到具體的irq_desc->handle_irq,如handle_level_irq(),而在具體的handle_xxx_irq()接口中,就會清除掉IRQS_WAITING標志。

總結一下,IRQS_AUTODETECT表示某個irq_desc[]變量處于自動偵測狀態,通過probe_irq_on()函數設置此狀態,通過probe_irq_off()清除此狀態。IRQS_WAITING表示某個irq_desc[]變量處于等待狀態,也即等待被處理,等待irq_desc->handle_irq的執行,此狀態通過probe_irq_on()設置,通過irq_desc->handle_irq,也即handle_xxx_irq()系列函數清除。IRQS_SPURIOUS_DISABLED狀態前面說IRQS_AUTODETECT狀態時,提到“偽中斷”,這個IRQS_SPURIOUS_DISABLED就是指這種情況。根據wiki上的說法:“一類不希望被產生的硬件中斷。發生的原因有很多種,如中斷線路上電氣信號異常,或是中斷請求設備本身有問題。”,猜測是哪些PIC上確實偵聽到了中斷信號,但實際上沒有發生中斷,或者沒有找到中斷處理函數的情況。Linux內核將某個irq_desc[]元素發生了10萬次中斷,卻有9.9萬次沒有處理的情況,視為“偽中斷”,會將其置位,意味著因為“偽中斷”而被禁用(緊接著會執行irq_disable(desc))。中斷發生次數統計通過desc->irq_count,中斷發生卻沒有處理的統計通過desc->irq_unhandled。內核在note_interrupt()函數中處理此情況。setup_irq()函數用于驅動程序將具體的中斷處理函數掛接到desc->action下,該函數執行時,將清除IRQS_SPURIOUS_DISABLED標志。對于“偽中斷”,內核并不是扔掉不管,而是有一套自己的處理方法,有人還對其做過優化。目前,內核采用poll_spurious_irqs()的方法來處理被IRQS_SPURIOUS_DISABLED的desc。poll_spurious_irqs()函數將輪詢所有的“偽中斷”,并嘗試在本地core上執行一次(通過try_one_irq()函數)。try_one_irq()函數有選擇的執行handle_irq_event(),(有些情況的偽中斷不予執行,比如PER_CPU的、嵌套的等等)。poll_spurious_irqs()函數并不清除IRQS_SPURIOUS_DISABLED標志,而是嘗試輪詢并執行他們一次。

? 總結一下,IRQS_SPURIOUS_DISABLED標志意味著某irq_desc[]元素被視為“偽中斷”,并被禁用。該標志被note_interrupt()函數設置,被setup_irq()函數清除。對于哪些偽中斷,系統嘗試時用poll_spurious_irqs()函數在本地CPU上輪詢并執行他們一次(在中斷線被PIC禁用時,仍可以執行中斷處理函數,即是中斷處理函數執行完畢,也不清除此標志)。

IRQS_POLL_INPROGRESS狀態

? in progress表示正處于過程當中,poll in progress字面意思就是中斷處理函數目前正在以poll的方式執行。硬件中斷處理函數通常是立即執行,而軟中斷才留在后面執行。

前面提到的try_one_irq()函數,在其執行handle_irq_event()函數前,將設置此標志表示中斷處理函數正在以poll的方式被執行,在其執行完畢handle_irq_event()后清除此標志,表示中斷處理函數執行poll完畢。而調用try_one_irq()函數的還由misrouted_irq()函數(用于嘗試執行一次可能時misrouted的情況,硬件產生了中斷信號,內核卻將其對應到錯誤的irq_desc[]元素上的情況)函數中被調用。這是對于“偽中斷”的輪詢,但對正常的中斷處理,并沒有采用poll的方法(NAPI采用了,另說),而是在具體的handle_xxx_irq()函數中需要執行irq_check_poll()方法,等待中斷處理函數poll完畢,因為驅動實現的中斷處理函數未必是可重入的。

總結一下,IRQS_POLL_INPROGRESS,表示一個irq_desc[]元素正處于輪詢調用action鏈的階段。此標志只在try_one_irq()函數(被poll_spurious_irqs()函數和misrouted_irq()函數調用)調用handle_irq_event()前被設置,在其調用handle_irq_event()后被清除。由于具體的中斷處理函數(desc->action)的設計未必是可重入的,因此desc->handle_irq,如handle_xxx_irq()需要等待其上的輪詢完畢后才能執行。這意味著,僅僅“偽中斷”才會被輪詢,并且一個中斷處理函數可以同時被“偽中斷”輪詢執行,也可以正常執行,但必須排隊執行。

這個地方還是不理解,既然被定為“偽中斷”,那么就會被irq_disable()——從硬件上屏蔽該中斷線,怎么還會接收到中斷、并執行中斷處理函數那?這可能就是“偽中斷”神奇的地方。

IRQS_ONESHOT狀態?開一槍狀態?應該是個不太重要的狀態。該狀態表示irq_desc[]元素的主處理函數不是非屏蔽的(直接說mask不就完了,難道除了mask,unmask還有半mask?)。在handle_fasteoi_irq()函數(其他handle_xxx_irq()中沒有,fasteoi一定是一種比較特殊的情況)中,如果該標志設置,就需要mask_irq()執行一下,然后就是硬件操作了。此處的mask應該是這樣的,mask意味著屏蔽中斷,也就是在中斷處理函數執行的時后,從CPU的角度短暫的禁止該中斷(應該是中斷向量,通過設置CPU的中斷屏蔽寄存器IMR,來完成此過程)。而是否需要mask中斷,是中斷處理函數的本身的需要,因此,也就是應該是desc->action->flags里的設置(IRQF_ONESHOT標志),而IRQS_ONESHOT狀態應該是源于IRQF_ONESHOT標志的,在setup_irq()函數被執行用來為desc添加action的時候,在非共享的方式下,如果發現action->flags中設置了IRQF_ONESHOT標志,則為desc->istate設置此狀態。在irq_finalize_oneshot()函數中,將會執行unmask_irq(),并清除該標志。irq_finalize_oneshot()函數在one shot中斷處理函數運行完畢(action->thread_fn)時被調用。總結,one shot,字面意思開一槍,這槍肯定是早期的步槍,沒能實現自動填裝彈藥。IRQS_ONESHOT更像一種屬性,而非狀態(在其被設置時,并不是中斷被屏蔽的時刻,而時表示此action是one shot類型的),在setup_irq()的時后被條件設置,在handle_fasteoi_irq()執行mask_irq()操作,在irq_finalize_oneshot()中執行unmask_irq()操作,并清除此標志。IRQS_REPLAY狀態?從字面意思上講,應該是重新發生一次中斷的意思。該標志在handle_xxx_irq()函數中的開始部分就被清除(避免多個core同時執行),在check_irq_resend()函數中,若判斷desc->istate包含IRQS_PENDING標志,則設置該狀態。IRQS_REPLAY狀態僅在check_irq_resend()函數中被設置,check_irq_resend()函數通過重新激發中斷控制器上的中斷信號來完成此過程(中斷硬件將不發送中斷信號,僅僅由PIC重新向CPU發送),對handle_level_irq()函數觸發的中斷,不予重新發送(不知道原因)。而check_irq_resend()函數又被__enable_irq()和irq_startup()所調用,最終被irq_set_chip_and_handler_name()和irq_set_handler()調用,他們的調用關系如下圖所示:

resend

總結,IRQS_REPLAY狀態表示需要重新激發一次中斷信號(正在重新發送IRQ信號),它在desc->handle_irq被初始化最后時刻被設置(被irq_set_chip_and_handler_name()函數設置):完成desc->handle_irq的掛接后要由PIC自動產生一次中斷,利用重新注冊action的機會,對“掛起”(IRQS_PENDING)狀態的中斷再觸發一遍,然后由handle_xxx_irq來查看中斷處理函數是否正常,該標志在handle_xxx_irq()時被清除。IRQS_PENDING狀態字面意思“正在掛起”,為什么要掛起?在什么情況下掛起?在handle_xxx_irq()函數(handle_ege_irq()函數和handle_edge_eoi_irq()函數比較特殊)中,如果發現以下情況中的一種,則掛起,掛起后直接釋放鎖并推出,并不執行handle_irq_event()函數:

desc->action = NULL,也即無具體的中斷處理函數;
desc->irq_data->state_use_accessors包含IRQD_IRQ_DISABLED,也即中斷控制器被禁用;
還有一種情況會“掛起”中斷處理,在probe_irq_on()時,對于所有的沒有分配action并且可以被PROBE的desc,需要重新irq_startup()一下,清掉此前mask it self的longstanding interrupt。在irq_startup()函數中,硬件會嘗試掛起該中斷向量,如果成功的話,desc->istate也需要置位IRQS_PENDING。

另一種需要“掛起”的情況是try_one_irq()函數中,如果desc->irq_data->state_use_accessors包含IRQD_IRQ_INPROGRESS標志(表示IRQ不處于),則為desc->istate置掛起標志并推出。

以下是清除IRQS_PENDING的場景:

handle_irq_event()函數開始處即清除IRQS_PENDING標志;
check_irq_resend()函數,會在PIC重新觸發、向CPU產生中斷信號前,清除掉IRQS_PENDING標志;
? 以下是校驗IRQS_PENDING的場景:

try_one_irq()函數中,如果desc->irq_data->state_use_accessors并不包含IRQD_IRQ_INPROGRESS標志,但是desc->istate卻包含IRQS_PENDING標志,并且desc->action不為空,則返回執行action鏈上的函數。
check_wakeup_irq()函數中,對所有的desc,如果desc->depth==1并且置位IRQS_PENDING標志,則返回-EBUSY(這個地方一定有特殊含義,就不細研究了)。
? 總結,IRQS_PENDING表示中斷處理被掛起,通常是因為沒有中斷處理函數或者中斷控制器被禁用。通常由handle_xxx_irq()設置,由handle_irq_event()清除。

IRQS_SUSPENDED狀態字面意思為“暫停”,下面是置位IRQS_SUSPENDED狀態的場景:

在__disable_irq()函數中,如果參數suspend設置為true,則置位IRQS_SUSPENDED狀態。suspend_device_irqs()函數調用__disable_irq()函數時,會設置此標志。
? 清除IRQS_SUSPENDED狀態場景:

在__enable_irq()函數中,如果參數resume為ture,則清除IRQS_SUSPENDED狀態。resume_device_irqs()函數調用__enable_irq()函數時,清除此標志。
校驗IRQS_SUSPENDED狀態的場景:

在__enable_irq()函數中,如果desc->depth=1,則校驗IRQS_SUSPENDED狀態,如果置位,則退出,不執行irq_enable()。
在check_wakeup_irqs()函數中,具備IRQS_SUSPENDED狀態成為是否執行mask_irq()的一個條件。
在suspend_device_irqs()函數,對所有具備IRQS_SUSPENDED狀態的desc執行 synchronize_irq()。
? 總結,IRQS_SUSPENDED標志表示所有中斷線均被disable,通過suspend_device_irqs()函數置位,通過resume_device_irqs()函數清除。

總體看來這些狀態代表著irq_desc[]的一些操作階段,但是他們之間是可以共存的,下圖粗略的描述了irq_desc->istate的各種狀態之間的關系,狀態的輸入箭頭表示狀態置位操作,狀態的輸出箭頭表示狀態的清除操作,函數的輸入箭頭表示被上游函數調用,函數的輸出箭頭表示調用了下游函數,虛線表示mask_irq()操作實際上是handle_xxx_irq的一個特例handle_fasteoi_irq()中的操作,直線沒箭頭表示對該狀態ONESHOT的直接操作,關于ONESHOT狀態,實際上是desc->action->thread_fn的一個屬性,將會在“八、中斷線程中詳細討論”:

istate

七、中斷向量、鎖和CPU
中斷向量這個詞經常說,那么到底什么是中斷向量那?內核中有兩個概念,一個叫vector,另一個叫irq。vector指的就是“中斷向量”,也就是PIC向CPU傳遞的用于表示發生中斷的IRQ線的一種編號。而irq,也就是常說的irq號,是內核維護的中斷請求的數組下標。以下說法或變量是等價的(x86架構):

中斷向量號
PIC向CPU傳遞的關于中斷向量號
IDT表的索引號
interrupt[]數組的下標+0x20
irq_desc[]數組的下標+0x20
irq_desc->action->irq+0x20
vector_irq[]數組的下標
irq號+0x20
硬件維護的中斷向量號,在x86平臺上,本質上是PIC向CPU傳遞的用于表示發生中斷的IRQ線的一種向量編號,CPU通過該向量找到IDT表中的對應的中斷門描述符。由于Intel保留了前0x20個中斷、異常號,所以內核就沒必要再維護這塊了,所以內核中的數組下標從0開始,就對應了中斷向量的0x20號。但是vector_irq[]數組比較特殊,它的下標代表vector,而內容代表irq,實際上就是irq到vector的一個影射:

vector_irq[]數組在arch/x86/include/asm/hw_irq.h文件中定義:

typedef int vector_irq_t[NR_VECTORS];
DECLARE_PER_CPU(vector_irq_t, vector_irq);
通過setup_vector_irq()函數初始化:

void setup_vector_irq(int cpu)
{
#ifndef CONFIG_X86_IO_APIC
int irq;

for (irq = 0; irq < legacy_pic->nr_legacy_irqs; irq++)
per_cpu(vector_irq, cpu)[IRQ0_VECTOR + irq] = irq;
#endif

__setup_vector_irq(cpu);

}
前文中提到在init_IRQ()函數中,會首先初始化vector_irq[]數組,而且vector_irq[]數組是PER_CPU的。vector_irq[]在聲明時被全部初始化為-1,-1表示未被使用。在init_IRQ()函數中,16個(0x30~0x3f,為什么是0x30開始,就不討論了)被初始化為0到15。

used_vectors按照內核注釋“used_vectors is BITMAP for irq is not managed by percpu vector_irq”的說法,used_vectors是vector_irq[]不管的中斷向量。used_vectors通過alloc_intr_gate()函數置位,alloc_intr_gate()函數的主要功能是set_intr_gate()。used_vectors[]在trap_init()時,前0x20個被置位。系統調用0x80隨后也被used_vectors置位,從名字上看,used_vectors應該時叫system vectors,就時IO設備中斷以外的中斷和異常。實際上used_vectors標明了鎖有的中斷、異常哪些是IO device中斷,哪些是非IO device中斷,這些非IO device中斷中包含了Intel預留的前0x20個中斷、異常,也包含Linux內核選取的0x80系統調用軟件中斷,當然還包括一些其他的中斷和異常,但并不做具體的區分。因此,used_vectors和vector_irq[]的關系就清晰了,下面是關于并發的操作。ULK上說:”簡而言之,當硬件設備產生了一個中斷信號時,多APIC系統就選擇其中的一個CPU,并把信號傳遞給相應的本地APIC,本地APIC又依次中斷它的CPU。這個時間不通報給其他所有的CPU“。我想這個地方所謂的”多APIC系統“,是指多IO APIC系統,而IO APIC選擇一個CPU,將信號傳遞給其Local APIC,然后接收到信號的Local APIC再選擇一個本地的Core通知其中斷發生,那么就意味著在多IO APIC系統中,選擇哪個CPU的哪個Core來處理IO設備中斷,是由IO APIC決定的,而且從硬件上就決定了其不會同時由兩個以上的Core收到相同的中斷信號。那么,Linux內核應該無需擔心兩個core同時處理一個中斷信號引起的desc->handle_irq的情況了,但是會發生如下情況:

某CPU在處理某中斷,此刻發生了相同的中斷,要相同的CPU來處理;丟中斷;
某CPU在處理某中斷,此刻發生了相同的中斷,要不同的CPU來處理;避免這種情況;
某CPU在處理某中斷,此刻發生了不同的中斷,要相同的CPU來處理;中斷嵌套;
某CPU在處理某中斷,此刻發生了不同的中斷,要不同的CPU來處理;正常處理;
對于第一種情況,起碼要有一種機制保證正常的丟失中斷,而不是總被自己打斷,而無法完成正常的中斷處理。而對第二種情況,應該盡量避免,因為中斷處理函數不一定是可重入的,因此必須順序執行,一次沒處理完,不能并發處理下一次。Linux內核通過自旋鎖來完成這個工作,也即在handle_xxx_irq()系列函數開始時,獲取鎖,退出時,釋放鎖。Linux內核中斷子系統中至少使用到了4種鎖用于同步不同的資源,分別是:

desc->irq_data.chip->irq_bus_lock
desc->lock
irq_domain_mutex
gc_lock
前面說的鎖就是desc->lock的用處。irq_desc->lock類型為raw_spinlock_t,是一個自選鎖。首先,得看一下自旋鎖的特點和操作。自旋鎖有以下特點:

自旋,同時只能被一個進程持有,只能由持有該鎖的進程解鎖;
忙等,如果其他進程想要獲取鎖,則會一致處于等待狀態而無法做其他事情。
遞歸死鎖,以上兩條說明會有這個現象。
自旋鎖可以時用下列方法去操作:

spin_lock()和spin_unlock()
raw_spin_lock()和raw_spin_unlock()
raw_spin_lock_irq()和raw_spin_unlock_irq()
raw_spin_lock_irqsave()和raw_spin_unlock_irqrestore()
內核在desc->handle_irq到handle_irq_event()接口的過程中,通常這樣處理:

handle_xxx_irq

為什么要著要么做?避免同時對調用handle_irq_event(),奇怪的是在handle_irq_event()里,在irqd_set(IRQD_IRQ_INPROGRESS)后,又解鎖,這是為什么?顯然,內核并不是害怕兩個CPU同時執行了desc->action(除了handle_xxx_irq()系列函數,內核還有其他位置調用了desc->action),而是害怕在鎖與解鎖之間的操作被交叉操作,這些操作包括(以handle_level_irq()為例,上圖中沒有標出):

mask_ack_irq(desc),向發出中斷的硬件ACK,并且暫時屏蔽該中斷;
如果irqd_irq_inprogress(),則執行irq_check_poll(),這表示該中斷可能正在“偽中斷”輪詢函數輪詢。(不知道有沒有這種情況,偽中斷輪詢函數沒有輪詢該中斷,desc->irq_data狀態卻不是IN_PROGRESS)
desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
kstat_incr_irqs_this_cpu(irq, desc)更新cpu相關的中斷統計;
如果desc->action為空,或者desc->irq_data處于DISABLE的狀態,執行desc->istate |= IRQS_PENDING;
看樣子,desc->lock只保護desc->istate狀態變化的操作,硬件中斷處理中PIC對中斷線的一些操作,并不保護desc->action。這意味著

八、中斷線程
Linux內核的硬中斷處理程序(desc->handle_irq)將處理中斷信號發生后需要立即完成的事情,如網卡數據包的拷貝,響應中斷等,而將不太緊急的工作留給軟中斷去完成。在“六、istate狀態”一節中有提到一種ONESHOT狀態,就與中斷線程相關。使用ps aux | grep ksoftirqd命令,會發現系統中正運行著幾個(core總個數)ksoftirq進程:

[rock3@e4310 linux-stable]$ ps aux | grep ksoftirqd
root 3 0.0 0.0 0 0 ? S 11月12 0:37 [ksoftirqd/0]
root 13 0.0 0.0 0 0 ? S 11月12 0:34 [ksoftirqd/1]
root 18 0.0 0.0 0 0 ? S 11月12 0:23 [ksoftirqd/2]
root 23 0.0 0.0 0 0 ? S 11月12 0:22 [ksoftirqd/3]
這些進程就是內核啟動的中斷線程,他是一種內核線程,每個core都有一個,用于處理不太緊急的中斷事務,本節就討論從硬中斷處理轉向軟中斷處理的過程,這得從setup_irq()函數(用于設置desc->action)說起。先看下調用關系:softirq

實際上干事的是__setup_irq()函數,__setup_irq(irq,desc,new)將new加入到desc->action鏈表中。該函數判斷如果加入的new->thread_fn不為空,則使用kthread_create()函數創建一個內核線程并將該線程加入到內核線程kthread_create_list鏈表中,kthread_create()函數返回task_struct結構體指針,該指針被賦值為new->thread,在函數退出__setup_irq()函數返回前,調用wake_up_process()函數來喚醒該new->thread。

當然創建的線程是irq_thread,不過irq_thread()函數將new->thread_fn又包裹了一層:irq_thread()通過irq_thread_fn()函數或者irq_forced_thread_fn()函數來調用new->thread_fn,而irq_thread_fn/irq_forced_thread_fn的補充操作為irq_finalize_oneshot(),即根據ONESHOT狀態來執行unmaks_irq()。irq_thread()函數還有不少其他操作,這里就不分析了。

通過kthread_create()創建內核線程,到wake_up_process()喚醒它,中斷子系統進入了軟中斷(softirq)的階段,該階段以內核線程的方式來處理中斷,被調度器調度。軟中斷通常有tasklet、工作隊列等具體方式。具體原理請詳見“軟中斷原理”一文。

九、總結
本篇博客實際上是逆向學習Linux中斷子系統的過程,本意為看下irq_desc[]數組初始化的過程,結果有很多不明白的地方,就一路跟蹤過來,雖然也學到了一些內核中斷子系統的東西,但整體上對框架和細節的把握并不到位,估計以后還需要重新系統學習:

帶著問題學,逆向思維很重要。
逆向學習的過程應該是先縱向、再橫向,但本篇博客中很多地方是先縱橫不分,類似籃球里的“盯球不盯人”,不好。
假設一定要驗證,“我以為”是失敗之母;
用20%的時間學會整體框架,而不是用80%的時間了解細節。
從別人那學,會快,但不深刻。
不要學究。
十、遺留問題
1、關于鎖的問題,還是沒想明白,從找到interrupt[i]的內容,執行do_IRQ()函數,到handle_xxx_irq(),再到handle_irq_event(),在handle_xxx_irq()開始處加鎖,而在desc->action處解鎖,那么說ISR并不是臨界區,可以在兩個CPU上執行,而desc->lock只保護desc->istate變化以及PIC的一些操作,而不保護ISR。這個問題很重要,涉及到SMP架構下對中斷的處理,涉及到如何與軟中斷配合。

網上查找了一下,說ISR is not a critical section,但是do_IRQ()是critical section。不明白。desc->action不一定是可重入的,兩個CPU同時執行它的時后,就有可能出現錯誤。

2、轉向軟中斷的一些細節。

參考資料:

1、Understanding Linux Kernel

2、Professional Linux Kernel Architecture

總結

以上是生活随笔為你收集整理的irq_desc操作的全部內容,希望文章能夠幫你解決所遇到的問題。

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

国产91精品一区二区 | 国产一区高清在线 | 国产精品久久久区三区天天噜 | 99久久99久久精品 | 97超碰在线久草超碰在线观看 | 99免费精品视频 | japanesexxx乱女另类 | 日本女人的性生活视频 | 亚洲视频999 | 中文av影院 | 亚洲国产999 | 国产一级片网站 | 免费视频一区二区 | 精品视频| 精品视频中文字幕 | 欧美精品久久久久久久久久 | 九色视频网址 | 五月婷影院 | 亚洲国产理论片 | 99久久综合狠狠综合久久 | 成人黄色资源 | 欧美亚洲一区二区在线 | 成人h动漫精品一区二 | 2024av在线播放| 91麻豆精品国产自产在线游戏 | 久久九九国产视频 | 日韩乱色精品一区二区 | 国产亚洲免费的视频看 | 中文字幕国产 | 毛片网在线观看 | 激情久久一区二区三区 | 亚洲年轻女教师毛茸茸 | 狠狠躁夜夜躁人人爽超碰97香蕉 | 人人舔人人爽 | 99久久99久久精品 | 91自拍成人 | 天天色天天草天天射 | 91黄站| 久久都是精品 | 亚洲理论在线观看电影 | 在线岛国av | 亚洲精品欧洲精品 | 97看片吧 | 久久久免费 | 人人爽人人爽人人爽人人爽 | 国产精品原创av片国产免费 | 亚洲春色奇米影视 | 一区二区视频欧美 | 成人国产精品久久久久久亚洲 | 国产美女主播精品一区二区三区 | 97操操操| 免费色视频网址 | 免费在线观看国产黄 | 超碰最新网址 | 欧美巨乳网 | 99久久精品国产一区二区成人 | 91成人区| 中文字幕资源在线观看 | 亚洲国产精品一区二区久久,亚洲午夜 | 亚洲日日射 | 久久久久久久国产精品视频 | 超碰在线观看av.com | 亚洲高清在线精品 | 最近免费中文视频 | 天天插狠狠插 | 国产精品淫片 | 亚洲精品视频观看 | 欧美国产三区 | 99久久久国产精品免费观看 | 中文字幕国产一区 | 99视频在线精品免费观看2 | 三级在线播放视频 | 91精品久久香蕉国产线看观看 | 国产精品麻豆视频 | 国产精品久久久久久久久久久久久久 | 在线观看欧美成人 | 亚洲国产精品日韩 | 日韩欧美一区视频 | 欧美日韩91 | 亚洲精品在线观看免费 | 特黄特色特刺激视频免费播放 | 黄色在线网站噜噜噜 | 最新在线你懂的 | 国内精品久久久久影院男同志 | 国产一区二区免费在线观看 | 91视频麻豆视频 | 三上悠亚一区二区在线观看 | 国产亚洲人成网站在线观看 | 久久免费黄色大片 | 天天久久综合 | 亚洲第一伊人 | 亚洲精品黄 | 日本3级在线观看 | 久久九九国产精品 | 国产五月 | 国产区在线 | 在线观看日韩专区 | 色丁香久久 | 国产精品久久久久久久久婷婷 | 国产精品久久久久久久午夜 | 天天干人人 | 久久国产精品99精国产 | 看国产黄色大片 | 天天综合天天做 | 日韩在线观看不卡 | 五月激情久久久 | 在线韩国电影免费观影完整版 | 久草| 97国产超碰 | 日b黄色片 | 在线视频 你懂得 | 91人人干| 日韩在线理论 | 亚洲视频精品在线 | 国产成人精品综合久久久 | 久草视频播放 | 国产一区在线看 | 99久久久久久久久久 | 欧美日韩国产精品一区二区三区 | 亚洲黄色小说网 | 日本三级久久久 | 综合精品在线 | 韩国一区在线 | 91精品国产欧美一区二区 | 亚洲九九九 | 国产高h视频 | 日韩精品一区二区三区免费观看 | 99久久国产免费看 | 九九免费视频 | 看黄色91 | 在线视频 亚洲 | 亚洲女在线| 国产玖玖视频 | 久久 亚洲视频 | 久久九九精品 | 久久另类视频 | 亚州天堂 | 免费在线观看av网站 | 国产精品久久av | 免费三级黄色片 | 久久国产精品99精国产 | 亚洲 欧洲 国产 精品 | 美女免费黄网站 | 免费在线91 | 91久久在线观看 | 亚洲日本中文字幕在线观看 | 亚洲日本欧美 | 国产精品亚洲精品 | 99在线视频免费观看 | 久久婷婷视频 | 国产不卡在线播放 | 成人午夜电影在线 | 国产又粗又硬又爽的视频 | 黄色国产高清 | 高清av网站 | 狠狠躁夜夜躁人人爽视频 | 欧美精品久久久久久久久久白贞 | 久99热| 国产精品亚洲综合久久 | 丁香婷婷亚洲 | 9幺看片 | 天天综合视频在线观看 | 视频国产区 | 超碰成人免费电影 | 在线看不卡av | 久久免费观看视频 | 亚洲精品国精品久久99热 | 黄网站色 | 麻豆国产精品永久免费视频 | 久久99这里只有精品 | 在线观看中文字幕网站 | 国产精品免费一区二区 | 日韩高清dvd | 国产精品一区二区中文字幕 | 一级大片在线观看 | a视频免费在线观看 | 中文字幕在线观看不卡 | 亚洲国产成人精品在线观看 | 日韩激情在线 | 亚洲日本在线视频观看 | 久久久久久免费网 | 伊人手机在线 | 亚洲国产一区在线观看 | 日本精品视频网站 | 久久人人添人人爽添人人88v | 成人免费 在线播放 | 黄色视屏免费在线观看 | 天天干天天操人体 | 国产午夜在线 | 亚洲精品毛片一级91精品 | 91激情小视频 | 在线观看中文字幕一区二区 | 亚洲激情影院 | 久久久久久久久久久久影院 | 国产视频每日更新 | 亚洲a网| 国产99自拍 | 人成在线免费视频 | 天天干天天搞天天射 | 97在线观看免费视频 | 开心激情五月网 | 国产v在线播放 | 91av中文字幕 | 天天操天天综合网 | 免费观看国产视频 | 亚洲欧美日韩国产一区二区三区 | 91精品久久香蕉国产线看观看 | 黄色小说视频网站 | 成人性生交大片免费看中文网站 | 亚洲男男gaygay无套同网址 | 青春草国产视频 | 欧美激情综合五月色丁香 | 国产亚洲一区 | 日韩在线免费高清视频 | 天天干,夜夜操 | 欧美色图视频一区 | 久久96国产精品久久99漫画 | 日韩三级一区 | 在线激情小视频 | 日韩电影在线观看一区二区三区 | 一级片免费观看 | 国产视频首页 | 91亚洲精品久久久蜜桃借种 | 国产精品毛片一区视频播 | 特及黄色片 | 国产精品综合久久久 | 伊人激情综合 | 天天做天天爱天天综合网 | 在线观看色视频 | 九色精品免费永久在线 | 日本精品视频在线播放 | 日本精品视频免费 | 日本精品一 | 精品国产黄色片 | 国产特级毛片aaaaaa毛片 | 日韩av片免费在线观看 | 国产亚洲精品中文字幕 | 亚洲黄色在线观看 | 国产电影黄色av | 97在线超碰| 国产网站av| 在线国产小视频 | 色99导航| 国产精品久久久久国产精品日日 | www.干| 国内精品久久久久久久影视麻豆 | 国产精品免费一区二区三区在线观看 | 亚洲免费激情 | 欧产日产国产69 | 最新av网址在线 | 国产精品免费视频网站 | 亚洲精品视 | 久久精品免费电影 | 国产黄色在线观看 | 91正在播放| 久久6精品 | 色婷婷九月| 国产精品电影一区 | 狠狠地操 | 国产精品国产三级国产aⅴ无密码 | 日韩区在线观看 | 18网站在线观看 | 99久热在线精品视频观看 | 99视频免费 | 99精品视频在线 | 视频一区二区在线 | 黄色片亚洲 | www.777奇米 | 国产成人在线观看 | 国产精品久久久久久av | 久久久首页 | 日本三级不卡视频 | www婷婷| 99视频| 激情开心站 | 亚洲一级黄色 | 麻豆极品 | 欧美日韩xxx| 欧美日韩高清一区 | 国产精品九九九九九九 | 狠狠躁夜夜av| 五月丁色 | 天天天天色综合 | 亚洲精品视频中文字幕 | 九九久久电影 | 欧洲精品视频一区二区 | 99视频精品全国免费 | 91视频网址入口 | 日本特黄一级 | 成人网在线免费视频 | 六月色丁| 在线一区二区三区 | 久久久久国产精品一区二区 | 日本美女xx | 在线播放精品一区二区三区 | 国产在线91精品 | 免费观看一级成人毛片 | 最新av观看 | 日本黄色免费观看 | 色婷婷综合久久久 | 91一区啪爱嗯打偷拍欧美 | 精品在线观看一区二区三区 | 激情文学综合丁香 | 久久久久久精 | 免费一级片视频 | 黄色一级在线视频 | 美女久久久久久久久久 | ww亚洲ww亚在线观看 | 97超级碰碰碰视频在线观看 | 国产中的精品av小宝探花 | av在线观 | 色视频 在线 | 亚洲激情视频在线观看 | 亚洲视频 视频在线 | 欧美va日韩va | 在线观看完整版 | 一区二区视频网站 | 欧美另类成人 | 国产精品一区二区三区在线 | 国产女人40精品一区毛片视频 | 久久久国产精华液 | 一区二区成人国产精品 | 久香蕉| 日本久久高清视频 | 久久99亚洲精品久久 | 久久免费视频6 | 日韩理论电影在线 | 伊人色综合久久天天网 | 婷婷丁香av | 91综合在线| 日韩免费观看av | 免费看的黄网站 | 超碰免费观看 | 久久精品欧美一 | 精品久久久久久亚洲综合网 | 91人人澡| 五月天婷婷在线视频 | 中文字幕在线第一页 | 国产精品网红直播 | 国产精品丝袜在线 | 中文字幕在线播放一区二区 | 最近中文字幕大全中文字幕免费 | 久久综合九色综合97_ 久久久 | 久久久毛片 | 特级aaa毛片 | 91黄色在线观看 | 免费成人短视频 | 91九色成人蝌蚪首页 | 久久久免费| 亚洲黄色区 | av中文字幕免费在线观看 | 色久网| 在线观看黄色av | 一区二区三区免费 | 免费不卡中文字幕视频 | 久久专区 | 在线视频欧美精品 | 午夜精品一区二区三区在线视频 | 在线精品播放 | 午夜精品999| av天天在线观看 | 97国产视频| 国产综合在线观看视频 | 黄色免费网站下载 | 色视频成人在线观看免 | 国产三级香港三韩国三级 | 丁香久久综合 | 99久久婷婷国产精品综合 | 人人澡人摸人人添学生av | 亚洲综合欧美激情 | 国产精品69av | 91桃色免费视频 | 在线观看国产 | 黄色在线观看免费 | 国产精品h在线观看 | www.久久精品视频 | 久久这里只有精品首页 | 色视频网址 | 国产在线播放一区二区三区 | 国产视频不卡 | 色婷婷综合久久久久 | 在线色亚洲 | 国产成人免费网站 | av在线短片 | 免费三级影片 | 麻豆一区在线观看 | 国产精品99久久免费观看 | 人人爽夜夜爽 | 黄色大片日本免费大片 | 国产香蕉久久 | 国产99久久久精品视频 | 91黄色免费网站 | 国内精品久久久久影院日本资源 | 久久久久久国产一区二区三区 | 超碰电影在线观看 | 丝袜美腿亚洲 | 免费观看久久 | 超碰在线最新网址 | 国产亚洲精品综合一区91 | 天天射色综合 | 亚洲天天摸日日摸天天欢 | 日韩最新av在线 | 国产一区二区在线免费观看 | 国产精品女同一区二区三区久久夜 | 久草在线视频免费资源观看 | 午夜精品久久久久久久99 | 又紧又大又爽精品一区二区 | 久久精品日产第一区二区三区乱码 | 四虎在线免费观看视频 | 五月天丁香视频 | 人人爽人人香蕉 | 在线观看国产区 | 久久爽久久爽久久av东京爽 | 精品嫩模福利一区二区蜜臀 | 免费午夜在线视频 | 久久国产一二区 | 国产一区二区久久 | 免费日p视频 | 欧美午夜寂寞影院 | 精品视频免费久久久看 | 精品福利av | 狠狠色丁香婷婷 | 国产精品乱码高清在线看 | 天天躁天天躁天天躁婷 | 久久婷婷精品视频 | 91亚洲网 | 色婷婷激情 | 果冻av在线 | 欧美在线观看禁18 | 日韩国产欧美在线播放 | 国产手机av | 婷婷激情综合五月天 | 国产一区网址 | 亚洲视频 一区 | 国产精品欧美久久久久无广告 | 黄a在线观看 | 中文字幕一区二区三区久久蜜桃 | av电影免费在线 | 福利一区在线视频 | 99se视频在线观看 | 91av视频播放| 久久久精品国产一区二区电影四季 | 久久好看免费视频 | 波多野结衣综合网 | 国产在线va | 久久夜色精品国产欧美一区麻豆 | 婷婷六月丁 | 99久久国产免费,99久久国产免费大片 | 九九视频免费观看视频精品 | 欧美大片aaa | 久久视奸 | 欧美一级片在线观看视频 | 亚洲精品视频在 | 欧美日韩在线免费视频 | 亚在线播放中文视频 | www日韩在线观看 | 久久精品成人 | 最新国产一区二区三区 | 狠狠狠的干 | 免费视频资源 | 中文字幕视频播放 | 久久免费视频7 | 天天干夜夜爽 | 中文字幕在线观看免费高清完整版 | 69精品视频 | 在线v片免费观看视频 | 亚洲精品在线观看中文字幕 | 欧美日韩午夜在线 | 六月丁香综合网 | 日韩免费在线观看视频 | 久精品视频免费观看2 | 国产精品久久久久久久久久不蜜月 | a天堂最新版中文在线地址 久久99久久精品国产 | 国产精品男女视频 | 在线观看亚洲国产精品 | 欧亚日韩精品一区二区在线 | 9热精品 | 亚洲自拍自偷 | www.狠狠色 | 波多野结衣在线视频一区 | 日韩精品一区二 | 81国产精品久久久久久久久久 | 久草免费在线观看视频 | 色就是色综合 | 亚洲成人频道 | 欧美另类高清 | 日韩一级精品 | 日韩一二区在线 | 久久涩视频 | 亚洲精品国偷拍自产在线观看蜜桃 | 日韩精品一区在线播放 | 国产精品久久99综合免费观看尤物 | 91少妇精拍在线播放 | 欧美aa一级 | 99re8这里有精品热视频免费 | 欧美 激情 国产 91 在线 | 久久免费中文视频 | 欧美ⅹxxxxxx | av黄色成人 | 在线观看蜜桃视频 | 中文字幕国语官网在线视频 | 亚洲乱码在线观看 | 日韩欧美精品在线观看视频 | 少妇bbb搡bbbb搡bbbb′ | 在线观看激情av | av电影久久 | 久草免费新视频 | 国产精品自产拍在线观看桃花 | 在线观看www视频 | 麻豆国产精品一区二区三区 | 久久久久久久网站 | 一区av在线播放 | 日日夜夜狠狠操 | 国产中年夫妇高潮精品视频 | 日韩电影久久久 | 九九有精品 | 97视频人人免费看 | 天堂av官网| 91黄色在线观看 | 国模一区二区三区四区 | 亚洲天堂色婷婷 | 久久久久久久久久福利 | 毛片随便看 | 99久久精品免费看国产免费软件 | 99久久精品无免国产免费 | 国产精品久久久久久久久费观看 | 日本黄色免费大片 | 久久久免费观看 | 九九色视频 | 亚洲性xxxx | 国产色拍 | 五月色丁香 | 国产91九色视频 | 国产精品理论视频 | 久草99| 中文字幕传媒 | 欧美精品中文 | 三上悠亚一区二区在线观看 | 国内精品久久久 | 国产小视频在线免费观看视频 | 99热999| 一区二区三区在线免费观看视频 | 最近中文字幕大全中文字幕免费 | 国产精品久久久久久久久久久免费 | 毛片一区二区 | 国产精品免费久久久久久久久久中文 | 草久久久久 | 亚洲在线视频免费观看 | 亚洲2019精品 | 最近中文字幕免费视频 | 久久久99精品免费观看乱色 | 4438全国亚洲精品在线观看视频 | 高清有码中文字幕 | 一区二区三区四区五区六区 | 免费观看福利视频 | 国产精品乱码高清在线看 | 日韩毛片在线免费观看 | 九九有精品| 亚洲欧美成人在线 | .精品久久久麻豆国产精品 亚洲va欧美 | 麻豆国产精品视频 | 国产高清久久 | 91高清免费在线观看 | 93久久精品日日躁夜夜躁欧美 | 国产亚洲日 | 国产黄在线免费观看 | 天天干天天摸天天操 | 国产精品毛片久久久久久久久久99999999 | 精品美女国产在线 | 日韩亚洲国产精品 | 精品亚洲男同gayvideo网站 | 色资源二区在线视频 | 免费看v片 | 精品久久久久久综合 | 狠狠狠狠狠狠狠干 | 97人人艹| 99精品视频在线观看播放 | 日日夜夜操操操操 | 99综合久久| av专区在线 | 天天射天天爽 | 国产精品专区一 | 91插插视频 | 九九热视频在线播放 | 中文字幕在线影院 | 四虎影视欧美 | 久久露脸国产精品 | 91完整视频 | 欧美资源在线观看 | 日韩av一区二区三区 | 亚洲国产天堂av | 美女免费视频一区二区 | 久久伊人热 | 成人超碰97 | 色在线最新 | 久久观看| 97精品电影院 | 国产精彩在线视频 | 青青草视频精品 | 狠狠色狠狠色合久久伊人 | 三级在线视频播放 | 国产精品电影在线 | 日韩资源在线观看 | 中文字幕乱视频 | 中文字幕日韩国产 | 奇米影视8888 | 色欧美日韩 | 国产精品美女视频网站 | 免费看成年人 | 久久综合婷婷国产二区高清 | 六月丁香激情综合色啪小说 | 亚洲精品影视 | 丁香5月婷婷久久 | 国产精品99久久久精品免费观看 | 免费看片黄色 | 精品国产理论 | 中文字幕日韩国产 | 国产一区二区视频在线播放 | 婷婷综合电影 | 福利在线看片 | 欧美日在线 | 在线视频你懂 | 国产色综合天天综合网 | 99久久精品国产一区 | 亚洲视屏| 国产精品高清在线观看 | 国产亚洲午夜高清国产拍精品 | 成人毛片一区 | 天操夜夜操 | 高清中文字幕av | 日韩欧美大片免费观看 | 精品国产一区二区三区四区vr | 99热这里只有精品久久 | 欧美成人性战久久 | 亚洲国产午夜视频 | 粉嫩av一区二区三区入口 | 99综合久久 | 香蕉视频在线观看免费 | 国产91精品看黄网站 | 色综合天天在线 | 国产精品18久久久 | 激情综合色综合久久综合 | 美女网站视频免费黄 | 久久a热6| 久久久久国产免费免费 | 日本久久久亚洲精品 | 99热国产精品| 成人欧美一区二区三区在线观看 | 麻豆国产精品永久免费视频 | 成人免费在线看片 | 久草在线免费资源 | 一级黄色片在线免费观看 | 手机看片久久 | 91女人18片女毛片60分钟 | 国产aaa免费视频 | sesese图片 | 久久国内精品视频 | 国产精品剧情在线亚洲 | 亚洲经典视频 | 久久久久久99精品 | 成人免费xxx在线观看 | 日韩电影久久久 | 久久久91精品国产一区二区精品 | 日韩中文字幕亚洲一区二区va在线 | 欧美在线视频一区二区三区 | 亚洲午夜av | 成人久久国产 | 黄av免费 | 中文字幕乱码一区二区 | 久草在线这里只有精品 | 高清色免费 | 国产精品va在线 | 三级动态视频在线观看 | 91视频麻豆| 91麻豆精品国产自产在线 | 亚洲一区美女视频在线观看免费 | 久久精品视频在线免费观看 | 欧美-第1页-屁屁影院 | 久久超| 欧美一二三视频 | 97精品超碰一区二区三区 | 国产精品一区二区在线观看 | 日韩免费播放 | 久久久久久久久福利 | 三级性生活视频 | 久久久久国产精品免费网站 | 婷婷深爱激情 | 中文字幕av一区二区三区四区 | 中国一级片免费看 | 国产九色在线播放九色 | 一区二区中文字幕在线播放 | 欧美一区二区三区激情视频 | 欧美成人精品欧美一级乱黄 | 亚洲国产欧洲综合997久久, | 天天插一插| 成年人免费在线播放 | 日本精品久久久一区二区三区 | 天天干,狠狠干 | 日本久久久精品视频 | 亚洲作爱 | 三上悠亚一区二区在线观看 | 国产黄色一级片 | 婷婷色伊人 | 日本公乱妇视频 | 在线视频你懂 | 一区二区三区四区五区在线视频 | 免费手机黄色网址 | 波多野结衣视频一区二区 | 精品国产一区二区三区不卡 | 五月婷婷中文 | 黄色毛片在线 | 天天综合色网 | 日韩欧美在线视频一区二区 | 国产亚洲视频中文字幕视频 | 97视频在线观看成人 | 五月婷婷六月丁香激情 | 99国内精品 | 日韩在线理论 | 免费开视频 | 婷婷九月丁香 | 天天干.com| 亚洲综合视频在线 | 热久久免费视频精品 | 久久综合狠狠狠色97 | 人人看97| 深爱激情开心 | 国产成人一区二区三区久久精品 | 日韩高清av | 国产男女免费完整视频 | www.夜色321.com| 久久不见久久见免费影院 | 中文字幕色婷婷在线视频 | 精品综合久久 | 国产精品久久久久久久久久久久午夜 | 中文字幕在线影视资源 | 久久美女精品 | 一区二区免费不卡在线 | 97成人资源| 久久久在线视频 | 国产精品久久久久999 | 欧美一级黄色视屏 | 2019天天干天天色 | 天天色天天射综合网 | 久草在线一免费新视频 | 亚洲精区二区三区四区麻豆 | 国产精品ⅴa有声小说 | 91日本在线播放 | 在线欧美a| 三级a毛片 | 91麻豆精品久久久久久 | 97激情影院| 久久99精品久久久久久久久久久久 | 亚洲一区黄色 | www..com黄色片 | 午夜视频在线观看一区 | 国产精品麻豆99久久久久久 | 中文在线天堂资源 | 日韩精品视频免费专区在线播放 | 99久久99久久精品国产片果冰 | 欧美一级片在线播放 | 国产中文字幕一区 | 欧美日韩在线观看一区 | 99色免费 | 国产成人亚洲在线观看 | 九九热视频在线播放 | 国产精品9999久久久久仙踪林 | 成人黄色毛片视频 | 国产精品欧美精品 | 91污在线观看 | 亚洲精品1234区 | 一级性av | 中文字幕影视 | 在线免费黄色片 | 国产精品免费在线视频 | 夜夜夜草 | 久久综合天天 | 亚洲成人频道 | 激情av在线资源 | 国产精品欧美久久久久天天影视 | 亚洲黄色网络 | 粉嫩高清一区二区三区 | 在线国产小视频 | 97精品国产97久久久久久久久久久久 | 日韩中文字幕亚洲一区二区va在线 | 99色| 99久久精品久久久久久动态片 | 国产又粗又长又硬免费视频 | 久久不射电影院 | 99热.com | 五月天激情在线 | 91在线超碰| 91福利社区在线观看 | 性色视频在线 | 国产精品久久久久久一区二区 | av在线专区| 国产精品视频免费 | 国产亚洲精品无 | 91视频在线免费下载 | 少妇性色午夜淫片aaaze | 一区二区三区在线免费播放 | 国产精品久久久久久模特 | 精品国产成人av | 免费亚洲黄色 | 菠萝菠萝蜜在线播放 | 一区二区视频网站 | 五月花婷婷 | 亚洲精品美女在线观看播放 | 中文字幕在线看视频国产 | 色国产精品一区在线观看 | 欧美一级特黄aaaaaa大片在线观看 | 亚洲1级片 | 在线观看完整版 | 中文字幕在线中文 | 成人h动漫精品一区二 | 免费在线国产视频 | av看片在线| 免费黄色一区 | 欧美 国产 视频 | 少妇资源站 | 热re99久久精品国产66热 | 黄色成人av网址 | 午夜精品导航 | 91精品久久久久久久久久入口 | 日日夜操 | 91成人免费在线视频 | 日日干日日操 | 日韩特黄av | 一区二区三区在线免费播放 | 综合在线观看 | 国内久久久| 亚洲精品91天天久久人人 | 国产成人精品在线观看 | 日本免费久久高清视频 | 欧美一级电影免费观看 | 欧美色噜噜| www.亚洲| 久久国产影院 | 成人av电影免费在线播放 | 亚洲一区 av| 国产手机视频在线观看 | 伊人中文网 | 欧美色婷 | 最近2019中文免费高清视频观看www99 | 男女激情免费网站 | 天天操天天谢 | 国产99久久久久 | 91av在线视频免费观看 | 日韩av伦理片 | 亚洲视频高清 | 色狠狠综合 | 婷婷成人在线 | 久久久久久久久免费视频 | 999国内精品永久免费视频 | 亚洲国产午夜 | 91麻豆产精品久久久久久 | 国内小视频 | bbbb操bbbb | 国产精品久久久久久久妇 | 免费观看www7722午夜电影 | 97超碰超碰 | 亚洲精品在线看 | 成人免费在线视频 | 久久夜色精品国产欧美乱极品 | 麻豆视频在线看 | 国产麻豆视频在线观看 | 97偷拍视频 | 美女久久久 | 亚洲激情 在线 | 国产一区二区高清不卡 | 国产在线精品区 | 亚洲黄色影院 | 国产破处精品 | 91自拍视频在线 | 不卡的一区二区三区 | 亚洲精品久久久久久久不卡四虎 | 亚洲精品电影在线 | 精品在线一区二区三区 | 久久精品2 | 国产精品永久免费观看 | 国产精品久久久久久久久久久久久久 | 亚洲精品一区二区精华 | 成人国产精品久久久 | 伊人天天干| 午夜精品久久久久久久99无限制 | 97精品超碰一区二区三区 | 国产直播av| 久久久免费看片 | 欧美不卡在线 | 亚洲在线视频播放 | 最近中文字幕第一页 | 在线视频中文字幕一区 | 最新中文字幕视频 | 97在线观 | 精品久久久99 | 中文字幕亚洲精品在线观看 | 黄色网址国产 | 正在播放 国产精品 | 国产不卡免费视频 | 国产精品区免费视频 | 黄色官网在线观看 | 午夜精品久久久久久久爽 | 亚洲视频1 | 中文字幕在线播放av | 午夜精品久久久 | 久久久久伦理电影 | www.eeuss影院av撸 | 国产91精品看黄网站在线观看动漫 | 一区二区视频在线免费观看 | 婷婷色网 | 国产在线播放一区二区三区 | 四虎www.| 伊人五月婷| 久久久国产99久久国产一 | 成年人免费在线播放 | 99久久精品无码一区二区毛片 | 久久精品香蕉视频 | 欧美日韩天堂 | 黄色av一级片 | 欧美成人基地 | 美女网站在线观看 | 久久久久久久免费看 | 一区二区三区在线免费观看 | 日韩一区二区免费在线观看 | 日韩免费成人av | 免费在线日韩 | 成年人看片网站 | 天天干夜夜爱 | 韩国一区二区三区在线观看 | 欧美黄网站 | 欧美精品免费在线观看 | 国产自制av | 日日爱网址 | 中文字幕在线播放一区 | 狠狠干成人综合网 | 五月天久久激情 | 日韩三级av| 欧美一级片在线播放 | 色婷五月天| 日韩电影在线观看一区二区 | 亚洲视频免费在线看 | 13日本xxxxxⅹxxx20| 黄色在线观看免费 | va视频在线| 伊人国产在线观看 | 右手影院亚洲欧美 | 婷婷去俺也去六月色 | 国产精品美女免费 | 午夜精品一区二区三区四区 | 久久久999精品视频 国产美女免费观看 | 在线看国产日韩 | 国产免费视频一区二区裸体 | 不卡视频在线看 | 贫乳av女优大全 | 97成人在线| 国产精品国产亚洲精品看不卡15 | 亚洲午夜精品一区二区三区电影院 | 日韩资源在线观看 | 黄色一级免费电影 | 午夜av剧场 | 欧美日韩伦理一区 | 五月婷婷一区二区三区 | www.亚洲视频.com | 国产精品99精品 | 91福利在线观看 | 综合久久2023 | 韩国av一区二区 | 99热精品国产一区二区在线观看 | 午夜精品一区二区三区可下载 | 欧美日韩18 | 天天拍天天干 | 日躁夜躁狠狠躁2001 | 国产精品美女毛片真酒店 | 久久激情五月婷婷 | 波多野结衣视频一区 | 精品嫩模福利一区二区蜜臀 | 亚洲精品视频第一页 | 欧美激情综合色 | 日韩羞羞 | 99这里只有 | 在线a视频免费观看 | 六月丁香综合网 | 国产亚洲久一区二区 | 9色在线视频 | 日韩在线一区二区免费 | 91免费试看 | 99热在线国产精品 | 人人干网| 日韩免费一区二区在线观看 | 成人毛片网 | 99精品视频在线观看播放 | 欧美精品久久久久久久久久久 | 国产视频亚洲精品 | 成人国产网站 | 精品久久一区 | 最新色视频 | 久久男人影院 | 91黄色成人 | 在线亚洲天堂网 |