楽天モバイル1本化へ

私はiPhoneで、 格安SIM(SIMカード)と楽天モバイル(eSIM)で使っていましたが、今回、楽天モバイルに1本化することにしました。
現在の楽天モバイルを解約し、格安SIMの電話番号を楽天モバイルMNPする流れです。

MNPの転入手続きですが、eSIMだと発行するものもないし、即時手続きができる、と思っていたのですが、甘かったです。

意外と時間がかかります。以下、記録として残しておきます。

 

流れ 

  1. 格安SIM側で、MNP転出手続きをする
    MNP転出手続きは、即時発行できた。うんうん、よかった。
  2. 楽天モバイルで、MNP転入手続きする
    eSIMを選択して、手続き開始!
    ・・・
    「手続き中…」
    ・・・
    説明文には「発送準備中」文字が・・・
    どういうこと?郵便物ないんじゃないの?
    しばらく待ってもそのまま。
  3. 現在契約中の楽天モバイルを解約する
    この時やらなくてもよかったのかもですが、なんか無駄だと思って「my楽天モバイル」から解約しました。
    解約は即時反映されました。
    楽天モバイルの eSIM設定を削除。iPhoneの設定 - モバイル通信 - モバイル通信プランを削除
    格安SIMだけでモバイル通信ができない!再起動しても解消せず。。。
    焦りましたが、格安SIMのAPN設定をもう一度行うことで解消しました。
    モバイル通信プランを削除する前に、データ通信を格安SIM側で行うように設定していればAPN設定のやり直しは不要だったのかもしれません。
  4. 楽天モバイルから郵便物が届く
    my楽天モバイルで、申込み履歴が「配送中」となる。
    やっぱり何か発送してるのか、と。
    結局、届くまで3日かかりました(21年3月末だったので、手続きが立て込んでいたのか)
    郵便で、本人の実在確認が必要だったのかな。にしても長い。
    郵便物には、MNP転入手続き用のQRコードがプリントされた手順書が入っています。
    eSIMなら、QRコードは送付不要なはずなのに。残念。
    実店舗があるキャリアならその場でMNP手続きできることを考えると、こういうところが普及を妨げる原因なんでしょうね。
  5. 楽天モバイル 回線開通手続きを行う
    eSIM設定をしても、設定 - モバイル通信で、RAKUTENではなくKDDIとなっている。
    手順書は、当然ながら正常系の手順しか記載されてないので、悩む悩む。。。
    結局、以下の2点に気づくのに、2時間ぐらい試行錯誤しました。
    いやー、ネットワーク設定を初期化しても直らない時は流石に焦りました。
    以下、ご注意ください。

 

楽天回線 開通手続きをする上でのコツ(MNP転入)

  • 格安SIM APN設定削除する
    設定 - 一般 - プロファイル から、削除
  • SIMカードiPhoneから抜き取る(重要!)
    解約されているSIMカードを抜き取らないと 設定 - モバイル通信 にSIMカード情報が残って、楽天のeSIM情報が変に上書きされて動作しない

 

楽天モバイルまとめ

私は格安SIM(SIMカード)と楽天モバイル(eSIM)という構成のiPhoneを、1年弱使用してきました。電話は格安SIM、データ通信は楽天モバイルで行う、という設定です。

20年は、私の生活圏では、自宅は楽天モバイルのエリア外で、ターミナル駅でも繋がらない箇所があったり、電車に乗ってると通信できない期間があったりで、いまいちな状態でした。

が、21年になり、楽天モバイルのCMが多くなってきた時期に合わせるかのように、自宅も楽天モバイルのエリア内に入り、繋がらない場所もなくなり、実質問題を感じることがなくなってきました。

で、1年の無料期間も終わるので、どうしようかと考えてきましたが、この1年お世話になった楽天モバイルにちゃんと支払いをしようと思い、楽天モバイルに一本化することにしました。

 

上記のように多少手間取り、途中、初期化しないとダメかと焦りましたが、なんとかできました。

よかったよかった。

楽天モバイル、テンセント出資のことで叩かれたりしてますが、ちゃんと運用して、これからもガンガン攻めてください。期待しています。

 

楽天モバイル(MNO) できること・できないこと iPhone eSIM編

はじめに

先日、楽天モバイル(nanoSIM)を使ってAndroid/iPhoneでできることできないことを調べ、iPhone単体では全ての機能を使えないことがわかりました。

でも、やっぱり、iPhone単体で楽天モバイル回線を使いたい!

ということで、音声・SMSは今のソフトバンク、データ通信は楽天にしようと思います。

ソフトバンク(主回線)の nanoSIM はそのまま、楽天(副回線)は nanoSIM から eSIM に変更します。

 

楽天モバイルを eSIM にプラン変更する

my楽天モバイルからプラン変更を行います。

iPhoneQR コードを読み取るので、iPhone 以外の端末からアクセスします。

 

トップページから「契約プランを表示」-「各種手続き」-「SIMカード交換」を選択し、

変更理由は「その他」、SIMカードタイプは「eSIM」で、再発行を申請します。

「確定」したら、完了です。

my楽天モバイルのトップページを見ると、新しい申込番号が表示されており、開通待ちになっています。クリックすると、QRコードが表示されました。

すばらしいですね。

プラン変更を申請してから、eSIMが発行されるまでは1分も掛かりませんでした。 

 

iPhone XR(iOS 13.4)に設定する

 

iPhoneのカメラでQRコード読んで、その流れで新しいモバイル回線を追加します。

以下、促されるまま選択するだけでした。

  • 現在刺さっているSIMとどちらを主回線にするのか
  • モバイルデータ通信にはどちらの回線を使用するのか
  • デフォルトの音声回線をどちらの回線にするのか

念のため、楽天回線でも電話できるようにするため、前回同様、以下のサイトの設定を行います。

kiritsume.com

 

できること・できないこと

楽天回線エリア外
  • 主回線(ソフトバンク)による、電話の発着信・SMSの送受信はOK
  • 副回線(楽天)による、データ通信もOK
  • 副回線(楽天)による、テザリングもOK
  • 副回線(楽天)による、電話の発着信はOK。SMSの送受信はどちらもNG。
    副回線での電話とSMSは特に必要ではないですが、一応、確認です。

iPhoneだけで、電話・SMS・データ通信、テザリングの一通り使えるようになりました。

 

楽天回線エリア内

現在、外出自粛しているので、まだ確認できていません。

エリア内に行けたら、更新しようと思います。

 

気になる

iPhone の「設定」「一般」「情報」 を開くと、「キャリア設定アップデート」のダイアログが表示されます。

何度、アップデートしても、主回線/副回線の情報は更新されません。

気にはなりますが、ひとまず様子見します。

 

考察

私は楽天回線エリア外で生活していますが、月々の容量5GBを超えても制限は1Mbpsなので、データ通信量をほぼ気にしない生活ができるようになりました。

ひとまず、この1年はこの形態で使っていきたいと思います。

楽天、ここ最近はいろんな逆風が吹いてますが、3大キャリアに対抗する軸としてこれからも頑張ってもらいたいなと思います。

楽天モバイル(MNO) できること・できないこと

はじめに

楽天モバイルが 2020-04-08 に MNO 事業 Rakuten UN-LIMIT を開始した。
当初、自分は iPhone ユーザーなのでスルーするつもりでした。が、android端末もあるし、docomo, au, softbank の寡占状態に風穴を開けて欲しい、という応援の意味も込めて、新規回線として、SIMカードを申し込んだ。

もちろん、1年間無料など、うまくすれば携帯電話料金を減らせる、という下心も持ちつつ。

 

