Solaris 内核统计信息 - 使用C访问libkstat

本文介绍了Solaris内核提供的统计信息数据结构和函数集,包括kstat链、kstat结构及其成员,以及如何通过C语言访问libkstat库来获取内核统计数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前段时间看了篇文章,高兴的时候就把它翻译过来了。哈哈,第一次翻译文章,水平有限,不足之处欢迎指正。
原文出处:http://developers.sun.com/solaris/articles/kstatc.html


Solaris 内核统计信息 - 使用C访问libkstat


引言


为了向外界导出特定模块的统计信息,Solaris内核向设备驱动程序和其它内核模块提供了很多数据结构和函数集。这个底层结构被称为kstat,它向Solaris软件开发者提供:

供设备驱动程序和其它内核模块提交统计信息的C语言函数。

供应用程序从Solaris取得统计信息数据而不需要直接访问内核内存的C语言函数。

使用基于Perl的命令行程序/usr/bin/kstat重复访问统计信息数据或者使用shell脚本(在solaris 8里介绍)。

Solarislibkstat库包含用于从应用程序访问kstatC语言函数。这些函数利用伪设备/dev/kstat提供一个到内核数据的安全接口,避免需要程序设置UIDroot

因为很多开发者对通过C程序访问内核统计信息很感兴趣,这篇文章关注使用libkstat。它解释数据结构和函数,并提供代码例子使你能够轻松开始使用这个程序库。

数据结构概述

Solaris的内核统计信息由一个被称为kstat链的链接表结构维护。每一个kstat都有一个通用头部和类型专属数据段。这个链在系统启动的时候被初始化,但是因为Solaris是一个动态操作系统,这个链会随时改变。如果内核需要可以向系统添加或者从系统里删除kstat的入口。例如,当使用动态配置向一个正在运行的系统添加插件板和它的所有附加组件,相应的新硬件驱动程序和内核模块kstat入口就被插入到链中。

图一:kstat

kstat结构中的ks_data成员指向kstat的数据段。很多数据类型被支持:raw

namedtimerinterruptI/O。这些将在数据类型小节解释。

1kstat头结构,来自/usr/include/kstat.h


typedef  struct  kstat {

/*
 * 与内核和用户都相关的域
 
*/

hrtime_t ks_crtime; 
/*  创建时间  */

struct  kstat  * ks_next;  /*  kstat链的下一个链接  */

kid_t ks_kid;  /*  kstat独一无二的ID  */

char  ks_module[KSTAT_STRLEN];  /*  模块名  */

uchar_t ks_resv;  /*  保留的  */

int  ks_instance;  /*  模块实例  */

char  ks_name[KSTAT_STRLEN];  /*  kstat名称  */

uchar_t ks_type;  /*  kstat数据类型  */

char  ks_class[KSTAT_STRLEN];  /*  kstat类型  */

uchar_t ks_flags; 
/*  kstat标志  */

void   * ks_data;  /*  kstat类型专属的数据  */

uint_t ks_ndata; 
/*  数据记录的个数  */

size_t ks_data_size; 
/*  kstat数据大小  */

hrtime_t ks_snaptime; 
/*  上一次拍数据快照的时间  */


/*
 * 仅与内核相关的域
 
*/

int  ( * ks_update)( struct  kstat  * int );

void   * ks_private;

int  ( * ks_snapshot)( struct  kstat  * void   * int );

void   * ks_lock;

} kstat_t;

有意义的成员包括:

ks_crtime:反映kstat被创建的时间,允许通过各种计数器计算从kstat被创建以后的等级(“从启动以后的等级”被更通用的概念“从kstat被创建以后的级级”替代)。

所有的与时间相关的kstat成员,例如创建时间,上一次拍快照的时间,kstat_timer_tkstat_io_t时间戳和类型的值都是64位纳秒值。

