OpenVMS Alpha
オペレーティング・システム
パーティショニングおよび Galaxy ガイド


前へ 次へ 目次 索引



付録 A
OpenVMS Galaxy CPU Load Balancer プログラム

この付録では,特権付きコード・アプリケーションのプログラムの例を示します。このプログラムは,OpenVMS Galaxy 内のインスタンス間で CPU リソースを動的に再割り当てします。

A.1 CPU Load Balancer の概要

OpenVMS Galaxy CPU Load Balancer プログラムは特権を必要とするアプリケーションであり,OpenVMS Galaxy で,インスタンス間で CPU リソースを動的に再割り当てします。

このプログラムは各インスタンスで実行しなければなりません。各イメージは小さな共用メモリ・セクションを作成し ( またはこのセクションにマップされ ),そのインスタンスの COM キューの深さに関する情報を定期的に送信します。このデータの平均値をもとに,各インスタンスは最も使用頻度の高いインスタンスと低いインスタンスを判断します。これらの要素が指定された期間に存在する場合は,使用可能なセカンダリ・プロセッサのある使用頻度の最低のインスタンスが,プロセッサの 1 つを使用頻度の最高のインスタンスに再割り当てすることにより,OpenVMS Galaxy でプロセッサの使用のバランス調整が効果的に行われます。このプログラムではコマンド・ライン引数を指定できるので,負荷バランス・アルゴリズムを細かく調整できます。ただし,このプログラムはエラー処理に関して不十分な部分があります。

このプログラムでは,次の OpenVMS Galaxy システム・サービスを使用します。

サービス 説明
SYS$CPU_TRANSITION CPU の再割り当て
SYS$CRMPSC_GDZRO_64 共用メモリの作成
SYS$SET_SYSTEM_EVENT OpenVMS Galaxy イベントの通知
SYS$*_GALAXY_LOCK_* OpenVMS Galaxy ロック

OpenVMS Galaxy リソースは常に,プッシュ・モデルによって再割り当てされ,このモデルでは所有者インスタンスだけがそのリソースを解放できるので,OpenVMS Galaxy の各インスタンスでこのプロセスのコピーを 1 つずつ実行しなければなりません。

このプログラムは OpenVMS バージョン 7.2 以上のマルチインスタンス Galaxy でのみ実行できます。

A.1.1 必要な特権

CPU キューを数えるには,CMKRNL 特権が必要です。共用メモリをマップするには,SHMEM 特権が必要です。

A.1.2 構築とコピーの手順

次の節からの説明に従ってサンプル・プログラムをコンパイルおよびリンクするか,SYS$EXAMPLES:GCU$BALANCER.EXE にあるコンパイル済みのイメージを SYS$COMMON:[SYSEXE]GCU$BALANCER.EXE にコピーします。

OpenVMS Galaxy インスタンスで個別のシステム・ディスクを使用する場合は,各インスタンスに対してここに示した操作を実行する必要があります。

サンプル・プログラムを変更する場合は,次のようにコンパイルとリンクを行ってください。


$ CC GCU$BALANCER.C+SYS$LIBRARY:SYS$LIB_C/LIBRARY 
$ LINK/SYSEXE GCU$BALANCER 

A.1.3 スタートアップ・オプション

このプログラムに対して DCL コマンドを設定しなければなりません。この目的でサンプル・コマンド・テーブル・ファイルが提供されます。新しいコマンドをインストールするには,次のように入力します。


$ SET COMMAND/TABLE=SYS$LIBRARY:DCLTABLES - 
_$ /OUT=SYS$COMMON:[SYSLIB]DCLTABLES GCU$BALANCER.CLD 

このコマンドは,新しいコマンド定義を共通のシステム・ディレクトリの DCLTABLES.EXE に挿入します。新しいコマンド・テーブルは,システムの再ブート時に有効になります。再ブートを行わない場合は,次のように入力します。


$ INSTALL REPLACE SYS$COMMON:[SYSLIB]DCLTABLES.EXE 

このコマンドの後,任意のアクティブ・プロセスからコマンドを使用するには,いったんログアウトした後,再びログインする必要があります。ログアウトしない場合は,Balancer を実行する各プロセスから次のように入力します。


$ SET COMMAND GCU$BALANCER.CLD 

コマンドを設定した後,さまざまなコマンド・ライン・パラメータを使用して,Balancer のアルゴリズムを制御できます。


$ CONFIGURE BALANCER{/STATISTICS} x y time 

このコマンドでは,x は負荷サンプルの数であり,y はリソースの再割り当てを起動するのに必要な,キューに登録されたプロセスの数です。
time は負荷サンプリングの間のデルタ時間です。