android端末(HUAWEI MediaPad M5 lite : JDN2-L09  OS : 9.0)

公式サポート対象外の端末ではあるが、
SIMカード挿すだけで、データ通信は使える。設定も不要。
楽天LinkによるSMS認証を行うと、無料電話・SMSができるようになる。
ただし、SMS認証は楽天回線エリア内でしかできない。
テザリングは普通にできる。
 

できること・できないこと

  • 楽天回線エリア内

    楽天Linkで無料電話、SMS使える

    標準アプリでも有料電話・SMS使える

  • 楽天回線エリア外

    楽天Linkで無料電話、SMS使える

    標準アプリでは電話もSMSも不可

注意

SMS認証する時だけは、楽天回線エリアに行く必要がある。
SIMカードを抜いてしまうと、再度SMS認証が必要になる。

 

iPhone端末 (iPhone XR   iOS : 13.4)

SIMカードを挿して設定すれば、データ通信は使える。
テザリングはで普通にできる。
設定はこちらの通り。

できること・できないこと

  • 楽天回線エリア内
    電話、SMS使える
    ただ、SMSが使えない時もあった
  • 楽天回線エリア外
    電話は使えるが、SMS使えない
 

考察

  • 通話・SMS・データ通信をできるようにしたい人は
    楽天回線はiPhoneでSMSが使えないので、サブ回線としてしか使用できない。
    iPhoneで完結させるなら、メイン回線を格安SIMにして、楽天回線をeSIMにした運用が良さそう。
    eSIMは、iPhoneXR以降の機種でないとダメです。
  • 通話・SMSが必要ない人は
    通話やSMSはLINEで代替して、楽天回線1本でデータ通信のみで生きていく。
    Instaとか、SMS認証が必須なアプリがあるので、そういうアプリは使用しない。
    うーん、やっぱり、自分には無理。

今後、楽天モバイルiPhone を正式対応してくれたら、家族にも勧められるかなー。
次は、楽天SIMを eSIM に変更して、格安SIM楽天eSIMで iPhone だけで生活できるようにしよう。 

プロセス間通信の比較

デバイスドライバによるプロセス間通信 - ka2yanの日記デバイスドライバは、既存のプロセス間通信と比べて本当に早いのか、を測定した。

調べること

100バイト程度のメッセージ(画像データ等の大容量データを対象にしているのではない)をやりとりするプロセス間通信で一番早いのは何か?

測定するプロセス間通信は、前回の日記に書いたデバイスドライバ(共有メモリ方式)とデバイスドライバ(メッセージRead/Write)、そして、メッセージキュー(POSIX)、UNIXドメインソケット、名前付パイプ(FIFO)の5種類。
メッセージキュー(System V)は、fd として扱えないので、測定しない。

測定プログラムの概要

−2プロセス間で128バイトのメッセージを送受信を繰り返すプログラム
−2プロセスは、NON_BLOCKING でメッセージをread/write
−read/writeする前に、select()する

測定内容

−メッセージの送受信回数 1000,000回
−timeコマンドでのコマンド実行時間(厳密な送受信時間ではない)

測定環境

−CentOS5.2 (2.6.18)
−CPU:Pentium4 2.8GHz Memory:2GB

結果