kstat时间戳的精确性依赖于机器,但是精确(位数)在所有平台上都是相同的。参看gethrtime(3C)man手册页获得与高分辨率时间戳相关的所有信息。

ks_nextkstat被保存在一个以NULL结束的链表或者链里。ks_next指向链中的下一个kstat

ks_kidkstat独一无二的标识符。

ks_moduleks_instance:这包含创建kstat的模块名和实例。假如只有一个实例,ks_instance就是0。参看kstat的手册页的Names小节获得更多的信息。

ks_name:给kstat一个有意义的名称。参看kstat手册页的Names小节获得更多的kstat名称空间信息。

ks_typekstat里的数据类型。在数据类型小节介绍kstat包含的数据类型。

ks_class:每一个kstat都可以被描述成某些主要类型的统计信息,例如总线(bus),磁盘(disk),网络(net),虚拟内存(vm)或者杂项(misc)。这个域可以用作提取相关kstat的过滤器。

Solaris当前使用的有下列值:

bus controller device_error disk hat

kmem_cache kstat misc net nfs

pages partition rpc ufs vm

vmem

ks_dataks_ndataks_data_sizeks_data指向kstat的数据段。这里保存的数据类型取决于ks_typeks_ndata表示数据记录数。仅有一些kstat类型支持多记录数据。

下列kstat支持多记录数据:

KSTAT_TYPE_RAW KSTAT_TYPE_NAMED KSTAT_TYPE_TIMER

下列kstat仅支持一个记录的数据:

KSTAT_TYPE_INTR KSTAT_TYPE_IO

ks_data_size:是数据段的总大小,以字节为单位。

ks_snaptime:这是最后一次拍数据快照的时间戳。可以使用下面的计算方法来计算活动级别:

rate = (new_count – old_count) / (new_snaptime – old_snaptime)


现在开始


使用kstat,程序必须先调用kstat_open()函数,它反回一个指向kstat控制结构的指针。例2展示了该结构的成员。

2kstat链控制结构


     
typedef  struct  kstat_ctl {    
    kid_t      kc_chain_id;    
/*  当前kstat链ID  */     
    kstat_t   
* kc_chain;        /*  指向kstat链的指针  */     
    
int         kc_kd;           /*  /dev/kstat文件描述符  */    
} kstat_ctl_t;

kc_chain指向kstat链拷贝的头部。一般通过遍历或者使用kstat_lookup()函数来查找和处理特定类型的kstatkc_chain_idkstat链的标识符,或者是你的kstat链拷贝的KCID。它的使用方法在kstat手册页的Names小节说明。

为了避免访问kstat数据不必要的开销,程序首先查找kstat链里感兴趣的数据类型,然后使用kstat_read()kstat_data_lookup()函数从内核里取得统计信息。

3是一个展示怎样打印出所有的与磁盘I/O信息相关的kstat入口。它遍历整个链查找ks_type值为KSTAT_TYPE_IO的中kstat,调用kstat_read()获取数据,然后用my_io_display()处理数据。

9展示了怎么实现这个示例函数。

3:通过磁盘I/O信息打印kstat入口

     kstat_ctl_t     * kc;  
     kstat_t       
* ksp;  
     kstat_io_t     kio;  
     kc 
=  kstat_open();  
     
for  (ksp  =  kc -> kc_chain; ksp  !=  NULL; ksp  =  ksp -> ks_next) {  
       
if  (ksp -> ks_type  ==  KSTAT_TYPE_IO) {  
          kstat_read(kc, ksp, 
& kio);  
          my_io_display(kio);  
       }  
     }  

 数据类型

 kstat的数据段可以保存五种类型中的一种,用ks_type域来标识。下面的kstat类型可以保存多条记录。记录数保存在ks_ndata里。

KSTAT_TYPE_RAW KSTAT_TYPE_NAMED KSTAT_TYPE_TIMER