/STATISTICS 修飾子を指定すると,状態行に情報が継続的に表示されます。これはパラメータを調整するのに役立ちます。この出力は,GCU を介して起動した場合など,Balancer が独立プロセスとして実行されている場合は表示されません。/STATISTICS 修飾子は,DECterm ウィンドウで DCL から直接 Balancer を起動する場合にだけ使用されます。次に例を示します。


$ CONFIG BAL 3 1 00:00:05.00 

これは Balancer を起動して,5 秒ごとにシステムの負荷をサンプリングします。3 回サンプリングした後,インスタンスの 1 つ以上のプロセスが COM キューに登録されている場合は,リソース (CPU) の再割り当てが行われ,このインスタンスに別の CPU が割り当てられます。

A.1.4 GCU からの Load Balancer の起動

GCU では,SYS$SYSTEM:GCU$BALANCER.EXE を起動するためのメニュー・アイテムと,Balancer アルゴリズムを変更するためのダイアログが表示されます。これらの機能は,次に説明するように,Balancer イメージが正しくインストールされている場合にだけ機能します。

GCU 常駐の Balancer スタートアップ・オプションを使用するには,次の操作を行わなければなりません。

A.1.5 シャットダウンの警告

OpenVMS Galaxy では,たとえばシャットダウンの場合など,インスタンスが Galaxy を終了する場合,プロセスがインスタンスに共用メモリをマップしたままにしておくことができません。GCU$BALANCER プログラムが SYSTEM UIC から実行されるときに,プロセスを停止するには,SYS$MANAGER:SYSHUTDWN.COM を変更しなければなりません。SYSTEM UIC グループのプロセスは,OpenVMS のシャットダウンや再ブートで,SHUTDWN.COM によって終了されません。インスタンスが Galaxy を終了するときに,プロセスがまだ共用メモリをマップしている場合は,インスタンスは GLXSHUTSHMEM バグチェックでクラッシュします。

この問題を回避するには,次の例に示すように,SYS$MANAGER:SYSHUTDWN.COM がプロセスを停止しなければなりません。また,適切な特権が与えられた SYSTEM UIC 以外でプロセスを実行することもできます。


** SYSHUTDWN.COM EXAMPLE - Paste into SYS$MANAGER:SYSHUTDWN.COM 
** 
**    $! 
**    $! If the GCU$BALANCER image is running, stop it to release shmem. 
**    $! 
**    $ procctx = f$context("process",ctx,"prcnam","GCU$BALANCER","eql") 
**    $ procid  = f$pid(ctx) 
**    $ if procid .NES. "" then $ stop/id='procid' 

$ STOP GCU$BALANCER 文を使用することもできます。

A.2 プログラム例


