文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>Linux 1.0 内核注解-linux/kernel/time.c

Linux 1.0 内核注解-linux/kernel/time.c

时间:2009-02-26  来源:taozhijiangscu

/********************************************
 *Created By: 陶治江
 *Date:    2009-2-24
 ********************************************/
 
/********************************************
 *1.0的时间要比.11的时间复杂的多的多,真的很头疼
 *写了这么多,还有一些部分没有弄懂!!
 ********************************************/
#include <linux/config.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/param.h>
#include <linux/string.h>
#include <asm/segment.h>
#include <asm/io.h>
#include <linux/mc146818rtc.h>
#define RTC_ALWAYS_BCD 1
#include <linux/timex.h>
extern struct timeval xtime;
// struct timeval {
// long tv_sec;  / seconds */
// long tv_usec; /* microseconds */
// };
#include <linux/mktime.h>
extern long kernel_mktime(struct mktime * time);
//struct mktime {
// int sec;
// int min;
// int hour;
// int day;
// int mon;
// int year;
//};
void time_init(void)
{
 struct mktime time;
 int i;
 
 
 //当控制寄存器B中的SET标志位为0时,MC146818芯片每秒都会
 //在芯片内部执行一个“更新周期”(Update Cycle),因为
 //MC146818在整个更新周期期间会把时间与日期寄存
 //器组从CPU总线上脱离,所以就无法读取了
 //第一个for循环不停地读取RTC频率选择寄存器中的UIP标志位,
 //并且只要读到UIP的值为1就马上退出这个for循环。第二个for
 //循环同样不停地读取UIP标志位,但他只要一读到UIP的值为0就马上退出这个for循环
 //此时RTC的Update Cycle已经结束了,可以读取时间了
 for (i = 0 ; i < 1000000 ; i++) /* may take up to 1 second... */
  if (CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP)
   break;
 for (i = 0 ; i < 1000000 ; i++) /* must try at least 2.228 ms*/
  if (!(CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP)) //read RTC exactly on falling edge of update flag
   break;
   
 do { /* Isn't this overkill ? UIP above should guarantee consistency */
  //连贯性??
  time.sec = CMOS_READ(RTC_SECONDS);
  time.min = CMOS_READ(RTC_MINUTES);
  time.hour = CMOS_READ(RTC_HOURS);
  time.day = CMOS_READ(RTC_DAY_OF_MONTH);
  time.mon = CMOS_READ(RTC_MONTH);
  time.year = CMOS_READ(RTC_YEAR);
 } while (time.sec != CMOS_READ(RTC_SECONDS)); //这里还是像以前一样,确保了读取的时间差不超过一秒
 
 //RTC_DM_BINARY:all time/date values are BCD if clear
 //BCD +0x30
 //#define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10)
 //将BCD值转换为数值,这里用的是打包的BCD值,一个字节表示两位
 if (!(CMOS_READ(RTC_CONTROL) & RTC_DM_BINARY) || RTC_ALWAYS_BCD)
   {
     BCD_TO_BIN(time.sec);
     BCD_TO_BIN(time.min);
     BCD_TO_BIN(time.hour);
     BCD_TO_BIN(time.day);
     BCD_TO_BIN(time.mon);
     BCD_TO_BIN(time.year);
   }
 time.mon--;   //将月份调到0-11
 xtime.tv_sec = kernel_mktime(&time); //将时间转换为秒数值
}
struct timezone sys_tz = { 0, 0};
//struct timezone {
// int tz_minuteswest; /* minutes west of Greenwich */
// int tz_dsttime; /* type of dst correction */
 /*the tz_dsttime field has
       never been used under Linux - it has not been and will not be supported
       by  libc or glibc*/  //:-(
      
//};
//获得当前时间,tloc是选择性的保存
asmlinkage int sys_time(long * tloc)
{
 int i, error;
 //#define CURRENT_TIME (xtime.tv_sec)当前时间
 i = CURRENT_TIME;
 
 if (tloc) {
  error = verify_area(VERIFY_WRITE, tloc, 4);
  //如果跑在486以上的机器,内核对CR0的WP置位,就可
  //以省去verify_area()的麻烦了,可惜当时Linus只有i386机器
  
  if (error)
   return error;
  put_fs_long(i,(unsigned long *)tloc); //将时间写入到用户空间中
 }
 return i;
}
//设置系统时间和日期
asmlinkage int sys_stime(long * tptr)
{
 if (!suser())
  return -EPERM;
 cli();
 xtime.tv_sec = get_fs_long((unsigned long *) tptr); /*秒*/
 xtime.tv_usec = 0;         /*微秒,没有这么精确哈*/
 time_status = TIME_BAD;        //时钟没有被同步标志
              //可能会在进程调度中被修改,猜的~
 time_maxerror = 0x70000000;
 time_esterror = 0x70000000;
 sti();
 return 0;
}