ks_data_size域整个数据段的大小,以字节为单位。 KSTAT_TYPE_RAWraw”类型的kstat被当做一个字节数组对待,通常被导出到一些众所周知的数据结构,例如vminfo(在/usr/include/sys/sysinfo.h里定义)。例4展示了打印这些信息的一种方法。

4:倾倒出一个Raw kstat

 
static   void  print_vminfo(kstat_t  * kp)  
{  
     vminfo_t 
* vminfop;  
     vminfop 
=  (vminfo_t  * )(kp -> ks_data);  
  
     printf(
" Free memory: %dn " , vminfop -> freemem);  
     printf(
" Swap reserved: %dn "  , vminfop -> swap_resv);  
     printf(
" Swap allocated: %dn "  , vminfop -> swap_alloc);  
     printf(
" Swap available: %dn " , vminfop -> swap_avail);  
     printf(
" Swap free: %dn " , vminfop -> swap_free);  
}

KSTAT_TYPE_NAMED

这一类型的kstat包含一个随机的name=value统计信息列表。例5展示了用于保存例命名的kstat数据结构。

5:命名的 kstat 定义在/usr/include/kstat.h


  
typedef  struct  kstat_named {  
    
char  name[KSTAT_STRLEN];          /*  计数器名字  */   
    uchar_t data_type;               
/*  数据类型  */   
    union {  
        
char  c[ 16 ];              /*  足够128位整数  */   
        int32_t i32;  
        uint32_t ui32;  
        int64_t i64;  
        uint64_t ui64;  
        
/*  这些数据成员是被废弃的  */   
        int32_t l;  
        uint32_t ul;  
        int64_t ll;  
        uint64_t ull;  
    } value;                  
/*  计数器值  */   
} kstat_named_t;  
  
#define  KSTAT_DATA_CHAR       0  
  
#define  KSTAT_DATA_INT32      1  
  
#define  KSTAT_DATA_UINT32     2  
  
#define  KSTAT_DATA_INT64      3  
  
#define  KSTAT_DATA_UINT64     4  
  
/*  这些类型是被废弃的  */   
  
#define  KSTAT_DATA_LONG       1  
  
#define  KSTAT_DATA_ULONG      2  
  
#define  KSTAT_DATA_LONGLONG   3  
  
#define  KSTAT_DATA_ULONGLONG  4  
  
#define  KSTAT_DATA_FLOAT      5  
  
#define  KSTAT_DATA_DOUBLE     6  

9中的程序使用一个函数my_named_display()展示怎么显示命名的kstat. 注意到如果类型是KSTAT_DATA_CHAR,不能保证那个16字节的值域是NULL结尾的。当你使用类似printf()的函数来打印这个值时,记住这个是很重要的。

KSTAT_TYPE_TIMER

这一类型的kstat保存有事件计时器的统计信息。它们为任何类型的事件提供计数与计时的基础信息。

6:计时器kstat 定义在/usr/include/kstat.h


  
typedef  struct  kstat_timer {  
    
char  name[KSTAT_STRLEN];          /*  事件名  */   
    uchar_t resv;                    
/*  保留  */   
    u_longlong_t num_events;         
/*  事件数  */       
    hrtime_t elapsed_time;           
/*  累积的逝去时间  */   
    hrtime_t min_time;               
/*  最短的事件持续时间  */   
    hrtime_t max_time;               
/*  最长的事件持续时间  */   
    hrtime_t start_time;             
/*  前一事件开始时间  */   
    hrtime_t stop_time;              
/*  前一事件停止时间  */   
} kstat_timer_t;  



KSTAT_TYPE_INTR 这一类型的kstat保存中断的统计信息。中断分类如下:

中断类型

定义

硬中断

来源于硬件设备自身的中断

软中断

一些系统中断源引起的中断

看门狗

周期性计时器调用引起的中断

伪中断

进入一个没有中断服务程序的中断入口点

多服务

检测到一个中断并且中断服务正在处理其它类型的中断

