Hatena::ブログ(Diary)

Windows より Mac より Linux がいい

2012-06-02

kevent にはあって epoll にはないもの

イベント関連のライブラリLinux では epoll、BSD では kevent が一般的に使われています。kevent はイベント発生時に、イベントディスクリプタとデータへのポインタの両方を取得することができますが、epoll ではそれができません。

言っている意味がわからないと思うので、例を示します。kevent の構造体は、以下のようになっています。

struct kevent {
        uintptr_t       ident;
        short           filter;
        u_short         flags;
        u_int           fflags;
        intptr_t        data;
        void            *udata;
};

この中の udata がポイントです。イベント登録には KV_SET を使いますが、最後の第7引数void * を指定できます。これは、プログラマ任意に入れることができ、イベント発生時にファイルディスクリプタと一緒に取得することができます。これは、多目的に用いることができますが、例えば、「accept 時に 送信元アドレスを取得したデータを malloc しておいて、そのポインタを イベントに登録しておいて、イベント発生時に、送信元アドレスを取得する」といったことができます。

次に、epoll の構造体を見てみましょう。以下が sys/epoll.h に書かれている構造体です。

struct epoll_event
{
  uint32_t events;      /* Epoll events */
  epoll_data_t data;    /* User data variable */
};

typedef union epoll_data
{
  void *ptr;
  int fd;
  uint32_t u32;
  uint64_t u64;
} epoll_data_t;

epoll は data というメンバ変数が定義されており、その中はユーザが使える変数となっています。が、しかし、そのデータは union になっており、よくサンプルで見かける 「epoll.data.fd = sock」を使うと「epoll.data.ptr」は使えません。(初めは、うほ fd と ptr が定義されているじゃんwwと思って、使ってみたもののうまく動かないんで、改めて見なおしてみると struct じゃないくて union かよ...ってなりました。)

epoll ので fd と送信元アドレスを扱いたい場合、

struct from_data {
    int fd;
    struct sockaddr_storage from;
    socklen_t fromlen;
};

といった、fd も含んだ構造体を定義して、fd への参照は構造体のメンバ変数を参照するように、ソースコードに変更する必要があります。

そこら辺が親切な kevent と、自分でやれよという epoll の方針の違いは、文化の違いってやつですか...

2012-05-11

C言語で、IPアドレスを sockaddr 型に変換

C言語で、IPアドレス(文字列)から、struct sockaddr 型に変換したい時があります。

そんなときは、inet_pton() を使って変換しますが、この関数は成功すると戻り値が 1 となり、

変換できない文字列なら 0 、何らかのエラーなら -1 を返します。

他の標準関数は 0 が成功を表しますが、この関数は 1 が成功を表すので、ちょっと違和感があります。

と、思っていたら、三項演算子を使うと完結にコードを書くことができました。

三項演算子の省略形

三項演算子の省略形を知らない人もいると思うので、ちょっとだけ解説します。

C言語を始め、さまざまな言語で三項演算子が定義されています。

max = x > y ? x y; 

三項演算子には、省略記法があり、二項目を省略することができます。

a = b ?: c;  // b が0以外なら a は b で、b が0なら a は c となる。

ただこれは、条件式と組み合わせると、うまく動作しなくなるので、なかなか使いどころが難しい。

a = b > 0 ?: c; // b が 0以上なら a は 1 となってしまう。

これを使って、IPアドレスを変換します。IPアドレスには、ご存知の通り、IPv4IPv6 のに種類があるので、区別して書く必要があります。

inet_pton を使う

幸い、IPv4 は inet_pton(AF_INET, ...) と書いて 1 なら成功 0 なら、IPv6 の可能性があり、inet_pton(AF_INET6, ...) と書けば 1 なら成功となります。

それを愚直に書くと、

char const * str = "::1";
struct sockaddr_storage ss;
int ret;

ret = inet_pton(AF_INET, str, &((struct sockaddr_in *)&ss)->sin_addr);
if (ret == 0) inet_pton(AF_INET6, str, &((struct sockaddr_in6 *)&ss)->sin_addr6);
if (ret != 1) printf("unkknown ip address for \"%s\"", str);
}

この形は、まさに三項演算子の省略形の出番です。

ret = inet_pton(AF_INET, str, &((struct sockaddr_in *)&ss)->sin_addr)
    ?: inet_pton(AF_INET6, str, &((struct sockaddr_in6 *)&ss)->sin6_addr);
if (ret != 1) printf(...);

と、完結に書くことができます。ただ、このままだと sa->sa_family に値がないので、カンマ演算子を使って、代入させます。

struct sockaddr * sa = &ss;
ret = (sa->sa_family = AF_INET, inet_pton(AF_INET, str, &((struct sockaddr_in *)sa)->sin_addr))
    ?= (sa->sa_family = AF_INET6, inet_pton(AF_INET6, str, &((struct sockaddr_in6 *)sa)->sin6_addr));

今までに、よく ient_pton() の戻り値を間違えて処理していましたが、これを使えば間違えることもすくなくなり、この関数を作った人も、きっとこういった思いで関数を定義したんだなーと関心。

2012-04-04

C言語で関数のネストを使って関数型っぽく書いてみる

まず、クロージャは"C言語"であれば、関数のネストを使うことで、それっぽく書くことができます。"C言語"ならOKですが、C++ではNGです。C++に拡張予定の場合は一切使えませんので、ご注意ください。

また、相互再帰は、auto ... で関数内で定義する関数の宣言文を記述できます。

部分適用は書けないので、関数のネストを使って地道に再定義を繰り返すしかなさそうです。

これらを使って、奇偶判定のコードを書いてみます。

#include <stdio.h>

