c语言显示cpuid_[C/C++] ccpuid:CPUID信息模块 V1.03版,改进mmx/sse指令可用性检查(使用signal、setjmp,支持纯C)、修正AVX检查Bug...

之前的ccpuid V1.02的mmx/sse指令可用性检查存在缺陷。现在的V1.03版改进了mmx/sse指令可用性检查,使用signal、setjmp,能够支持纯C程序。修正了AVX检查Bug。增加多文件链接ccpuid的测试例程。

一、更新说明

1.1 改进mmx/sse指令可用性检查

1.1.1 问题背景

以前是使用结构化异常处理来确认当前环境是否能运行mmx/sse指令的。该方法存在两个问题。

首先,仅有C++支持结构化异常处理,而纯C是不支持的。所以在V1.02版中,是根据__cplusplus宏来做条件编译的。造成仅有C++版支持指令可用性检查,而纯C版没有该检查能力。

更重大的问题是——结构化异常处理并不能捕获mmx/sse指令错误,程序会崩溃。

这是因为——当运行了当前环境不支持指令时,不会触发结构化异常处理,而是触发SIG_ILL(非法指令)信号,若没有该信号的处理函数,程序就崩溃了。

本来我以为“硬件支持MMX/SSE,但当前运行环境不支持”这种情况是很罕见的,因为现在的主流操作系统(Windows、Linux、Mac OS X等)都是支持mmx和sse。

但后来发现,这种情况是很容易出现的。这是因为AVX指令集的影响。

当gcc用上“-mavx”编译参数时,gcc会将所有的SSE代码编译为“VEX前缀编码的SSE指令”,而不是“传统SSE编码的指令”。“VEX前缀编码的SSE指令”实际上是128位的AVX指令,只有CPU硬件支持AVX,并且操作系统支持AVX时,才能运行“VEX前缀编码的SSE指令”。

就算你的代码中没有使用SSE,但是由于编译器的自动矢量化编译优化,也可能会自动生成VEX SSE指令,导致程序崩溃。

而VC在这方面好一点,依然是生成传统编码的SSE指令,程序能够在非AVX环境下运行。

1.1.2 解决办法

怎么捕获SIG_ILL(非法指令)信号呢?C标准库提供了,用于处理信号。

从SIG_ILL信号中怎么恢复呢?C标准库提供了,可以实现非局部转移。

但是还存在一个问题——在信号处理函数中,怎么知道应该回到哪一个位置呢?这时只有靠一个全局变量来存放返回位置。

而使用全局变量,可能会引起线程安全问题。暂时不管了,目前的跨平台多线程处理很麻烦。还是等C11普及后再说吧。

1.1.3 具体实现1(修改ccpuid.h)

在具体实现时,会遇到这个问题——纯C版的ccpuid只是一个头文件,按照惯例只能含有声明性内容,不能含有定义性内容。而信号处理函数必须是一个实际定义的函数,这样才能获取它的函数指针进行注册。

如果再添加一个ccpuid.c源文件,在它里面编写信号处理函数。虽然这样更符合C语言惯例,但这样用起来很麻烦,而且可能还需要修改代码才能与新版的ccpuid兼容。

所以我决定还是将信号处理函数及相关的全局变量放在头文件中。为了避免外部暴露,可以加上static关键字,使它们仅在文件内可见、不参与链接。

由于现在需要检查MMX与SSE这两类指令,所以可以考虑将这种检查封装成为一个函数——

119285205_1_20171217114351596.gif

//收到SIGILL信号时的跳回地址.

static jmp_buf *volatile simd_pjmpback_sigill = NULL;

//处理SIGILL信号.

static void simd_catch_sigill(int sig)

{

jmp_buf * pjmp = simd_pjmpback_sigill;

//能够跳回.

if (NULL!=pjmp)

{

longjmp(*pjmp, 1); //跳回.

}

//不能跳回.//fprintf(stderr, "!SIGILL!");

abort();

}

//尝试调用一个可能会发生SIGILL信号的函数.//

//result: 若正常运行,就返回pfunc的返回值. 否则返回0.//pfunc: 欲测试的函数.//puserdata: 用户自定参数, 在调用pfunc时会传递该参数.

static int simd_try_sigill(int (*pfunc)(void*), void* puserdata)

