arm linux 启动时间,armlinux启动之时钟初始化

1、相关数据结构

include/linux/notifier.h

struct notifier_block {

int (*notifier_call)(struct notifier_block *, unsigned long, void *);

struct notifier_block *next;

int priority;

};

通知链中的元素,记录了当发出通知时,应该执行的操作(即回调函数)

链头中保存着指向元素链表的指针。通知链元素结构则保存着回调函数的类型以及优先级

2、时钟初始化

2.1 内核初始化部分( start_kernel 函数)和时钟相关的过程主要有以下几个:

tick_init()

init_timers()

hrtimers_init()

time_init()

其中函数 hrtimers_init() 和高精度时钟相关,下面将详细介绍这几个函数。

2.2.1 tick_init 函数

kernel/time/tick-common.c

void __init tick_init(void)

{

clockevents_register_notifier(&tick_notifier);

}

static struct notifier_block tick_notifier = {

.notifier_call = tick_notify,

};

函数 tick_init() 很简单,调用 clockevents_register_notifier 函数向 clockevents_chain 通知链注册元素: tick_notifier。这个元素的回调函数指明了当时钟事件设备信息发生变化(例如新加入一个时钟事件设备等等)时,应该执行的操作,该回调函数为 tick_notify

kernel/time/tick-common.c

static int tick_notify(struct notifier_block *nb, unsigned long reason,

void *dev)

{

switch (reason) {

case CLOCK_EVT_NOTIFY_ADD:

return tick_check_new_device(dev);

case CLOCK_EVT_NOTIFY_BROADCAST_ON:

case CLOCK_EVT_NOTIFY_BROADCAST_OFF:

case CLOCK_EVT_NOTIFY_BROADCAST_FORCE:

tick_broadcast_on_off(reason, dev);

break;

case CLOCK_EVT_NOTIFY_BROADCAST_ENTER:

case CLOCK_EVT_NOTIFY_BROADCAST_EXIT:

tick_broadcast_oneshot_control(reason);

break;

case CLOCK_EVT_NOTIFY_CPU_DYING:

tick_handover_do_timer(dev);

break;

case CLOCK_EVT_NOTIFY_CPU_DEAD:

tick_shutdown_broadcast_oneshot(dev);

tick_shutdown_broadcast(dev);

tick_shutdown(dev);

break;

case CLOCK_EVT_NOTIFY_SUSPEND:

tick_suspend();

tick_suspend_broadcast();

break;

case CLOCK_EVT_NOTIFY_RESUME:

tick_resume();

break;

default:

break;

}

return NOTIFY_OK;

}

2.2.2 init_timers 函数

函数 init_timers() 的实现如清单2-1(省略了部分和

主要功能无关的内容,以后代码同样方式处理)

kernel/timer.c

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);

}

static struct notifier_block __cpuinitdata timers_nb = {

.notifier_call = timer_cpu_notify,

};

代码解释:

初始化本 CPU 上的软件时钟相关的数据结构,参见3.2节

向 cpu_chain 通知链注册元素 timers_nb ,该元素的回调函数用于初始化指定 CPU 上的软件时钟相关的数据结构

初始化时钟的软中断处理函数

kernel/timer.c

static int __cpuinit timer_cpu_notify(struct notifier_block *self,

unsigned long action, void *hcpu)

{

long cpu = (long)hcpu;

int err;

switch(action) {

case CPU_UP_PREPARE:

case CPU_UP_PREPARE_FROZEN:

err = init_timers_cpu(cpu);

if (err < 0)

return notifier_from_errno(err);

break;

#ifdef CONFIG_HOTPLUG_CPU

case CPU_DEAD:

case CPU_DEAD_FROZEN:

migrate_timers(cpu);

break;

#endif

default:

break;

}

return NOTIFY_OK;

}

2.2.3 hrtimers_init函数

kernel/hrtimer.c

void __init hrtimers_init(void)

{

hrtimer_cpu_notify(&hrtimers_nb, (unsigned long)CPU_UP_PREPARE,

(void *)(long)smp_processor_id());

register_cpu_notifier(&hrtimers_nb);

#ifdef CONFIG_HIGH_RES_TIMERS

open_softirq(HRTIMER_SOFTIRQ, run_hrtimer_softirq);

#endif

}

static struct notifier_block __cpuinitdata hrtimers_nb = {

.notifier_call = hrtimer_cpu_notify,

};

