Getting TSC rate in x86 kernel

后端 未结 3 1864
故里飘歌
故里飘歌 2020-12-19 05:50

I have an embedded Linux system running on an Atom, which is a new enough CPU to have an invariant TSC (time stamp counter), whose frequency the kernel measures on startup.

3条回答
  •  天涯浪人
    2020-12-19 06:16

    The TSC rate is directly related to "cpu MHz" in /proc/cpuinfo. Actually, the better number to use is "bogomips". The reason is that while the freq for TSC is the max CPU freq, the current "cpu Mhz" can vary at time of your invocation.

    The bogomips value is computed at boot. You'll need to adjust this value by number of cores and processor count (i.e. the number of hyperthreads) That gives you [fractional] MHz. That is what I use to do what you want to do.

    To get the processor count, look for the last "processor: " line. The processor count is + 1. Call it "cpu_count".

    To get number of cores, any "cpu cores: " works. number of cores is . Call it "core_count".

    So, the formula is:

    smt_count = cpu_count;
    if (core_count)
        smt_count /= core_count;
    cpu_freq_in_khz = (bogomips * scale_factor) / smt_count;
    

    That is extracted from my actual code, which is below.


    Here's the actual code I use. You won't be able to use it directly because it relies on boilerplate I have, but it should give you some ideas, particularly with how to compute

    // syslgx/tvtsc -- system time routines (RDTSC)
    
    #include 
    #include 
    
    tgb_t systvinit_tgb[] = {
        { .tgb_val = 1, .tgb_tag = "cpu_mhz" },
        { .tgb_val = 2, .tgb_tag = "bogomips" },
        { .tgb_val = 3, .tgb_tag = "processor" },
        { .tgb_val = 4, .tgb_tag = "cpu_cores" },
        { .tgb_val = 5, .tgb_tag = "clflush_size" },
        { .tgb_val = 6, .tgb_tag = "cache_alignment" },
        TGBEOT
    };
    
    // _systvinit -- get CPU speed
    static void
    _systvinit(void)
    {
        const char *file;
        const char *dlm;
        XFIL *xfsrc;
        int matchflg;
        char *cp;
        char *cur;
        char *rhs;
        char lhs[1000];
        tgb_pc tgb;
        syskhz_t khzcpu;
        syskhz_t khzbogo;
        syskhz_t khzcur;
        sysmpi_p mpi;
    
        file = "/proc/cpuinfo";
    
        xfsrc = fopen(file,"r");
        if (xfsrc == NULL)
            sysfault("systvinit: unable to open '%s' -- %s\n",file,xstrerror());
    
        dlm = " \t";
    
        khzcpu = 0;
        khzbogo = 0;
    
        mpi = &SYS->sys_cpucnt;
        SYSZAPME(mpi);
    
        // (1) look for "cpu MHz : 3192.515" (preferred)
        // (2) look for "bogomips : 3192.51" (alternate)
        // FIXME/CAE -- on machines with speed-step, bogomips may be preferred (or
        // disable it)
        while (1) {
            cp = fgets(lhs,sizeof(lhs),xfsrc);
            if (cp == NULL)
                break;
    
            // strip newline
            cp = strchr(lhs,'\n');
            if (cp != NULL)
                *cp = 0;
    
            // look for symbol value divider
            cp = strchr(lhs,':');
            if (cp == NULL)
                continue;
    
            // split symbol and value
            *cp = 0;
            rhs = cp + 1;
    
            // strip trailing whitespace from symbol
            for (cp -= 1;  cp >= lhs;  --cp) {
                if (! XCTWHITE(*cp))
                    break;
                *cp = 0;
            }
    
            // convert "foo bar" into "foo_bar"
            for (cp = lhs;  *cp != 0;  ++cp) {
                if (XCTWHITE(*cp))
                    *cp = '_';
            }
    
            // match on interesting data
            matchflg = 0;
            for (tgb = systvinit_tgb;  TGBMORE(tgb);  ++tgb) {
                if (strcasecmp(lhs,tgb->tgb_tag) == 0) {
                    matchflg = tgb->tgb_val;
                    break;
                }
            }
            if (! matchflg)
                continue;
    
            // look for the value
            cp = strtok_r(rhs,dlm,&cur);
            if (cp == NULL)
                continue;
    
            zprt(ZPXHOWSETUP,"_systvinit: GRAB/%d lhs='%s' cp='%s'\n",
                matchflg,lhs,cp);
    
            // process the value
            // NOTE: because of Intel's speed step, take the highest cpu speed
            switch (matchflg) {
            case 1:  // genuine CPU speed
                khzcur = _systvinitkhz(cp);
                if (khzcur > khzcpu)
                    khzcpu = khzcur;
                break;
    
            case 2:  // the consolation prize
                khzcur = _systvinitkhz(cp);
    
                // we've seen some "wild" values
                if (khzcur > 10000000)
                    break;
    
                if (khzcur > khzbogo)
                    khzbogo = khzcur;
                break;
    
            case 3:  // remember # of cpu's so we can adjust bogomips
                mpi->mpi_cpucnt = atoi(cp);
                mpi->mpi_cpucnt += 1;
                break;
    
            case 4:  // remember # of cpu cores so we can adjust bogomips
                mpi->mpi_corecnt = atoi(cp);
                break;
    
            case 5:  // cache flush size
                mpi->mpi_cshflush = atoi(cp);
                break;
    
            case 6:  // cache alignment
                mpi->mpi_cshalign = atoi(cp);
                break;
            }
        }
    
        fclose(xfsrc);
    
        // we want to know the number of hyperthreads
        mpi->mpi_smtcnt = mpi->mpi_cpucnt;
        if (mpi->mpi_corecnt)
            mpi->mpi_smtcnt /= mpi->mpi_corecnt;
    
        zprt(ZPXHOWSETUP,"_systvinit: FINAL khzcpu=%d khzbogo=%d mpi_cpucnt=%d mpi_corecnt=%d mpi_smtcnt=%d mpi_cshalign=%d mpi_cshflush=%d\n",
            khzcpu,khzbogo,mpi->mpi_cpucnt,mpi->mpi_corecnt,mpi->mpi_smtcnt,
            mpi->mpi_cshalign,mpi->mpi_cshflush);
    
        if ((mpi->mpi_cshalign == 0) || (mpi->mpi_cshflush == 0))
            sysfault("_systvinit: cache parameter fault\n");
    
        do {
            // use the best reference
            // FIXME/CAE -- with speed step, bogomips is better
    #if 0
            if (khzcpu != 0)
                break;
    #endif
    
            khzcpu = khzbogo;
            if (mpi->mpi_smtcnt)
                khzcpu /= mpi->mpi_smtcnt;
            if (khzcpu != 0)
                break;
    
            sysfault("_systvinit: unable to obtain cpu speed\n");
        } while (0);
    
        systvkhz(khzcpu);
    
        zprt(ZPXHOWSETUP,"_systvinit: EXIT\n");
    }
    
    // _systvinitkhz -- decode value
    // RETURNS: CPU freq in khz
    static syskhz_t
    _systvinitkhz(char *str)
    {
        char *src;
        char *dst;
        int rhscnt;
        char bf[100];
        syskhz_t khz;
    
        zprt(ZPXHOWSETUP,"_systvinitkhz: ENTER str='%s'\n",str);
    
        dst = bf;
        src = str;
    
        // get lhs of lhs.rhs
        for (;  *src != 0;  ++src, ++dst) {
            if (*src == '.')
                break;
            *dst = *src;
        }
    
        // skip over the dot
        ++src;
    
        // get rhs of lhs.rhs and determine how many rhs digits we have
        rhscnt = 0;
        for (;  *src != 0;  ++src, ++dst, ++rhscnt)
            *dst = *src;
    
        *dst = 0;
    
        khz = atol(bf);
        zprt(ZPXHOWSETUP,"_systvinitkhz: PRESCALE bf='%s' khz=%d rhscnt=%d\n",
            bf,khz,rhscnt);
    
        // scale down (e.g. we got xxxx.yyyy)
        for (;  rhscnt > 3;  --rhscnt)
            khz /= 10;
    
        // scale up (e.g. we got xxxx.yy--bogomips does this)
        for (;  rhscnt < 3;  ++rhscnt)
            khz *= 10;
    
        zprt(ZPXHOWSETUP,"_systvinitkhz: EXIT khz=%d\n",khz);
    
        return khz;
    }
    

    UPDATE:

    Sigh. Yes.

    I was using "cpu MHz" from /proc/cpuinfo prior to the introduction of processors with "speed step" technology, so I switched to "bogomips" and the algorithm was derived empirically based on that. When I derived it, I only had access to hyperthreaded machines. However, I've found an old one that is not and the SMT thing isn't valid.

    However, it appears that bogomips is always 2x the [maximum] CPU speed. See http://www.clifton.nl/bogo-faq.html That hasn't always been my experience on all kernel versions over the years [IIRC, I started with 0.99.x], but it's probably a reliable assumption these days.

    With "constant TSC" [which all newer processors have], denoted by constant_tsc in the flags: field in /proc/cpuinfo, the TSC rate is the maximum CPU frequency.

    Originally, the only way to get the frequency information was from /proc/cpuinfo. Now, however, in more modern kernels, there is another way that may be easier and more definitive [I had code coverage for this in other software of mine, but had forgotten about it]:

    /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq
    

    The contents of this file is the maximum CPU frequency in kHz. There are analogous files for the other CPU cores. The files should be identical for most sane motherboards (e.g. ones that are composed of the same model chip and don't try to mix [say] i7s and atoms). Otherwise, you'd have to keep track of the info on a per-core basis and that would get messy fast.

    The given directory also has other interesting files. For example, if your processor has "speed step" [and some of the other files can tell you that], you can force maximum performance by writing performance to the scaling_governor file. This will disable use of speed step.

    If the processor did not have constant_tsc, you'd have to disable speed step [and run the cores at maximum rate] to get accurate measurements

提交回复
热议问题