試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

Vala言語でUNIXドメインソケットを用いてcpufreqdのデーモンと通信するテスト(コード例)

Vala言語でUNIXドメインソケットを用いてcpufreqdのデーモンと通信するテスト(メモ)」の続き。

コード例

ここではUNIXドメインソケットを用いてcpufrqdのデーモンにプロファイル一覧の問い合わせコマンドを送り、その結果を受け取って解析してリスト構造(GLib.SList型)に追加し、切断後にそれらを順に表示する(処理時間としては一瞬で完了する)。
正常にプログラムを動作させるには/etc/cpufreqd.confの「remote_group」に書かれているグループに属したユーザの権限でcpufreqdの動作時に実行する必要がある。ただ、まだテスト段階なので、コードの中におかしな部分がある可能性はある。コンパイルにはバージョン2.22以上のGLibの開発パッケージが必要。
[任意]ファイル名: cpufreqdremotetest.vala

using Posix;

/*
 * GLib 2.22+
 * valac --pkg gio-2.0 --pkg gio-unix-2.0 --pkg posix -o cpufreqdremotetest cpufreqdremotetest.vala
 */

namespace CpufreqdRemoteTest
{
  class CpufreqdProfile : GLib.Object
  {
    /*
     * オブジェクト初期化時にのみ書き込み可、読み込みは常に可なプロパティ
     * (GLib.Objectクラスを継承する必要がある)
     */
    public uint num { get; construct; }
    public bool active { get; construct; }
    public string pfname { get; construct; }
    public string govname { get; construct; }
    public uint minfreq { get; construct; }
    public uint maxfreq { get; construct; }
    public static SList<CpufreqdProfile> profiles;
    public CpufreqdProfile (uint num, bool active, string pfname, string govname, uint minfreq, uint maxfreq)
    {
      /* プロパティ一括代入(GLib.Objectの派生クラスのみ) */
      GLib.Object (num: num, active: active, pfname: pfname, govname: govname, minfreq: minfreq, maxfreq: maxfreq);
    }
  }
  class CpufreqdRemote
  {
    string sockpath;
    GLib.Socket sock;
    /*
     * cpufreqd remote interface
     * ソケットを通じてcpufreqdと通信するためのインターフェース
     * cpufreqdのソースツリーに含まれるsrc/cpufreqd_remote.hを参照
     * 使い方はutilsディレクトリ以下の
     * cpufreqd-get/cpufreqd-setコマンドのソースを参考にしている
     */
    public static const uint32 CMD_SHIFT         = 16;
//    public static const uint32 CMD_UPDATE_STATE  = 1;
//    public static const uint32 CMD_SET_PROFILE   = 2;  /* profile index */
    public static const uint32 CMD_LIST_PROFILES = 3;
//    public static const uint32 CMD_SET_RULE      = 4;  /* rule index */
//    public static const uint32 CMD_LIST_RULES    = 5;
//    public static const uint32 CMD_SET_MODE      = 6;  /* mode */
//    public static const uint32 CMD_CUR_PROFILES  = 7;
//    public static const uint32 ARG_MASK          = (uint32) 0x0000ffff;
//    public static const uint32 MODE_DYNAMIC      = 1;
//    public static const uint32 MODE_MANUAL       = 2;
//    public static const uint32 INVALID_CMD       = (uint32) 0xffffffff;
//    public static uint32 remote_cmd (uint32 c)
//    {
//      return (c >> CpufreqdRemote.CMD_SHIFT);
//    }
//    public static uint32 remote_arg (uint32 c)
//    {
//      return (c & CpufreqdRemote.ARG_MASK);
//    }
    public static uint32 make_command (uint32 cmd, uint32 arg)
    {
      return ((cmd << CpufreqdRemote.CMD_SHIFT) | arg);
    }
    public void find_socket () throws GLib.FileError
    {
      GLib.Dir dir;
      string tmpsockpath;
      weak string item = null;  // システム側が所有するので弱い参照
      time_t mtime_last = 0;
      Posix.Stat statbuf;
      dir = GLib.Dir.open ("/tmp", 0);
      /*
       * /tmp内の項目を一覧して"cpufreqd-"で始まるディレクトリの中にある
       * ソケット"cpufreqd"の内、最も新しいもののパスを
       * メンバsockpathに入れる
       */
      for (;;)
      {
        item = dir.read_name ();
        if (item == null)
          break;
        if (item.has_prefix ("cpufreqd-"))  // この文字列で始まればtrue
        {
          GLib.debug ("\"%s\".has_prefix(\"cpufreqd-\") == true", item);
          tmpsockpath = GLib.Path.build_path ("/", "/tmp", item, "cpufreqd");
          if (Posix.stat (tmpsockpath, out statbuf) != 0)
            GLib.debug ("cannot stat \"%s\"", tmpsockpath);
          else
          {
            if (statbuf.st_mtime > mtime_last)
            {
              GLib.debug ("most recent socket(%ld > %ld): %s",
                          statbuf.st_mtime, mtime_last, tmpsockpath);
              mtime_last = statbuf.st_mtime;
              this.sockpath = tmpsockpath;
            }
          }
        }  // if (item.has_prefix ("cpufreqd-"))
        GLib.debug ("%s\n", item);
      }  // for (;;)
      /* 該当するソケットファイルがないまま終わった場合は例外を送ることにする */
      if (this.sockpath == null)
        throw new GLib.FileError.FAILED ("no cpufreqd sockets found");
      GLib.debug ("using socket \"%s\"", this.sockpath);
    }
    public bool connect ()
    {
      /*
       * GLib.Socketオブジェクトを作成し
       * メンバ関数connect()にGLib.UnixSocketAddressオブジェクトを指定して
       * 接続を行う
       */
      try
      {
        this.sock = new GLib.Socket (GLib.SocketFamily.UNIX,
                                     GLib.SocketType.STREAM,
                                     GLib.SocketProtocol.DEFAULT);
        return this.sock.connect (new GLib.UnixSocketAddress (this.sockpath), null);
      }
      catch (GLib.Error e)
      {
        GLib.warning ("%s", e.message);
        return false;
      }
    }
    public void disconnect ()
    {
      this.sock = null;  // 切断される
    }
    public bool query_profiles ()
    {
      GLib.IOChannel ioch;
      uint32 cmd;
      string line;
      /* scanf()を正しく行えるようにするため、十分な長さを確保した文字列を用意 */
      string pfname = string.nfill (255, '\0');  // 255 = MAX_STRING_LEN in src/cpufreqd.h
      string govname = string.nfill (255, '\0');
      size_t length, terminator_pos;
      uint active, minfreq, maxfreq;
      int i = 1;
      CpufreqdProfile.profiles = new SList<CpufreqdProfile> ();
      /* プロファイル一覧を要求するコマンドを送信 */
      if (! this.connect ())
      {
        GLib.warning ("connect failed");
        return false;
      }
      GLib.debug ("connected");
      /* プロファイル一覧を得るコマンドを作成 */
      cmd = CpufreqdRemote.make_command (CpufreqdRemote.CMD_LIST_PROFILES, 0);
      /* ファイル記述子はソケットオブジェクトのメンバ(プロパティ)fd */
      if (Posix.write (this.sock.fd, &cmd, 4) != 4)
      {
        GLib.warning ("Posix.write() failed");
        return false;
      }
      /* IOチャンネルを用いてプロファイル一覧を受け取る */
      ioch = new GLib.IOChannel.unix_new (this.sock.fd);
      try
      {
        ioch.set_encoding (null);
        ioch.set_flags (GLib.IOFlags.IS_READABLE);
      }
      catch (GLib.IOChannelError e)
      {
        ;
      }
      for (;;)
      {
        try
        {
          /* 出力を1行(1プロファイル分)受け取る */
          if (ioch.read_line (out line, out length, out terminator_pos) != GLib.IOStatus.NORMAL)
            break;
        }
        catch (GLib.ConvertError e)
        {
          ;
        }
        catch (GLib.IOChannelError e)
        {
          try
          {
            ioch.shutdown (false);
          }
          catch (GLib.IOChannelError e)
          {
            ;
          }
          finally
          {
            this.disconnect ();
            GLib.debug ("disconnected");
          }
          return false;
        }
        GLib.debug ("line: [%s]", line);
        /* 行を解析して値を変数に設定 */
        if (line.scanf ("%d/%[^/]/%u/%u/%[^\n]\n", out active, pfname, out minfreq, out maxfreq, govname) != 5)
        {
          GLib.debug ("scanf() != 5");
          break;
        }
        /* 線形リストに逆向き(高速)に追加 */
        CpufreqdProfile.profiles.prepend (new CpufreqdProfile (i, (active == 1), pfname, govname, minfreq, maxfreq));
        i++;
      }  // for (;;)
      try
      {
        GLib.debug ("ioch.shutdown()");
        ioch.shutdown (false);
      }
      catch (GLib.IOChannelError e)
      {
        ;
      }
      finally
      {
        this.disconnect ();
        GLib.debug ("disconnected");
      }
      /* 逆向きに追加していったので元の向きに戻す */
      CpufreqdProfile.profiles.reverse ();
      return true;
    }
  }
  class MainClass
  {
    public static int main (string[] args)
    {
      bool read_successful;
      CpufreqdRemote remote = new CpufreqdRemote ();
      try
      {
        remote.find_socket ();
      }
      catch (GLib.FileError e)
      {
        GLib.critical ("%s", e.message);
        return 1;
      }
      /* プロファイル一覧を問い合わせ、成功したらそれぞれを表示する */
      read_successful = remote.query_profiles ();
      if (read_successful)
      {
        foreach (CpufreqdProfile p in CpufreqdProfile.profiles)
        {
          print ("\nnum:%u\nactive:%d\npfname:%s\nfreq:%u-%u\ngovname:%s\n",
                 p.num, ((p.active == true) ? 1 : 0), p.pfname, p.minfreq, p.maxfreq, p.govname);
        }
      }
      return 0;
    }
  }
}