kernel/hrtimer.c

static int __cpuinit hrtimer_cpu_notify(struct notifier_block *self,

unsigned long action, void *hcpu)

{

int scpu = (long)hcpu;

switch (action) {

case CPU_UP_PREPARE:

case CPU_UP_PREPARE_FROZEN:

init_hrtimers_cpu(scpu);

break;

#ifdef CONFIG_HOTPLUG_CPU

case CPU_DYING:

case CPU_DYING_FROZEN:

clockevents_notify(CLOCK_EVT_NOTIFY_CPU_DYING, &scpu);

break;

case CPU_DEAD:

case CPU_DEAD_FROZEN:

{

clockevents_notify(CLOCK_EVT_NOTIFY_CPU_DEAD, &scpu);

migrate_hrtimers(scpu);

break;

}

#endif

default:

break;

}

return NOTIFY_OK;

}

2.2.4 time_init 函数

相关数据结构

system_timer定义在arch/arm/kernel/time.c文件开头

struct sys_timer *system_timer;

struct sys_timer定义在arch/arm/include/asm/mach/time.h文件

struct sys_timer {

struct sys_device dev;

void (*init)(void);

void (*suspend)(void);

void (*resume)(void);

#ifdef CONFIG_ARCH_USES_GETTIMEOFFSET

unsigned long (*offset)(void);

#endif

};

在分析time_init 函数前我们先来看看嵌入式Linux内核时钟初始化问题

首先搞清楚RTC在kernel内的作用: linux系统有两个时钟:一个是由主板电池驱动的“Real Time Clock”也叫做RTC或者叫CMOS时钟,硬件时钟。当操作系统关机的时候,用这个来记录时间,但是对于运行的系统是不用这个时间的。

另一个时间是 “System clock”也叫内核时钟或者软件时钟,是由软件根据时间中断来进行计数的,

内核时钟在系统关机的情况下是不存在的,所以,当操作系统启动的时候,内核时钟是要读取RTC时间来进行时间同步。并且在系统关机的时候将系统时间写回RTC中进行同步。 如前所述,Linux内核与RTC进行互操作的时机只有两个:

1) 内核在启动时从RTC中读取启动时的时间与日期;

2) 内核在需要时将时间与日期回写到RTC中。 系统启动时,内核通过读取RTC来初始化内核时钟,又叫墙上时间,该时间放在xtime变量中。

struct timespec {

__kernel_time_t tv_sec;   /* seconds */

long  tv_nsec;  /* nanoseconds */

};

问题1:系统启动时在哪读取RTC的值并设置内核时钟进行时间同步的呢?

最有可能读取RTC设置内核时钟的位置应该在arch/arm/kernel/time.c里的time_init函数内.time.c为系统的时钟驱动部分.time_init函数会在系统初始化时,由init/main.c里的start_kernel函数内调用.X86架构就是在这里读RTC值并初始化系统时钟xtime的. ARM架构的time_init代码如下

void __init time_init(void)

{

system_timer->init(); //这行实际执行的就是s3c2410_timer_init

}

其中system_timer与体系结构相关,对于2410它在arch/arm/plat-samsung/time.c初始化

struct sys_timer s3c24xx_timer = {

.init  = s3c2410_timer_init,

.offset  = s3c2410_gettimeoffset,

.resume  = s3c2410_timer_setup

};

system_timer在setup_arch(arch/arm/kernel/setup.c)内通过map_desc机制被初始化为s3c24xx_timer. 如上面s3c2410时钟驱动代码所示,s3c24xx_timer的init成员即指向s3c2410_timer_init函数。

s3c2410_timer_init

arch/arm/plat-samsung/time.c

static void __init s3c2410_timer_init(void)

{

s3c2410_timer_resources();//首先初始化timerclk、tin和tdiv三个结构体,后两个和pwm相关

s3c2410_timer_setup();//这里做了一些时钟初始化设置,但是针对timer4的

setup_irq(IRQ_TIMER4, &s3c2410_timer_irq);//设置中断处理函数

}

不过 s3c2410_timer_init()也没有读RTC的代码.整个时钟驱动初始化的过程大致就执行这些代码.既然在系统时钟驱动初始化的过程中没有读RTC值并设置内核时钟,那会在哪设置呢? 我搜了一下,发现内核好象只有在arch/cris/kernel/time.c里有RTC相关代码,如下

unsigned long

get_cmos_time(void)

