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
*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
相关阅读 更多 +
排行榜 更多 +