Skip to content

中断

linux并没有利用分段实现虚拟内存, 但是却利用了dpl功能实现了权限控制. 用户态DPL是3, 内核态DPL是0, 当用户态程序 CPL为3时直接访问内核态的地址时,会因权限不足而报错。所以要通过syscal, int等特别指令触发切换, 这些指令会改变CPL值

通过如下两个函数注册中断, irq是OS软件层面的概念,系统运行过程中唯一对应一个中断,硬件并不清楚这个值。在注册中断的过程中,在x86上面会申请vector,与irq对应的irq_desc结构体关联。

c
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
	    const char *name, void *dev)
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)

下面是注册中断时,申请并激活vector的函数调用关系。在assign_vector_locked里申请一个新的vetor, 在apic_update_vector里 通过语句 per_cpu(vector_irq, newcpu)[newvec] = desc; 将irq对应的结构体irq_desc与cpu, vector关联。到这里只是完成软件 层面的关联, 后续通过x86_vector_msi_compose_msg将这些信息写入到硬件acpi里。

./efunc trace -e "request_threaded_irq" -a ":arch/x86/kernel/apic/*"
TIME: 19:33:00.781051 -> 19:33:00.782405 PID/TID: 101632/101632 (kworker/u8:3 kworker/u8:3) 
 CPU   DURATION | FUNCTION GRAPH
 ---   -------- | --------------
  0)            | → request_threaded_irq irq=38 handler=0xffffffffabe27330 thread_fn=0x0 irqflags=2097152 devname=0xffff891fc47d6c00 dev_id=0xffff891feae2e900 
  0)            |   → x86_vector_activate dom=0xffff891fc01db300 irqd=0xffff891fc40c9ea0 reserve=false 
  0)            |     → assign_irq_vector_any_locked irqd=0xffff891fc40c9ea0 
  0)            |       → assign_vector_locked irqd=0xffff891fc40c9ea0 dest=0xffff891fc0069f30 
  0)      476ns |         ↔ apic_update_vector irqd=0xffff891fc40c9ea0 newvec=42 newcpu=0 ret=void
  0)            |         → apic_update_irq_cfg irqd=0xffff891fc40c9ea0 vector=42 cpu=0 
  0)      365ns |           ↔ apic_default_calc_apicid cpu=0 ret=0
  0)    1.986µs |         ← apic_update_irq_cfg ret=void
  0)    4.909µs |       ← assign_vector_locked ret=0
  0)    6.193µs |     ← assign_irq_vector_any_locked ret=0
  0)    7.618µs |   ← x86_vector_activate ret=0
  0)            |   → x86_vector_msi_compose_msg data=0xffff891fc40c9ea0 msg=0xffffac94471e36a8 
  0)      412ns |     ↔ __irq_msi_compose_msg cfg=0xffff891fc32c8100 msg=0xffffac94471e36a8 dmar=false ret=void
  0)    1.217µs |   ← x86_vector_msi_compose_msg ret=void
  0)            |   → msi_set_affinity irqd=0xffff891fc6badc28 mask=0xffffffffae480e40 force=false 
  0)      351ns |     ↔ irqd_cfg irqd=0xffff891fc6badc28 ret=0xffff891fc32c8100
  0)            |     → apic_set_affinity irqd=0xffff891fc40c9ea0 dest=0xffffffffae480e40 force=false 
  0)      417ns |       ↔ assign_vector_locked irqd=0xffff891fc40c9ea0 dest=0xffff891fc0069f30 ret=0
  0)    1.222µs |     ← apic_set_affinity ret=0
  0)      360ns |     ↔ __irq_msi_compose_msg cfg=0xffff891fc32c8100 msg=0xffffac94471e3638 dmar=false ret=void
  0)   25.806µs |   ← msi_set_affinity ret=0
  0)            |   → __sysvec_apic_timer_interrupt regs=0xffffac94471e3658 
  0)    3.308µs |     ↔ lapic_next_deadline delta=236268 evt=0xffff8920e8c20140 ret=0
  0)    6.189µs |   ← __sysvec_apic_timer_interrupt ret=void
  0) 1.353121ms | ← request_threaded_irq ret=0

当中断发生时, 获悉触发中断的cpu和vector的前提下, 主要的函数调用流程如下,多个irqaction可以共享中断, 一个irq对应一个irq_desc, 一个irq_desc可以有多个irqaction。

c
__common_interrupt
	desc = __this_cpu_read(vector_irq[vector]); // 获取desc
	--> handle_irq(desc, regs);  //处理具体的中断
        --> desc->handle_irq(desc);
            --> handle_edge_irq
                --> handle_irq_event
                    --> __handle_irq_event_percpu
                    	for_each_action_of_desc(desc, action) {  
                        trace_irq_handler_entry(irq, action);
                        res = action->handler(irq, action->dev_id);
                        trace_irq_handler_exit(irq, action, res);
                        }

irq中断号与irq_desc指针的关系存储在全局变量irq_desc_tree里, 结构式是xarray, 在crash里可使用下面命令查询。

tree -t xarray irq_desc_tree -s irq_desc

参考: https://cloud.tencent.com/developer/article/1518703

时间

