xuhdev
xuhdev

Reputation: 9323

Shell script: Portable way to programmably obtain the CPU vendor on POSIX systems

Is there a portable way to programmably obtain the CPU vendor info on POSIX systems in shell scripts? In particular, I need to tell whether an x86_64/AMD64 CPU is vended by Intel or AMD. The approach does not have to work on all POSIX systems, but it should work on a decent range of common POSIX systems: GNU/Linux, MacOS, and *BSD. As an example, a Linux only approach is to extract the info from /proc/cpuinfo.

Upvotes: 3

Views: 1282

Answers (3)

Pitto
Pitto

Reputation: 8579

I would proceed writing exact commands to get the cpu vendor for every supported OS and then run the appropriate set of commands for the given OS detection.

I wrote an example that can be easily improved / extended, taking in consideration the Operating Systems in your question:

OS="`uname`"
case "$OS" in
  SunOS*)    /usr/platform/`uname -m`/sbin/prtdiag -v ;;
  Darwin*)   sysctl -n machdep.cpu.vendor ;;
  Linux*)    lscpu | grep Vendor | awk '{print $NF}' ;;
  FreeBSD*)  sysctl -n hw.model | awk 'NR==1{print $NF}' ;;
  *)         echo "unknown: $OS" ;;
esac

Upvotes: 4

bishop
bishop

Reputation: 39354

POSIX (IEEE Std 1003.1-2017) does not mandate a system utility or shell variable holding the CPU brand. The closest you'll get is uname -m, which is the "hardware type on which the system is running". Unfortunately, that command doesn't have standardized output, so while you might get amd64 on some older machines, you'll mostly get i686 or x86_64 these days.

POSIX does mandate c99, a basic C compiler interface, be present when a C compiler is available at all. You can use that to compile a naive version of cpuid:

$ cat cpuid.c 
#include <stdio.h>
#include <string.h>
#include <stdint.h>
int main() {
    uint32_t regs[4] = { 0 };
    char brand[13] = { 0 };
    #ifdef _WIN32
        __cpuidex((int *)regs, 0, 0);
    #else
        __asm volatile("cpuid" : "=a" (regs[0]), "=b" (regs[1]), "=c" (regs[2]), "=d" (regs[3]) : "a" (0), "c" (0));
    #endif
    memcpy(&brand[0], &regs[1], 4);
    memcpy(&brand[4], &regs[3], 4);
    memcpy(&brand[8], &regs[2], 4);
    printf("%s\n", brand);
    return 0;
}

On a variety of test machines, here's what I get:

$ c99 -o cpuid cpuid.c && ./cpuid # MacOS X
GenuineIntel
$ c99 -o cpuid cpuid.c && ./cpuid # Intel-based AWS EC2 (M5)
GenuineIntel
$ c99 -o cpuid cpuid.c && ./cpuid # AMD-based AWS EC2 (T3a)
AuthenticAMD

Wikipedia lists numerous other possible vendor brands based on the cpuid instruction, but the ones likely most interesting for your defined use case are:

  • GenuineIntel - Intel
  • AMDisbetter! - AMD
  • AuthenticAMD - AMD

Provided you had this simple executable available in your path, the POSIX-y logic would look like:

if cpuid | grep -q AMD; then
    : # AMD logic here
elif cpuid | grep -q Intel; then
    : # Intel logic here
else # neither Intel nor AMD
    echo "Unsupported CPU vendor: $(cpuid)" >&2
fi

If you have a very, very old multi-core motherboard from the days when AMD was pin-equivalent with Intel, then you might care to know if CPU0 and CPU1 are the same vendor, in which case the C program above can be modified in the assembly lines to check processor 1 instead of 0 (the second argument to the respective asm functions).

This illustrates one particular benefit of this approach: if what you really want to know is whether the CPU supports a particular feature set (and are just using vendor as a proxy), then you can modify the C code to check whether the CPU feature is actually available. That's a quick modification to the EAX value given to the assembly code and a change to the interpretation of the E{B,C,D}X result registers.