{

int rt = 0;

jmp_buf myjmp;

jmp_buf* poldjmp=NULL; //以前的跳回地址.

void (*old_signal)(int) = SIG_DFL; //以前的信号处理函数.//注册跳转.

if (0==setjmp(myjmp))

{

//登记跳回.

poldjmp = simd_pjmpback_sigill;

simd_pjmpback_sigill = &myjmp;

//注册信号处理函数.

old_signal = signal(SIGILL, simd_catch_sigill);

//[try]

rt = pfunc(puserdata);

}

else

{

//[catch]

}

//恢复信号处理函数.

simd_pjmpback_sigill = poldjmp;

signal(SIGILL, old_signal);

return rt;

}

119285205_1_20171217114351596.gif

验证MMX指令是否能运行——

119285205_1_20171217114351596.gif

//验证mmx指令是否能运行_实际指令测试.

static int simd_try_mmx_pfunc(void* puserdata)

{

#if defined(_M_X64) && defined(_MSC_VER) && !defined(__INTEL_COMPILER)

//VC编译器不支持64位下的MMX.

return 0;

#else

_mm_empty(); //MMX instruction: emms

return 1;

#endif //#if defined(_M_X64) && defined(_MSC_VER)

}

//验证mmx指令是否能运行.

static int simd_try_mmx()

{

int rt = 0;

#if defined(_M_X64) && defined(_MSC_VER) && !defined(__INTEL_COMPILER)

//VC编译器不支持64位下的MMX.

#else

rt = simd_try_sigill(simd_try_mmx_pfunc, 0);

#endif //#if defined(_M_X64) && defined(_MSC_VER)

return rt;

}

119285205_1_20171217114351596.gif

验证SSE指令是否能运行——

119285205_1_20171217114351596.gif

//验证sse指令是否能运行_实际指令测试.

static int simd_try_sse_pfunc(void* puserdata)

{

int rt = 0;

volatile __m128 xmm1 = _mm_setzero_ps(); //SSE instruction: xorps

int* pxmm1 = (int*)&xmm1; //避免GCC的 -Wstrict-aliasing 警告.

if (0==*pxmm1) rt = 1; //避免Release模式编译优化时剔除_mm_setzero_ps.

return rt;

}

//验证sse指令是否能运行.

static int simd_try_sse()

{

int rt = simd_try_sigill(simd_try_sse_pfunc, 0);

return rt;

}

119285205_1_20171217114351596.gif

然后再修改原来的simd_mmx、simd_sse_level函数,在最后调用simd_try_mmx、simd_try_sse验证当前环境——

119285205_1_20171217114351596.gif

//是否支持MMX指令集.//

//result: 返回当前运行环境是否支持MMX指令集. 非0表示支持, 0表示不支持.//phwmmx: 返回硬件是否支持MMX指令集. 非0表示支持, 0表示不支持.

INLINE int simd_mmx(int* phwmmx)

{

int rt = 0; //result

#ifdef CCPUID_X86

const uint32_t BIT_D_MMX = 0x00800000; //bit 23

uint32_t dwBuf[4];

//check processor support

getcpuid(dwBuf, 1); //Function 1: Feature Information

if ( dwBuf[3] & BIT_D_MMX ) rt=1;

if (NULL!=phwmmx) *phwmmx=rt;

//check OS support

if ( rt>0 )

{

if (!simd_try_mmx()) rt=0;

}

#else //#ifdef CCPUID_X86

if (NULL!=phwmmx) *phwmmx=rt;

#endif //#ifdef CCPUID_X86

return rt;

}

//检测SSE系列指令集的支持级别.//

//result: 返回当前运行环境的SSE系列指令集支持级别. 详见SIMD_SSE_常数.//phwmmx: 返回硬件的SSE系列指令集支持级别. 详见SIMD_SSE_常数.

INLINE int simd_sse_level(int* phwsse)