7:中断kstat,定义在/usr/include/kstat.h

 
#define  KSTAT_INTR_HARD      0   
#define  KSTAT_INTR_SOFT      1   
#define  KSTAT_INTR_WATCHDOG  2   
#define  KSTAT_INTR_SPURIOUS  3   
#define  KSTAT_INTR_MULTSVC   4   
#define  KSTAT_NUM_INTRS      5   
typedef 
struct  kstat_intr {           
    uint_t intrs[KSTAT_NUM_INTRS]; 
/*  中断计数器  */    
} kstat_intr_t;

KSTAT_TYPE_IO

8I/O kstat, 定义在/usr/include/kstat.h

 
typedef  struct  kstat_io {  
    
/*   
     * 基础计数器。 
     
*/   
    u_longlong_t  nread;           
/*  己读字节数  */   
    u_longlong_t  nwritten;        
/*  己写字节数  */   
    uint_t        reads;           
/*  读操作的次数  */   
    uint_t        writes;          
/*  写操作的次数  */   
    hrtime_t      wtime;           
/*  累积的等待(前一服务)时间  */   
    hrtime_t      wlentime;        
/*  累积等待 length * time 积  */   
    hrtime_t      wlastupdate;     
/*  上一次等待队列改变 */   
    hrtime_t      rtime;           
/*  累积的运行(服务)时间  */   
    hrtime_t      rlentime;        
/*  累积的运行 length *time 积  */   
    hrtime_t      rlastupdate;     
/*  上一次运行队列改变时间  */   
    uint_t        wcnt;            
/*  处于等待状态的元素个数  */   
    uint_t        rcnt;            
/*  处于运行状态的元素个数  */   
} kstat_io_t;

累积时间和队列长度统计信息

时间的统计信息为活跃任务的时间总和,队列长的统计信息为所有各队列长与此队列消逝所用时间的乘积之和,一个队列长的黎曼和包括了时间。图2对对队列/时间作了简单的描述。

在每一个状态改变点(任一个队列入口和出口),如果在这期间队列的长度不为0,从上一次状态改变逝去的时间将被加入到活动时间。

逝去时间和队列长度的积被加到长度(wlentime或者rlentime域)乘以时间的运行总和上。

2:队列长度示例 状态方案:

if (队列长度 != 0) {

     时间 += 自上一次状态改变逝去的时间;

    时间长度 += (自上一次状态改变逝去的时间 * 队列长度);

}

广义上这种方法可以测量任何规定的系统。用未完成的到X服务器的RPC调用分别代替队列长度。

许多I/O子系统有至少两个基础“链表”管理它们的事务。

1、一个链表用于那些已经被接收,但还没开始处理的事务。

2、一个链表用于那些正在处理,但还没有结束的事务。

为此,定义有两个累各时间统计信息:

1、预服务(等待)时间

2、服务(运行)时间

累积的繁忙时间按纳秒累计。

kstat 名称

kstat的名字空间由kstat结构的三个域定义:

ks_module ks_instance ks_name

这三个域的组合被确保是唯一的。

例如,设想一个系统有4块高速以太网接口。Sun的高速以太网控制器设备驱动模块被叫做“hme”。第一个以太网接口常常是实例0,第二个是1,以此类推。“hme”驱动程序提供两种类型的kstat给每一个接口。第一种包含命名的kstat和性能统计信息。第二个包含中断统计信息。

ks_module == “hme”, ks_instance == 0ks_name == “hme0”下找到kstat数据中第一个接口的网络统计信息。中断的统计信息包含在以ks_module == “hme”, ks_instance == 0ks_name == “hmec0”标识的kstat中。

在这个例子中,模块名和实例数的组合使得ks_name域(“hme0”和“hmec0”)仅仅是这个驱动的一个规约。其它驱动程序可能使用相似的命名规约来公布复合的kstat数据类型,但是不要求这样;内核模块保证这个组合是唯一的。