これを実行すると、途中のデバッグ向け出力の後で現在のcpufreqdのプロファイルが順に表示される。内容は/etc/cpufreqd.confのプロファイル定義に従って表示される。
下は出力例。

num:1
active:0
pfname:ondemand min-low
freq:1000000-1800000
govname:ondemand

num:2
active:0
pfname:ondemand min-mid
freq:1000000-2000000
govname:ondemand

(中略)

num:11
active:0
pfname:fixed max
freq:2200000-2200000
govname:performance

プロファイルの手動切り替えやモード切り替えについては「接続後コマンドを生成してそれを送信する」という流れが同じなので、ここでは扱わない。

関連:言語バインディングに問題?

Vala 0.7.9の時点では中間ファイルのC言語のコードがコンパイルできない問題が発生し、vapi/gio-unix-2.0.vapi(/usr/share/vala/vapi/gio-unix-2.0.vapi)に対して以下の修正を行う必要があった。

--- vala-0.7.9.orig/vapi/gio-unix-2.0.vapi
+++ vala-0.7.9/vapi/gio-unix-2.0.vapi
@@ -87,7 +87,7 @@
 		public bool close_fd { get; set; }
 		public int fd { get; construct; }
 	}
-	[CCode (cheader_filename = "gio/gunixmounts.h")]
+	[CCode (cheader_filename = "gio/gunixsocketaddress.h")]
 	public class UnixSocketAddress : GLib.SocketAddress, GLib.SocketConnectable {
 		[CCode (type = "GSocketAddress*", has_construct_function = false)]
 		public UnixSocketAddress (string path);

(2010/4/1)Vala 0.8.0ではこの問題は修正されている。

関連記事:

使用したバージョン:

  • Vala 0.7.9, 0.8.0
  • GLib, GIO 2.22.2