{

int rt = SIMD_SSE_NONE; //result

#ifdef CCPUID_X86

const uint32_t BIT_D_SSE = 0x02000000; //bit 25

const uint32_t BIT_D_SSE2 = 0x04000000; //bit 26

const uint32_t BIT_C_SSE3 = 0x00000001; //bit 0

const uint32_t BIT_C_SSSE3 = 0x00000100; //bit 9

const uint32_t BIT_C_SSE41 = 0x00080000; //bit 19

const uint32_t BIT_C_SSE42 = 0x00100000; //bit 20

uint32_t dwBuf[4];

//check processor support

getcpuid(dwBuf, 1); //Function 1: Feature Information

if ( dwBuf[3] & BIT_D_SSE )

{

rt = SIMD_SSE_1;

if ( dwBuf[3] & BIT_D_SSE2 )

{

rt = SIMD_SSE_2;

if ( dwBuf[2] & BIT_C_SSE3 )

{

rt = SIMD_SSE_3;

if ( dwBuf[2] & BIT_C_SSSE3 )

{

rt = SIMD_SSE_3S;

if ( dwBuf[2] & BIT_C_SSE41 )

{

rt = SIMD_SSE_41;

if ( dwBuf[2] & BIT_C_SSE42 )

{

rt = SIMD_SSE_42;

}

}

}

}

}

}

if (NULL!=phwsse) *phwsse=rt;

//check OS support

if ( rt>0 )

{

if (!simd_try_sse()) rt=SIMD_SSE_NONE;

}

#else //#ifdef CCPUID_X86

if (NULL!=phwmmx) *phwmmx=rt;

#endif //#ifdef CCPUID_X86

return rt;

}

119285205_1_20171217114351596.gif

1.1.4 具体实现2(修改ccpuid.hpp、ccpuid.cpp)

再考虑一下如何修改CCPUID类。原先是在调用RefreshProperty函数时,就一起更新_mmx、_sse等变量。而现在simd_try_mmx、simd_try_sse等验证函数不是线程安全的,如果还是一起更新的话,恐怕会影响稳定性。

所以设计为在需要时才验证的模式更好。具体做法是,simd_mmx、simd_sse_level返回-1,由mmx、sse负责检查。

mmx、sse原先设计为 const成员函数,而现在需要保存验证结果而修改_mmx、_sse变量,该怎么办呢?有两种方案——

1. 去掉该函数的const关键字,使其成为普通成员函数。

2. 将_mmx、_sse变量加上mutable关键字,使其能在 const成员函数 中修改。

考虑到旧代码的兼容性,我选择了方案2。

修改ccpuid.hpp:给_mmx、_sse变量加上mutable关键字,将mmx、sse函数挪至cpp文件中——

int mmx() const; //系统支持MMX.

int sse() const; //系统支持SSE.

...

mutable int _mmx; //系统支持MMX.

mutable int _sse; //系统支持SSE.

修改ccpuid.cpp:simd_mmx、simd_sse_level返回-1,由mmx、sse负责检查——

119285205_1_20171217114351596.gif

int CCPUID::simd_mmx(int* phwmmx) const

{

int rt = 0; //result

#ifdef CCPUID_X86

const uint32_t BIT_D_MMX = 0x00800000; //bit 23

uint32_t dwBuf[4];

//check processor support

GetData(dwBuf, 1); //Function 1: Feature Information

if ( dwBuf[3] & BIT_D_MMX ) rt=1;

if (NULL!=phwmmx) *phwmmx=rt;

//check OS support

rt = -1; //需要时才检查, 见mmx().

#else //#ifdef CCPUID_X86

if (NULL!=phwmmx) *phwmmx=rt;

#endif //#ifdef CCPUID_X86

return rt;

}

int CCPUID::simd_sse_level(int* phwsse) const

{

int rt = SIMD_SSE_NONE; //result

#ifdef CCPUID_X86

const uint32_t BIT_D_SSE = 0x02000000; //bit 25

const uint32_t BIT_D_SSE2 = 0x04000000; //bit 26

const uint32_t BIT_C_SSE3 = 0x00000001; //bit 0

const uint32_t BIT_C_SSSE3 = 0x00000100; //bit 9

const uint32_t BIT_C_SSE41 = 0x00080000; //bit 19

const uint32_t BIT_C_SSE42 = 0x00100000; //bit 20

uint32_t dwBuf[4];

//check processor support

GetData(dwBuf, 1); //Function 1: Feature Information

if ( dwBuf[3] & BIT_D_SSE )

{

rt = SIMD_SSE_1;

if ( dwBuf[3] & BIT_D_SSE2 )

{

rt = SIMD_SSE_2;

if ( dwBuf[2] & BIT_C_SSE3 )

{

rt = SIMD_SSE_3;

if ( dwBuf[2] & BIT_C_SSSE3 )

{

rt = SIMD_SSE_3S;

if ( dwBuf[2] & BIT_C_SSE41 )

{

rt = SIMD_SSE_41;

if ( dwBuf[2] & BIT_C_SSE42 )

{

rt = SIMD_SSE_42;

}

}

}

}

}

}

if (NULL!=phwsse) *phwsse=rt;

//check OS support

rt = -1; //需要时才检查, 见sse().

#else //#ifdef CCPUID_X86

if (NULL!=phwsse) *phwsse=rt;

#endif //#ifdef CCPUID_X86

return rt;

}