{

unsigned int year, mon, day, hour, min, sec;

if(!have_rtc)

return 0;

sec = CMOS_READ(RTC_SECONDS);

min = CMOS_READ(RTC_MINUTES);

hour = CMOS_READ(RTC_HOURS);

day = CMOS_READ(RTC_DAY_OF_MONTH);

mon = CMOS_READ(RTC_MONTH);

year = CMOS_READ(RTC_YEAR);

sec = bcd2bin(sec);

min = bcd2bin(min);

hour = bcd2bin(hour);

day = bcd2bin(day);

mon = bcd2bin(mon);

year = bcd2bin(year);

if ((year += 1900) < 1970)

year += 100;

return mktime(year, mon, day, hour, min, sec);

}

这个函数会在read_persistent_clock内被调用:

void read_persistent_clock(struct timespec *ts)

{

ts->tv_sec = get_cmos_time();

ts->tv_nsec = 0;

}

另外还有设置rtc的函数

int set_rtc_mmss(unsigned long nowtime); /* write time into RTC chip */ 不过我加了printk测试了一下,好象arch/cris/kernel/time.c这个文件和这两个函数只是适用与X86?

arm平台启动时并不走这边.因此执行不到这些函数。那arm平台启动时,系统是在哪读RTC的值并对内核时钟(WallTime)进行初始化的呢?

嵌入式Linux内核(arm)是在系统启动时执行/etc/init.d/hwclock.sh脚本,这个脚本会调用hwclock小程序读取RTC的值并设置系统时钟。

(换句话说,这要取决于你制作的文件系统里是否有这样的脚本)

/* /etc/init.d/hwclock.sh */DAEMON1=/sbin/hwclock

start() {

local RET ERROR=    [ ! -f /etc/adjtime ] &&  echo "0.0 0 0.0" > /etc/adjtime

log_status_msg "Setting the System Clock using the Hardware Clock as reference..." -n    # Copies Hardware Clock time to System Clock using the correct

# timezone for hardware clocks in local time, and sets kernel

# timezone. DO NOT REMOVE.

[ "$HWCLOCKACCESS" != no ] && $DAEMON1 --hctosys $GMT $BADYEAR    #

# Now that /usr/share/zoneinfo should be available,

# announce the local time.

#

log_status_msg "System Clock set. Local time: `date`"

log_status_msg ""

return 0

}

hwclock最先读取的设备文件是 /dev/rtc  ,busybox里面的hwclock是这样实现的:

static int xopen_rtc(int flags)

{

int rtc; if (!rtcname) {

rtc = open("/dev/rtc", flags);

if (rtc >= 0)

return rtc;

rtc = open("/dev/rtc0", flags);

if (rtc >= 0)

return rtc;

rtcname = "/dev/misc/rtc";

}

return xopen(rtcname, flags);

}

2. 内核如何更新RTC时钟?

通过set_rtc函数指针指向的函数,set_rtc在arch/arm/kernel/time.c内

/* arch/arm/kernel/time.c */

/*

* hook for setting the RTC's idea of the current time.

*/

int (*set_rtc)(void);但是set_rtc函数指针在哪初始化的呢?set_rtc应该是和RTC驱动相关的函数.搜索kernel源码后发现,好象内核其他地方并没有对其初始化。待解决!

set_rtc在do_set_rtc内调用

do_set_rtc在timer_tick里调用

void timer_tick(struct pt_regs *regs)

{

profile_tick(CPU_PROFILING, regs);

do_leds();

do_set_rtc();

do_timer(1);

……

}

arch/arm/kernel/time.c

#ifndef CONFIG_GENERIC_CLOCKEVENTS

/*

* Kernel system timer support.

*/

void timer_tick(void)

{

profile_tick(CPU_PROFILING);

do_leds();

write_seqlock(&xtime_lock);

do_timer(1);

write_sequnlock(&xtime_lock);

#ifndef CONFIG_SMP

update_process_times(user_mode(get_irq_regs()));

#endif

}

#endif

timer_tick为Kernel提供的体系架构无关的时钟中断处理函数,通常会在体系架构相关的时钟中断处理函数内调用它。如s3c2410是这样的:在arch/arm/plat-samsung/time.c中

static irqreturn_t

s3c2410_timer_interrupt(int irq, void *dev_id)

{

timer_tick();

return IRQ_HANDLED;

}