/* 
** COPYRIGHT (c) 1998 BY COMPAQ COMPUTER CORPORATION ALL RIGHTS RESERVED. 
** 
** THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED AND COPIED 
** ONLY  IN  ACCORDANCE  OF  THE  TERMS  OF  SUCH  LICENSE  AND WITH THE 
** INCLUSION OF THE ABOVE COPYRIGHT NOTICE. THIS SOFTWARE OR  ANY  OTHER 
** COPIES THEREOF MAY NOT BE PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY 
** OTHER PERSON.  NO TITLE TO AND  OWNERSHIP OF THE  SOFTWARE IS  HEREBY 
** TRANSFERRED. 
** 
** THE INFORMATION IN THIS SOFTWARE IS  SUBJECT TO CHANGE WITHOUT NOTICE 
** AND  SHOULD  NOT  BE  CONSTRUED  AS A COMMITMENT BY COMPAQ COMPUTER 
** CORPORATION. 
** 
** COMPAQ ASSUMES NO RESPONSIBILITY FOR THE USE  OR  RELIABILITY OF ITS 
** SOFTWARE ON EQUIPMENT WHICH IS NOT SUPPLIED BY COMPAQ OR DIGITAL. 
** 
**===================================================================== 
** WARNING - This example is provided for instructional and demo 
**           purposes only.  The resulting program should not be 
**           run on systems which make use of soft-affinity 
**           features of OpenVMS, or while running applications 
**           which are tuned for precise processor configurations.  
**           We are continuing to explore enhancements such as this 
**           program which will be refined and integrated into 
**           future releases of OpenVMS. 
**===================================================================== 
** 
** GCU$BALANCER.C - OpenVMS Galaxy CPU Load Balancer. 
** 
** This is an example of a privileged application which dynamically 
** reassigns CPU resources among instances in an OpenVMS Galaxy.  The 
** program must be run on each participating instance.  Each image 
** will create, or map to, a small shared memory section and periodically 
** post information regarding the depth of that instances' COM queues. 
** Based upon running averages of this data, each instance will 
** determine the most, and least busy instance.  If these factors 
** exist for a specified duration, the least busy instance having 
** available secondary processors, will reassign one of its processors 
** to the most busy instance, thereby effectively balancing processor 
** usage across the OpenVMS Galaxy.  The program provides command line 
** arguments to allow tuning of the load balancing algorithm. 
** The program is admittedly shy on error handling. 
** 
** This program uses the following OpenVMS Galaxy system services: 
** 
**      SYS$CPU_TRANSITION   - CPU reassignment 
**      SYS$CRMPSC_GDZRO_64  - Shared memory creation 
**      SYS$SET_SYSTEM_EVENT - OpenVMS Galaxy event notification 
**      SYS$*_GALAXY_LOCK_*  - OpenVMS Galaxy locking 
** 
** Since OpenVMS Galaxy resources are always reassigned via a "push" 
** model, where only the owner instance can release its resources, 
** one copy of this process must run on each instance in the OpenVMS 
** Galaxy. 
** 
** ENVIRONMENT: OpenVMS V7.2 Multiple-instance Galaxy. 
** 
** REQUIRED PRIVILEGES:  CMKRNL required to count CPU queues 
**                       SHMEM  required to map shared memory 
** 
** BUILD/COPY INSTRUCTIONS: 
** 
** Compile and link the example program as described below, or copy the 
** precompiled image found in SYS$EXAMPLES:GCU$BALANCER.EXE to 
** SYS$COMMON:[SYSEXE]GCU$BALANCER.EXE 
** 
** If your OpenVMS Galaxy instances utilize individual system disks, you 
** will need to do the above for each instance. 
** 
** If you change the example program, compile and link it as follows: 
** 
**   $ CC GCU$BALANCER.C+SYS$LIBRARY:SYS$LIB_C/LIBRARY 
**   $ LINK/SYSEXE GCU$BALANCER 
** 
** STARTUP OPTIONS: 
** 
** You must establish a DCL command for this program.  We have provided a 
** sample command table file for this purpose.  To install the new command, 
** do the following: 
** 
**    $ SET COMMAND/TABLE=SYS$LIBRARY:DCLTABLES - 
**      /OUT=SYS$COMMON:[SYSLIB]DCLTABLES GCU$BALANCER.CLD 
** 
** This command inserts the new command definition into DCLTABLES.EXE 
** in your common system directory.  The new command tables will take 
** effect when the system is rebooted.  If you would like to avoid a 
** reboot, do the following: 
** 
**    $ INSTALL REPLACE SYS$COMMON:[SYSLIB]DCLTABLES.EXE 
** 
** After this command, you will need to log out, then log back in to 
** use the command from any active processes.  Alternatively, if you 
** would like to avoid logging out, do the following from each process 
** you would like to run the balancer from: 
** 
**    $ SET COMMAND GCU$BALANCER.CLD 
** 
** Once your command has been established, you may use the various 
** command line parameters to control the balancer algorithm. 
** 
**    $ CONFIGURE BALANCER{/STATISTICS} x y time 
** 
** Where: "x" is the number of load samples to take. 
**        "y" is the number of queued processes required to trigger 
**            resource reassignment. 
**        "time" is the delta time between load sampling. 
** 
** The /STATISTICS qualifier causes the program to display a 
** continuous status line.  This is useful for tuning the parameters. 
** This output is not visible if the balancer is run detached, as is 
** the case if it is invoked via the GCU.  It is intended to be used 
** only when the balancer is invoked directly from DCL in a DECterm 
** window. 
** 
** For example: $ CONFIG BAL 3 1 00:00:05.00 
** 
**        Starts the balancer which samples the system load every 
**        5 seconds.  After 3 samples, if the instance has one or 
**        more processes in the COM queue, a resource (CPU) 
**        reassignment will occur, giving this instance another CPU. 
** 
** GCU STARTUP: 
** 
** The GCU provides a menu item for launching SYS$SYSTEM:GCU$BALANCER.EXE 
** and a dialog for altering the balancer algorithm.  These features will 
** only work if the balancer image is properly installed as described 
** the the following paragraphs.  
** 
** To use the GCU-resident balancer startup option, you must: 
** 
** 1) Compile, link, or copy the balancer image as described previously. 
** 2) Invoke the GCU via: $ CONFIGURE GALAXY   You may need to set your 
**    DECwindows display to a suitably configured workstation or PC. 
** 3) Select the "CPU Balancer" entry from the "Galaxy" menu. 
** 4) Select appropriate values for your system.  This may take some 
**    testing.  By default, the values are set aggressively so that 
**    the balancer action can be readily observed.  If your system is 
**    very heavily loaded, you will need to increase the values 
**    accordingly to avoid excessive resource reassignment.  The GCU 
**    does not currently save these values, so you may want to write 
**    them down once you are satisfied. 
** 5) Select the instance/s you wish to have participate, then select 
**    the "Start" function, then press OK.  The GCU should launch the 
**    process GCU$BALANCER on all selected instances.  You may want to 
**    verify these processes have been started. 
** 
** SHUTDOWN WARNING: 
** 
** In an OpenVMS Galaxy, no process may have shared memory mapped on an 
** instance when it leaves the Galaxy, as during a shutdown. Because of 
** this, SYS$MANAGER:SYSHUTDWN.COM must be modified to stop the process 
** if the GCU$BALANCER program is run from a SYSTEM UIC.  Processes in the 
** SYSTEM UIC group are not terminated by SHUTDOWN.COM when shutting down 
** or rebooting OpenVMS. If a process still has shared memory mapped when 
** an instance leaves the Galaxy, the instance will crash with a 
** GLXSHUTSHMEM bugcheck.  
** 
** To make this work, SYS$MANAGER:SYSHUTDWN.COM must stop the process as 
** shown in the example below.  Alternatively, the process can be run 
** under a suitably privileged, non-SYSTEM UIC. 
** 
** SYSHUTDWN.COM EXAMPLE - Paste into SYS$MANAGER:SYSHUTDWN.COM 
** 
**    $! 
**    $! If the GCU$BALANCER image is running, stop it to release shmem. 
**    $! 
**    $ procctx = f$context("process",ctx,"prcnam","GCU$BALANCER","eql") 
**    $ procid  = f$pid(ctx) 
**    $ if procid .NES. "" then $ stop/id='procid' 
** 
** Note, you could also just do a "$ STOP GCU$BALANCER" statement. 
** 
** OUTPUTS: 
** 
**    If the logical name GCU$BALANCER_VERIFY is defined, notify the 
**    SYSTEM account when CPUs are reassigned.  If the /STATISTICS 
**    qualifier is specified, a status line is continually displayed, 
**    but only when run directly from the command line. 
** 
** REVISION HISTORY: 
** 
** 02-Dec-1998 Greatly improved instructions. 
** 03-Nov-1998 Improved instructions. 
** 24-Sep-1998 Initial code example and integration with GCU. 
*/ 
#include <BRKDEF>
#include <BUILTINS>
#include <CSTDEF>
#include <DESCRIP>
#include <GLOCKDEF>
#include <INTS>
#include <PDSCDEF>
#include <PSLDEF>
#include <SECDEF>
#include <SSDEF>
#include <STARLET>
#include <STDIO>
#include <STDLIB>
#include <STRING>
#include <SYIDEF>
#include <SYSEVTDEF>
#include <VADEF>
#include <VMS_MACROS>
#include <CPUDEF>
#include <IOSBDEF.H>
#include <EFNDEF.H>
/* For CLI */ 
#include <cli$routines.h> 
#include <chfdef.h> 
#include <climsgdef.h> 
 