int main(void)
{
    void even_odd(int x) {
        auto void odd(int x, int y);
        
        void even(int x, int y) {
            if (x == y) printf("偶数です");
            else odd(x + 1, y);
        }
        
        void odd(int x, int y) {
            if (x == y) printf("奇数です");
            else even(x + 1, y);
        }
        
        even(0, x);
    }

    even_odd(1);  // 奇数です
    even_odd(10);  // 偶数です
    return 0;
}

GCC のバージョン縛りもありますが、4.5 以上であれば、コンパイルは大丈夫でしょう。当然ですが GCC 以外ではコンパイルできません。

ちなみに、このソースの元ネタは以下の本です。

入門OCaml ~プログラミング基礎と実践理解~

入門OCaml ~プログラミング基礎と実践理解~

2012-04-03

GCCの警告をその場だけ無効にする方法

GCCでは const 修飾子がついた変数に対して、破壊的代入を行うことはできません。そこで、普通は キャスト して無理やり代入してしまうのですが、警告が出ることがあります。

プログラム

関数の外から見れば値は変わっていないが、内部では変数を書き換えているという場合は、const が邪魔になってしまいます。(const を外せばいいじゃんというツッコミはなしで...)

int foo(char const * str)
{
    char bk_str;

    // いろんな処理

    bk_str = *str;
    *(char *)str = '\0';

    // いろんな処理

    *(char *)str = bk_str;

    return 0;
}
% gcc -Wall -Wextra -Wcast-qual -fsyntax-only foo.c 
foo.c: 関数 ‘foo’ 内:
foo.c:6:6: 警告: cast discards ‘__attribute__((const))’ qualifier from pointer target type [-Wcast-qual]
foo.c:7:6: 警告: cast discards ‘__attribute__((const))’ qualifier from pointer target type [-Wcast-qual]

GCCconst に非常に緩い設計になっているため、僕は普段から安全のため、コンパイルオプションに -Wcast-qual をつけています。しかし、この場合は、それが逆にアダとなってしまいます。

警告を無効にするプラグマの書き方

{
    char bk_str;

    // いろんな処理

    bk_str = *str;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
    *(char *)str = '\0';
#pragma GCC diagnostic pop

    // いろんな処理

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
    *(char *)str = bk_char;
#pragma GCC diagnostic pop

    return 0;
}

詳しい説明は公式マニュアルに書かれています。no title を参照してください。

2012-03-18

Arch Linux で Bluetooth ヘッドセットを使う

skype などの音声チャット中に席を外すことも想定し、bluetooth ワイヤレスヘッドセットを購入しました。

型番は ロジテック LBT-MPHS02BK (1,500円) です。値段相応で、音質はゴミっカスですが、試すにはちょうどいい値段であります。


また、PC本体に、Bluetooth トランシーバが搭載されていないので、USBタイプのもを購入しました。

こちらは、プラネックス BT-Micro4 です。何気に Bluetooth 4.0 に対応していたりします。(1,500円)

ヘッドセットを認識させるまで

まず、USB Bluetoth トランシーバを挿して認識するかを試みます。型番すら調べずに購入したので、ドキドキでしたが、普通に認識しました。

で、Linux では bluetooth は blueZ というパッケージが標準になっているようで bluez をインストールし、デーモンを起動させます。

% yaourt -S extra/bluez

% /etc/rc.d/bluetooth start

bluetooth トランシーバの操作は hci{attach,config,tool} で可能です。hciconfig で現在有効なトランシーバを確認できます。ifconfig のように up/down することも可能です。

% hciconfig
hci0:   Type: BR/EDR  Bus: USB
        BD Address: 00:11:22:33:44:55  ACL MTU: 310:10  SCO MTU: 64:8
        UP RUNNING
        RX bytes:862 acl:0 sco:0 events:38 errors:0
        TX bytes:655 acl:0 sco:0 commands:37 errors:0

hcitool では、ペアリング検索を行う scan や、特定の bluetooth レシーバが存在するかを確認する name などがあります。name はbluetoothデバイスを一意に決める bluetooth device address(bdaddr) さえ分かれば、存在を確認することができ、接近検知としても使えます。すでに、Bluetooth近接検知装置 - 地下室の手記 で提案されています。



話がそれました。bluetooth レシーバを認識させるために「ペアリング」を行います。そのとき、hcitool scan とすると、ペアリングしているトランシーバから応答があり、bdaddr を知ることができます。仮に 55:44:33:22:11:00 とします。

bluetooth用の ping も用意されており

l2ping 55:44:33:22:11:00

とすると、普通はレシーバから応答があるらしいのですが、この安物ヘッドセットは応答しませんでした...orz (応答しないから、ペアリングできないんじゃなね?と焦りましたが、不親切設計なだけで大丈夫でした。)

ペアリングの仕方ですが、bluetooth-wizard や bluez-test-device などがあるようですが、どちらもペアリング中に disconnect してしまいうまくできません。

負けた気がしますが、KDEbluetooth を扱うアプリケーションとして bluedevil が提供されており、これを使ったら、すんなりペアリングできました。


ヘッドセットから音を出す

僕は alsa を使っていましたが、alsa ではヘッドセットを認識できません。.asoundrc に bluetooth の設定を書き込んで認識させる方法もダメでした。alsamixer や aplay -L で bluetooth デバイスが表示されず、音を出すことができません。

alsa を諦めて別の サウンドシステムを検討中に PulseAudio - Wikipediabluetoothオーディオデバイスを動的検知と書かれており、pluseaudio をインストールしたところ、うまく音が出ました。

yaourt -S extra/pulseaudio extra/pulseaudio-alsa

また pulseaudio の管理ツールが非常に便利でアプリケーション単位で入出力サウンドデバイスを制御できます。AUR 版しかないですが...

yaourt -S aur/pasystray-git aur/paman