static struct irqaction s3c2410_timer_irq = {

.name  = "S3C2410 Timer Tick",

.flags  = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,

.handler = s3c2410_timer_interrupt,

};

Request_irq和setup_irq的区别

request_irq在2.6.36核中它是request_threaded_irq的封装,如下:

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);

}

因此,Linux内核提供了两个注册中断处理函数的接口:setup_irq和request_threaded_irq。这两个函数都定义在kernel/irq/manage.c里。

这两个函数有什么样的区别呢?

先看看setup_irq

Setup_irq通常用在系统时钟(GP Timer)驱动里,注册系统时钟驱动的中断处理函数。

源码如下:

int setup_irq(unsigned int irq, struct irqaction *act)

{

struct irq_desc *desc = irq_to_desc(irq);

return __setup_irq(irq, desc, act);

}

EXPORT_SYMBOL_GPL(setup_irq);

下面举个列子, 如s3c2410 timer驱动:

arch/arm/plat-samsung/time.c初始化

static struct irqaction s3c2410_timer_irq = {

.name  = "S3C2410 Timer Tick",

.flags  = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,

.handler = s3c2410_timer_interrupt,

};

static void __init s3c2410_timer_init(void)

{

s3c2410_timer_resources();

s3c2410_timer_setup();

setup_irq(IRQ_TIMER4, &s3c2410_timer_irq);

}

struct sys_timer s3c24xx_timer = {

.init  = s3c2410_timer_init,

.offset  = s3c2410_gettimeoffset,

.resume  = s3c2410_timer_setup

};

可以看到,setup_irq的使用流程很简单。首先定义s3c2410 timer驱动的irqaction结构体,该结构体用于描述timer中断的基本属性包括中断名、类别以及该中断handler等。然后通过setup_irq函数将timer的irqaction注册进内核。其中,IRQ_TIMER4为s3c2410 timer的中断号。

再看看request_threaded_irq

request_threaded_irq源码如下:

/* kernel/irq/manage.c */

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)

{

struct irqaction *action;

struct irq_desc *desc;

int retval;

/*

* Sanity-check: shared interrupts must pass in a real dev-ID,

* otherwise we'll have trouble later trying to figure out

* which interrupt is which (messes up the interrupt freeing

* logic etc).

*/

if ((irqflags & IRQF_SHARED) && !dev_id)/* 使用共享中断但没有提供非NULL的dev_id则返回错误 */

return -EINVAL;

desc = irq_to_desc(irq);

if (!desc)

return -EINVAL;

if (desc->status & IRQ_NOREQUEST) /* 该中断号已被使用并且未共享 */

return -EINVAL;

if (!handler) {

if (!thread_fn)

return -EINVAL;

handler = irq_default_primary_handler;

}

action = kzalloc(sizeof(struct irqaction), GFP_KERNEL); /* 动态创建一个irqaction */

if (!action)

return -ENOMEM;

/* 下面几行是根据request_threaded_irq传进来的参数对irqaction结构体赋值 */

action->handler = handler;

action->thread_fn = thread_fn;

action->flags = irqflags;

action->name = devname;

action->dev_id = dev_id;

chip_bus_lock(irq, desc);

retval = __setup_irq(irq, desc, action);/* 调用__setup_irq注册该中断的irqaction结构体 */

chip_bus_sync_unlock(irq, desc);

if (retval)

kfree(action);

#ifdef CONFIG_DEBUG_SHIRQ

if (!retval && (irqflags & IRQF_SHARED)) {

/*

* It's a shared IRQ -- the driver ought to be prepared for it

* to happen immediately, so let's make sure....

* We disable the irq to make sure that a 'real' IRQ doesn't

* run in parallel with our fake.

*/

unsigned long flags;

disable_irq(irq);

local_irq_save(flags);

handler(irq, dev_id);

local_irq_restore(flags);

enable_irq(irq);

}

#endif

return retval;

}

由上可以看出,request_threaded_irq的大致流程为先对申请的中断线进行安全检测,然后根据request_threaded_irq传进来的参数,动态创建该中断对应的irqaction结构体,最后通过setup_irq函数将该irqaction注册进内核适当的位置。

这两个函数的使用流程搞清楚了,那么两者之间的联系也就清楚了:

1) setup_irq的注册过程包含__setup_irq,最终是调用__setup_irq。

2) request_threaded_irq比setup_irq多一套错误检测机制,即kzalloc前面3行if语句。