/sys/devices/system/clocksource/clocksource0/current_clocksource 显示当前使用的时钟源 /sys/devices/system/clocksource/clocksource0/available_clocksource 显示当前可用的时钟源 x86的虚拟机一般使用tsc, 这个是intelCPU自带的,比kvm-clock更准确。
软中断里关于时钟中断有两个, TIMER 和 HRTIMER。TIMER 的单位是jiffies(在HZ为1000的情况下是1ms), HRTIMER是高精度的,单位是1ns。

TIMER里有两个重要的结构体, timer_list timer_base

c
struct timer_list {
    struct hlist_node entry;
    unsigned long expires;
    void (*function)(struct timer_list *);
    u32 flags;
    unsigned long rh_reserved1;
    unsigned long rh_reserved2;
}

struct timer_base {
	raw_spinlock_t		lock;
	struct timer_list	*running_timer;
	unsigned long		clk;
	unsigned long		next_expiry;
	unsigned int		cpu;
	bool			next_expiry_recalc;
	bool			is_idle;
	bool			timers_pending;
	DECLARE_BITMAP(pending_map, WHEEL_SIZE);
	struct hlist_head	vectors[WHEEL_SIZE];
} ____cacheline_aligned;

timer_list 代表一个定时器,定义了下一次过期时间和要执行的函数。 timer_base,每个cpu上面有两个,每个定时器都挂在base上面,触发 软中断后,就会遍历上面的定时器,谁过期了就执行谁。

时钟源现在基本都是高精度的,单位是1ns。 它不受HZ的控制,可根据当前所有的定时器的实际过期时间动态调整频率。 用于统计进程cpu信息, 是否需要抢占的调度中断运行在高精度的时间中断里。调用栈如下:

TIME: 22:14:28.862008 -> 22:14:28.862010 PID/TID: 0/0 (swapper/1 swapper/1) 
 CPU   DURATION | FUNCTION GRAPH
 ---   -------- | --------------
  1)      981ns | ↔ account_process_tick p=0xffff891fc02e8000 user_tick=0 ret=void

 (inline) run_local_timers                                      kernel/time/timer.c:1999
update_process_times+0x18                                       kernel/time/timer.c:2024
tick_sched_handle+0x21                                          kernel/time/tick-sched.c:256
tick_nohz_highres_handler+0x6d                                  kernel/time/tick-sched.c:1523
 (inline) __run_hrtimer                                         kernel/time/hrtimer.c:1813
__hrtimer_run_queues+0x10f                                      kernel/time/hrtimer.c:1877
hrtimer_interrupt+0xff                                          kernel/time/hrtimer.c:1942
 (inline) local_apic_timer_interrupt                            arch/x86/kernel/apic/apic.c:1044
__sysvec_apic_timer_interrupt+0x51                              arch/x86/kernel/apic/apic.c:1061
sysvec_apic_timer_interrupt+0x6d                                arch/x86/kernel/apic/apic.c:1055
asm_sysvec_apic_timer_interrupt+0x16                            arch/x86/include/asm/idtentry.h:649
cpuidle_enter_state+0xbc                                        drivers/cpuidle/cpuidle.c:292
cpuidle_enter+0x29                                              drivers/cpuidle/cpuidle.c:391
cpuidle_idle_call+0xf8                                          kernel/sched/idle.c:213
do_idle+0x77                                                    kernel/sched/idle.c:307
cpu_startup_^C
hrtimer_interrupt+0xff                                          kernel/time/hrtimer.c:1942
 (inline) local_apic_timer_interrupt                            arch/x86/kernel/apic/apic.c:1044
__sysvec_apic_timer_interrupt+0x51                              arch/x86/kernel/apic/apic.c:1061
sysvec_apic_timer_interrupt+0x6d                                arch/x86/kernel/apic/apic.c:1055
asm_sysvec_apic_timer_interrupt+0x16                            arch/x86/include/asm/idtentry.h:649
cpuidle_enter_state+0xbc                                        drivers/cpuidle/cpuidle.c:292
cpuidle_enter+0x29                                              drivers/cpuidle/cpuidle.c:391
cpuidle_idle_call+0xf8                                          kernel/sched/idle.c:213
do_idle+0x77                                                    kernel/sched/idle.c:307
cpu_startup_entry+0x25                                          kernel/sched/idle.c:402
 (inline) ap_starting                                           arch/x86/kernel/smpboot.c:206
start_secondary+0x115                                           arch/x86/kernel/smpboot.c:286
secondary_startup_64_no_verify+0x187                            arch/x86/kernel/head_64.S:462

```hrtimer_interrupt`函数主要工作如下:

c
void hrtimer_interrupt(struct clock_event_device *dev)
{
	struct hrtimer_cpu_base *cpu_base = this_cpu_ptr(&hrtimer_bases); //或许当前cpu的 base

	if (!ktime_before(now, cpu_base->softirq_expires_next)) {
		cpu_base->softirq_expires_next = KTIME_MAX;
		cpu_base->softirq_activated = 1;
		raise_hrtimer_softirq();
	}   // 如果hrtimer的软中断也过期了则触发

	__hrtimer_run_queues(cpu_base, now, flags, HRTIMER_ACTIVE_HARD);   //遍历所有定时器,如果过期就执行。

	/* Reevaluate the clock bases for the [soft] next expiry */
	expires_next = hrtimer_update_next_event(cpu_base);
	tick_program_event(expires_next, 1);    //动态控制硬件下次触发中断的周期。 
}

hrtimer 使用红黑树管理所有的定时器。

Released under the MIT License.