如何检测内核提供了些什么kstat。在Solaris 8里一个最容易的办法就是不带参数运行/usr/bin/kstat。它将打印几乎所有的当前kstat数据。Solariskstat命令可以倾泄出绝大多数己知的类型为KSTAT_TYPE_RAWkstat

函数

下面的函数非常有用对于从用户空间的C程序访问kstat数据:

kstat_ctl_t *kstat_open(void);

初始化一个kstat控制结构,提供到内核统计信息库的入口。它返回一个指向这一结构的指针,这个指针必须供给随后的libkstat函数调用的kc参数。

kstat_t *kstat_lookup(kstat_ctl_t *kc, char *ks_module, int ks_instance, char *ks_name);

遍历kstat链用给定的ks_moduleks_instanceks_name域查找kstat。如果ks_moduleNULLks_instance-1或者ks_nameNULL,那么这些域在查找的时候将被忽略。例如kstat_lookup(kc, NULL, -1, “foo”)将仅仅查找名字为“foo”的第一个kstat

void *kstat_data_lookup(kstat_t *ksp, char *name);

查找给定名字的kstat数据段记录。仅当kstat类型带有命名的数据记录时这个操作才有效。目前,仅有KSTAT_TYPE_NAMEDKSTAT_TYPE_TIMER类型的kstat带有命名的数据记录。必须先调用一次kstat_read()从内核取得数据。然后用这个子函数在数据段里查找特定的数据记录。

kid_t kstat_read(kstat_ctl_t *kc, kstat_t *ksp, void *buf);

从内核中取特定kstat的数据。

kid_t kstat_write(kstat_ctl_t *kc, kstat_t *ksp, void *buf);

写数据到内核中特定的kstat。只有超级用户可以使用kstat_write()

kid_t kstat_chain_update(kstat_ctl_t *kc);

促使用户的kstat链头与内核的同步。

int kstat_close(kstat_ctl_t *kc);

释放与kstat控制结构相关的所有资源。当调用exit(2)execve()时会自动做这个。(查看exec(2)man手册页了解关于exit(2)execve()的更多的信息)

链更新处理

数据结构概述里提到kstat链的动态性。libkstat库的kstat_open()函数返回一个内核kstat链的拷贝。因此,内核kstat链的内容有可能会改变,程序应该在适当的时间调用kstat_chain_update()函数来查看kstat链的私有拷贝是否与内核的相同。这就是KCID的目的(保存在kstat控制结构的kc_chain_id)。

每一次内核模块向系统链里添加或者从系统链里删除一个kstatKCID都被增加。当你的程序调用kstat_chain_update()时,函数检查你的程序的控制结构中的kc_chain_id是否与内核的相匹配。如果不匹配,kc_chain_update()重建程序的本地kstat链并且返回:

新的KCID,如果链已经被改变

0,如果没有改变

-1,如果检测到某些错误

如果程序从前一次调用kstat库缓冲某些本地数据,则新的KCID作为一个标志表示你已经更新过信息。你可以再次搜索kstat链查看你的程序感兴趣的数据是否已经被添加或者删除。

一个实际的例子是iostat系统命令。它缓冲系统磁盘的一些内部数据并且辨别一个磁盘是在线或者下线。如果带着一个间隔时间参数调用iostat,它每隔一个间隔时间打印一次I/O统计信息。每一次循环,它都调用一次kstat_chain_update()查看是否某些东西已经被改变。如果发生改变,它判断它感兴趣的设备是已经被添加或者删除。

所有的汇总到一起

你的C程序必须包含:

#include <kstat.h>

当链接你的程序时,编译命令行必须包含参数-lkstat

cc -o print_some_kstats -lkstat print_some_kstats.c