而setup_irq通常是直接注册irqaction,并没针对相应中断线进行错误检测,如该irq 线是否已经被占用等。因此setup_irq通常只用在特定的中断线上,如System timer。除系统时钟驱动外,大部份驱动还是通过request_threaded_irq注册中断。

这里有个小问题:

既然request_threaded_irq实际上就是包含了__setup_irq的注册过程,那系统时钟驱动(GP Timer Driver)中断可以用request_threaded_irq来注册吗?

做个小试验, 将s3c2410 timer驱动的setup_irq那行去掉,改为用request_irq注册。

修改后代码如下:

static void __init s3c2410_timer_init(void)

{

s3c2410_timer_resources();//首先初始化timerclk、tin和tdiv三个结构体,后两个和pwm相关

s3c2410_timer_setup();//这里做了一些时钟初始化设置,但是针对timer4的

//setup_irq(IRQ_TIMER4, &s3c2410_timer_irq);

request_irq(IRQ_TIMER4, &s3c2410_timer_irq,IRQF_DISABLED | IRQF_TIMER, "S3C2410 Timer Tick", NULL);

}

编译运行。

结果:内核挂掉

为什么呢?很明显,系统时钟驱动中断不能用request_irq注册,大致搜了一下源码也发现,看到其他平台相关的时钟驱动中断部分都是用的setup_irq注册的。

我们来分析一下原因。

看看request_threaded_irq和setup_irq 还有哪些细节不一样?

仔细观察后注意到request_irq内有这么一行代码:

action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);

作用为动态创建一个irqaction。

kzalloc实际上是调用了 kmalloc进行分配的。源码如下:

/* include/linux/slab.h */

static inline void *kzalloc(size_t size, gfp_t flags)

{

return kmalloc(size, flags | __GFP_ZERO);

}

而kmalloc实际上也是使用的slab机制进行分配的。源码如下:

/* include/linux/slab.h */

static inline void *kmalloc(size_t size, gfp_t flags)

{

if (__builtin_constant_p(size)) {

int i = 0;

#define CACHE(x) \

if (size <= x) \

goto found; \

else \

i++;

#include "kmalloc_sizes.h"

#undef CACHE

{

extern void __you_cannot_kmalloc_that_much(void);

__you_cannot_kmalloc_that_much();

}

found:

return kmem_cache_alloc((flags & GFP_DMA) ?

malloc_sizes[i].cs_dmacachep :

malloc_sizes[i].cs_cachep, flags);

}

return __kmalloc(size, flags);

}

使用slab机制分配内存必须先对slab进行初始化,包括mem_init和kmem_cache_init。

看看kernel的初始化流程:

/* init/main.c */

asmlinkage void __init start_kernel(void)

{

……

time_init();

……

time_init();

profile_init();

if (!irqs_disabled())

printk(KERN_CRIT "start_kernel(): bug: interrupts were "

"enabled early\n");

early_boot_irqs_on();

local_irq_enable();

/* Interrupts are enabled now so all GFP allocations are safe. */

gfp_allowed_mask = __GFP_BITS_MASK;

kmem_cache_init_late();---- set up the general caches

……

}

Time_init函数在kmem_cache_init_late之前被调用,而time_init会调用体系结构相关部分系统时钟驱动的初始化函数。拿s3c2410的例子来说,time_init最终会调用s3c2410_timer_init函数,进行s3c2410时钟驱动的初始化和注册中断处理函数。

具体过程如下:

time_init函数定义在arch/arm/kernel/time.c内:

void __init time_init(void)

{

system_timer->init(); //这行实际执行的就是s3c2410_timer_init

}

system_timer在setup_arch(arch/arm/kernel/setup.c)内通过map_desc机制被初始化为s3c24xx_timer. 如上面s3c2410时钟驱动代码所示,s3c24xx_timer的init成员即指向s3c2410_timer_init函数。

现在我们搞清楚了,我们大概的估计是系统时钟驱动(GP Timer Driver)的中断处理函数不能用request_irq注册是因为request_irq内会调用kmalloc动态分配内存创建timer的irqaction结构体。而kmalloc也是使用的slab内存分配机制,使用kmalloc前必须先对kernel的slab以及mem data structure进行初始化。而这部分初始化工作是在系统时钟驱动初始化之后才进行的,所以造成kmalloc失败,从而造成系统时钟驱动的中断未注册成功,进而内核挂掉。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值