検索条件
全5件
(1/1ページ)
/* initcalls are now grouped by functionality into separate * subsections. Ordering inside the subsections is determined * by link order. * For backwards compatibility, initcall() puts the call in * the device init subsection. * * The `id' arg to __define_initcall() is needed so that multiple initcalls * can point at the same handler without causing duplicate-symbol build errors. */ #define __define_initcall(level,fn,id) \ static initcall_t __initcall_##fn##id __used \ __attribute__((__section__(".initcall" level ".init"))) = fn /* * Early initcalls run before initializing SMP. * * Only for built-in code, not modules. */ #define early_initcall(fn) __define_initcall("early",fn,early) /* * A "pure" initcall has no dependencies on anything else, and purely * initializes variables that couldn't be statically initialized. * * This only exists for built-in code, not for modules. */ #define pure_initcall(fn) __define_initcall("0",fn,0) #define core_initcall(fn) __define_initcall("1",fn,1) #define core_initcall_sync(fn) __define_initcall("1s",fn,1s) #define postcore_initcall(fn) __define_initcall("2",fn,2) #define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s) #define arch_initcall(fn) __define_initcall("3",fn,3) #define arch_initcall_sync(fn) __define_initcall("3s",fn,3s) #define subsys_initcall(fn) __define_initcall("4",fn,4) #define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s) #define fs_initcall(fn) __define_initcall("5",fn,5) #define fs_initcall_sync(fn) __define_initcall("5s",fn,5s) #define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs) #define device_initcall(fn) __define_initcall("6",fn,6) #define device_initcall_sync(fn) __define_initcall("6s",fn,6s) #define late_initcall(fn) __define_initcall("7",fn,7) #define late_initcall_sync(fn) __define_initcall("7s",fn,7s)
#define INITCALLS \ *(.initcallearly.init) \ VMLINUX_SYMBOL(__early_initcall_end) = .; \ *(.initcall0.init) \ *(.initcall0s.init) \ *(.initcall1.init) \ *(.initcall1s.init) \ *(.initcall2.init) \ *(.initcall2s.init) \ *(.initcall3.init) \ *(.initcall3s.init) \ *(.initcall4.init) \ *(.initcall4s.init) \ *(.initcall5.init) \ *(.initcall5s.init) \ *(.initcallrootfs.init) \ *(.initcall6.init) \ *(.initcall6s.init) \ *(.initcall7.init) \ *(.initcall7s.init) #define INIT_CALLS \ VMLINUX_SYMBOL(__initcall_start) = .; \ INITCALLS \ VMLINUX_SYMBOL(__initcall_end) = .; ... #define INIT_DATA_SECTION(initsetup_align) \ .init.data : AT(ADDR(.init.data) - LOAD_OFFSET) { \ INIT_DATA \ INIT_SETUP(initsetup_align) \ INIT_CALLS \ CON_INITCALL \ SECURITY_INITCALL \ INIT_RAM_FS \ } ...以上のマクロ・ldscriptから、"__initcall_start"でアドレスを参照できますね。
extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[]; static void __init do_initcalls(void) { initcall_t *fn; for (fn = __early_initcall_end; fn < __initcall_end; fn++) do_one_initcall(*fn); } ... static void __init do_basic_setup(void) { cpuset_init_smp(); usermodehelper_init(); init_tmpfs(); driver_init(); init_irq_proc(); do_ctors(); do_initcalls(); } ... static void __init do_pre_smp_initcalls(void) { initcall_t *fn; for (fn = __initcall_start; fn < __early_initcall_end; fn++) do_one_initcall(*fn); } ... static int __init kernel_init(void * unused) // この子は __init_refok rest_init(void)からthread化される。 { /* * Wait until kthreadd is all set-up. */ wait_for_completion(&kthreadd_done); /* Now the scheduler is fully set up and can do blocking allocations */ gfp_allowed_mask = __GFP_BITS_MASK; /* * init can allocate pages on any node */ set_mems_allowed(node_states[N_HIGH_MEMORY]); /* * init can run on any cpu. */ set_cpus_allowed_ptr(current, cpu_all_mask); cad_pid = task_pid(current); smp_prepare_cpus(setup_max_cpus); do_pre_smp_initcalls(); /// ★early init callに属する関数を実行。 lockup_detector_init(); smp_init(); sched_init_smp(); do_basic_setup(); /// ★init callを実行。early init以外を順次呼び出す. ...
__mmap_switched: b start_kernelFILE: init/main.c
asmlinkage void __init start_kernel(void) { char * command_line; extern const struct kernel_param __start___param[], __stop___param[]; smp_setup_processor_id(); /* * Need to run as early as possible, to initialize the * lockdep hash: */ lockdep_init(); debug_objects_early_init(); /* * Set up the the initial canary ASAP: */ boot_init_stack_canary(); cgroup_init_early(); local_irq_disable(); early_boot_irqs_disabled = true; /* * Interrupts are still disabled. Do necessary setups, then * enable them */ tick_init(); boot_cpu_init(); page_address_init(); printk(KERN_NOTICE "%s", linux_banner); setup_arch(&command_line); //★ARMの場合はヴェクタ設定、メモリ領域の確保、MMU初期設定まで完了する mm_init_owner(&init_mm, &init_task); mm_init_cpumask(&init_mm); setup_command_line(command_line); setup_nr_cpu_ids(); setup_per_cpu_areas(); smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */ build_all_zonelists(NULL); page_alloc_init(); printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line); parse_early_param(); parse_args("Booting kernel", static_command_line, __start___param, __stop___param - __start___param, &unknown_bootoption); /* * These use large bootmem allocations and must precede * kmem_cache_init() */ setup_log_buf(0); pidhash_init(); vfs_caches_init_early(); sort_main_extable(); trap_init(); mm_init(); /* * Set up the scheduler prior starting any interrupts (such as the * timer interrupt). Full topology setup happens at smp_init() * time - but meanwhile we still have a functioning scheduler. */ sched_init(); /* * Disable preemption - early bootup scheduling is extremely * fragile until we cpu_idle() for the first time. */ preempt_disable(); if (!irqs_disabled()) { printk(KERN_WARNING "start_kernel(): bug: interrupts were " "enabled *very* early, fixing it\n"); local_irq_disable(); } idr_init_cache(); perf_event_init(); rcu_init(); radix_tree_init(); /* init some links before init_ISA_irqs() */ early_irq_init(); init_IRQ(); prio_tree_init(); init_timers(); hrtimers_init(); softirq_init(); timekeeping_init(); time_init(); profile_init(); call_function_init(); if (!irqs_disabled()) printk(KERN_CRIT "start_kernel(): bug: interrupts were " "enabled early\n"); early_boot_irqs_disabled = false; local_irq_enable(); kmem_cache_init_late(); /* * HACK ALERT! This is early. We're enabling the console before * we've done PCI setups etc, and console_init() must be aware of * this. But we do want output early, in case something goes wrong. */ console_init(); if (panic_later) panic(panic_later, panic_param); lockdep_info(); /* * Need to run this when irqs are enabled, because it wants * to self-test [hard/soft]-irqs on/off lock inversion bugs * too: */ locking_selftest(); #ifdef CONFIG_BLK_DEV_INITRD if (initrd_start && !initrd_below_start_ok && page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) { printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - " "disabling it.\n", page_to_pfn(virt_to_page((void *)initrd_start)), min_low_pfn); initrd_start = 0; } #endif page_cgroup_init(); enable_debug_pagealloc(); debug_objects_mem_init(); kmemleak_init(); setup_per_cpu_pageset(); numa_policy_init(); if (late_time_init) late_time_init(); sched_clock_init(); calibrate_delay(); pidmap_init(); anon_vma_init(); #ifdef CONFIG_X86 if (efi_enabled) efi_enter_virtual_mode(); #endif thread_info_cache_init(); cred_init(); fork_init(totalram_pages); proc_caches_init(); buffer_init(); key_init(); security_init(); dbg_late_init(); vfs_caches_init(totalram_pages); signals_init(); /* rootfs populating might need page-writeback */ page_writeback_init(); #ifdef CONFIG_PROC_FS proc_root_init(); #endif cgroup_init(); cpuset_init(); taskstats_init_early(); delayacct_init(); check_bugs(); acpi_early_init(); /* before LAPIC and SMP init */ sfi_init_late(); ftrace_init(); /* Do the rest non-__init'ed, we're now alive */ rest_init(); //★ kernel_init()をkthread_startするよ }
func.ptr | 目的 | remark |
---|---|---|
fixup | setup_arch()のほぼ先頭、setup_machine_tags()の後半に呼ばれる。ATAGなど修正入れられる。 | |
reserve | reserve any platform specific memblock areas | setup_arch()の中頃, arm_memblock_init()の後半で呼ばれる。(paging_init()より早い) |
map_io | Ask the machine support to map in the statically mapped devices. | setup_arch()の中頃, paging_init()の先で呼ばれる |
init_early | setup_arch()の最後, start_kernel()から呼ばれる. | |
init_irq | start_kernel()の中頃、init_IRQ()経由。(setup_arch()の呼び出しより後) | |
init_machine | customize_machine()から呼ばれる。 arch_initcall()マクロで括られているので、kernel_init()スレッドから実行。 start_kernelの最後、rest_init()の最後のほう. | |
handle_irq | CONFIG_MULTI_IRQ_HANDLER定義時のみ存在。 setup_arch()のinit_early手前でglobal変数"handle_arch_irq"に保存、irq handlerから呼ばれる。 |
struct machine_desc { unsigned int nr; /* architecture number */ const char *name; /* architecture name */ unsigned long boot_params; /* tagged list */ const char **dt_compat; /* array of device tree 'compatible' strings */ unsigned int nr_irqs; /* number of IRQs */ unsigned int video_start; /* start of video RAM */ unsigned int video_end; /* end of video RAM */ unsigned int reserve_lp0 :1; /* never has lp0 */ unsigned int reserve_lp1 :1; /* never has lp1 */ unsigned int reserve_lp2 :1; /* never has lp2 */ unsigned int soft_reboot :1; /* soft reboot */ void (*fixup)(struct machine_desc *, struct tag *, char **, struct meminfo *); void (*reserve)(void); /* reserve mem blocks */ void (*map_io)(void); /* IO mapping function */ void (*init_early)(void); void (*init_irq)(void); struct sys_timer *timer; /* system tick timer */ void (*init_machine)(void); #ifdef CONFIG_MULTI_IRQ_HANDLER void (*handle_irq)(struct pt_regs *); #endif };FILE: arch/arm/mm/init.c
void __init arm_memblock_init(struct meminfo *mi, struct machine_desc *mdesc) ここの処理の最後に呼び出している。この前に initrd領域もreservedにしてしまう。 /* reserve any platform specific memblock areas */ if (mdesc->reserve) mdesc->reserve();
void __init paging_init(struct machine_desc *mdesc) static void __init devicemaps_init(struct machine_desc *mdesc) ... if (mdesc->map_io) mdesc->map_io(); ここでkernel管理のPage tableを初期化する。 ヴェクタ用pageの確保、high/lo vector処理、VMALLOC_ENDカラ後ろのアクセス禁止設定を行う。
void __init setup_arch(char **cmdline_p) ... paging_init(mdesc); // ★ ioremap設定するので、map_io()はこの先で呼ばれる。 ... if (mdesc->init_early) mdesc->init_early();FILE: arch/arm/kernel/setup.c
static int __init customize_machine(void) { /* customizes platform devices, or adds new ones */ if (machine_desc->init_machine) machine_desc->init_machine(); return 0; } arch_initcall(customize_machine);
filename | remark |
---|---|
00-INDEX | this file |
Booting | requirements for booting |
Interrupts | ARM Interrupt subsystem documentation |
msm | MSM specific documentation |
Netwinder | Netwinder specific documentation |
Porting | Symbol definitions for porting Linux to a new ARM machine. |
Setup | Kernel initialization parameters on ARM Linux |
README | General ARM documentation |
SA1100/ | SA1100 documentation |
Samsung-S3C24XX | S3C24XX ARM Linux Overview |
Sharp-LH | Linux on Sharp LH79524 and LH7A40X System On a Chip (SOC) |
SPEAr | ST SPEAr platform Linux Overview |
VFP/ | Release notes for Linux Kernel Vector Floating Point support code |
empeg/ | Ltd's Empeg MP3 Car Audio Player |
mem_alignment | alignment abort handler documentation |
memory.txt | description of the virtual memory layout |
nwfpe/ | NWFPE floating point emulator documentation |
swp_emulation | SWP/SWPB emulation handler/logging description |
Kernel Memory Layout on ARM Linux
Russell King <rmk@arm.linux.org.uk>
November 17, 2005 (2.6.15)
This document describes the virtual memory layout which the Linux kernel uses for ARM processors.このドキュメントでは、LinuxカーネルがARMプロセッサ用に使用する仮想メモリのレイアウトを記述します。
It indicates which regions are free for platforms to use, and which are used by generic code.
The ARM CPU is capable of addressing a maximum of 4GB virtual memory space,ARM CPUは、最大4GBの仮想メモリ空間アドレッシングを許容します。
and this must be shared between user space processes, the kernel, and hardware devices.
As the ARM architecture matures, it becomes necessary to reserve certain regions of VM space for use for new facilities;ARMアーキテクチャは静寂するにつれて、新しい設備(機能)を使うために、仮想空間の特定の領域を予約する必要が出てきます。
therefore this document may reserve more VM space over time.
Start | End | Use |
---|---|---|
ffff8000 | ffffffff | copy_user_page / clear_user_page で使う。 SA11xx と Xscale では、minicache mappingの設定に使う。 |
ffff4000 | ffffffff | ARMv6アーキ以降の cache aliasing |
ffff1000 | ffff7fff | Reserved. Platformsはこの領域を使ってはいけない |
ffff0000 | ffff0fff | CPU vector page. CPUがヴェクタリロケーションをサポートしていれば、ここにマップされる(Control RegisterのV bitで設定) |
fffe0000 | fffeffff | XScale cache flush area. This is used in proc-xscale.S to flush the whole data cache. (XScale does not have TCM.) |
fffe8000 | fffeffff | DTCM mapping area. for platforms with DTCM mounted inside the CPU. |
fffe0000 | fffe7fff | ITCM mapping area for platforms with ITCM mounted inside the CPU. |
fff00000 | fffdffff | Fixmap mapping region. fix_to_virt()で与えられるアドレスは、ここに配置される。 |
ffc00000 | ffefffff | DMA memory mapping region. the dma_alloc_xxx()で返ってくるメモリが、ここに動的にマップされる。 |
ff000000 | ffbfffff | Reserved for future expansion of DMA mapping region. |
fee00000 | feffffff | Mapping of PCI I/O space. これは、vmalloc空間の静的マッピングです。 |
VMALLOC_START | VMALLOC_END-1 | vmalloc() / ioremap() 空間。 vmalloc(),ioremap()によって返されるメモリは、この領域に動的に配置される。 マシン固有の静的マッピングも、iotable_init()によって、ここに配置される。 VMALLOC_STARTは、high_memory変数の値に基づいて得られる。VMALLOC_ENDは0xff000000である(ヘッダで定義)。 |
PAGE_OFFSET | high_memory-1 | Kernel direct-mapped RAM region. これは、プラットフォームのRAM、通常は、1:1の関係にあるすべてのプラットフォームのRAMを配置します。 CONFIG_PAGE_OFFSETで設定する。一般的な32bit kernelでは 0xC0000000(kernel:user=1GB:3GB設定)となっている。 |
PKMAP_BASE | PAGE_OFFSET-1 | 永続的なkernelマッピング。 カーネル空間にHIGHMEMページを配置する方法の一つ。 |
MODULES_VADDR | MODULES_END-1 | Kernel module space. insmodにより挿入されるKernel modulesは、ここに動的に配置されます。 |
00001000 | TASK_SIZE-1 | User space mappings. スレッド毎のマッピングは、mmap()システムコールにより、ここに配置される。 |
00000000 | 00000fff | CPU vector page / null pointer trap. ヴェクタremapをサポートしていないCPUは、ヴェクタページをここに配置します。 kernel空間・user空間の双方から、NULLポインタデリファレンスもまた、この配置から捕まえられます。 |
Please note that mappings which collide with the above areas may result in a non-bootable kernel,上記領域と衝突するマッピングは、非ブートカーネルになったり、実行時に(最終的に)kernel panic要因となるかもしれないことに、ご注意ください。
or may cause the kernel to (eventually) panic at run time.
Since future CPUs may impact the kernel mapping layout,将来のCPUでは、kernelマッピングレイアウトに影響があるかもしれないので、ユーザプログラムは、0x0001000からTASK_SIZEまでのアドレス範囲から外れたメモリをアクセスしてはならない。
user programs must not access any memory which is not mapped inside their 0x0001000 to TASK_SIZE address range.
If they wish to access these areas,
they must set up their own mappings using open() and mmap().
drivers/clocksource/dw_apb_timer.c
drivers/clocksource/arm_arch_timer.c
drivers/clocksource/arm_global_timer.c
drivers/clocksource/dummy_timer.c
static struct clocksource clocksource_jiffies = { .name = "jiffies", .rating = 1, /* lowest valid rating*/ .read = jiffies_read, .mask = 0xffffffff, /*32bits*/ .mult = NSEC_PER_JIFFY << JIFFIES_SHIFT, /* details above */ .shift = JIFFIES_SHIFT, }; EXPORT_SYMBOL(jiffies); static int __init init_jiffies_clocksource(void) { return clocksource_register(&clocksource_jiffies); } core_initcall(init_jiffies_clocksource); struct clocksource * __init __weak clocksource_default_clock(void) { return &clocksource_jiffies; }
./arch/x86/kernel/setup.c:1215: register_refined_jiffies(CLOCK_TICK_RATE);
struct clocksource { /* * Hotpath data, fits in a single cache line when the * clocksource itself is cacheline aligned. */ cycle_t (*read)(struct clocksource *cs); // clocksourceを引数で渡して、サイクル値を返す。 cycle_t cycle_last; // read()メンバ呼び出しで最近読みだしたcycle値。 cycle_t mask; // 非64bitカウンタの引き算のための2の補数のためのbitmask値。 u32 mult; // cycleからナノ秒の乗数 u32 shift; // cycleからナノ秒の除数(2の累乗) u64 max_idle_ns; // clocksourceによって許容される最大アイドル時間(ナノ秒) u32 maxadj; // 最大調整値?to multiってなんだ。。。(11%以下) #ifdef CONFIG_ARCH_CLOCKSOURCE_DATA struct arch_clocksource_data archdata; // architecture固有のデータ #endif const char *name; // clocksourceの名前 struct list_head list; // システムに登録するためのリストヘッド int rating; // 複数登録された時の選択基準(大きいのが選ばれる) int (*enable)(struct clocksource *cs); // [option] clocksourceを有効にする。 void (*disable)(struct clocksource *cs); // [option] clocksourceを無効にする。 unsigned long flags; // 特別な属性を記述するためのフラグ void (*suspend)(struct clocksource *cs); // 必要ならば、サスペンド機能 void (*resume)(struct clocksource *cs); // 必要ならば、レジューム機能 /* private: */ #ifdef CONFIG_CLOCKSOURCE_WATCHDOG /* Watchdog related data, used by the framework */ struct list_head wd_list; cycle_t cs_last; cycle_t wd_last; #endif struct module *owner; // module reference.. clocksource moduleで設定される(書きこむなという意) } ____cacheline_aligned;ratingのインフレを避けるため、クロックソースのratingを割り当てる方法に関して、
value | meanings |
---|---|
1-99 | 実際の利用には適さない。起動時やテスト目的にのみ使用可能。 |
100-199 | 基本レベルのユーザビリティ。実際の使用のための機能であるが、望ましくない。 |
200-299 | 良い。正しく、使えるクロックソース。 |
300-399 | 望まれる。適度に高速かつ正確なクロックソース。 |
400-499 | 完全に理想的なクロックソース。利用可能な場合、使用する必要があります。 |
symbol | value | remark |
---|---|---|
CLOCK_SOURCE_IS_CONTINUOUS | 0x01 | ?通常セットされるようなものじゃないと使えないぽい?? |
CLOCK_SOURCE_MUST_VERIFY | 0x02 | clocksource監視用には使えない(監視される側のみ)を宣言する。 |
CLOCK_SOURCE_WATCHDOG | 0x10 | ※内部フラグと思われる |
CLOCK_SOURCE_VALID_FOR_HRES | 0x20 | ※内部フラグ?CONTINUOUSがセットされていれば、コレも各所でセットされる |
CLOCK_SOURCE_UNSTABLE | 0x40 | clocksource_unstable()でセットされる。HRES,WATCHDOGフラグが落とされる。 |
CLOCK_SOURCE_SUSPEND_NONSTOP | 0x80 | ※セットするのはx86固有コードになってる |
CLOCK_SOURCE_RESELECT | 0x100 | watchdogでソース切り替えが発生条件を満たしてセット、kthreadで再選択後にクリアするぽい |
/* Clock event notification values */ enum clock_event_nofitiers
symbol | val | meaning |
---|---|---|
CLOCK_EVT_NOTIFY_ADD | 0 | |
CLOCK_EVT_NOTIFY_BROADCAST_ON | 1 | |
CLOCK_EVT_NOTIFY_BROADCAST_OFF | 2 | |
CLOCK_EVT_NOTIFY_BROADCAST_FORCE | 3 | |
CLOCK_EVT_NOTIFY_BROADCAST_ENTER | 4 | |
CLOCK_EVT_NOTIFY_BROADCAST_EXIT | 5 | |
CLOCK_EVT_NOTIFY_SUSPEND | 6 | |
CLOCK_EVT_NOTIFY_RESUME | 7 | |
CLOCK_EVT_NOTIFY_CPU_DYING | 8 | |
CLOCK_EVT_NOTIFY_CPU_DEAD | 9 |
struct clock_event_device { void (*event_handler)(struct clock_event_device *); // event sourceの低レベルハンドラから呼ばれるために、フレームワークによってアサインされる。 int (*set_next_event)(unsigned long evt, struct clock_event_device *); // clocksource deltaを使って次のイベントをセットする関数 int (*set_next_ktime)(ktime_t expires, struct clock_event_device *); // ktime値を直接使って次のイベントをセットする関数 ktime_t next_event; // oneshot mode時の次のイベントのためのローカルストレージ u64 max_delta_ns; // 最大の差分値(ナノ秒) u64 min_delta_ns; // 最小の差分値(ナノ秒) u32 mult; // ナノ秒からサイクル数への乗数 u32 shift; // サイクル数からナノ秒の除数(2の累乗) enum clock_event_mode mode; // マネージメントコードによって代入される、動作モード unsigned int features; // 機能 unsigned long retries; // 強制プログラムの再回数 void (*broadcast)(const struct cpumask *mask); // broadcastイベント関数(?) void (*set_mode)(enum clock_event_mode mode, struct clock_event_device *); // 動作モード設定関数 void (*suspend)(struct clock_event_device *); void (*resume)(struct clock_event_device *); unsigned long min_delta_ticks; // リコンフィギュレーションのために保存されたtickの最小デルタ値 unsigned long max_delta_ticks; // リコンフィギュレーションのために保存されたtickの最大デルタ値 const char *name; // clock event名へのポインタ int rating; // clockeventの選択基準値 int irq; // IRQ number (only for non CPU local devices) const struct cpumask *cpumask; // このデバイスが動作するCPUについて示す値 struct list_head list; // マネージメントコードのためのリストヘッド struct module *owner; // module参照 } ____cacheline_aligned;
void clockevents_register_device(struct clock_event_device *dev) { unsigned long flags; BUG_ON(dev->mode != CLOCK_EVT_MODE_UNUSED); if (!dev->cpumask) { WARN_ON(num_possible_cpus() > 1); dev->cpumask = cpumask_of(smp_processor_id()); } raw_spin_lock_irqsave(&clockevents_lock, flags); list_add(&dev->list, &clockevent_devices); tick_check_new_device(dev); // ★★★ここ。この先で使えると思ったCPUが奪っていく。 clockevents_notify_released(); raw_spin_unlock_irqrestore(&clockevents_lock, flags); }
void tick_check_new_device(struct clock_event_device *newdev) { struct clock_event_device *curdev; struct tick_device *td; int cpu; cpu = smp_processor_id(); if (!cpumask_test_cpu(cpu, newdev->cpumask)) goto out_bc; td = &per_cpu(tick_cpu_device, cpu); curdev = td->evtdev; /* cpu local device ? */ if (!tick_check_percpu(curdev, newdev, cpu)) goto out_bc; /* Preference decision */ if (!tick_check_preferred(curdev, newdev)) goto out_bc; if (!try_module_get(newdev->owner)) return; /* * Replace the eventually existing device by the new * device. If the current device is the broadcast device, do * not give it back to the clockevents layer ! */ if (tick_is_broadcast_device(curdev)) { clockevents_shutdown(curdev); curdev = NULL; } clockevents_exchange_device(curdev, newdev); tick_setup_device(td, newdev, cpu, cpumask_of(cpu)); if (newdev->features & CLOCK_EVT_FEAT_ONESHOT) tick_oneshot_notify(); return; out_bc: /* * Can the new device be used as a broadcast device ? */ tick_install_broadcast_device(newdev); }
/** * clockevents_set_mode - set the operating mode of a clock event device * @dev: device to modify * @mode: new mode * * Must be called with interrupts disabled ! */ void clockevents_set_mode(struct clock_event_device *dev, enum clock_event_mode mode) { if (dev->mode != mode) { dev->set_mode(mode, dev); dev->mode = mode; /* * A nsec2cyc multiplicator of 0 is invalid and we'd crash * on it, so fix it up and emit a warning: */ if (mode == CLOCK_EVT_MODE_ONESHOT) { if (unlikely(!dev->mult)) { dev->mult = 1; WARN_ON(1); } } } }このモード、以下のヘッダで定義されている。コメントと使用している部位で期待している動作を抽出してみる。
symbol | value | meaning |
---|---|---|
CLOCK_EVT_MODE_UNUSED | 0 | device登録時にこの値である必要がある。 clockevents_notify()でCLOCK_EVT_NOTIFY_CPU_DEADを受けた時もこれになる。 |
CLOCK_EVT_MODE_SHUTDOWN | 1 | 動作を停止させる。スケジューリング予定も全てキャンセル |
CLOCK_EVT_MODE_PERIODIC | 2 | 周期タイマの初期化(周期は"HZ"で定義されているもの)と開始 |
CLOCK_EVT_MODE_ONESHOT | 3 | 周期タイマ動作を止めて、タイマの設定を待つ。 |
CLOCK_EVT_MODE_RESUME | 4 | suspendからの復帰で呼ばれる。suspend前は、shutdownを呼ばれるけれど、改めてモード設定からやってくれる@ |
void hrtimer_interrupt(struct clock_event_device *dev) ... /* Reprogramming necessary ? */ if (expires_next.tv64 == KTIME_MAX || !tick_program_event(expires_next, 0)) { cpu_base->hang_detected = 0; return; } ... tick_program_event(expires_next, 1);
void hrtimer_peek_ahead_timers(void) static void __hrtimer_peek_ahead_timers(void) { td = &__get_cpu_var(tick_cpu_device); if (td && td->evtdev) hrtimer_interrupt(td->evtdev);ざっくりこんな感じで呼ばれるので、デバイス自体はregisterしたときに割りつけられてそう。
DECLARE_PER_CPU(struct tick_device, tick_cpu_device);
/* * The 64-bit jiffies value is not atomic - you MUST NOT read it * without sampling the sequence number in xtime_lock. * jiffies is defined in the linker script... */ void do_timer(unsigned long ticks) { jiffies_64 += ticks; update_wall_time(); calc_global_load(ticks); }コイツが大ボス. jiffiesそのものじゃないけどいいんか( system call "times"で返している)
8xxxxxxx D jiffies 8xxxxxxx D jiffies_64
/* * Must be called with interrupts disabled ! */ static void tick_do_update_jiffies64(ktime_t now) { unsigned long ticks = 0; ktime_t delta; /* * Do a quick check without holding xtime_lock: */ delta = ktime_sub(now, last_jiffies_update); if (delta.tv64 < tick_period.tv64) return; /* Reevalute with xtime_lock held */ write_seqlock(&xtime_lock); delta = ktime_sub(now, last_jiffies_update); if (delta.tv64 >= tick_period.tv64) { delta = ktime_sub(delta, tick_period); last_jiffies_update = ktime_add(last_jiffies_update, tick_period); /* Slow path for long timeouts */ if (unlikely(delta.tv64 >= tick_period.tv64)) { s64 incr = ktime_to_ns(tick_period); ticks = ktime_divns(delta, incr); last_jiffies_update = ktime_add_ns(last_jiffies_update, incr * ticks); } do_timer(++ticks); /* Keep the tick_next_period variable up to date */ tick_next_period = ktime_add(last_jiffies_update, tick_period); } write_sequnlock(&xtime_lock); }tick_do_update_jiffies64()を呼び出すヒト.
static void tick_nohz_update_jiffies(ktime_t now) void tick_nohz_stop_sched_tick(int inidle) tick_program_event()って? static void tick_nohz_restart(struct tick_sched *ts, ktime_t now) void tick_nohz_restart_sched_tick(void) static void tick_nohz_handler(struct clock_event_device *dev) // The nohz low res interrupt handler static enum hrtimer_restart tick_sched_timer(struct hrtimer *timer) // high-res. timer持っているとき.いずれも、ktime_get();で現時刻をとってきている(CPU寝ていても回っているカウンタを想定)
ktime_t ktime_get(void) { unsigned int seq; s64 secs, nsecs; WARN_ON(timekeeping_suspended); do { seq = read_seqbegin(&xtime_lock); secs = xtime.tv_sec + wall_to_monotonic.tv_sec; nsecs = xtime.tv_nsec + wall_to_monotonic.tv_nsec; nsecs += timekeeping_get_ns(); /* If arch requires, add in gettimeoffset() */ nsecs += arch_gettimeoffset(); } while (read_seqretry(&xtime_lock, seq)); /* * Use ktime_set/ktime_add_ns to create a proper ktime on * 32-bit architectures without CONFIG_KTIME_SCALAR. */ return ktime_add_ns(ktime_set(secs, 0), nsecs); } EXPORT_SYMBOL_GPL(ktime_get);ktime_get()は、xtimeを見ている。あれ..?
/** * update_wall_time - Uses the current clocksource to increment the wall time * * Called from the timer interrupt, must hold a write on xtime_lock. */ static void update_wall_time(void) { struct clocksource *clock; cycle_t offset; int shift = 0, maxshift; /* Make sure we're fully resumed: */ if (unlikely(timekeeping_suspended)) return; clock = timekeeper.clock; // ★★コレがSoC全域で使える、cpu idleでも時間を刻むクロックソースを握っている。 #ifdef CONFIG_ARCH_USES_GETTIMEOFFSET offset = timekeeper.cycle_interval; #else offset = (clock->read(clock) - clock->cycle_last) & clock->mask; #endif timekeeper.xtime_nsec = (s64)xtime.tv_nsec << timekeeper.shift; /* * With NO_HZ we may have to accumulate many cycle_intervals * (think "ticks") worth of time at once. To do this efficiently, * we calculate the largest doubling multiple of cycle_intervals * that is smaller then the offset. We then accumulate that * chunk in one go, and then try to consume the next smaller * doubled multiple. */ shift = ilog2(offset) - ilog2(timekeeper.cycle_interval); shift = max(0, shift); /* Bound shift to one less then what overflows tick_length */ maxshift = (8*sizeof(tick_length) - (ilog2(tick_length)+1)) - 1; shift = min(shift, maxshift); while (offset >= timekeeper.cycle_interval) { offset = logarithmic_accumulation(offset, shift); if(offset < timekeeper.cycle_interval<<shift) shift--; } /* correct the clock when NTP error is too big */ timekeeping_adjust(offset); /* * Since in the loop above, we accumulate any amount of time * in xtime_nsec over a second into xtime.tv_sec, its possible for * xtime_nsec to be fairly small after the loop. Further, if we're * slightly speeding the clocksource up in timekeeping_adjust(), * its possible the required corrective factor to xtime_nsec could * cause it to underflow. * * Now, we cannot simply roll the accumulated second back, since * the NTP subsystem has been notified via second_overflow. So * instead we push xtime_nsec forward by the amount we underflowed, * and add that amount into the error. * * We'll correct this error next time through this function, when * xtime_nsec is not as small. */ if (unlikely((s64)timekeeper.xtime_nsec < 0)) { s64 neg = -(s64)timekeeper.xtime_nsec; timekeeper.xtime_nsec = 0; timekeeper.ntp_error += neg << timekeeper.ntp_error_shift; } /* * Store full nanoseconds into xtime after rounding it up and * add the remainder to the error difference. */ xtime.tv_nsec = ((s64) timekeeper.xtime_nsec >> timekeeper.shift) + 1; timekeeper.xtime_nsec -= (s64) xtime.tv_nsec << timekeeper.shift; timekeeper.ntp_error += timekeeper.xtime_nsec << timekeeper.ntp_error_shift; /* * Finally, make sure that after the rounding * xtime.tv_nsec isn't larger then NSEC_PER_SEC */ if (unlikely(xtime.tv_nsec >= NSEC_PER_SEC)) { int leap; xtime.tv_nsec -= NSEC_PER_SEC; xtime.tv_sec++; leap = second_overflow(xtime.tv_sec); xtime.tv_sec += leap; wall_to_monotonic.tv_sec -= leap; if (leap) clock_was_set_delayed(); } timekeeping_update(false); }
/** * timekeeper_setup_internals - Set up internals to use clocksource clock. * * @clock: Pointer to clocksource. * * Calculates a fixed cycle/nsec interval for a given clocksource/adjustment * pair and interval request. * * Unless you're the timekeeping code, you should not be using this! */ static void timekeeper_setup_internals(struct clocksource *clock) { cycle_t interval; u64 tmp, ntpinterval; timekeeper.clock = clock; clock->cycle_last = clock->read(clock); 以下略この関数でセットしている。張り替えもできる模様。
static int change_clocksource(void *data)FILE: kernel/time/timekeeping.c
/* * timekeeping_init - Initializes the clocksource and common timekeeping values */ void __init timekeeping_init(void) { struct clocksource *clock; unsigned long flags; struct timespec now, boot; read_persistent_clock(&now); read_boot_clock(&boot); write_seqlock_irqsave(&xtime_lock, flags); ntp_init(); clock = clocksource_default_clock(); if (clock->enable) clock->enable(clock); timekeeper_setup_internals(clock); ...おうふ...
/** * timekeeping_notify - Install a new clock source * @clock: pointer to the clock source * * This function is called from clocksource.c after a new, better clock * source has been registered. The caller holds the clocksource_mutex. */ void timekeeping_notify(struct clocksource *clock) { if (timekeeper.clock == clock) return; stop_machine(change_clocksource, clock, NULL); tick_clock_notify(); }これか. inline関数になってる。SMPとSTOP_MACHINEが定義されていなければ関数呼び出しに置換される。
#ifndef CONFIG_ARCH_USES_GETTIMEOFFSET /** * clocksource_select - Select the best clocksource available * * Private function. Must hold clocksource_mutex when called. * * Select the clocksource with the best rating, or the clocksource, * which is selected by userspace override. */ static void clocksource_select(void)コレ呼び出しているところが多数ある。。
static void clocksource_enqueue(struct clocksource *cs)
/* * Don't call __clocksource_register_scale directly, use * clocksource_register_hz/khz */ extern int __clocksource_register_scale(struct clocksource *cs, u32 scale, u32 freq); extern void __clocksource_updatefreq_scale(struct clocksource *cs, u32 scale, u32 freq); static inline int clocksource_register_hz(struct clocksource *cs, u32 hz) { return __clocksource_register_scale(cs, 1, hz); }FILE: kernel/time/clocksource.c
/** * __clocksource_register_scale - Used to install new clocksources * @t: clocksource to be registered * @scale: Scale factor multiplied against freq to get clocksource hz * @freq: clocksource frequency (cycles per second) divided by scale * * Returns -EBUSY if registration fails, zero otherwise. * * This *SHOULD NOT* be called directly! Please use the * clocksource_register_hz() or clocksource_register_khz helper functions. */ int __clocksource_register_scale(struct clocksource *cs, u32 scale, u32 freq) { /* Initialize mult/shift and max_idle_ns */ __clocksource_updatefreq_scale(cs, scale, freq); // ★★★ここでcs->max_idle_nsに最長時間を設定してくれる。 /* Add clocksource to the clcoksource list */ mutex_lock(&clocksource_mutex); clocksource_enqueue(cs); clocksource_enqueue_watchdog(cs); clocksource_select(); mutex_unlock(&clocksource_mutex); return 0; } EXPORT_SYMBOL_GPL(__clocksource_register_scale);この最長時間を返す関数が以下にある。(timerクロックリソースを保持するオブジェクト(ファイルスコープ))
u64 timekeeping_max_deferment(void)これを使っていて、スケジューラ絡みのソースは以下。
/** * tick_nohz_stop_sched_tick - stop the idle tick from the idle task * * When the next event is more than a tick into the future, stop the idle tick * Called either from the idle loop or from irq_exit() when an idle period was * just interrupted by an interrupt which did not cause a reschedule. */ void tick_nohz_stop_sched_tick(int inidle) { if (ts->nohz_mode == NOHZ_MODE_HIGHRES) { hrtimer_start(&ts->sched_timer, expires, HRTIMER_MODE_ABS_PINNED); /* Check, if the timer was already in the past */ if (hrtimer_active(&ts->sched_timer)) goto out; } else if (!tick_program_event(expires, 0)) goto out;tickを止める前に、タイマを貼ってますなぁ。ハイレゾが有効ならそれ。違えば普通の。
/** * hrtimer_start_range_ns - (re)start an hrtimer on the current CPU * @timer: the timer to be added * @tim: expiry time * @delta_ns: "slack" range for the timer * @mode: expiry mode: absolute (HRTIMER_ABS) or relative (HRTIMER_REL) * * Returns: * 0 on success * 1 when the timer was active */ int hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim, unsigned long delta_ns, const enum hrtimer_mode mode) { return __hrtimer_start_range_ns(timer, tim, delta_ns, mode, 1); } EXPORT_SYMBOL_GPL(hrtimer_start_range_ns);"nohz_mode = NOHZ_MODE_HIGHRES"を設定しているのは、以下。
/** * tick_setup_sched_timer - setup the tick emulation timer */ void tick_setup_sched_timer(void) { struct tick_sched *ts = &__get_cpu_var(tick_cpu_sched); ktime_t now = ktime_get(); /* * Emulate tick processing via per-CPU hrtimers: */ hrtimer_init(&ts->sched_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); ts->sched_timer.function = tick_sched_timer; /* Get the next period (per cpu) */ hrtimer_set_expires(&ts->sched_timer, tick_init_jiffy_update()); for (;;) { hrtimer_forward(&ts->sched_timer, now, tick_period); hrtimer_start_expires(&ts->sched_timer, HRTIMER_MODE_ABS_PINNED); /* Check, if the timer was already in the past */ if (hrtimer_active(&ts->sched_timer)) break; now = ktime_get(); } #ifdef CONFIG_NO_HZ if (tick_nohz_enabled) { ts->nohz_mode = NOHZ_MODE_HIGHRES; printk(KERN_INFO "Switched to NOHz mode on CPU #%d\n", smp_processor_id()); } #endif } #endif /* HIGH_RES_TIMERS */
/** * tick_program_event */ int tick_program_event(ktime_t expires, int force) { struct clock_event_device *dev = __this_cpu_read(tick_cpu_device.evtdev); return tick_dev_program_event(dev, expires, force); }
/* * This function runs timers and the timer-tq in bottom half context. */ static void run_timer_softirq(struct softirq_action *h) { struct tvec_base *base = __this_cpu_read(tvec_bases); hrtimer_run_pending(); if (time_after_eq(jiffies, base->timer_jiffies)) __run_timers(base); }
void __init init_timers(void) { int err = timer_cpu_notify(&timers_nb, (unsigned long)CPU_UP_PREPARE, (void *)(long)smp_processor_id()); init_timer_stats(); BUG_ON(err != NOTIFY_OK); register_cpu_notifier(&timers_nb); open_softirq(TIMER_SOFTIRQ, run_timer_softirq); }
/* * Called from timer softirq every jiffy, expire hrtimers: * * For HRT its the fall back code to run the softirq in the timer * softirq context in case the hrtimer initialization failed or has * not been done yet. */ void hrtimer_run_pending(void) { if (hrtimer_hres_active()) //★★ ->hres_activeを返す。 return; /* * This _is_ ugly: We have to check in the softirq context, * whether we can switch to highres and / or nohz mode. The * clocksource switch happens in the timer interrupt with * xtime_lock held. Notification from there only sets the * check bit in the tick_oneshot code, otherwise we might * deadlock vs. xtime_lock. */ if (tick_check_oneshot_change(!hrtimer_is_hres_enabled())) // hrtimer_is_hres_enabled()は真になりやすい(hrtimer_hres_enabledを返す) hrtimer_switch_to_hres(); }hrtimer_hres_active()が
/* * Switch to high resolution mode */ static int hrtimer_switch_to_hres(void) { int i, cpu = smp_processor_id(); struct hrtimer_cpu_base *base = &per_cpu(hrtimer_bases, cpu); unsigned long flags; if (base->hres_active) return 1; local_irq_save(flags); if (tick_init_highres()) { local_irq_restore(flags); printk(KERN_WARNING "Could not switch to high resolution " "mode on CPU %d\n", cpu); return 0; } base->hres_active = 1; for (i = 0; i < HRTIMER_MAX_CLOCK_BASES; i++) base->clock_base[i].resolution = KTIME_HIGH_RES; tick_setup_sched_timer(); /* "Retrigger" the interrupt to get things going */ retrigger_next_event(NULL); local_irq_restore(flags); return 1; }clocksourceとclockeventとhrtimerと。
#ifndef CONFIG_GENERIC_CLOCKEVENTS /* * Kernel system timer support. */ void timer_tick(void) { profile_tick(CPU_PROFILING); do_leds(); xtime_update(1); #ifndef CONFIG_SMP update_process_times(user_mode(get_irq_regs())); #endif } #endif