#define HEARTBEAT_RESTART     0 /* Flags for synchronization            */ 
#define HEARTBEAT_ALIVE       1 
#define HEARTBEAT_TRANSPLANT  2 
 
#define GLOCK_TIMEOUT    100000 /* Sanity check, max time holding gLock */ 
#define _failed(x) (!((x) & 1)) 
 
$DESCRIPTOR(system_dsc, "SYSTEM");              /* Brkthru account name     */ 
$DESCRIPTOR(gblsec_dsc, "GCU$BALANCER");        /* Global section name      */ 
 
struct  SYI_ITEM_LIST {                         /* $GETSYI item list format */ 
  short buflen,item; 
  void *buffer,*length; 
}; 
 
/* System information and an item list to use with $GETSYI */ 
 
static unsigned long total_cpus; 
static uint64   partition_id; 
static long     max_instances = 32;             
iosb            g_iosb; 
 
struct SYI_ITEM_LIST syi_itemlist[3] = { 
     {sizeof (long), SYI$_ACTIVECPU_CNT,&total_cpus,  0}, 
     {sizeof (long), SYI$_PARTITION_ID, &partition_id,0}, 
     {0,0,0,0}}; 
 
extern uint32 *SCH$AQ_COMH;             /* Scheduler COM queue address  */ 
unsigned long PAGESIZE;                 /* Alpha page size              */ 
uint64        glock_table_handle;       /* Galaxy lock table handle     */ 
 
/* 
** Shared Memory layout (64-bit words): 
** ==================================== 
** 0  to  n-1:  Busy count, where 100 = 1 process in a CPU queue 
** n  to  2n-1: Heartbeat (status) for each instance 
** 2n to  3n-1: Current CPU count on each instance 
** 3n to  4n-1: Galaxy lock handles for modifying heartbeats 
** 
** where n = max_instances * sizeof(long). 
** 
** We assume the entire table (easily) fits in two Alpha pages. 
*/ 
 
/* Shared memory pointers must be declared volatile */ 
 
volatile uint64  gs_va = 0;             /* Shmem section address        */ 
volatile uint64  gs_length = 0;         /* Shmem section length         */ 
volatile uint64 *gLocks;                /* Pointers to gLock handles    */ 
volatile uint64 *busycnt,*heartbeat,*cpucount;  
 