下面是一个短小和示例程序。首先,它用kstat_lookup()kstat_read来查找系统的CPU速率。然后进入一个无限循环,打印所有关于KSTAT_TYPE_IO类型的kstat的信息总计。注意到循环的顶部,它调用kstat_chain_update()来检查你拥有的是当前数据。如果kstat链已经被改变,它在标准错误上给出一个简短的消息。

9:打印不同类型kstat的示例程序

 
/*   print_some_kstats.c:  
 *  print out a couple of interesting things  
 
*/   
#include 
< kstat.h >   
#include 
< stdio.h >
#include 
< inttypes.h >
#define  SLEEPTIME 10  
  
void  my_named_display( char   * char   * , kstat_named_t  * );  
void  my_io_display( char   * char   * , kstat_io_t);  
  
main(
int  argc,  char   ** argv)  
{  
     kstat_ctl_t   
* kc;  
     kstat_t       
* ksp;  
     kstat_io_t     kio;  
     kstat_named_t 
* knp;  
  
     kc 
=  kstat_open();  
  
     
/*   
      * Print out the CPU speed. We make two assumptions here:  
      * 1) All CPUs are the same speed, so we'll just search for the  
      *    first one;  
      * 2) At least one CPU is online, so our search will always  
      *    find something. :)  
      
*/   
     ksp 
=  kstat_lookup(kc,  " cpu_info " - 1 , NULL);  
     kstat_read(kc, ksp, NULL);  
     
/*  lookup the CPU speed data record  */   
     knp 
=  kstat_data_lookup(ksp,  " clock_MHz " );   
     printf(
" CPU speed of system is  " );  
     my_named_display(ksp
-> ks_name, ksp -> ks_class, knp);  
     printf(
" n " );  
  
     
/*  dump some info about all I/O kstats every  
        SLEEPTIME seconds  
*/   
     
while ( 1 ) {  
        
/*  make sure we have current data  */   
         
if (kstat_chain_update(kc))  
             fprintf(stderr, 
" <<State Changed>>n " );   
         
for  (ksp  =  kc -> kc_chain; ksp  !=  NULL; ksp  =  ksp -> ks_next) {  
           
if  (ksp -> ks_type  ==  KSTAT_TYPE_IO) {  
              kstat_read(kc, ksp, 
& kio);  
              my_io_display(ksp
-> ks_name, ksp -> ks_class, kio);  
           }  
         }  
         sleep(SLEEPTIME);  
     } 
/*  while(1)  */   
  
}  
  
void  my_io_display( char   * devname,  char   * class , kstat_io_t k)  
{  
     printf(
" Name: %s Class: %sn " ,devname, class );  
     printf(
" tnumber of bytes read %lldn " , k.nread);  
     printf(
" tnumber of bytes written %lldn " , k.nwritten);  
     printf(
" tnumber of read operations %dn " , k.reads);  
     printf(
" tnumber of write operations %dnn " , k.writes);  
}  
  
void    
my_named_display(
char   * devname,  char   * class , kstat_named_t  * knp)  
{  
     
switch (knp -> data_type) {  
     
case  KSTAT_DATA_CHAR:  
          printf(
" %.16s " ,knp -> value.c);  
          
break ;  
     
case  KSTAT_DATA_INT32:  
          printf(
" % "  PRId32,knp -> value.i32);  
          
break ;  
     
case  KSTAT_DATA_UINT32:  
          printf(
" % "  PRIu32,knp -> value.ui32);  
          
break ;  
     
case  KSTAT_DATA_INT64:  
          printf(
" % "  PRId64,knp -> value.i64);  
          
break ;  
     
case  KSTAT_DATA_UINT64:  
          printf(
" % "  PRIu64,knp -> value.ui64);  
    }  
}

附加信息

这篇文章的大多数信息来自各种各样的Sun解决方案文档,Solaris白皮书和Solaris man手册页(第3小节 KSTAT)。详细的API信息,参看《Solaris 8的参考手册》和《编写设备驱动程序》。两者都可以在docs.sun.com获得。

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值