显卡属于PCI设备,可以通过枚举PCI设备,然后使用一些过滤条件将显卡设备筛选出来。Linux没有Windows那样直接枚举显卡的函数,只有借用shell脚本或者读取PCI设备配置空间来获取PCI设备信息。
lspci命令执行后效果如下:
各字段含义:
例:00:02.0 VGA compatible [0300]: Intel Corporation4 Series Chipset Integrated Graphics Controller [8086:2e12] (rev 03)
00:02.0:总线号:设备号.功能号
VGA compatible controller:设备类名
[0300]:设备类号
Intel Corporation:厂商名称
4 Series Chipset Integrated GraphicsController:设备名称
[8086:2e12]:[Vendor ID:Device ID]
(rev 03):版本号
利用lspci命令枚举出所有PCI设备后,可以利用VGA、Graphics等与显卡相关的关键字将显卡条目过滤出来。若知道显卡的Vendor ID和Device ID可将显卡条目精确过滤出来。
注:从程序中获取lspci输出见附件1。
每个PCI设备都有最多256个连续的配置空间.配置空间中包含了设备的厂商ID,设备ID,IRQ,设备存储区信息等。在x86上,保留了0xCF8~0xCFF的8个寄存器。实际上就是对应地址为0xCF8的32位寄存器和地址为0xCFC的32位寄存器。在0xCF8寄存中写入要访问设备对应的总线号,设备号、功能号和寄存器号组成的一个32位数写入0xCF8,然后从0xCFC上就可以取出对应PCI设备的信息。
PCI设备的配置空间如下:
通过该方法可以枚举出所有PCI设备的Vendor ID、Device ID和ClassID,通过这3个值,可以从/usr/share/hwdata/pci.ids数据文件中检索出设备的具体描述信息,可以从http://pciids.sourceforge.net/获取最新的pci.ids,以便识别最新的显卡设备。[8086:2e12]可以在pci.ids中查询到如下信息:
8086 Intel Corporation
2e12 4 Series Chipset Integrated GraphicsController
注:枚举PCI设备C程序示例见附件2。
从应用层暂时没有找到可以智能区别集成和独立显卡,若从驱动层与设备交互可能可以智能地识别集成和独立显卡,技术难道较大。若主机硬件不是经常更换的话,建议用以下三个方法来区别,推荐第三种。
例:00:02.0 VGAcompatible controller [0300]: Intel Corporation 4 Series Chipset IntegratedGraphics Controller [8086:2e12] (rev 03)
可以从Intel Corporation上知道该显卡是集成显卡,若是独立显卡一般都有NVIDIA或者ATI等关键字(由于没有AMD的CPU,暂不知道AMD的核显如何描述)。此方法需要管理员人工识别。
集成显卡的显存一般都小,而独立显卡的显存目前至少都是512MB、1GB不等,因此也可以从此参数来区分。
每个PCI设备的设备号都是全球唯一的,显卡设备也一样,因此,只要我们知道了显卡的设备号,即[Vendor ID:Device ID],不仅能够从一大堆PCI设备中精确筛选出显卡设备,还能够区分集成显卡和独立显卡。
若主机显卡不是经常更换的话,可以用事先获取的设备号去匹配主机上的PCI设备,此方法最简单,但是每次更换主机显卡,都需要更新区分代码,比较麻烦。
另外一种方法就是建立一个显卡[Vendor ID:Device ID]数据库(区分集成和独立显卡),程序可以直接读取数据库即可从PCI设备中识别出集成显卡和独立显卡。因为显卡的型号也不是很多,这种方法是可行的。若觉得麻烦,可以只创建常用的显卡的ID数据库,也是比较快的。数据库的实现方式可以用文本文件或其他可编辑文本记录,这样在更新数据库时,不需要重新编译代码。
使用popen
在学习unix编程的过程中,发现系统还提供了一个popen函数,可以非常简单的处理调用shell,其函数原型如下:
FILE*popen(const char *command, const char *type);
该函数的作用是创建一个管道,fork一个进程,然后执行shell,而shell的输出可以采用读取文件的方式获得。采用这种方法,既避免了创建临时文件,又不受输出字符数的限制,推荐使用。
popen使用FIFO管道执行外部程序。
#include<stdio.h>
FILE*popen(const char *command, const char *type);
intpclose(FILE *stream);
popen 通过type是r还是w确定command的输入/输出方向,r和w是相对command的管道而言的。r表示command从管道中读入,w表示 command通过管道输出到它的stdout,popen返回FIFO管道的文件流指针。pclose则用于使用结束后关闭这个指针。
下面看一个例子:
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
int main(void )
{
FILE *stream;
FILE *wstream;
char buf[1024];
memset( buf, '\0', sizeof(buf));//初始化buf,以免后面写如乱码到文件中
stream = popen( "ls -l","r" ); //将“ls -l”命令的输出 通过管道读取(“r”参数)到FILE*stream
wstream = fopen("test_popen.txt", "w+"); //新建一个可写的文件
fread( buf, sizeof(char),sizeof(buf), stream); //将刚刚FILE* stream的数据流读取到buf中
fwrite( buf, 1, sizeof(buf),wstream );//将buf中的数据写到FILE *wstream对应的流中,也是写到文件中
pclose( stream );
fclose( wstream );
return 0;
}
[root@localhostsrc]# gcc popen.c
[root@localhostsrc]# ./a.out
[root@localhostsrc]# cat test_popen.txt
总计 128
-rwxr-xr-x 1root root 5558 09-30 11:51 a.out
-rwxr-xr-x 1root root 542 09-30 00:00 child_fork.c
-rwxr-xr-x 1root root 480 09-30 00:13 execve.c
-rwxr-xr-x 1root root 1811 09-29 21:33 fork.c
-rwxr-xr-x 1root root 162 09-29 18:54 getpid.c
-rwxr-xr-x 1root root 1105 09-30 11:49 popen.c
-rwxr-xr-x 1root root 443 09-30 00:55 system.c
-rwxr-xr-x 1root root 0 09-30 11:51 test_popen.txt
-rwxr-xr-x 1root root 4094 09-30 11:39 test.txt
/*
*Enum all pci device via the PCI config register(CF8 and CFC).
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/io.h>
#define PCI_MAX_BUS 255 /* 8 bits (0 ~ 255)*/
#define PCI_MAX_DEV 31 /* 5 bits (0 ~ 31)*/
#define PCI_MAX_FUN 7 /* 3 bits (0 ~ 7) */
#define CONFIG_ADDRESS 0xCF8
#define CONFIG_DATA 0xCFC
#define PCICFG_REG_VID 0x00 /* Vendor id, 2bytes */
#define PCICFG_REG_DID 0x02 /* Device id, 2bytes */
#define PCICFG_REG_CMD 0x04 /* Commandregister, 2 bytes */
#define PCICFG_REG_STAT 0x06 /* Statusregister, 2 bytes */
#define PCICFG_REG_RID 0x08 /* Revision id,1 byte */
void list_pci_devices()
{
unsignedint bus, dev, fun;
unsignedint addr, data;
for(bus = 0; bus <= PCI_MAX_BUS; bus++)
{
for(dev = 0; dev <= PCI_MAX_DEV; dev++)
{
for(fun = 0; fun <= PCI_MAX_FUN; fun++)
{
addr= 0x80000000L | (bus<<16) | (dev<<11) | (fun<<8);
outl(addr,CONFIG_ADDRESS);
data= inl(CONFIG_DATA);
/*Identify vendor ID */
if((data != 0xFFFFFFFF) && (data != 0))
{
printf("%02X:%02X:%02X", bus, dev, fun);
printf("%04X:%04X",data&0xFFFF, data>>16);
addr= 0x80000000L | (bus<<16) | (dev<<11) | (fun<<8) |PCICFG_REG_RID;
outl(addr,CONFIG_ADDRESS);
data= inl(CONFIG_DATA);
printf("[%04X]", data>>16);
if(data&0xFF)
{
printf("(rev %02X)\n", data&0xFF);
}else
{
printf("\n");
}
}
}
}// end device
}// end bus
}
int main()
{
intret;
/*Enable r/w permission of all 65536 ports */
ret= iopl(3);
if(ret < 0)
{
perror("ioplset error");
return1;
}
list_pci_devices();
/*Disable r/w permission of all 65536 ports */
ret= iopl(0);
if(ret < 0)
{
perror("ioplset error");
return1;
}
return0;
}