/************************************************************************/ 
/* FUNCTION init_lock_tables - Map to the Galaxy locking table and      */ 
/* create locks if needed. Place the lock handles in a shared memory    */ 
/* region, so all processes can access the locks.                       */ 
/*                                                                      */ 
/* ENVIRONMENT: Requires SHMEM and CMKRNL to create tables.             */ 
/* INPUTS:      None.                                                   */ 
/* OUTPUTS:     Any errors from lock table creation.                    */ 
/************************************************************************/ 
int init_lock_tables (void) 
{ 
    int status,i; 
    unsigned long sanity; 
    uint64 handle; 
    unsigned int min_size, max_size; 
 
    /* Lock table names are 15-byte padded values, unique across a Galaxy. */ 
    char table_name[] = "GCU_BAL_GLOCK  "; 
 
    /* Lock names are 15-byte padded values, but need not be unique. */ 
    char lock_name[] = "GCU_BAL_LOCK   "; 
 
    /* Get the size of a Galaxy lock */ 
    status = sys$get_galaxy_lock_size(&min_size,&max_size); 
    if (_failed(status)) return (status); 
 
    /* 
    ** Create or map to a process space Galaxy lock table. We assume 
    ** one page is enough to hold the locks. This will work for up 
    ** to 128 instances. 
    */ 
    status = sys$create_galaxy_lock_table(table_name,PSL$C_USER, 
                PAGESIZE,GLCKTBL$C_PROCESS,0,min_size,&glock_table_handle); 
    if (_failed(status)) return (status); 
 
    /* 
    ** Success case 1: SS$_CREATED                                   
    ** We created the table, so  populate it with locks and          
    ** write the handles to shared memory so the other partitions    
    ** can access them. Only one instance can receive SS$_CREATED    
    ** for a given lock table; all other mappers will get SS$_NORMAL. 
    */ 
    if (status == SS$_CREATED) 
    { 
      printf ("%%GCU$BALANCER-I-CRELOCK, Creating G-locks\n"); 
      for (i=0; i<max_instances; i++) 
      { 
        status = sys$create_galaxy_lock(glock_table_handle,lock_name, 
                   min_size,0,0,0,&handle); 
        gLocks[i] = handle; 
        if (_failed(status)) return (status); 
      } 
    } 
    else 
    { 
    /* 
    ** Success case 2: SS$_NORMAL                              
    ** We mapped the table, but did not create it. Spin until  
    ** the creator fills the lock handles. NOTE: If the creator 
    ** fails in the loop above and does not finish creating the 
    ** locks, then we will be stuck waiting forever - so we    
    ** use a sanity check here. Process space lock tables and  
    ** memory regions are automatically deleted when all       
    ** processes mapping them are deleted, so the worst case   
    ** is this:                                                 
    **                                                          
    ** - Process 1 starts, creates gLock table                  
    ** - Processes 2-n start and are waiting on gLock creation  
    ** - Process 1 dies before completing gLock creation        
    ** - Processes 2-n time out and exit; the half-initialized  
    **   section and lock tables are deleted by VMS.            
    ** - The user (or script) receives SS$_TIMEOUT and can      
    **   now restart all processes with a "clean slate".        
    */ 
      sanity = 0; 
      printf ("%%GCU$BALANCER-I-WAITLOCK, Waiting for G-lock creation...\n"); 
      while (gLocks[max_instances-1] == 0) 
      { 
        if (sanity++ > 1000000) return (SS$_TIMEOUT); 
      } 
    } 
    return (SS$_NORMAL); 
} 
 
/************************************************************************/ 
/* FUNCTION update_cpucount - Update the number of CPUs in this instance*/ 
/*                                                                      */ 
/* ENVIRONMENT: Called directly or via a system event AST.              */ 
/* INPUTS:      None.                                                   */ 
/* OUTPUTS:     Updates this instance's CPU count in shared memory.     */ 
/************************************************************************/ 
void update_cpucount(int unused) 
{ 
   sys$getsyiw(EFN$C_ENF,0,0,&syi_itemlist,&g_iosb,0,0); 
   cpucount[partition_id] = total_cpus; 
} 
 
/************************************************************************/ 
/* FUNCTION cpu_q - Count the number of processes in CPU COM queues     */ 
/*                                                                      */ 
/* ENVIRONMENT: OpenVMS Kernel Mode.                                    */ 
/* INPUTS:      None.                                                   */ 
/* OUTPUTS:     Returns the number of processes on the COM queues.      */ 
/************************************************************************/ 
long cpu_q(void) 
{ 
   uint32 *head, *tmp; 
   long procs = 0; 
   int p; 
 
   head = SCH$AQ_COMH;          /* Head of 1st COM queue                */ 
   sys_lock(SCHED,1,0);         /* Obtain SCHED spinlock                */ 
 
   for (p=64; p>0; p--)         /* Queues to scan (32 COM + 32 COMO)    */ 
   { 
     tmp = (uint32 *) *head;    /* Look at first flink                  */ 
     while (tmp != head)        /* Compare vs. head of queue            */ 
     { 
       procs++;                 /* Different, count a job waiting       */ 
       tmp = (uint32 *) *tmp;   /* Go to next queue entry               */ 
     } 
     head = head + 2;           /* Go to next queue (increment by 2*32) */ 
   }                            /* And scan it (loop to "for p...")     */ 
 
   sys_unlock(SCHED,0,0);       /* Release SCHED spinlock               */ 
   return procs; 
} 
 
