• date 命令实现源码分析


    http://qgjie456.blog.163.com/blog/static/354513672008465031709/

    hwclock可以设置系统时间,大家可能都用过。但是我想每个人对知道此命令的途径却不完全相同。
    我陈述一下自己得知此命令的过程,希望能那些对linux望而却步的初学者有些帮助,linux本身
    公开源码,这对那些追根问底的人是个不错的选择。
    文中过程废话较多,假设读者是初学者,对一些过程不是很清楚。高手见谅!
    几天前要做几台服务器的时间同步,和同事讨论系统时间设置的问题,我模糊记得date可以设置
    系统时间,和他打赌可以使用(为此输了三条鱼,:(  )比如:
    [root@TestRA test]# date -s "Fri Jan 11 15:32:16 GMT 2002"
    Fri Jan 11 23:32:16 CST 2002
    再用date一看成功了!
    正得意间,同事的reboot了机器,重启后发现时间未变。郁闷,找原因。
    先看看这个date是哪个土程序写的?
    [root@TestRA test]# whereis date
    date: /bin/date /usr/man/man1/date.1
    [root@TestRA test]# rpm -qa -filesbypkg |grep "/bin/date"
    sh-utils                  /bin/date
    原来是sh-utils这个包,从redhat7.1的光盘中找到sh-utils-2.0.src.rpm文件,解开用source-insight
    看了看,发现在date.c中有date命令的实现。发现其实现非常简单:
    date.c:    main()->stime()->settimeofday()  这个settimeofday就没有实现了,在linux下man了一下
    发现原来是个libc中的函数,可是在libc的工程中也未发现其实现(建议大家手头最好有glibc的源码)
    估计是个系统调用,打开linux-kernel的工程,查找sys_settimeofday,果然有此函数,发现
    kernel/time.c#L191
    191 asmlinkage int sys_settimeofday(struct timeval *tv, struct timezone *tz)
    192 {
    193         struct timeval  new_tv;
    194         struct timezone new_tz;
    195
    196         if (tv) {
    197                 if (copy_from_user(&new_tv, tv, sizeof(*tv)))
    198                         return -EFAULT;
    199         }
    200         if (tz) {
    201                 if (copy_from_user(&new_tz, tz, sizeof(*tz)))
    202                         return -EFAULT;
    203         }
    204
    205         return do_sys_settimeofday(tv ? &new_tv : NULL, tz ? &new_tz : NULL);
    206 }
    函数很简单,从用户空间得到参数,然后调do_sys_settimeofday(kernel中常用的命名方式)
    也在time.c中。
    165 int do_sys_settimeofday(struct timeval *tv, struct timezone *tz)
    166 {
    167         static int firsttime = 1;
    168
    169         if (!capable(CAP_SYS_TIME))
    170                 return -EPERM;
    171                
    172         if (tz) {
    173                 /* SMP safe, global irq locking makes it work. */
    174                 sys_tz = *tz;
    175                 if (firsttime) {
    176                         firsttime = 0;
    177                         if (!tv)
    178                                 warp_clock();
    179                 }
    180         }
    181         if (tv)
    182         {
    183                 /* SMP safe, again the code in arch/foo/time.c should
    184                  * globally block out interrupts when it runs.
    185                  */
    186                 do_settimeofday(tv);
    187         }
    188         return 0;
    189 }
    也很简单,判断了一下是否是root,不然没有权限return -PERM,主要是do_settimeofday。
    arch/i386/kernel/time.c#L264
    264 void do_settimeofday(struct timeval *tv)
    265 {
    266         write_lock_irq(&xtime_lock);
    267         /* This is revolting. We need to set the xtime.tv_usec
    268          * correctly. However, the value in this location is
    269          * is value at the last tick.
    270          * Discover what correction gettimeofday
    271          * would have done, and then undo it!
    272          */
    273         tv->tv_usec -= do_gettimeoffset();
    274
    275         while (tv->tv_usec < 0) {
    276                 tv->tv_usec += 1000000;
    277                 tv->tv_sec--;
    278         }
    279
    280         xtime = *tv;
    281         time_adjust = 0;                /* stop active adjtime() */
    282         time_status |= STA_UNSYNC;
    283         time_maxerror = NTP_PHASE_LIMIT;
    284         time_esterror = NTP_PHASE_LIMIT;
    285         write_unlock_irq(&xtime_lock);
    286 }
    最终的实现,发现只是对一些系统变量的设置,果然没有对CMOS的write操作。不甘心,为了那三条鱼
    拼了,觉得既然变量变了,是不是在时钟中断时会写CMOS同步呢,找到time中断函数timer_interrupt
    在原文中有一段描述,
    356 /*
    357  * timer_interrupt() needs to keep up the real-time clock,
    358  * as well as call the "do_timer()" routine every clocktick
    359  */
    欣喜若狂,再看在do_timer_interrupt中有实现
    380         /*
    381          * If we have an externally synchronized Linux clock, then update
    382          * CMOS clock accordingly every ~11 minutes. Set_rtc_mmss() has to be
    383          * called as close as possible to 500 ms before the new second starts.
    384          */
    385         if ((time_status & STA_UNSYNC) == 0 &&
    386             xtime.tv_sec > last_rtc_update + 660 &&
    387             xtime.tv_usec >= 500000 - ((unsigned) tick) / 2 &&
    388             xtime.tv_usec <= 500000 + ((unsigned) tick) / 2) {
    389                 if (set_rtc_mmss(xtime.tv_sec) == 0)
    390                         last_rtc_update = xtime.tv_sec;
    391                 else
    392                         last_rtc_update = xtime.tv_sec - 600; /* do it again in 60 s */
    393         }
    我kao,敌人搞得这么复杂,懒得看程序,看看敌人的描述吧。。。好像和程序实现差不多。主要是
    set_rtc_mmss(xtime.tv_sec),那个程序也不短,不全copy了,免得像是灌水(也差不多了),主要是
    314         /*
    315          * since we're only adjusting minutes and seconds,
    316          * don't interfere with hour overflow. This avoids
    317          * messing with unknown time zones but requires your
    318          * RTC not to be off by more than 15 minutes
    319          */
    320         real_seconds = nowtime % 60;
    321         real_minutes = nowtime / 60;
    322         if (((abs(real_minutes - cmos_minutes) + 15)/30) & 1)
    323                 real_minutes += 30;             /* correct for half hour time zone */
    324         real_minutes %= 60;
    325
    326         if (abs(real_minutes - cmos_minutes) < 30) {
    327                 if (!(save_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) {
    328                         BIN_TO_BCD(real_seconds);
    329                         BIN_TO_BCD(real_minutes);
    330                 }
    331                 CMOS_WRITE(real_seconds,RTC_SECONDS);
    332                 CMOS_WRITE(real_minutes,RTC_MINUTES);
    sorry,不是灌水,不要打我!这段比较重要。看了看发现又是理解不对,敌人并不同步分钟以上的
    时间,没有搞头了,不过发现CMOS_WRITE这个顺眼的程序,TNND,这么大个操作系统总该有设系统
    时间的地方吧。不过找CMOS_WRITE还是比较痛苦的,比我头发都多(有些夸张,是我头发比较少),
    有RTC_SECONDS嘛,找其定义
    include/linux/mc146818rtc.h#L32
    32 #define RTC_SECONDS             0
    33 #define RTC_SECONDS_ALARM       1
    34 #define RTC_MINUTES             2
    35 #define RTC_MINUTES_ALARM       3
    36 #define RTC_HOURS               4
    37 #define RTC_HOURS_ALARM         5
    38 /* RTC_*_alarm is always true if 2 MSBs are set */
    39 # define RTC_ALARM_DONT_CARE    0xC0
    40
    41 #define RTC_DAY_OF_WEEK         6
    42 #define RTC_DAY_OF_MONTH        7
    43 #define RTC_MONTH               8
    44 #define RTC_YEAR                9
    有定义就好办,系统中总应该有写年的地方吧,找。。。
    drivers/char/rtc.c#L
    363                 CMOS_WRITE(yrs, RTC_YEAR);
    364                 CMOS_WRITE(mon, RTC_MONTH);
    365                 CMOS_WRITE(day, RTC_DAY_OF_MONTH);
    366                 CMOS_WRITE(hrs, RTC_HOURS);
    367                 CMOS_WRITE(min, RTC_MINUTES);
    368                 CMOS_WRITE(sec, RTC_SECONDS);
    应该是此处,看看是在char/rtc.c中,怎么跑到驱动中来了,发现是在ioctl中的RTC_SET_TIME中,
    原来系统实现将对CMOS时间的设置搞到这里,看看系统/dev中果然有rtc文件,试试。。
    #include <stdio.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <sys/ioctl.h>
    #include <fcntl.h>
    #include <linux/rtc.h>
    #include <sys/time.h>
    #include <unistd.h>
    #include "getdate.h"
    extern long timezone;
    int main(int argc,char *argv[])
    {
            struct rtc_time time;
            struct tm *tm;
            struct timeval tt;
            int fd = -1;
            int ret;
            if (argc != 2)
            {
                    printf("settime [date string]\n");
                    return -1;
            }
            timezone = 8;
            tt.tv_sec = get_date(argv[1],NULL);
            tt.tv_usec = 0;
            if (tt.tv_sec <= 0)
            {
                    printf("[date string] is not right\n");
                    return -2;
            }
            memset(&time,0,sizeof(struct rtc_time));
            memset(&tm,0,sizeof(struct tm));
            fd = open("/dev/rtc",O_RDONLY);
            if (fd >= 0)
            {
                    tm = localtime(&tt.tv_sec);
                    memcpy(&time,tm,sizeof(struct rtc_time));
                    ret = ioctl(fd,RTC_SET_TIME,&time);
                    settimeofday(&tt,NULL);
            }
            printf("return is %d\n",ret);
            if (fd >= 0 ) close(fd);
            return ret;
    }
    get_date函数是我从sh-utils中抄的,试了一下,OK了。
    高兴之余觉得不对吧,敌人作出来不会只为编程用,命令行应该也有现成的,果然在/sbin中
    [root@TestRA /sbin]# strings * -f|grep "/dev/rtc"
    找到hwclock和clock命令,man了一下,发现hwclock可以设置CMOS时间。
    FT,白忙了这么半天!!!发现最终在用date设时间后只要hwclock -w一下就可以写到CMOS中,
    原理当然也是/dev/rtc。
    结论:敌人的东西很完善,不要怀疑,有问题从自己身上找原因。呵呵!

  • 相关阅读:
    MySql错误解决方案汇总
    不适合做管理的人zz
    linux 自动执行 crontab学习笔记
    Google Megastore分布式存储技术全揭秘zz
    【算法】n个人围成一圈报数,报到3的退出,下面接着从1开始报,问最后剩下的是谁?
    大数据下的数据分析平台架构zz
    ETL的可扩展性和可维护性zz
    【算法】各种排序算法测试代码
    谈爱情故事,谈观察者模式
    解读设计模式单例模式(Singleton Pattern)
  • 原文地址:https://www.cnblogs.com/leaven/p/1821939.html
Copyright © 2020-2023  润新知