//时钟滴答的时间间隔:Linux用全局变量tick来表示时钟滴答的时间间隔长度
//long tick = (1000000 + HZ/2) / HZ; /* timer interrupt period */
//大概10ms,进行圆整操作,以us计数就是10000了
#define TICK_SIZE tick
//调用函数do_gettimeoffset()计算从上一次时钟中断发生到
//执行do_gettimeofday()函数的当前时刻之间的时间间隔offset_usec
//因为一旦进行了一次的时钟中断,计数器就设置为了LATCH然后递减了
//读取计数器的count就可以得到从上次定时中断到现在的差距时间了
static inline unsigned long do_gettimeoffset(void)
{
 int count;
 unsigned long offset = 0;
 //8253计时器操作
 //outb_p(val,addr)
 outb_p(0x00, 0x43);  /* latch the count ASAP */
 count = inb_p(0x40); /* read the latched count */
 count |= inb(0x40) << 8;
 
 //宏LATCH:Linux用宏LATCH来定义要写到PIT通道0的计数器中的值,它
 //表示PIT将没隔多少个时钟周期产生一次时钟中断。显然LATCH应该由下列公式计算:
 //LATCH=(1秒之内的时钟周期个数)÷(1秒之内的时钟中断次数)=(CLOCK_TICK_RATE)÷(HZ)
 //#define LATCH  ((CLOCK_TICK_RATE + HZ/2) / HZ)
 //大概1.2W左右
 /* we know probability of underflow is always MUCH less than 1% */
 //以我个人的理解就是上次时钟中段到现在的差距
 //如果count差值来看,应该是超过了一个滴答了!!
 if (count > (LATCH - LATCH/100)) {
  
  //这个操作的意思实际就是将计数器清零,然后重新开始
  //计数,而且把时间偏差设置为1个滴答
  outb_p(0x0a, 0x20);
  if (inb(0x20) & 1)
   offset = TICK_SIZE;
 }
 
 //从时间中断的产生到timer_interrupt()函数真正执行这段时间内,
 //以一共流逝了((LATCH-1)-count)个时钟周期
 //上述被除数表达式中的LATCH/2就是用来将结果向上圆整成整数的
 //乘以TICK_SIZE得到的就是微妙的时间差了
 count = ((LATCH-1) - count) * TICK_SIZE;
 count = (count + LATCH/2) / LATCH;  //向上取整的
 
 return offset + count;
}
//这里可以读取时间,记住的是xtime是全局的时间变量
static inline void do_gettimeofday(struct timeval *tv)
{
#ifdef __i386__
 cli();
 *tv = xtime;
 tv->tv_usec += do_gettimeoffset();  //呃 ,就是小于s的微秒的补偿
 
 //对于毫秒的解决方案
 if (tv->tv_usec >= 1000000) {
  tv->tv_usec -= 1000000;
  tv->tv_sec++; //进位
 }
 sti();
#else /* not __i386__ */
 cli();
 *tv = xtime;
 sti();
#endif /* not __i386__ */
}
//获取时间和时区
asmlinkage int sys_gettimeofday(struct timeval *tv, struct timezone *tz)
{
 int error;
 if (tv) {
  struct timeval ktv;
  error = verify_area(VERIFY_WRITE, tv, sizeof *tv);
  if (error)
   return error;
  //时间
  do_gettimeofday(&ktv);
  
  put_fs_long(ktv.tv_sec, (unsigned long *) &tv->tv_sec);
  put_fs_long(ktv.tv_usec, (unsigned long *) &tv->tv_usec);
 }
 if (tz) {
  error = verify_area(VERIFY_WRITE, tz, sizeof *tz);
  if (error)
   return error;
  put_fs_long(sys_tz.tz_minuteswest, (unsigned long *) tz);
  put_fs_long(sys_tz.tz_dsttime, ((unsigned long *) tz)+1);
 }
 return 0;
}
inline static void warp_clock(void)
{
 cli();  /*将本地时间转换为Greenwich标准时间,添加时差*/
 xtime.tv_sec += sys_tz.tz_minuteswest * 60;
 sti();
}
/*设定时间和时区*/
asmlinkage int sys_settimeofday(struct timeval *tv, struct timezone *tz)
{
 static int firsttime = 1;
 if (!suser())
  return -EPERM;
 if (tz) {
  sys_tz.tz_minuteswest = get_fs_long((unsigned long *) tz);
  sys_tz.tz_dsttime = get_fs_long(((unsigned long *) tz)+1);
  
  /*第一次使用时需要将时间设置为标准时间,这里使用了一个静态
   *标志变量,可能标志了这个函数只能调用一次哦
   *这通常是在/etc/rc脚本中执行的,应该是一次哈*/
    //Under Linux there is some peculiar `warp clock' semantics associated to
       //the settimeofday system call if on the very first call (after  booting)
       //that  has  a  non-NULL  tz  argument,  the  tv argument is NULL and the
       //tz_minuteswest field is nonzero. In such a case it is assumed that  the
       //CMOS  clock is on local time, and that it has to be incremented by this
       //amount to get UTC system time.  No doubt it is a bad idea to  use  this
       //feature.
  if (firsttime) { 
   firsttime = 0;
   if (!tv)
    warp_clock();
  }
 }
 if (tv) {
  int sec, usec;
  sec = get_fs_long((unsigned long *)tv);
  usec = get_fs_long(((unsigned long *)tv)+1);
 
  cli();
  /*?????
   *为什么要退回一秒呢???
   *现在有一点相通了,为什么呢?其实内核就是想把设置的时间点
   *设置到了时钟中断点上,这样count就没有误差了(尽管不知道这样有没有必要)*/
  usec -= do_gettimeoffset();
  if (usec < 0)
  {
   usec += 1000000;
   sec--;
  }
  xtime.tv_sec = sec;
  xtime.tv_usec = usec;
  time_status = TIME_BAD;
  time_maxerror = 0x70000000;
  time_esterror = 0x70000000;
  sti();
 }
 return 0;
}
asmlinkage int sys_adjtimex(struct timex *txc_p)
{
    long ltemp, mtemp, save_adjust;
 int error;
 /* Local copy of parameter */
 struct timex txc;
 error = verify_area(VERIFY_WRITE, txc_p, sizeof(struct timex));
 if (error)
   return error;
 /* Copy the user data space into the kernel copy
  * 这里是拷贝的数据结构了
  * 将用户态的txc_p拷贝到了内核的txc*/
 memcpy_fromfs(&txc, txc_p, sizeof(struct timex));
 /*当mode!=0时,就需要改变东西了,这时候必须是超级管理员了*/
 if (txc.mode && !suser())
  return -EPERM;
 /* Now we validate the data before disabling interrupts*/
 //进行教程参数的合法性的检测
 
 
 if (txc.mode & ADJ_OFFSET)
   /* Microsec field limited to -131000 .. 131000 usecs */
   if (txc.offset <= -(1 << (31 - SHIFT_UPDATE))
       || txc.offset >= (1 << (31 - SHIFT_UPDATE)))
     return -EINVAL;
 /* time_status must be in a fairly small range */
 if (txc.mode & ADJ_STATUS)
   if (txc.status < TIME_OK || txc.status > TIME_BAD)
     return -EINVAL;
 /* if the quartz is off by more than 10% something is VERY wrong ! */
 //就是允许的校准的晶振周期的差距不能超过10ms的10%
 if (txc.mode & ADJ_TICK)
   if (txc.tick < 900000/HZ || txc.tick > 1100000/HZ)
     return -EINVAL;
 cli();  /* Save for later - semantics of adjtime is to return old value */
 save_adjust = time_adjust;
 /* If there are input parameters, then process them */
 if (txc.mode)
 {
     if (time_status == TIME_BAD)
  time_status = TIME_OK;
     if (txc.mode & ADJ_STATUS)
  time_status = txc.status;
  
  //#define SHIFT_KF 20  /* shift for frequency increment */
     if (txc.mode & ADJ_FREQUENCY)
  time_freq = txc.frequency << (SHIFT_KF - 16);
     if (txc.mode & ADJ_MAXERROR)
  time_maxerror = txc.maxerror;
     if (txc.mode & ADJ_ESTERROR)
  time_esterror = txc.esterror;
     if (txc.mode & ADJ_TIMECONST)
  time_constant = txc.time_constant;
     if (txc.mode & ADJ_OFFSET)
       if (txc.mode == ADJ_OFFSET_SINGLESHOT)
  {
    time_adjust = txc.offset;
  }
       else /* XXX should give an error if other bits set */
  {
  
  /////////////////////////
  //I do really get confused!!! help!!!!
  /////////////////////////
  
    time_offset = txc.offset << SHIFT_UPDATE;
    mtemp = xtime.tv_sec - time_reftime;
    time_reftime = xtime.tv_sec;
    if (mtemp > (MAXSEC+2) || mtemp < 0)
      mtemp = 0;
    if (txc.offset < 0)
      time_freq -= (-txc.offset * mtemp) >>
        (time_constant + time_constant);
    else
      time_freq += (txc.offset * mtemp) >>
        (time_constant + time_constant);
    ltemp = time_tolerance << SHIFT_KF;     if (time_freq > ltemp)
      time_freq = ltemp;
    else if (time_freq < -ltemp)
      time_freq = -ltemp;
  }
  
     if (txc.mode & ADJ_TICK)
       tick = txc.tick;
 }
 
 txc.offset    = save_adjust;
 txc.frequency    = ((time_freq+1) >> (SHIFT_KF - 16));
 txc.maxerror    = time_maxerror;
 txc.esterror    = time_esterror;
 txc.status    = time_status;
 txc.time_constant  = time_constant;
 txc.precision    = time_precision;
 txc.tolerance    = time_tolerance;
 txc.time    = xtime;
 txc.tick    = tick;
 sti();  memcpy_tofs(txc_p, &txc, sizeof(struct timex));
 return time_status;
}
//对CMOS的时间中的分和秒的控制,就是虽然可能参数提供的时间
//比较全面,但是影响的只是分和秒
int set_rtc_mmss(unsigned long nowtime)
{
  int retval = 0;
  short real_seconds = nowtime % 60, real_minutes = (nowtime / 60) % 60;
  unsigned char save_control, save_freq_select, cmos_minutes;
  save_control = CMOS_READ(RTC_CONTROL); /* tell the clock it's being set */
  CMOS_WRITE((save_control|RTC_SET), RTC_CONTROL);
  save_freq_select = CMOS_READ(RTC_FREQ_SELECT); /* stop and reset prescaler */
  CMOS_WRITE((save_freq_select|RTC_DIV_RESET2), RTC_FREQ_SELECT);
  cmos_minutes = CMOS_READ(RTC_MINUTES);
  if (!(save_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD)
    BCD_TO_BIN(cmos_minutes);
  /*这里只对分和秒进行设置,很巧妙的避免了时区问题
   *(应该时区都是以60min为单位的)
   *这里对时间的偏差进行了30min的估计(30min是对
   *快和慢的一个权衡!),因为超过了这个偏差
   *就会导致可能的时间中小时的进位问题*/
  if (((cmos_minutes < real_minutes) ?
       (real_minutes - cmos_minutes) :
       (cmos_minutes - real_minutes)) < 30)
    {
    //默认的CMOS的时间都是使用BCD值来表示的,记住!!
      if (!(save_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD)
  {
    BIN_TO_BCD(real_seconds);
    BIN_TO_BCD(real_minutes);
  }
       CMOS_WRITE(real_seconds,RTC_SECONDS);
       CMOS_WRITE(real_minutes,RTC_MINUTES);
    }
  else
    retval = -1;
  CMOS_WRITE(save_freq_select, RTC_FREQ_SELECT);
  CMOS_WRITE(save_control, RTC_CONTROL);
  return retval;
}
  文档地址:http://blogimg.chinaunix.net/blog/upfile2/090226204743.pdf
相关阅读 更多 +
排行榜 更多 +
dazzly绚石工坊中文最新版下载

dazzly绚石工坊中文最新版下载

休闲益智 下载
木筏漂流游戏下载

木筏漂流游戏下载

休闲益智 下载
真实赛车3北美版下载

真实赛车3北美版下载

赛车竞速 下载