/************************************************************************/ 
/* FUNCTION lockdown - Lock the cpu_q routine into the working set      */ 
/*                    so that it can't pagefault while at elevated IPL  */ 
/*                                                                      */ 
/* ENVIRONMENT: Requires CMKRNL privilege.                              */ 
/* INPUTS:      None.                                                   */ 
/* OUTPUTS:     None.                                                   */ 
/************************************************************************/ 
void lockdown(void) 
{ 
   struct pdscdef *proc_desc = (void *)cpu_q; 
   unsigned long sub_addr[2], locked_head[2], locked_code[2]; 
   unsigned long status; 
 
   sub_addr[0] = (unsigned long) cpu_q; 
   sub_addr[1] = sub_addr[0] + PAGESIZE; 
   if (__PAL_PROBER((void *)sub_addr[0],sizeof(int),PSL$C_USER) != 0) 
     sub_addr[1] = sub_addr[0]; 
 
   status = sys$lkwset(sub_addr,locked_head,PSL$C_USER); 
   if (_failed(status)) exit(status); 
 
   sub_addr[0] = proc_desc->pdsc$q_entry[0]; 
   sub_addr[1] = sub_addr[0] + PAGESIZE; 
   if (__PAL_PROBER((void *)sub_addr[0],sizeof(int),PSL$C_USER) != 0) 
        sub_addr[1] = sub_addr[0]; 
 
   status = sys$lkwset(sub_addr,locked_code,PSL$C_USER); 
   if (_failed(status)) exit(status); 
} 
 
/************************************************************************/ 
/* FUNCTION reassign_a_cpu - Reassign a single CPU to another instance. */ 
/*                                                                      */ 
/* ENVIRONMENT: Requires CMKRNL privilege.                              */ 
/* INPUTS:      most_busy_id: partition ID of destination.              */ 
/* OUTPUTS:     None.                                                   */ 
/*                                                                      */ 
/* Donate one CPU at a time - then wait for the remote instance to      */ 
/* reset its heartbeat and recalculate its load.                        */ 
/************************************************************************/ 
void reassign_a_cpu(int most_busy_id) 
{ 
  int status,i; 
  static char op_msg[255]; 
  static char iname_msg[1]; 
  $DESCRIPTOR(op_dsc,op_msg); 
  $DESCRIPTOR(iname_dsc,""); 
  iname_dsc.dsc$w_length = 0; 
 
  /* Update CPU info */ 
 
  status = sys$getsyiw(EFN$C_ENF,0,0,&syi_itemlist,&g_iosb,0,0); 
  if (_failed(status)) exit(status); 
 
  /* Don't attempt reassignment if we are down to one CPU */ 
 
  if (total_cpus > 1) 
  { 
    status = sys$acquire_galaxy_lock(gLocks[most_busy_id],GLOCK_TIMEOUT,0); 
    if (_failed(status)) exit(status); 
    heartbeat[most_busy_id] = HEARTBEAT_TRANSPLANT; 
    status = sys$release_galaxy_lock(gLocks[most_busy_id]); 
    if (_failed(status)) exit(status); 
 
    status = sys$cpu_transitionw(CST$K_CPU_MIGRATE,CST$K_ANY_CPU,0, 
                                  most_busy_id,0,0,0,0,0,0); 
    if (status & 1) 
    { 
      if (getenv ("GCU$BALANCER_VERIFY")) 
      { 
        sprintf(op_msg, 
                "\n\n*****GCU$BALANCER: Reassigned a CPU to instance %li\n", 
                most_busy_id); 
        op_dsc.dsc$w_length = strlen(op_msg); 
        sys$brkthru(0,&op_dsc,&system_dsc,BRK$C_USERNAME,0,0,0,0,0,0,0); 
      } 
      update_cpucount(0);  /* Update the CPU count after donating one */ 
    } 
  } 
} 
 