プロセス間通信 real[s] user[s] sys[s]
デバイスドライバ(共有メモリ方式) 7.322 0.716 2.922
デバイスドライバ(メッセージRead/Write方式) 7.663 0.715 3.124
メッセージキュー(POSIX 6.935 0.792 2.755
UNIXドメインソケット 9.679 1.716 7.956
名前付パイプ(FIFO 8.272 1.465 6.801


real :実時間(コマンドを起動してから終了するまでの時間)
user :ユーザCPU時間(ユーザープロセスが動作している時間)
sys :システムCPU時間(カーネルでの動作時間)


なんと、メッセージキュー(POSIX)が一番早い!


デバイスドライバが一番早いことを期待していたのだが、残念。
ただ、デバイスドライバ(メッセージRead/Write方式)は、メッセージキュー(POSIX)より
速度が1割程度遅くても、プロセス間通信のログが取れるというメリットがある。


そんなに遅くはないけど、なんとかもう少し早くならないかなぁ。

TODO

デバイスドライバ(メッセージRead/Write方式)をもう少し使えるドライバにする。
# もう少し2.6ドライバーらしくする
# 複数プロセス対応
今は、2プロセスでしか使えない。
# 複数メッセージ対応
今は、1メッセージしか保持できない
# シェルスクリプトからのメッセージ受信処理の実装

測定に使ったプログラム達

実行順序は、デバイスドライバと同様で、read 側を実行しておき、write 側の引数に1000000を渡す。
プログラムが多いので、Makefile と 実行例は省略する。

メッセージキュー(POSIX

mqread.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <mqueue.h>
#include <fcntl.h>
#include <errno.h>

#define DPRINTF(...)
//#define DPRINTF(...) printf(__VA_ARGS__)

int main(int argc, char *argv[])
{
        mqd_t fd1, fd2;
        fd_set rfds, wfds;
        char buf[128];
        int read_start;
        unsigned int prio=10;
        struct mq_attr attr;
        char *rbuf;

        memset(&attr, 0, sizeof(attr));

        mq_unlink("/mq1");
        mq_unlink("/mq2");
        if ((fd1 = mq_open("/mq1", O_RDONLY|O_NONBLOCK|O_CREAT|O_EXCL,
                         S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) == -1) {
                perror("open /mq1");
                exit(-1);
        }
        if ((fd2 = mq_open("/mq2", O_WRONLY|O_NONBLOCK|O_CREAT|O_EXCL,
                         S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) == -1) {
                perror("open /mq2");
                exit(-1);
        }

        mq_getattr(fd1,&attr);
        if((rbuf = malloc(attr.mq_msgsize)) == NULL) {
                fprintf(stderr, "malloc error\n");
                exit(-1);
        }

        read_start=1;
        while (1) {
                if (read_start) {
                        buf[0] = '\0';

                        FD_ZERO(&rfds);
                        FD_SET(fd1, &rfds);

                        DPRINTF("selecting read...\n");
                        if (select(fd1+1, &rfds, NULL, NULL, NULL) == -1) {
                                perror("select");
                        }
                        if (FD_ISSET(fd1, &rfds)) {
                                int n;
                                n = mq_receive(fd1, rbuf, attr.mq_msgsize, &prio);
                                if (n > 0 ) {
                                        DPRINTF("%d = read [%s] prio=%d\n", n, rbuf, prio);
                                } else {
                                        perror("read");
                                        exit(-1);
                                }
                        }
                        read_start = 0;
                } else {
                        memset(buf, 'r', sizeof(buf));
                        buf[sizeof(buf)-1] = '\0';

                        FD_ZERO(&wfds);
                        FD_SET(fd2, &wfds);
                        DPRINTF("selecting write...\n");
                        if (select(fd2+1, NULL, &wfds, NULL, NULL) == -1) {
                                perror("select");
                                exit(-1);
                        }

                        if (FD_ISSET(fd2, &wfds)) {
                                if (mq_send(fd2, buf, sizeof(buf), prio) != 0) {
                                        perror("write");
                                        exit(-1);
                                }
                        }
                        read_start = 1;
//                      sleep(1);
                }
        }
        mq_close(fd1);
        mq_close(fd2);

        return 0;
}

mqwrite.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <mqueue.h>
#include <fcntl.h>
#include <errno.h>

#define DPRINTF(...)
//#define DPRINTF(...) printf(__VA_ARGS__)

int main(int argc, char *argv[])
{
        mqd_t fd1, fd2;
        fd_set rfds, wfds;
        char buf[128];
        int read_start;
        unsigned int prio=10;
        int nloop, i;
        struct mq_attr attr;
        char *rbuf;

        if (argc != 2) {
                printf("usage:\n./mqwrite <NLOOP>\n");
                exit(-1);
        }
        nloop = atoi(argv[1]);

        memset(&attr, 0, sizeof(attr));

        if ((fd1 = mq_open("/mq2", O_RDONLY|O_NONBLOCK)) == -1) {
                perror("open /mq2");
                exit(-1);
        }
        if ((fd2 = mq_open("/mq1", O_WRONLY|O_NONBLOCK)) == -1) {
                perror("open /mq1");
                exit(-1);
        }
        mq_getattr(fd1,&attr);
        if((rbuf = malloc(attr.mq_msgsize)) == NULL) {
                fprintf(stderr, "malloc error\n");
                exit(-1);
        }

        read_start=0;
        for(i=0; i<nloop; i++) {
                if (read_start) {
                        buf[0] = '\0';

                        FD_ZERO(&rfds);
                        FD_SET(fd1, &rfds);

                        DPRINTF("selecting read...\n");
                        if (select(fd1+1, &rfds, NULL, NULL, NULL) == -1) {
                                perror("select");
                        }

                        if (FD_ISSET(fd1, &rfds)) {
                                int n;
                                n = mq_receive(fd1, rbuf, attr.mq_msgsize, &prio);
                                if (n > 0 ) {
                                        DPRINTF("%d = read [%s] prio=%d\n", n, rbuf, prio);
                                } else {
                                        perror("read");
                                        exit(-1);
                                }
                        }
                        read_start = 0;
                } else {
                        memset(buf, 'w', sizeof(buf));
                        buf[sizeof(buf)-1] = '\0';

                        FD_ZERO(&wfds);
                        FD_SET(fd2, &wfds);
                        DPRINTF("selecting write...\n");
                        if (select(fd2+1, NULL, &wfds, NULL, NULL) == -1) {
                                perror("select");
                                exit(-1);
                        }

                        if (FD_ISSET(fd2, &wfds)) {
                                if (mq_send(fd2, buf, sizeof(buf), prio) != 0) {
                                        perror("write");
                                        exit(-1);
                                }
                        }
                        read_start = 1;
//                      sleep(1);
                }
        }
        printf("write loop= %d\n", i/2);
        mq_close(fd1);
        mq_close(fd2);

        return 0;
}
UNIXドメインソケット

sread.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/select.h>
#include <sys/ioctl.h>
#include <errno.h>

#define DPRINTF(...)
//#define DPRINTF(...) printf(__VA_ARGS__)

#define SOCK_NAME "/tmp/test_socket"

int main(int argc, char *argv[])
{
        int    fd1, fd2;
        struct sockaddr_un    saddr;
        struct sockaddr_un    caddr;
        int    len, val;
        fd_set rfds, wfds;
        char buf[128];
        int read_start;


        if ((fd1 = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
                perror("socket");
                exit(-1);
        }

        memset((char *)&saddr, 0, sizeof(saddr));
        saddr.sun_family = AF_UNIX;
        strcpy(saddr.sun_path, SOCK_NAME);

        unlink(SOCK_NAME);
        if (bind(fd1, (struct sockaddr *)&saddr,
                         sizeof(saddr.sun_family) + strlen(SOCK_NAME)) < 0){
                perror("bind");
                exit(-1);
        }

        if (listen(fd1, 1) < 0) {
                perror("listen");
                exit(-1);
        }

        len = sizeof(caddr);
        if ((fd2 = accept(fd1, (struct sockaddr *)&caddr, (socklen_t *)&len)) < 0) {
                perror("accept");
                exit(-1);
        }
        close(fd1);

        /* set non-blocking */
        val = 1;
        ioctl(fd2, FIONBIO, &val);

        read_start=1;
        while (1) {
                if (read_start) {
                        buf[0] = '\0';

                        FD_ZERO(&rfds);
                        FD_SET(fd2, &rfds);

                        DPRINTF("selecting read...\n");
                        if (select(fd2+1, &rfds, NULL, NULL, NULL) == -1) {
                                perror("select");
                        }

                        if (FD_ISSET(fd2, &rfds)) {
                                int n;
                                n = read(fd2, buf, sizeof(buf));
                                if (n > 0 ) {
                                        DPRINTF("%d = read [%s]\n", n, buf);
                                } else {
                                        perror("read");
                                        exit(-1);
                                }
                        }
                        read_start = 0;
                } else {
                        memset(buf, 'r', sizeof(buf));
                        buf[sizeof(buf)-1] = '\0';

                        FD_ZERO(&wfds);
                        FD_SET(fd2, &wfds);
                        DPRINTF("selecting write...\n");
                        if (select(fd2+1, NULL, &wfds, NULL, NULL) == -1) {
                                perror("select");
                                exit(-1);
                        }

                        if (FD_ISSET(fd2, &wfds)) {
                                if (write(fd2, buf, sizeof(buf)) != sizeof(buf)) {
                                        perror("write");
                                        exit(-1);
                                }
                        }
                        read_start = 1;
//                      sleep(1);
                }
        }
        close(fd2);

        return 0;
}

swrite.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/select.h>
#include <sys/ioctl.h>
#include <errno.h>

#define DPRINTF(...)
//#define DPRINTF(...) printf(__VA_ARGS__)

#define SOCK_NAME "/tmp/test_socket"

int main(int argc, char *argv[])
{
        int    fd;
        struct sockaddr_un    addr;
        int    len, val;
        fd_set rfds, wfds;
        char   buf[128];
        int    i, nloop;
        int    read_start;

        if (argc != 2) {
                printf("usage:\n./swrite <NLOOP>\n");
                exit(-1);
        }
        nloop = atoi(argv[1]);

        if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
                perror("socket");
                exit(-1);
        }

        memset((char *)&addr, 0, sizeof(addr));
        addr.sun_family = AF_UNIX;
        strcpy(addr.sun_path, SOCK_NAME);

        /* set non-blocking */
        val = 1;
        ioctl(fd, FIONBIO, &val);

        if (connect(fd, (struct sockaddr *)&addr,
                                sizeof(addr.sun_family) + strlen(SOCK_NAME)) < 0){
                perror("connect");
                exit(1);
        }

        read_start=0;
        for (i=0; i<nloop; i++) {
                if (read_start) {
                        buf[0] = '\0';

                        FD_ZERO(&rfds);
                        FD_SET(fd, &rfds);

                        DPRINTF("selecting read...\n");
                        if (select(fd+1, &rfds, NULL, NULL, NULL) == -1) {
                                perror("select");
                        }

                        if (FD_ISSET(fd, &rfds)) {
                                int n;
                                n = read(fd, buf, sizeof(buf));
                                if (n > 0 ) {
                                        DPRINTF("%d = read [%s]\n", n, buf);
                                } else {
                                        perror("read");
                                        exit(-1);
                                }
                        }
                        read_start = 0;
                } else {
                        memset(buf, 'w', sizeof(buf));
                        buf[sizeof(buf)-1] = '\0';

                        FD_ZERO(&wfds);
                        FD_SET(fd, &wfds);
                        DPRINTF("selecting write...\n");
                        if (select(fd+1, NULL, &wfds, NULL, NULL) == -1) {
                                perror("select");
                                exit(-1);
                        }

                        if (FD_ISSET(fd, &wfds)) {
                                if (write(fd, buf, sizeof(buf)) != sizeof(buf)) {
                                        perror("write");
                                        exit(-1);
                                }
                        }
                        read_start = 1;
//                      sleep(1);
                }
        }
        printf("write loop= %d\n", i/2);
        close(fd);

        return 0;
}
名前付きパイプ(FIFO

fread.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <fcntl.h>
#include <errno.h>

#define DPRINTF(...)
//#define DPRINTF(...) printf(__VA_ARGS__)

int main(int argc, char *argv[])
{
        int fd1, fd2;
        fd_set rfds, wfds;
        char buf[128];
        int read_start;

        unlink("/tmp/fifo1");
        unlink("/tmp/fifo2");

        if (mkfifo("/tmp/fifo1", 0666) == -1) {
                perror("mkfifo /tmp/fifo1");
                exit(-1);
        }
        if (mkfifo("/tmp/fifo2", 0777) == -1) {
                perror("mkfifo /tmp/fifo2");
                exit(-1);
        }

        if ((fd1 = open("/tmp/fifo1", O_RDONLY|O_NONBLOCK)) == -1) {
                perror("open /tmp/fifo1");
                exit(-1);
        }
        if ((fd2 = open("/tmp/fifo2", O_RDWR|O_NONBLOCK)) == -1) {
                perror("open /tmp/fifo2");
                exit(-1);
        }

        read_start=1;
        while (1) {
                if (read_start) {
                        buf[0] = '\0';

                        FD_ZERO(&rfds);
                        FD_SET(fd1, &rfds);

                        DPRINTF("selecting read...\n");
                        if (select(fd1+1, &rfds, NULL, NULL, NULL) == -1) {
                                perror("select");
                        }

                        if (FD_ISSET(fd1, &rfds)) {
                                int n;
                                n = read(fd1, buf, sizeof(buf));
                                if (n > 0 ) {
                                        DPRINTF("%d = read [%s]\n", n, buf);
                                } else {
                                        perror("read");
                                        exit(-1);
                                }
                        }
                        read_start = 0;
                } else {
                        memset(buf, 'r', sizeof(buf));
                        buf[sizeof(buf)-1] = '\0';

                        FD_ZERO(&wfds);
                        FD_SET(fd2, &wfds);
                        DPRINTF("selecting write...\n");
                        if (select(fd2+1, NULL, &wfds, NULL, NULL) == -1) {
                                perror("select");
                                exit(-1);
                        }

                        if (FD_ISSET(fd2, &wfds)) {
                                if (write(fd2, buf, sizeof(buf)) != sizeof(buf)) {
                                        perror("write");
                                        exit(-1);
                                }
                        }
                        read_start = 1;
//                      sleep(1);
                }
        }
        close(fd1);
        close(fd2);

        return 0;
}
                                                     

fwrite.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <fcntl.h>
#include <errno.h>

#define DPRINTF(...)
//#define DPRINTF(...) printf(__VA_ARGS__)

int main(int argc, char *argv[])
{
        int fd1, fd2;
        fd_set rfds, wfds;
        char buf[128];
        int read_start;
        int nloop, i;

        if (argc != 2) {
                printf("usage:\n./fwrite <NLOOP>\n");
                exit(-1);
        }
        nloop = atoi(argv[1]);

        if ((fd1 = open("/tmp/fifo2", O_RDONLY|O_NONBLOCK)) == -1) {
                perror("open /tmp/fifo2");
                exit(-1);
        }
        if ((fd2 = open("/tmp/fifo1", O_WRONLY|O_NONBLOCK)) == -1) {
                perror("open /tmp/fifo1");
                exit(-1);
        }

        read_start=0;
        for (i=0; i<nloop; i++) {
                if (read_start) {
                        buf[0] = '\0';

                        FD_ZERO(&rfds);
                        FD_SET(fd1, &rfds);

                        DPRINTF("selecting read...\n");
                        if (select(fd1+1, &rfds, NULL, NULL, NULL) == -1) {
                                perror("select");
                        }

                        if (FD_ISSET(fd1, &rfds)) {
                                int n;
                                n = read(fd1, buf, sizeof(buf));
                                if (n > 0 ) {
                                        DPRINTF("%d = read [%s]\n", n, buf);
                                } else {
                                        perror("read");
                                        exit(-1);
                                }
                        }
                        read_start = 0;
                } else {
                        memset(buf, 'w', sizeof(buf));
                        buf[sizeof(buf)-1] = '\0';

                        FD_ZERO(&wfds);
                        FD_SET(fd2, &wfds);
                        DPRINTF("selecting write...\n");
                        if (select(fd2+1, NULL, &wfds, NULL, NULL) == -1) {
                                perror("select");
                                exit(-1);
                        }

                        if (FD_ISSET(fd2, &wfds)) {
                                if (write(fd2, buf, sizeof(buf)) != sizeof(buf)) {
                                        perror("write");
                                        exit(-1);
                                }
                        }
                        read_start = 1;
//                      sleep(1);
                }
        }
        printf("write loop= %d\n", i/2);
        close(fd1);
        close(fd2);

        return 0;
}

デバイスドライバを用いたプロセス間通信

だいぶ前から、こんなことに興味があった。

  • プロセス間通信って色々あるけど、せいぜい100バイトぐらいのメッセージをやり取りするのに一番軽いのって何?
  • プロセス間通信のログ取得も簡単にON/OFFしたい
    デバッグ/評価/市場トラブル時にアプリケーションに手を入れることなくプロセス間通信のログが取れるのは非常にありがたい。
  • シェルスクリプトからも各プロセスに指示が出せるとうれしい

今から、2年近くも前だろうか、以下の記事を読ませて頂いた。
http://cheesy.dip.jp/tutorialog/archives/6


これを参考に、以下のようなプロセス間通信を実装した。

  • 共有メモリを使うのではなく、メッセージデータをデバイスドライバにread(2)/write(2)する
    上記したような用途(100バイト程度のメッセージ)ならデバイスドライバでメッセージをコピーしてもあまりスループットに影響しないのではないか?と予想。
    プロセス間通信のログを取るために、read/writeする、という意味もある。
  • /procfs を使って、プロセス間通信のログを簡単に保存できるようにする。


(実行に移すのにえらく時間がかかったなぁ・・・)。

以下、サンプルとして、
2プロセス間で128バイトのメッセージを送受信を繰り返すプログラム書く。
まずは、上記した記事のプログラムを参考に共有メモリ方式でのプログラム、
次に、共有メモリ方式ではなく、メッセージをデバイスドライバにread/writeするプログラム、
を準備し、結果の比較をする。

  1. デバイスドライバ+共有メモリを用いたプログラム

http://cheesy.dip.jp/tutorialog/archives/6 をベースに、連続でメッセージの送受信するように修正したプログラム。変更したのは、ワークをプロセス数分用意して、read(2)/write(2)時に相手のキューを検索して、wake_up_interruptible_sync()を呼ぶようにした点。
wake_up_interruptible_sync()を呼ぶようにした理由は、こっちの方が高速に動作したから。後々、他のプロセス間通信との速度比較もしてみたいため、wake_up_interruptible_sync()を使っていく。

まずは、改造したデバイスドライバ(ipc_shmem.c)

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/poll.h>

#define DEV_NAME "ipc_shmem" // デバイス名称
#define IPC_MAJOR 100        // メジャー番号

struct IPC_DEVICE {
        int use;
        int is_readable;
        wait_queue_head_t write_q;  // write用のキュー
        wait_queue_head_t read_q;   // read用のキュー
};

static struct IPC_DEVICE *ipc_devs; // デバイスのワーク
static int ipc_devs_nr = 2;         // 用意するデバイス数
static struct semaphore ipc_sem;    // ipc_devs 操作用のセマフォ

static int
ipc_open(struct inode* inode, struct file* filp)
{
        struct IPC_DEVICE *dev=NULL;
        int i;

        // 空いているデバイスを見つける
        for(i=0; i<ipc_devs_nr; i++) {
                dev = ipc_devs + i;
                if (!dev->use)
                        break;   // 空きデバイスが見つかった
        }
        if (i == ipc_devs_nr) {  // 全てのデバイスが使用中
                return -EBUSY;
        }

        if (down_interruptible(&ipc_sem)) {
                printk(KERN_INFO "down_interruptible for read failed\n");
                return -ERESTARTSYS;
        }
        dev->use = 1;
        dev->is_readable = 0;
        up(&ipc_sem);

        // ワークを保存しておく
        filp->private_data = (void *)dev;

        return 0;
}

static int
ipc_close(struct inode* inode, struct file* filp)
{
        struct IPC_DEVICE *dev = (struct IPC_DEVICE *)filp->private_data;

        if (down_interruptible(&ipc_sem)) {
                printk(KERN_INFO "down_interruptible for read failed\n");
                return -ERESTARTSYS;
        }
        dev->use = 0;
        up(&ipc_sem);

        return 0;
}

static ssize_t
ipc_read(struct file* filp, char* buf, size_t count, loff_t* pos)
{
        struct IPC_DEVICE *dev = (struct IPC_DEVICE *)filp->private_data;
        struct IPC_DEVICE *dst;
        int i;

        if (down_interruptible(&ipc_sem)) {
                printk(KERN_INFO "down_interruptible for read failed\n");
                return -ERESTARTSYS;
        }
        dev->is_readable = 0;
        for(i=0; i<ipc_devs_nr; i++) {
                dst = ipc_devs + i;

                // 自分じゃないデバイス=書き込みしてきたデバイスを探す
                if (dst != dev && dst->use) {
                        // wake_up 関数中で相手プロセスを起こさないよう、_sync を使う
                        wake_up_interruptible_sync(&dst->write_q);
                        break;
                }
        }
        up(&ipc_sem);

        return 0;
}

static ssize_t
ipc_write(struct file* filp, const char* buf, size_t count, loff_t* pos)
{
        struct IPC_DEVICE *dev = (struct IPC_DEVICE *)filp->private_data;
        struct IPC_DEVICE *dst;
        int i;

        if (down_interruptible(&ipc_sem)) {
                printk(KERN_INFO "down_interruptible for write failed\n");
                return -ERESTARTSYS;
        }
        for(i=0; i<ipc_devs_nr; i++) {
                dst = ipc_devs + i;

                // 自分じゃないデバイス=書き込み先デバイスを探す
                if (dst != dev && dst->use) {
                        dst->is_readable = 1;

                        // wake_up 関数中で相手プロセスを起こさないよう、_sync を使う
                        wake_up_interruptible_sync(&dst->read_q);
                        break;
                }
        }
        up(&ipc_sem);

        return 0;
}

static unsigned int
ipc_poll(struct file* filp, poll_table* wait)
{
        struct IPC_DEVICE *dev = (struct IPC_DEVICE *)filp->private_data;
        unsigned int retmask = 0;

        poll_wait(filp, &dev->read_q,  wait);
        poll_wait(filp, &dev->write_q, wait);

        if (dev->is_readable) {
                retmask |= (POLLIN | POLLRDNORM);
        }
        if (!dev->is_readable) {
                retmask |= (POLLOUT | POLLWRNORM);
        }

        return retmask;
}

static struct file_operations ipc_fops =
{
        .owner   = THIS_MODULE,
        .read    = ipc_read,
        .write   = ipc_write,
        .poll    = ipc_poll,
        .open    = ipc_open,
        .release = ipc_close,
};

int
init_module(void)
{
        struct IPC_DEVICE *dev;
        int i;

        if (register_chrdev(IPC_MAJOR, DEV_NAME, &ipc_fops)) {
                printk(KERN_INFO "register_chrdev failed\n");
                return -EBUSY;
        }

        // 全デバイス分のワークエリアを確保
        ipc_devs = (struct IPC_DEVICE *)kmalloc((sizeof(*dev) * ipc_devs_nr), GFP_KERNEL);
        if (!ipc_devs) {
                return -ENOMEM;
        }

        for(i=0; i<ipc_devs_nr; i++) {
                dev = ipc_devs + i;
                dev->use = 0;
                dev->is_readable = 0;
                init_waitqueue_head(&dev->write_q);
                init_waitqueue_head(&dev->read_q);
                printk(KERN_INFO "init %d\n", i);
        }
        sema_init(&ipc_sem, 1);

        return 0;
}

void
cleanup_module(void)
{
        if (unregister_chrdev(IPC_MAJOR, DEV_NAME) ) {
                printk(KERN_INFO "unregister_chrdev failed\n");
        }
        kfree(ipc_devs);
}


Makefile はこちら。

obj-m := ipc_shmem.o

all:
        make -C /lib/modules/$(shell uname -r)/build M=`pwd` V=1 modules

clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

まずは、デバイスファイルを作成する。

# /bin/mknod /dev/ipc_shmem c 100 0
# chmod 777 /dev/ipc_shmem

次に、コンパイルして、ipc_shmem.ko をカーネルに組み込む。

# make
... 出力結果は省略 ...
# /sbin/insmod ipc_shmem.ko

次は動作確認用のユーザープロセス(rd.c)。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/select.h>
#include <fcntl.h>

#define DPRINTF(...)
//#define DPRINTF(...) printf(__VA_ARGS__)

#define BUFSIZE 1024

int main(int argc, char *argv[])
{
        int pagesize, mapsize;
        int fd, dev;
        char *map;
        fd_set rfds, wfds;
        char buf[128];
        int read_start;

        pagesize = sysconf(_SC_PAGE_SIZE);
        mapsize = ((BUFSIZE-1)/pagesize+1) * pagesize;

        if((fd = open("./map.shm", O_RDWR|O_CREAT, 0666)) == -1) {
                perror("open map");
                exit(-1);
        }
        if ((dev = open("/dev/ipc_shmem", O_RDWR)) == -1) {
                perror("open ipc_shmem");
                exit(-1);
        }
        if (ftruncate(fd, mapsize) == -1) {
                perror("ftruncate");
                exit(-1);
        }
        map = (char *) mmap(0, mapsize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
       if (map == MAP_FAILED) {
                perror("mmap");
                return -1;
        }

        read_start=1;   // メッセージのreadから開始
        while (1) {
                if (read_start) {
                        FD_ZERO(&rfds);
                        FD_SET(dev, &rfds);

                        DPRINTF("selecting read...\n");
                        if (select(dev+1, &rfds, NULL, NULL, NULL) == -1) {
                                perror("select");
                        }

                        if (FD_ISSET(dev, &rfds)) {
                                DPRINTF("read [%s]\n", map);  // 受信メッセージの表示
                                read(dev, buf, sizeof(buf));
                        }
                        read_start = 0;
                } else {
                        memset(buf, 'r', sizeof(buf));   // 送信メッセージの作成
                        buf[sizeof(buf)-1] = '\0';

                        FD_ZERO(&wfds);
                        FD_SET(dev, &wfds);
                        DPRINTF("selecting write...\n");
                        if (select(dev+1, NULL, &wfds, NULL, NULL) == -1) {
                                perror("select");
                                exit(-1);
                        }

                        if (FD_ISSET(dev, &wfds)) {
                                strncpy(map, buf, sizeof(buf));
                                if (write(dev, NULL, 1) == -1) {
                                        perror("write");
                                        exit(-1);
                                }
                        }
                       read_start = 1;
//                      sleep(1);
                }
        }

        munmap(map, mapsize);
        close(fd);
        close(dev);

        return 0;
}

次は動作確認用のユーザープロセス(wr.c)。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/select.h>
#include <fcntl.h>

#define DPRINTF(...)
//#define DPRINTF(...) printf(__VA_ARGS__)

#define BUFSIZE 1024

int main(int argc, char *argv[])
{
        int pagesize, mapsize;
        int fd, dev;
        char *map;
        fd_set rfds, wfds;
        char buf[128];
        int read_start, i, nloop;

        if (argc != 2) {
                printf("usage:\n./wr <NLOOP>\n");
                exit(-1);
        }
        nloop = atoi(argv[1]);

        pagesize = sysconf(_SC_PAGE_SIZE);
        mapsize = ((BUFSIZE-1)/pagesize+1) * pagesize;

        if((fd = open("./map.shm", O_RDWR|O_CREAT, 0666)) == -1) {
                perror("open map");
                exit(-1);
        }
        if ((dev = open("/dev/ipc_shmem", O_RDWR)) == -1) {
                perror("open ipc_shmem");
                exit(-1);
       }
        if (ftruncate(fd, mapsize) == -1) {
                perror("ftruncate");
                exit(-1);
        }
        map = (char *) mmap(0, mapsize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        if (map == MAP_FAILED) {
                perror("mmap");
                exit(-1);
        }

        read_start=0;   // メッセージのwriteから開始
        for (i=0; i<nloop; i++) {
                if (read_start) {
                        FD_ZERO(&rfds);
                        FD_SET(dev, &rfds);

                        DPRINTF("selecting read...\n");
                        if (select(dev+1, &rfds, NULL, NULL, NULL) == -1) {
                                perror("select");
                        }

                        if (FD_ISSET(dev, &rfds)) {
                                DPRINTF("read [%s]\n", map);  // 受信メッセージの表示
                                read(dev, buf, sizeof(buf));  // メッセージ領域を解放
                        }
                        read_start = 0;
                } else {
                        memset(buf, 'w', sizeof(buf));  // 送信メッセージの作成
                        buf[sizeof(buf)-1] = '\0';

                        FD_ZERO(&wfds);
                        FD_SET(dev, &wfds);
                        DPRINTF("selecting write...\n");
                        if (select(dev+1, NULL, &wfds, NULL, NULL) == -1) {
                                perror("select");
                                exit(-1);
                        }

                        if (FD_ISSET(dev, &wfds)) {
                               strncpy(map, buf, sizeof(buf));
                                if (write(dev, NULL, 1) == -1) {  // メッセージの送信
                                        perror("write");
                                        exit(-1);
                                }
                        }
                        read_start = 1;
//                      sleep(1);
                }
        }
        printf("write loop = %d\n", i/2);
        munmap(map, mapsize);
        close(fd);
        close(dev);

        return 0;
}
all:
        gcc -g -o wr wr.c
        gcc -g -o rd rd.c
clean:
        rm -f *.o wr rd

早速実行して、プロセス間通信を100000回ぐらいやった時の時間を測定する。
測定PCは、VMWarePlayer上のFedoraCore4(CPU:Pentium4 メモリ割当:256MB)、ホストOSは、WinXP HomeEdition メモリ1GB。
結果をみても、ちょっとした組込み機器ぐらいの能力しかないなぁ。そのうち、Linuxのネイティブ機で測定しなおそう。

$ make
gcc -g -o wr wr.c
gcc -g -o rd rd.c
$ ./rd&
$ time ./wr 100000
write loop = 50000

real    0m5.535s
user    0m0.008s
sys     0m2.784s
  1. デバイスドライバにRead/Writeするプログラム

次に、共有メモリではなく、デバイスドライバに対してメッセージをread(2)、write(2)することでプロセス間通信するプログラムを書く。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/poll.h>

#define DPRINTK(...)
//#define DPRINTK(...) printk(__VA_ARGS__)

#define DEV_NAME "ipc"              // デバイス名称
#define PROC_NAME "driver/ipc"      // procfs 上の名称。/proc/driver/ipc となる。
#define IPC_MAJOR 101               // メジャー番号、ipc_memより1つずらす

struct IPC_DEVICE {
        int use;                    // open 中は、use=1
        int is_readable;
        wait_queue_head_t write_q;  // write用のキュー
        wait_queue_head_t read_q;   // read用のキュー
        char msg[128];              // read待ちのメッセージ
};

static struct IPC_DEVICE *ipc_devs; // デバイスのワーク
static int ipc_devs_nr = 2;         // 用意するデバイス数
static struct semaphore ipc_sem;    // ipc_devs 操作用のセマフォ
static char proc_data[32];          // procfs で書き込まれたデータ
static int log = 0;                 // ログ取得するかどうかのフラグ

static int
ipc_open(struct inode* inode, struct file* filp)
{
        struct IPC_DEVICE *dev=NULL;
        int i;

        // 空いているデバイスを探す
        for(i=0; i<ipc_devs_nr; i++) {
                dev = ipc_devs + i;
                if (!dev->use)
                        break;   // 空きデバイスあり
        }
        if (i == ipc_devs_nr) {  // 空きデバイスなし
                return -EBUSY;
        }

        if (down_interruptible(&ipc_sem)) {
                printk(KERN_INFO "down_interruptible for read failed\n");
                return -ERESTARTSYS;
        }
        dev->use = 1;
        dev->is_readable = 0;
        up(&ipc_sem);

        // ワークを保存
        filp->private_data = (void *)dev;

        DPRINTK(KERN_INFO "%s no=%d\n", __func__, i);

        return 0;
}

static int
ipc_close(struct inode* inode, struct file* filp)
{
        struct IPC_DEVICE *dev = (struct IPC_DEVICE *)filp->private_data;

        if (down_interruptible(&ipc_sem)) {
                printk(KERN_INFO "down_interruptible for read failed\n");
                return -ERESTARTSYS;
        }
        dev->use = 0;
        up(&ipc_sem);

        DPRINTK(KERN_INFO "%s \n", __func__);

        return 0;
}

static ssize_t
ipc_read(struct file* filp, char* buf, size_t count, loff_t* pos)
{
        struct IPC_DEVICE *dev = (struct IPC_DEVICE *)filp->private_data;
        struct IPC_DEVICE *dst;
        int i;

        if (down_interruptible(&ipc_sem)) {
                printk(KERN_INFO "down_interruptible for read failed\n");
                return -ERESTARTSYS;
        }

        if (count < sizeof(dev->msg) ||
            copy_to_user(buf, dev->msg, sizeof(dev->msg))) {
                up(&ipc_sem);
                return -EFAULT;
        }
        dev->is_readable = 0;
        for(i=0; i<ipc_devs_nr; i++) {
                dst = ipc_devs + i;
                // 自分じゃないデバイス=書き込みしてきたデバイスを探す
                if (dst != dev && dst->use) {
                        // wake_up 関数中で相手プロセスを起こさないよう、_sync を使う
                        wake_up_interruptible_sync(&dst->write_q);
                        DPRINTK(KERN_INFO "%s wake_up %d\n", __func__, i);
                        break;
                }
        }

        up(&ipc_sem);

        if(log) {
                // ログ取得処理。ここでは、ひとまずsyslogに出力する。
                // 本来なら、他のログ取得専用のプロセスでメッセージを取得した方がいい。
                printk(KERN_INFO "%s %s\n", __func__, dev->msg);
        }

        return sizeof(dev->msg);
}

static ssize_t
ipc_write(struct file* filp, const char* buf, size_t count, loff_t* pos)
{
        struct IPC_DEVICE *dev = (struct IPC_DEVICE *)filp->private_data;
        struct IPC_DEVICE *dst = NULL;
        int i;

        if (down_interruptible(&ipc_sem)) {
                printk(KERN_INFO "down_interruptible for write failed\n");
                return -ERESTARTSYS;
        }

        for(i=0; i<ipc_devs_nr; i++) {
                dst = ipc_devs + i;
                // 自分じゃないデバイス=書き込み先バイスを探す
                if (dst != dev && dst->use) {
                        if (!dst->is_readable) {
                                if(count > sizeof(dst->msg))
                                        count = sizeof(dst->msg);
                                if(copy_from_user(dst->msg, buf, count)) {
                                        up(&ipc_sem);
                                        return -EFAULT;
                                }
                                dst->is_readable = 1;
                                // wake_up 関数中で相手プロセスを起こさないよう、_sync を使う
                                wake_up_interruptible_sync(&dst->read_q);
                                DPRINTK(KERN_INFO "%s wake_up %d\n", __func__, i);
                        }
                        break;
                }
        }

        up(&ipc_sem);

        if(log) {
                // ログ取得処理。ここでは、ひとまずsyslogに出力する。
                // 本来なら、他のログ取得専用のプロセスでメッセージを取得した方がいい。
                printk(KERN_INFO "%s %s\n", __func__, dst->msg);
        }

        return count;
}

static unsigned int
ipc_poll(struct file* filp, poll_table* wait)
{
        struct IPC_DEVICE *dev = (struct IPC_DEVICE *)filp->private_data;
        unsigned int retmask = 0;

        poll_wait(filp, &dev->read_q,  wait);
        poll_wait(filp, &dev->write_q, wait);

        if (dev->is_readable) {
                retmask |= (POLLIN | POLLRDNORM);
        }
        if (!dev->is_readable) {
                retmask |= (POLLOUT | POLLWRNORM);
        }
        DPRINTK(KERN_INFO "%s %x\n", __func__, retmask);

        return retmask;
}

static struct file_operations ipc_fops =
{
        .owner   = THIS_MODULE,
        .read    = ipc_read,
        .write   = ipc_write,
        .poll    = ipc_poll,
        .open    = ipc_open,
        .release = ipc_close,
};

// procfs の書き込み処理。 echo -n "XXX" > /proc/driver/ipc というのを想定。
static int
proc_write( struct file *filp, const char *buf, unsigned long len, void *data )
{
        if (len >= (sizeof(proc_data)-1)) {
                printk(KERN_INFO "%s length is over\n", __func__);
                return -ENOSPC;
        }

        // 書き込みデータの保存
        if (copy_from_user(proc_data, buf, len)) {
                return -EFAULT;
        }
        proc_data[len] = '\0';

        if (!strcmp(proc_data, "on")) {
                log = 1;   // ログ取得開始
        } else {
                log = 0;   // ログ取得停止
        }

        DPRINTK(KERN_INFO "%s =%s(%d) log=%d\n", __func__, proc_data, (int)len, log);
        return len;
}

// procfs の読み出し処理。 cat /proc/driver/ipc というのを想定。
// 今回のプログラムでは必須ではないが、書き込んだ文字列を読み出す。
static int
proc_read( char *page, char **start, off_t offset, int count, int *eof, void *data )
{
        int ret;
        ret = snprintf(page, count, "%s", proc_data);
        *eof = 1;
        DPRINTK(KERN_INFO "%s =%s(%d)\n", __func__, proc_data, ret);

        return ret;
}

int
init_module(void)
{
        struct IPC_DEVICE *dev;
        struct proc_dir_entry* entry;
        int i;

        if (register_chrdev(IPC_MAJOR, DEV_NAME, &ipc_fops)) {
                printk(KERN_INFO "register_chrdev failed\n");
                return -EBUSY;
        }
        // 全デバイス分の領域を取得
        ipc_devs = (struct IPC_DEVICE *)kmalloc((sizeof(*dev) * ipc_devs_nr), GFP_KERNEL);
        if (!ipc_devs) {
                return -ENOMEM;
        }

        for(i=0; i<ipc_devs_nr; i++) {
                dev = ipc_devs + i;
                dev->use = 0;
                dev->is_readable = 0;
                init_waitqueue_head(&dev->write_q);
                init_waitqueue_head(&dev->read_q);
                memset(dev->msg, 0, sizeof(dev->msg));
                printk(KERN_INFO "init %d\n", i);
        }
        sema_init(&ipc_sem, 1);

        // procfs用のエントリを作成
        entry = create_proc_entry(PROC_NAME, 0666, NULL);
        if (entry) {
                // write, read 用の関数を登録
                entry->write_proc  = proc_write;
                entry->read_proc  = proc_read;
                entry->owner = THIS_MODULE;
        } else {
                printk(KERN_ERR "create_proc_entry failed\n");
                return -EBUSY;
        }
        DPRINTK(KERN_INFO "ipc driver is loaded\n");

        return 0;
}

void
cleanup_module(void)
{
        if (unregister_chrdev(IPC_MAJOR, DEV_NAME) ) {
                printk(KERN_INFO "unregister_chrdev failed\n");
        }
        kfree(ipc_devs);

        remove_proc_entry(PROC_NAME, NULL);

        DPRINTK(KERN_INFO "ipc driver is unloaded\n");
}

オブジェクト名しか変わらないが、Makefile はこちら。

obj-m := ipc.o

all:
        make -C /lib/modules/$(shell uname -r)/build M=`pwd` V=1 modules

clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

同様に、デバイスファイルを作成する。

# /bin/mknod /dev/ipc c 101 0
# chmod 777 /dev/ipc

同様に、コンパイルして、ipc.ko をカーネルに組み込む。

# make
... 出力結果は省略 ...
# /sbin/insmod ipc.ko

次は動作確認用のユーザープロセス(rd.c)。
共有メモリの操作がなくなり、メッセージ送信はwrite(2)、メッセージ受信はread(2) と、より直感的なコードになった。POSIXのメッセージキューとよく似たプログラムだなぁ。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/select.h>
#include <fcntl.h>
#include <errno.h>

#define DPRINTF(...)
//#define DPRINTF(...) printf(__VA_ARGS__)

int main(int argc, char *argv[])
{
        int dev;
        fd_set rfds, wfds;
        char buf[128];
        int read_start;

        if ((dev = open("/dev/ipc", O_RDWR)) == -1) {
                perror("open ipc");
                exit(-1);
        }

        read_start=1;   // メッセージ受信から開始
        while (1) {
                if (read_start) {
                        buf[0] = '\0';

                        FD_ZERO(&rfds);
                        FD_SET(dev, &rfds);

                        DPRINTF("selecting read...\n");
                        if (select(dev+1, &rfds, NULL, NULL, NULL) == -1) {
                                perror("select");
                        }

                        if (FD_ISSET(dev, &rfds)) {
                               int n;
                                n = read(dev, buf, sizeof(buf));
                                if (n > 0 ) {
                                        // 受信メッセージの表示
                                        DPRINTF("%d = read [%s]\n", n, buf);
                                } else {
                                        perror("read");
                                        exit(-1);
                                }
                        }
                        read_start = 0;
                } else {
                        memset(buf, 'r', sizeof(buf));  // 送信メッセージの作成
                        buf[sizeof(buf)-1] = '\0';

                        FD_ZERO(&wfds);
                        FD_SET(dev, &wfds);
                        DPRINTF("selecting write...\n");
                        // 今回、writeのselectはなくてもよいが、共有メモリ方式と
                        // 処理内容を合わせるために使用
                        if (select(dev+1, NULL, &wfds, NULL, NULL) == -1) {
                                perror("select");
                                exit(-1);
                        }

                        if (FD_ISSET(dev, &wfds)) {
                                // メッセージの送信
                                if (write(dev, buf, sizeof(buf)) != sizeof(buf)) {
                                        perror("write");
                                        exit(-1);
                                }
                        }
                        read_start = 1;
//                      sleep(1);
                }
        }
        close(dev);

        return 0;
}


次は動作確認用のユーザープロセス(wr.c)。

include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/select.h>
#include <fcntl.h>
#include <errno.h>

#define DPRINTF(...)
//#define DPRINTF(...) printf(__VA_ARGS__)

int main(int argc, char *argv[])
{
        int dev;
        fd_set rfds, wfds;
        char buf[128];
        int read_start, i, nloop;

        if (argc != 2) {
                printf("usage:\n./wr <NLOOP>\n");
                exit(-1);
        }
        nloop = atoi(argv[1]);

        if ((dev = open("/dev/ipc", O_RDWR)) == -1) {
                perror("open ipc");
                exit(-1);
        }

        read_start=0;  // メッセージの送信から開始
        for (i=0; i<nloop; i++) {
                if (read_start) {
                        buf[0] = '\0';

                        FD_ZERO(&rfds);
                        FD_SET(dev, &rfds);

                        DPRINTF("selecting read...\n");
                        if (select(dev+1, &rfds, NULL, NULL, NULL) == -1) {
                                perror("select");
                        }

                        if (FD_ISSET(dev, &rfds)) {
                                int n;
                                n = read(dev, buf, sizeof(buf));
                                if (n > 0 ) {
                                        // 受信メッセージの表示
                                        DPRINTF("%d = read [%s]\n", n, buf);
                                } else {
                                        perror("read");
                                        exit(-1);
                                }
                        }
                        read_start = 0;
                } else {
                        memset(buf, 'w', sizeof(buf));  // 送信メッセージの作成
                        buf[sizeof(buf)-1] = '\0';

                        FD_ZERO(&wfds);
                        FD_SET(dev, &wfds);
                        DPRINTF("selecting write...\n");
                        // 今回、writeのselectはなくてもよいが、共有メモリ方式と
                        // 処理内容を合わせるために使用
                         if (select(dev+1, NULL, &wfds, NULL, NULL) == -1) {
                                perror("select");
                                exit(-1);
                        }

                        if (FD_ISSET(dev, &wfds)) {
                                // メッセージの送信
                                if (write(dev, buf, sizeof(buf)) != sizeof(buf)) {
                                        perror("write");
                                        exit(-1);
                                }
                        }
                        read_start = 1;
//                      sleep(1);
                }
        }
        printf("write loop= %d\n", i/2);
        close(dev);

        return 0;
}
all:
        gcc -g -o wr wr.c
        gcc -g -o rd rd.c
clean:
        rm -f *.o wr rd

早速実行して、プロセス間通信を100000回ぐらいやった時の時間を測定する。

$ make
gcc -g -o wr wr.c
gcc -g -o rd rd.c
$ ./rd&
$ time ./wr 100000
write loop= 50000

real    0m5.624s
user    0m0.004s
sys     0m2.792s


共有メモリ方式とメッセージread/write方式で、実行時間は 5.535 秒から 5.624 秒へと101.6%長くなった。この1.6%がメッセージのread/writeにかかるコストということになる。
128バイトのコピーがプロセス間通信の1.6%を占める?
長すぎるような気がしないわけでもないが、そういうことらしい。
近いうちに、Linuxネイティブ環境でも測定してみよう。



また、echoコマンド1つで、syslogにプロセス間通信のログ取得をON/OFFすることができるようになりました。他の文字をechoするまで、ログを取得し続けます。

$ echo -n "on" > /proc/driver/ipc   # ログ取得開始
$ echo -n "off" > /proc/driver/ipc   # ログ取得停止
  1. まとめ

デバイスドライバを用いて比較的高速なプロセス間通信ができました。
また、このデバイスドライバシェルスクリプトをから、プロセス間通信のログ取得を簡単にON/OFFできるので、アプリケーションのデバッグ/評価/市場トラブル(?)等で役に立つのではないか、と思います。

  1. TODO

近日中に以下をやっていきたいと思っています。

カーネル読書会

YLUG カーネル読書会行ってきました、実は今日で3回目、、

今回は Ottawa Linux Symposium の発表者や参加者のお話を聞くことができました。

今回の発表で特に印象的だったのは、
中村さんのselinuxを組み込み対応させていくよ(いるよ)、もう世の中そういう方向だよ、と言う話。
自分も組み込みlinux技術者の端くれだが、selinuxとかを載せて行こう、とまで考えたことはなかった。
TOMOYO Linuxの原田さんもおっしゃってたが、組み込み業界ではセキュリティは大切という認識はあるが
対応は二の次になっている(あれ?ちょっとニュアンスちがう?)
まさにその通りで、実際、
自分達のハードウェアにlinuxをポーティングする、
ドライバとアプリケーションを開発する(ドライバは scratchから書く場合が多い)、
という所から始まり、
求められる性能を出す(性能が出るような設計をする、がもちろん前提)、
長時間安定して動作させる、
というところで一杯一杯になる(私の場合)

要求仕様面からも、アクセス制限ぐらいあれば上等、という程度だったり、
開発者も本格的なサーバー管理を経験したことがある人が少ない、というのも理由にあるかもしれない。


というところに、組み込み機器のセキュリティ動向の話が聞けたので、私の中で多少なりとも意識改革ができた。

まずはSELinuxやTOMOYOLinuxなど、自分の環境で使ってみて、製品実装の可能性を探ってみよう。
性能面への影響も心配だしね!


それにしても、まっちゃだいふくさん、パワーあったなー。
私はセキュリティに関して素人ですが、同じ世代だし、親近感が沸きましたw

samba memo

samba で他の人が作成したファイルを変更したい。削除したい。
ということで、ユーザグループとsambaメモ。


debian では、ユーザ名と所属グループは同じ。
なんで?

Googleさまのお告げだと、以下のような感じらしい。
[debian-users:03313] Re: why groupname = username?


了解デス、
でも、自分今までこんな風にちゃんとグループ設定ってしたことない。
大人数で使うlinuxを設定したことないし。

どうせ今回も玄箱を使うのは家族の数人だし、
その間でこのファイルだけは他の人に見せたくない、なんてことは言わない(言わせない)
ということで、


debian な方々には申し訳ないが、今回(だけ、多分)は

全員同じユーザグループで、

ファイル属性は 664

となるように設定してみた。だってもう眠いし。


まず、グループを追加(わかりやすいようにGIDは1000)
# groupadd -g 1000 my_group

useradd 時にグループ指定するときは、
# useradd -g my_group newuser


次に各ユーザ(ここでは、user1, user2)のグループ変更
# usermod -g my_group user1
# id -a user1
で確認

# usermod -g my_group user2
# id -a user2
で確認


次に各ユーザの生成ファイルのデフォルトの属性を 664 に。
# echo "umask 002" >> ~/.bashrc


次に samba で作成するファイルのデフォルトの属性を 664 に。

# cat /etc/samba/smb.conf      <共有設定部分だけ、抜粋>
[share]
comment = KURO-BOX Share Folder
path = /mnt/share
valid users = user1, user2
read only = No
create mask = 0664



次回は、debian の流儀に従ってやってみよう。