Chinaunix首页 | 论坛 | 博客
  • 博客访问: 379630
  • 博文数量: 87
  • 博客积分: 983
  • 博客等级: 准尉
  • 技术积分: 685
  • 用 户 组: 普通用户
  • 注册时间: 2012-06-25 07:20
文章分类

全部博文(87)

文章存档

2016年(1)

2015年(3)

2014年(55)

2013年(13)

2012年(15)

分类: LINUX

2014-07-09 15:37:01

原文地址:armlinux启动之时钟初始化 作者:mutes

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失败,从而造成系统时钟驱动的中断未注册成功,进而内核挂掉。
阅读(784) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~