/************************************************************************/ 
/* IMAGE ENTRY - MAIN                                                   */ 
/*                                                                      */ 
/* ENVIRONMENT: OpenVMS Galaxy                                          */ 
/* INPUTS:      None.                                                   */ 
/* OUTPUTS:     None.                                                   */ 
/************************************************************************/ 
int main(int argc, char **argv) 
{ 
   int           show_stats = 0; 
   long          busy,most_busy,nprocs; 
   int64         delta; 
   unsigned long status,i,j,k,system_cpus,instances; 
   unsigned long arglst         = 0; 
   uint64        version_id[2]  = {0,1}; 
   uint64        region_id      = VA$C_P0; 
   uint64        most_busy_id,cpu_hndl = 0; 
 
/* Static descriptors for storing parameters.  Must match CLD defs */ 
 
   $DESCRIPTOR(p1_desc,"P1");           
   $DESCRIPTOR(p2_desc,"P2"); 
   $DESCRIPTOR(p3_desc,"P3"); 
   $DESCRIPTOR(p4_desc,"P4"); 
   $DESCRIPTOR(stat_desc,"STATISTICS"); 
 
/* Dynamic descriptors for retrieving parameter values */ 
 
   struct dsc$descriptor_d samp_desc = {0,DSC$K_DTYPE_T,DSC$K_CLASS_D,0}; 
   struct dsc$descriptor_d proc_desc = {0,DSC$K_DTYPE_T,DSC$K_CLASS_D,0}; 
   struct dsc$descriptor_d time_desc = {0,DSC$K_DTYPE_T,DSC$K_CLASS_D,0}; 
 
   struct SYI_ITEM_LIST syi_pagesize_list[3] = { 
     {sizeof (long), SYI$_PAGE_SIZE      ,&PAGESIZE     ,0}, 
     {sizeof (long), SYI$_GLX_MAX_MEMBERS,&max_instances,0}, 
     {0,0,0,0}}; 
/* 
** num_samples and time_desc determine how often the balancer should check 
** to see if any other instance needs more CPUs. num_samples determines the 
** number of samples used to calculate the running average, and sleep_dsc 
** determines the amount of time between samples. 
** 
** For example, a sleep_dsc of 30 seconds and a num_samples of 20 means that 
** a running average over the last 10 minutes (20 samples * 30 secs) is used 
** to balance CPUs. 
** 
** load_tolerance is the minimum load difference which triggers a CPU 
** migration. 100 is equal to 1 process in the computable CPU queue. 
*/ 
   int num_samples;     /* Number of samples in running average      */ 
   int load_tolerance;  /* Minimum load diff to trigger reassignment */ 
 
/* Parse the CLI */ 
                                                /* CONFIGURE VERB */ 
   status      = CLI$PRESENT(&p1_desc);         /* BALANCER       */ 
   if (status != CLI$_PRESENT) exit(status); 
   status      = CLI$PRESENT(&p2_desc);         /* SAMPLES        */ 
   if (status != CLI$_PRESENT) exit(status); 
   status      = CLI$PRESENT(&p3_desc);         /* PROCESSES      */ 
   if (status != CLI$_PRESENT) exit(status); 
   status      = CLI$PRESENT(&p4_desc);         /* TIME           */ 
   if (status != CLI$_PRESENT) exit(status); 
 
   status     = CLI$GET_VALUE(&p2_desc,&samp_desc); 
   if (_failed(status)) exit(status); 
   status     = CLI$GET_VALUE(&p3_desc,&proc_desc); 
   if (_failed(status)) exit(status); 
   status     = CLI$GET_VALUE(&p4_desc,&time_desc); 
   if (_failed(status)) exit(status); 
   status     = CLI$PRESENT(&stat_desc); 
   show_stats = (status == CLI$_PRESENT) ? 1 : 0; 
 
   num_samples = atoi(samp_desc.dsc$a_pointer); 
   if (num_samples <= 0) num_samples = 3; 
 
   load_tolerance = (100 * (atoi(proc_desc.dsc$a_pointer))); 
   if (load_tolerance <= 0) load_tolerance = 100; 
 
   if (show_stats) 
     printf("Args: Samples: %d, Processes: %d, Time: %s\n", 
        num_samples,load_tolerance/100,time_desc.dsc$a_pointer); 
 
   lockdown();                  /* Lock down the cpu_q subroutine */ 
 
   /* Get the page size and max members for this system */ 
 
   status = sys$getsyiw(EFN$C_ENF,0,0,&syi_pagesize_list,&g_iosb,0,0); 
   if (_failed(status)) return (status); 
 
   if (max_instances == 0) max_instances = 1; 
 
   /* Get our partition ID and initial CPU info */ 
 
   status = sys$getsyiw(EFN$C_ENF,0,0,&syi_itemlist,&g_iosb,0,0); 
   if (_failed(status)) return (status); 
 
   /* Map two pages of shared memory */ 
 
   status = sys$crmpsc_gdzro_64(&gblsec_dsc,version_id,0,PAGESIZE+PAGESIZE, 
              &region_id,0,PSL$C_USER,(SEC$M_EXPREG|SEC$M_SYSGBL|SEC$M_SHMGS), 
              &gs_va,&gs_length); 
   if (_failed(status)) exit(status); 
 
   /* Initialize the pointers into shared memory */ 
 
   busycnt   = (uint64 *) gs_va; 
   heartbeat = (uint64 *) gs_va     + max_instances; 
   cpucount  = (uint64 *) heartbeat + max_instances; 
   gLocks    = (uint64 *) cpucount  + max_instances; 
 
   cpucount[partition_id] = total_cpus; 
 
   /* Create or map the Galaxy lock table */ 
 
   status = init_lock_tables(); 
   if (_failed(status)) exit(status); 
 
   /* Initialize delta time for sleeping */ 
 
   status = sys$bintim(&time_desc,&delta); 
   if (_failed(status)) exit(status); 
 
   /* 
   ** Register for CPU migration events. Whenever a CPU is added to 
   ** our active set, the routine "update_cpucount" will fire. 
   */ 
   status = sys$set_system_event(SYSEVT$C_ADD_ACTIVE_CPU, 
              update_cpucount,0,0,SYSEVT$M_REPEAT_NOTIFY,&cpu_hndl); 
   if (_failed(status)) exit(status); 
 
   /* Force everyone to resync before we do anything */ 
 
   for (j=0; j<max_instances; j++) 
   { 
     status = sys$acquire_galaxy_lock(gLocks[j],GLOCK_TIMEOUT,0); 
     if (_failed(status)) exit(status); 
     heartbeat[j] = HEARTBEAT_RESTART; 
     status = sys$release_galaxy_lock (gLocks[j]); 
     if (_failed(status)) exit(status); 
   } 
 
   printf("%%GCU$BALANCER-S-INIT, CPU balancer initialized.\n\n"); 
 
   /*** Main loop ***/ 
   do 
   { 
     /* Calculate a running average and update it */ 
 
     nprocs = sys$cmkrnl(cpu_q,&arglst) * 100; 
 
     /* Check out our state... */ 
 
     switch (heartbeat[partition_id]) 
     { 
       case HEARTBEAT_RESTART: /* Mark ourself for reinitializition. */ 
       { 
         update_cpucount(0); 
         status = sys$acquire_galaxy_lock(gLocks[partition_id],GLOCK_TIMEOUT,0); 
         if (_failed(status)) exit(status); 
         heartbeat[partition_id] = HEARTBEAT_ALIVE; 
         status = sys$release_galaxy_lock(gLocks[partition_id]); 
         if (_failed(status)) exit(status); 
         break; 
       } 
       case HEARTBEAT_ALIVE: /* Update running average and continue. */ 
       { 
         busy = (busycnt[partition_id]*(num_samples-1)+nprocs)/num_samples; 
         busycnt[partition_id] = busy; 
         break; 
       } 
       case HEARTBEAT_TRANSPLANT:  /* Waiting for a new CPU to arrive. */ 
       { 
         /* 
         ** Someone just either reset us, or gave us a CPU and put a wait on 
         ** further donations.  Reassure the Galaxy that we're alive, and 
         ** calculate a new busy count. 
         */ 
         busycnt[partition_id] = nprocs; 
         status = sys$acquire_galaxy_lock(gLocks[partition_id],GLOCK_TIMEOUT,0); 
         if (_failed(status)) exit(status); 
         heartbeat[partition_id] = HEARTBEAT_ALIVE; 
         status = sys$release_galaxy_lock(gLocks[partition_id]); 
         if (_failed(status)) exit(status); 
         break; 
       } 
       default:         /* This should never happen. */ 
       { 
         exit(0); 
         break; 
       } 
     } 
 
     /* Determine the most_busy instance. */ 
 
     for (most_busy_id=most_busy=i=0; i<max_instances; i++) 
     { 
       if (busycnt[i] > most_busy) 
       { 
         most_busy_id = (uint64) i; 
         most_busy    = busycnt[i]; 
       } 
     } 
 
     if (show_stats) 
       printf("Current Load: %3Ld, Busiest Instance: %Ld, Queue Depth: %4d\r", 
             busycnt[partition_id],most_busy_id,(nprocs/100)); 
 
     /* If someone needs a CPU and we have an extra, dontate it. */ 
 
     if ((most_busy > busy + load_tolerance) && 
         (cpucount[partition_id] > 1) && 
         (heartbeat[most_busy_id] != HEARTBEAT_TRANSPLANT) && 
         (most_busy_id != partition_id)) 
     { 
        reassign_a_cpu(most_busy_id); 
     } 
 
     /* Hibernate for a while and do it all again. */ 
 
     status = sys$schdwk(0,0,&delta,0); 
     if (_failed(status)) exit(status); 
     status = sys$hiber(); 
     if (_failed(status)) exit(status); 
 
   } while (1); 
   return (1); 
} 
 
 


前へ 次へ 目次 索引