With regards to the availability of c99, note that:

  1. A POSIX conforming system without c99 is proof that no C compiler's available on that system. If your target systems do not have c99, then you need to select and install a C compiler (gcc, clang, msvc, etc.) or attempt a fallback detection with eg /proc/cpuinfo.

  2. The standard declares that "Unlike all of the other non-OB-shaded utilities in this standard, a utility by this name probably will not appear in the next version of this standard. This utility's name is tied to the current revision of the ISO C standard at the time this standard is approved. Since the ISO C standard and this standard are maintained by different organizations on different schedules, we cannot predict what the compiler will be named in the next version of the standard." So you should consider compiling with something along the lines of ${C99:-c99} -o cpuid cpuid.c, which lets you flex as the binary name changes over time.

Upvotes: 5

Lizardx
Lizardx

Reputation: 1195

This is the basic logic you need:

  1. Detect OS type: linux OR BSD if BSD darwin OR other bsd. If other BSD, openbsd, freebsd, netbsd, dragonfly bsd. If darwin, you'll need darwin specific handling. If not a bsd and not linux, is it a proprietary type Unix? Are you going to try to handle it? If not, you need a safe fallback. This will determine what methods you use to do some, but not all, of the detections.

  2. If linux, it's easy if all you want is intel or amd, unless you need solid 32/64 bit detections, you specified 64 bit only, is this the running kernel or the cpu? So that has to be handled if it's relevant. Does it matter what type of intel/amd cpu it is? They make some SOC variants for example.

  3. sysctl for BSDs will give whatever each BSD decided to put in there. dragonfly and freebsd will be similar or the same, openbsd you have to check release to release, netbsd... is tricky. Some installs will require root to read sysctl, that's out of your hands, so you have to handle it case by case, and have error handling to detect root required, that varies, the usual is to make it user readable data, but not always. Note that the bsds can and do change the syntax of some field's data in the output, so you have to keep up on it if you actually want bsd support. Apple in general does not seem to care at all about real unix tools being able to work with their data, so it's empirical, don't assume without seeing several generations of the output. And they don't include a lot of standard unix tools by default so you can't assume things are actually installed in the first place.

  4. /proc/cpuinfo will cover all linux system for amd/intel, and a variety of methods can be used to pinpoint if it's running 32 bit or 64 bit, and if it's a 32 or 64 bit cpu.

  5. vm's can help, but only go part of the way since the cpu will be your host machine's, or part of it. Getting reliable current and last generation data that is reliable and real is a pain. But if you have intel and amd systems to work with, you can install most of the bsd variants except darwin/osx and debug on those, so that gets you to most of the os types, except darwin, which requires having a mac of some type available.

  6. Does failure matter? Does it actually matter if the detection fails? If so, how is failure handled? Does ARM/MIPS/PPC matter? What about other CPUs, like Elbrus? that have many intel-like features, but which are not amd or intel?

Like the comment said, read the cpu block in inxi to pick out what you need, but it's not easy to do, and requires a lot of data examples, and you'll be sad because one day FreeBSD or osx or openbsd will change something at total random for a new release.

If you ignore OSX, and pretend it doesn't exist, on the bright side, you'll get 98% support out of the box with very little code if all you need is intel/amd detection via /proc/cpuinfo, that prints it out as neat as can be desired. If you must have OSX, then you have to add the full suite of BSD handlers, which is a pain. Personally I wouldn't touch a project like that unless I got paid to do it, re OSX. Usually you can get FreeBSD and maybe OpenBSD reasonably readily, though you have to check every new major release to see if it all still works.

If you add more requirements, like cpus other than intel/amd, then it gets a lot harder and takes much more code.

Note that on darwin, currently all osx is I believe intel, though there's rumors apple is looking to leave intel. previously they were powerpc, so it also comes down to how robust the solution has to be, that is, do you care if it fails on a mac powerpc? do you care if it fails on a future mac that is not intel powered?

Further note that if BSD is specified, that excludes a wide variety of even more fragmented Unix systems, like openindiana, solaris proper, the proprietary unices of ibm, hp, and so on, which all use different tools.

Upvotes: 2

Related Questions