...

int CCPUID::mmx() const

{

if (_mmx<0)

{

//发现未检查, 进行检查.

_mmx = _hwmmx;

if (_mmx>0)

{

if (!simd_try_mmx()) _mmx=0;

}

}

return _mmx;

}

int CCPUID::sse() const

{

if (_sse<0)

{

//发现未检查, 进行检查.

_sse = _hwsse;

if (_sse>0)

{

if (!simd_try_sse()) _sse=SIMD_SSE_NONE;

}

}

return _sse;

}

119285205_1_20171217114351596.gif

1.2 修正AVX检查Bug

V1.02版因X86平台判断功能改动了AVX检查代码,不小心留下了一个Bug——当操作系统不支持OSXSAVE时,simd_avx_level函数却返回一个正数,报告当前环境支持AVX。而按照规定,它应该返回0。

经过检查,发现是条件判断覆盖问题。于是修改了 simd_avx_level、CCPUID::simd_avx_level,修正了此Bug。

1.3 增加多文件链接ccpuid的测试例程

因SIGILL信号处理,现在ccpuid.h这个头文件中不得不含有定义性代码。一般来说,当头文件中含有定义性代码时,有时会造成多文件链接时报错。

虽然我们已经加上了static关键字使那部分代码变为仅文件内可见、不参与链接。但是为了验证,还是编写一个测试例程比较好。

于是在C++测试例程项目中 增加两个文件——test2.h和test2.cpp。

test2.h用于声明test2函数——

void test2(void);

在test2.cpp中也引用了ccpuid.hpp,并写了简单的测试代码——

119285205_1_20171217114351596.gif

#include

#include "ccpuid.hpp"

#include "test2.h"

//测试调用 const对象 的 const成员函数.

void test2_const(const CCPUID& ccid)

{

printf("test2_SSE:\t%d\n",ccid.sse());

}

//测试多文件链接使用ccpuid.

void test2(void)

{

test2_const(CCPUID::cur());

}

119285205_1_20171217114351596.gif

最后在testccpuid.cpp中添加对test2函数的调用——

#include "test2.h"

...

test2();

二、全部代码

(略)

2.1 ccpuid模块

2.1.1 ccpuid.h: CPUID信息(纯C版)

全部代码——

2.1.2 ccpuid.hpp: CPUID信息(C++版头文件)

全部代码——

2.1.3 ccpuid.cpp: CPUID信息(C++版源文件)

全部代码——

2.2 纯C版测试例程

2.2.1 testccpuidc.c : [C] 测试ccpuid.h, 显示CPUID信息

全部代码——

2.3 C++版测试例程

2.3.1 testccpuid.cpp : [C++] 测试ccpuid.hpp, 显示所有的CPUID信息

全部代码——

2.3.2 test2.h: 多文件链接ccpuid的测试(头文件)

全部代码——

2.3.3 test2.cpp: 多文件链接ccpuid的测试(源文件)

全部代码——

2.4 makefile

makefile——

三、编译测试

在以下编译器中成功编译——

VC6:x86版。

VC2003:x86版。

VC2005:x86版。

VC2010:x86版、x64版。

GCC 4.7.0(Fedora 17 x64):x86版、x64版。

GCC 4.6.2(MinGW (20120426)):x86版。

GCC 4.7.1(TDM-GCC (MinGW-w64)):x86版、x64版。

llvm-gcc-4.2(Mac OS X Lion 10.7.4, Xcode 4.4.1):x86版、x64版。

119285205_2_20171217114351689.png

119285205_3_2017121711435217.png

119285205_4_20171217114352596.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值