shimada-kの日記

ソフトウェア・エンジニアのブログです

Interface Builderで置いたBarButtonItemにアイコン画像を設定する

UIBarButtonItemのアイコンを独自のものに差し替えたい場合の手段です。若干はまったのでメモ書き。下の画像のように2つのBarButtonItemが配置されているとします。

f:id:shimada-k:20140308232251p:plain

facebookのいいね!の画像をアイコンとして設定することとします。

like40x36.png
f:id:shimada-k:20140308232728p:plain

コードから指定する場合
    UIImage *image = [UIImage imageNamed:@"like40x36.png"];
    [_item1 setImage:image];

僕はsetImageではなくsetBackgroundImageを使用してたためBarButtonItem.titleが表示されてしまい途方にくれていました。setBackgroundImageではなくsetImageを使用します。

Interface Builderから指定する場合

対象のUIBarButtonItemオブジェクトを選択した状態でimageを指定します。画像ファイルがプロジェクトに正しく追加されていれば右側の矢印ボタンから選択できます。ここではitem2の方の画像をいいね画像に差し替えます。

f:id:shimada-k:20140308232302p:plain

ここまでの状態でInterface Builder上では下のような見え方になります。

f:id:shimada-k:20140308232500p:plain

実行すると両方いいね!の画像にアイコン画像が変更されているはずです。

f:id:shimada-k:20140308234004p:plain

コードからアイコン画像を指定する場合はBarButtonItemをアウトレット接続しないといけないのでInterface Builderから指定する方法の方がスマートだと思います。

ソース全文

//
//  BBIViewController.m
//  barButtonIcon
//
//  Created by 島田克弥 on 2014/03/08.
//  Copyright (c) 2014年 shimada-k. All rights reserved.
//

#import "BBIViewController.h"

@interface BBIViewController ()
@property (weak, nonatomic) IBOutlet UIBarButtonItem *item1;
- (IBAction)tabItem1:(UIBarButtonItem *)sender;
- (IBAction)tabItem2:(UIBarButtonItem *)sender;

@end

@implementation BBIViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
    UIImage *image = [UIImage imageNamed:@"like40x36.png"];
    [_item1 setImage:image];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (IBAction)tabItem1:(UIBarButtonItem *)sender {
    NSLog(@"item1が押されたよ");
}

- (IBAction)tabItem2:(UIBarButtonItem *)sender {
    NSLog(@"item2が押されたよ");
}
@end

CollectionViewとWebViewで画像ビューワを作ってみる

せっかくiOSの開発環境が手元にあるので試しにアプリを作ってみました。
webの画像をCollectionViewで表示する画像ビューワです。

Googleのロゴ画像を出しています。

f:id:shimada-k:20140215232209p:plain

WebViewでロゴ画像のURLを指定してCollectionViewで並べてるだけです。

まずCollectionViewを追加します。ControllerではなくViewのほう。

f:id:shimada-k:20140215232521p:plain

次にCellの中にWebViewを追加します。

f:id:shimada-k:20140215232552p:plain

親子関係はこんな感じ。

f:id:shimada-k:20140215232612p:plain

CollectionViewのセルにIDを振っておきます。ソースからアクセスするためです。

WebViewにタグを付けておきます。これもソースからアクセスするためです。

f:id:shimada-k:20140215233419p:plain

最後にCollectionViewをソースとアウトレット接続します。mファイルに接続しておきます。

これでIBの操作は終わり。あとはソースを書いていきます。

データソースの指定をやります。viewDidLoadの中。

    [_ViewerContents setDataSource:(id)self];

numberOfSectionsInCollectionViewとnumberOfItemsInSectionメソッドを追加します。セクションは1つだけで今回はセルの数を36とか適当に指定しておきます。

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
    //セクションは1つ
    return 1;
}

// セルの個数を返すメソッド
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
    return 36;
}

cellForItemAtIndexPathを追加します。こいつはセルごとに呼び出されるメソッドです。今回はGoogleのロゴ画像を36個分、同じものを表示するので、indexPath.rowでごにょごにょする必要なし。

ここまでで先ほどのアプリが完成。ただし「Google」のGの上の方しか表示されてないのでIBでWebViewを選択して「Scales Page To Fit」にチェックを入れておきます。これで画像ファイルの大きさがWebViewのサイズにあわせて自動的にスケールしてくれます。

f:id:shimada-k:20140215233622p:plain

これで縦のサイズはWebViewにスケールしてくれました。まあ、Googleは横長なんでしょうがないってことで。

f:id:shimada-k:20140215233704p:plain

ソース全文
IVViewController.m

//
//  IVViewController.m
//  ImageViewer
//
//  Created by 島田克弥 on 2014/02/15.
//  Copyright (c) 2014年 shimada-k. All rights reserved.
//

#import "IVViewController.h"

@interface IVViewController ()
@property (weak, nonatomic) IBOutlet UICollectionView *ViewerContents;

@end

@implementation IVViewController

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
    //セクションは1つ
    return 1;
}

// セルの個数を返すメソッド
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
    return 36;
}

//Method to create cell at index path
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    
    NSLog(@"セル番号だよ index_path.row:%ld", indexPath.row);
    UICollectionViewCell *cell;
    
    cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell1"forIndexPath:indexPath];
    cell.backgroundColor = [UIColor blackColor];
    
    UIWebView *image = (UIWebView *)[cell viewWithTag:1];
    
    NSString *str_url = @"https://www.google.co.jp/images/srpr/logo11w.png";
    NSURL * url = [NSURL URLWithString:str_url];
    NSURLRequest *urlReq = [NSURLRequest requestWithURL:url];
    [image loadRequest:urlReq];
    
    return cell;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
    [_ViewerContents setDataSource:(id)self];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

参考サイト
Collection Viewの使い方

HYPER ORIGINALITY IIにエンジニアとして参加しました

HYPER ORIGINALITY IIへ技術サイドとして参加しました。

作品としてのコンセプトは下記HPをご参照ください
HYPER ORIGINALITY II

製作物としては、サーバサイドはRubyとNode.js、クライアントサイドはJQueryで構築しました。

一言で言うとリアルタイム画像共有サービスです。PCからもスマホからもアクセス可能です。

f:id:shimada-k:20131105021619j:plain

PC用IF R.G.B. Intaractive Image Grid

モバイル用IF HOII WEB UPLOADER

PC用のページを展覧会場(新宿眼科画廊)のスクリーンに表示しています。スマホからアップロードされた画像がスクリーン上にリアルタイムに表示され、アップロードされた画像は作品として展示物の一部となります。
新宿眼科画廊

12月11日(水)まで展覧会は開催しておりますので、興味がある方はぜひいらしてください。

Titanium Mobileでscanditモジュールを使う

Titanium MobileでQRコードの読み取りをやってみました。

環境
Titanium Studio 2.1
Android 4.0

scanditにサインインして確認メールのURLからダウンロードページに行きます。
そこからzipファイルをダウンロードできます。

Titanium Mobileでモジュールを有効にするときはプロジェクト直下のディレクトリにzipファイルを配置します。

f:id:shimada-k:20130108225525p:plain

この状態でビルドすると自動的にzipファイルが展開されてプロジェクトにモジュールがインストールされます。

あとはscanditのホームページにサンプルのコードが載っているのでプロジェクトのapp.jsにコピーします。

さらにtiapp.xmlにscanditのモジュールを追加して、scanditにサインインした時に付与されるIDをサンプルの「--- ENTER YOUR SCANDIT SDK APP KEY HERE --- SIGN UP AT WWW.SCANDIT.COM」って書いてあるところを置き換えればよいです。

しかしこのままだとAndroidの場合QRコードのスキャンができません。AndroidではQRコードの読み取りはデフォルトでOFFになっているので、有効にしないといけないのです。

picker.setQrEnabled(true);

これでQRコードのスキャンができるようになりました。scanditはQRコードらしき物体を画面内から検出してくれてファインダーが移動してくれるので、多少角度がついていても読み取ってくれます。

左上の懐中電灯をタップするとライトをつけることもできます。

しかしながら、stopScanning()を使おうとするとエラーがでてしまいます。

謎です。stopScanning()の実行に成功された方教えてください。APIの例のまま使ってるだけなんですけどね。。

一部動かせなかった部分もあるんですが、スキャン自体の動作は担保できました。バーコードスキャンのライブラリはそもそも絶対数が少ないので、ある程度使えてTitanium用にビルドされたものがあるだけでもラッキーでした。

FUSEで遊んでみました

これは「カーネル/VM Advent Calendar2012」17日目の記事です。

今回はFUSEを使って画像の中にデータを記録していくファイルシステムを作ってみました。

基本的にファイルシステムを作るときはシステムコールの実装になります(これはOS直書きのファイルシステムでもFUSEでも一緒です)。

ただ、FUSEの場合はシステムコールを直接フックするわけではなく、カーネルモジュールとglibcを間に挟んでいます。FUSEのすばらしいところはユーザランドでFSが書けるのところで、豊富なライブラリを遠慮なくリンクできるのです。

しかもRubyPHPPythonなど主要なLLでのバインディングも充実しているのでLLでファイルシステムを書くことができるわけです。RubyPHPなどのLLを使う場合はさらに言語のエクステンションを間に挟むことになります。

実装はRuby + FUSE + Imlib2です。ソースはgithubにあります。画像の1ピクセル(R・G・B)3バイトにデータを格納する仕組みです。

スクリプトを実行するとデフォルトの設定なら自動でデスクトップにマウントされます。

環境:Linux 2.6.32-5-amd64 [debian squeeze]

./base.rb "マウントポイントのパス" hoge.yml

マウントされたファイルシステムnautilusで表示させたスクリーンショットです。ディレクトリも作ることができます。

f:id:shimada-k:20121217220814p:plain

FUSEはオンメモリのファイルシステムなのでアンマウントするとデータが消えてしまいます。なのでYAMLにデータを保存して永続性を担保しています。この辺はfusefsをインストールした段階でサンプルも入っているのでそちらを参考にしました。

nautilusで開くと上のスクリーンショットのような状態になります。nautilusには「画像のデータである」と返しているのでnautilusは画像データと判断してサムネイルを表示しています。

ファイルブラウザで見る限り一見画像に見えますが、実体はテキストなので通常のテキストエディタで開くことができますし、コンパイルすることができます。

f:id:shimada-k:20121217220834p:plain

上の画像は以下のソースをコンパイルしたものです。透過的な実装というやつです。

#include<stdio.h>

int main(int argc, char *argv[])
{
	puts("笑い男、それがお前の正体か");
	return 0;
}

今回は縦200×横182ピクセルのサイズの画像を扱っています。

テキストにすると上記のソースは120バイト程の内容なので、そのまま画像に格納すると120 / 3 = 40で、横が182ピクセルなので画像のピクセル上1行の4分の1もいかない程度のデータ量ですがコンパイルすると標準ライブラリをリンクしている都合上、バイナリのサイズが大きくなっている様子が可視化できます。

画像の中にピクセルの中にデータを保存しているのでノイズ(というかデータ)が多く入っています。

ただ現状、拡張子がついているとnautilus拡張子の辞書を見に行って、辞書に登録された拡張子ならファイルの内容まで見に行かずにアイコンを決定してしまうという課題があります。

なのでtest.cというファイルを作ると画像形式の内容をnautilusに返してもC言語のソースと判断されてアイコンが目的の画像にはならないということです。

ファイルの拡張子というものはあったらあったで不便ですが、無いとそれはそれで不便なものですね。

FUSEは色んな言語のバインディングがリリースされててアイディア勝負でサクッとファイルシステムが作れるので面白かったです。

apache-passengerで動かすsinatra環境のよさげな設定

自宅サーバsinatraアプリを動かすことにしたので、既存のslimアプリとバッティングしない設定を試した時のメモです。

/var/www/slimにslimアプリがあるとします。slimアプリもsinatraアプリも

http://www.hoge.jp/slim/
http://www.hoge.jp/sinatra/

のようなパスでアプリを起動させるところを目指します。

環境:Linux debian 3.2.0-3-amd64 [debian wheezy]

apacheはすでにインストール済みとします。

sinatraアプリの開発環境のインストール

#apt-get install ruby rubygems
#gem install sinatra

sinatraapacheで動かすためにpassengerをインストールします。

#gem install passenger

passengerをインストールします。

#passenger-install-apache2-module

足りないものがあればコンソールに表示されるので、追加でインストールします。

apacheの設定

PassengerRootとPassengerRubyの箇所をmods-enabled/passenger.conに設定します。

<IfModule mod_passenger.c>
   RackBaseURI /sinatra
   PassengerRoot /var/lib/gems/1.9.1/gems/passenger-3.0.18
   PassengerRuby /usr/bin/ruby1.9.1
</IfModule>

sinatraアプリに必要なファイルを作成します。アプリのファイルはapp.rbにします。今回アプリのファイルは

#cd /var/www/sinatra
#mkdir public
#mkdir tmp
#touch app.rb

sinatraアプリはプロジェク直下のpublicディレクトリをDocumentRootに設定しないといけないのですが、その設定をしてしまうと他のアプリに影響がでることが多々あるかと思います。

sinatra
├── app.rb
├── config.ru
├── log.txt
├── public
└── tmp

そこでpublicディレクトリのシンボリックリンクを/var/wwwに以下に張って対応します。

#cd /var/www
#ln -s "リンク先" sinatra

これだけじゃ動かないのでRackBaseURLを設定します。私は上述のmods-enabled/passenger.confに設定しました。

さらにsinatraアプリの直下にconfig.ruを配置します。このファイルはpassengerが自動で読んでくれます。

require './app.rb'

run Sinatra::Application

以上でApacheを再起動すれば

http://www.hoge.jp/sinatra
sinatraアプリにアクセスできます。

ちなみに、リンクさえApacheが読めるところに貼ってやれば、実際のソースファイルの場所はどこでもOKです。

clistを使ってカーネル側とユーザ側で通信させてみました

以前の記事で作成したカーネル側の循環リストライブラリ(clist)を使って、実際にカーネル空間とユーザ空間でデータ通信をしてみました。

LinuxカーネルではftraceやSystemTapなどのトレーサが使えます。両者とも非常にすばらしいツールなのですが、リングバッファを使用しているので

  1. ある程度長い期間で一連のデータを発生順序を保証してトレースするには不向き
  2. テキストI/Fなので、二次的な可視化にはパーサが必要

という課題もあるように思います。

ということで、発生順序を保証しつつ長い期間でトレースするためにclistを使い、Excelやその他の可視化ツールで扱いやすいように、バイナリ形式でトレースしたデータを通信する仕組みを考えました。

clistはカーネルツリーに仕込んであって、ビルド済みという前提です(仕込み方はコチラ)。通信にはキャラクタデバイスとシグナルを使うことにしました。

f:id:shimada-k:20111227154822p:image:w360

カーネル側では

  • カーネルイベントが発生する箇所で循環リストへオブジェクトがpushされる
  • ドライバコード内で定期的に循環リストをポーリングして、データが溜まっていたら一時メモリにpullする
  • ユーザアプリにシグナルを投げる

ユーザ側では

  • ドライバ側にioctl(2)して自分のPID、シグナル番号、その他必要な設定情報を登録する
  • カーネルからシグナルが届くとシグナルハンドラ内でドライバからデータをread(2)し、ファイルにwrite(2)する

カーネル側もユーザ側もコード内で冒頭の「イベント固有の設定」の箇所を変更すればそのまま適用可能なコードになっています。

まずはカーネル側のコードです。キャラクタデバイスを作成するのと、システムコールの実装があります。全部だと長いので本質的な部分だけ載せておきます。

環境:Linux debian 3.0.0-amd64 [debian wheezy]

clist_benchmark.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/proc_fs.h>	/* alloc_chrdev_region */
#include <linux/sched.h>

#include <asm/uaccess.h>	/* copy_from_user, copy_to_user */
#include <linux/cdev.h>	/* cdev_init */
#include <linux/ioctl.h>	/* _IO* */
#include <linux/cpumask.h>	/* cpumask_weight() */

#include <linux/clist.h>	/* 自作循環リストライブラリ */

#define MODNAME "clist_benchmark"
#define MINOR_COUNT 1

enum signal_status{
	SIG_READY,
	SIGRESET_REQUEST,
	SIGRESET_ACCEPTED,
	MAX_STATUS
};

/* ユーザからデバイス初期化時に送られるデータ構造 */
struct ioc_submit_spec{
	int pid;
	int signo, flush_period;
	int nr_node, node_nr_composed;
	int dummy;
};

struct signal_spec{	/* ユーザ空間とシグナルで通信するための管理用構造体 */
	enum signal_status sr_status;
	int signo, flush_period;
	struct siginfo info;
	struct task_struct *t;
	struct timer_list flush_timer;
};

static char *log_prefix = "module[clist_benchmark]";

static dev_t dev_id;  /* デバイス番号 */
static struct cdev c_dev; /* キャラクタデバイス用構造体 */

static struct clist_controller *clist_ctl;
static struct signal_spec sigspec;

/* プロトタイプ宣言 */
extern int send_sig_info(int sig, struct siginfo *info, struct task_struct *p);
static int clbench_open(struct inode *inode, struct file *filp);
static int clbench_release(struct inode* inode, struct file* filp);
static ssize_t clbench_read(struct file* filp, char* buf, size_t count, loff_t* offset);
static long clbench_ioctl(struct file *flip, unsigned int cmd, unsigned long arg);

/*
	システムコールを担当する関数たち
*/
static struct file_operations clbench_fops = {
	.owner   = THIS_MODULE,
	.open    = clbench_open,
	.release = clbench_release,
	.read    = clbench_read,
	.write   = NULL,
	.unlocked_ioctl   = clbench_ioctl,	/* kernel 2.6.36以降はunlocked_ioctl */
};


/**********************************************************
*
*	イベント固有の設定
*	扱うイベントに合わせて変更が必要な箇所
*
**********************************************************/

/*
	注意!	・ユーザ空間とやりとりするオブジェクトはパッディングが発生しない構造にすること
		・この構造体のメンバのsizeof()の合計がsizeof(struct object)と一致するようにすること

		取得したいイベントにあわせてこの構造体とclbench_add_object()を作成する
*/
struct object{
	unsigned long i_ino;
	long long ppos;
	long sec, usec;
};

/*
	balance_tasks()@sched.cで呼び出される関数 ロードバランスが行われている箇所で呼び出される
	@p ロードバランスされたtask_structのアドレス
	@src_cpu 最も忙しいCPU番号
	@this_cpu ロードバランス先のCPU番号

	※カーネルイベントが補足される箇所にこの関数を挿入する
*/
void clbench_add_object(unsigned long i_ino, long long ppos)
{
	struct object obj;
	struct timeval t;

	if(sigspec.sr_status != SIG_READY){	/* シグナルを送信できる状態かどうか */
		return;
	}

	do_gettimeofday(&t);

	obj.i_ino = i_ino;
	obj.ppos = ppos;
	obj.sec = (long)t.tv_sec;
	obj.usec = (long)t.tv_usec;

	/* この関数はフック先でしか実行されていないので、エラー処理は行っていない */

	clist_push_one((void *)&obj, clist_ctl);
}
EXPORT_SYMBOL(clbench_add_object);

/**********************************************************
	イベント固有の設定ここまで
**********************************************************/
(以下省略)

次にユーザ側のコードです。キャラクタデバイスへのioctl(2)とシグナルを受ける設定、シグナルハンドラ内でキャラクタデバイスにread(2)/write(2)しています。こちらは比較的短いので、すべてのコードを載せておきます。

clbench_listener.c

#include <stdio.h>
#include <stdlib.h>		/* calloc(3) */
#include <unistd.h>		/* open(2), sleep(3) */
#include <sys/types.h>
#include <signal.h>		/* getpid(2) */

#include <fcntl.h>
#include <sys/ioctl.h>



/**********************************************************
*
*	イベント固有の設定
*	扱うイベントに合わせて変更が必要な箇所
*
**********************************************************/

#define DEVICE_FILE			"/dev/clbench"
#define FLUSH_PERIOD			1500	/* デバイス側でCLISTに対してポーリングする周期(ミリ秒で指定) */
#define CLIST_NR_NODE		10	/* CLISTでのノード数 */
#define CLIST_NODE_NR_COMPOSED	100	/* CLISTで1ノードに含まれるオブジェクト数 */
#define READ_NR_OBJECT		250	/* デバイスファイルに読みにいく際の最大オブジェクト数 */

/*
	注意!	・ユーザ空間とやりとりする構造体はパッディングが発生しない構造にすること
		・この構造体のメンバのsizeof()の合計がsizeof(struct object)と一致するようにすること
		・この構造体の定義はドライバ側のものと同一であること
*/
struct object{
	unsigned long i_ino;
	long long ppos;
	long sec, usec;
};

/**********************************************************
	イベント固有の設定ここまで
**********************************************************/

#define IO_MAGIC				'k'
#define IOC_USEREND_NOTIFY			_IO(IO_MAGIC, 0)	/* ユーザアプリ終了時 */
#define IOC_SIGRESET_REQUEST		_IO(IO_MAGIC, 1)	/* send_sig_argをリセット要求 */
#define IOC_SUBMIT_SPEC			_IOW(IO_MAGIC, 2, struct signal_spec *)	/* ユーザからのパラメータ設定 */

struct ioc_submit_spec{
	int pid;
	int signo, flush_period;
	int nr_node, node_nr_composed;
	int dummy;	/* padding防止のための変数 */
};

int dev, out;	/* ファイルディスクリプタ */
int count;
void *buffer;

/*
	カーネルからのシグナルのハンドラ関数
	@sig シグナルハンドラの仕様
*/
void clbench_handler(int sig)
{
	ssize_t size;

	/* カーネルのメモリを読む */
	size = read(dev, buffer, sizeof(struct object) * READ_NR_OBJECT);
	/* ファイルに書き出す */
	write(out, buffer, (size_t)size);

	printf("read(オブジェクト数): %d\n", (int)(size / sizeof(struct object)));

	count += (int)(size / sizeof(struct object));

	lseek(dev, 0, SEEK_SET);
}

int main(int argc, char *argv[])
{
	int nr_wcurr, signo, nr_picked, grain;
	struct sigaction act;
	struct ioc_submit_spec submit_spec;
	ssize_t size;

	dev = open(DEVICE_FILE, O_RDONLY);
	out = open("./output.clbench", O_CREAT|O_WRONLY|O_TRUNC);

	buffer = (struct object *)calloc(READ_NR_OBJECT, sizeof(struct object));

	/* デバイスの準備 */
	submit_spec.pid = (int)getpid();
	submit_spec.signo = SIGUSR1;
	submit_spec.flush_period = FLUSH_PERIOD;
	submit_spec.nr_node = CLIST_NR_NODE;
	submit_spec.node_nr_composed = CLIST_NODE_NR_COMPOSED;

	ioctl(dev, IOC_SUBMIT_SPEC, &submit_spec);

	/* シグナルハンドリングの準備 */
	act.sa_handler = clbench_handler;
	act.sa_flags = 0;

	sigemptyset(&act.sa_mask);
	sigaction(SIGUSR1, &act, NULL);

	/* SIGTERMをブロックするための設定 */
	sigaddset(&act.sa_mask, SIGTERM);
	sigprocmask(SIG_BLOCK, &act.sa_mask, NULL);

	/* シグナルが届くまでmainスレッドは無限ループ */
	while(1){
		if(sigwait(&act.sa_mask, &signo) == 0){	/* シグナルが受信できたら */
			if(signo == SIGTERM){
				puts("main:SIGTERM recept");
				break;
			}
		}
	}

	/***						***
	***	シグナルを受信するとここに到達する	***
	***						***/

	/* カーネル側に終了通知を送る */
	ioctl(dev, IOC_USEREND_NOTIFY, &nr_wcurr);

	printf("wcurr_len:%d\n", nr_wcurr);

	/* clist_pull_end()でpull残しがないように大きい方でメモリを確保 */
	if(nr_wcurr >= READ_NR_OBJECT){
		/* 一端freeして、再度calloc */
		free(buffer);
		buffer = calloc(nr_wcurr, sizeof(struct object));

		grain = nr_wcurr;
	}
	else{
		/* bufferをそのまま使うのでcalloc無し */
		grain = READ_NR_OBJECT;
	}

	/* grainだけひたすら読んでread(2)が0を返したらbreakする */
	while(1){
		/* カーネルのメモリを読む */
		size = read(dev, buffer, sizeof(struct object) * grain);

		if(size == 0){	/* ここを通るということはclist_benchmark側がSIGRESET_ACCEPTEDになったということ */
			break;
		}

		/* ファイルに書き出す */
		write(out, buffer, (size_t)size);

		printf("read(オブジェクト数): %d\n", (int)(size / sizeof(struct object)));

		count += (int)(size / sizeof(struct object));

		lseek(dev, 0, SEEK_SET);
	}

	putchar('\n');

	/* ベンチマーク結果を出力 */
	puts("------------ベンチマーク結果---------------");
	printf("入出力オブジェクト総数:%d\n", count);
	printf("読み込み粒度(オブジェクト数):%d\n", READ_NR_OBJECT);
	printf("clistのノード数:%d\n", 10);
	printf("clistのノードに含まれるオブジェクト数:%d\n", 100);

	/* リソース解放 */
	free(buffer);

	close(out);
	close(dev);
}

clist_benchmark.cのclbench_add_object()を実装してイベントを取得可能な箇所へフックをかけるのですが、今回はページキャッシュのミスを計測することにしました。フックはdo_generic_file_read()にかけました。ページキャッシュミスについては以前の記事にあります。

mm/filemap.c

■追加ここから■
#ifdef CONFIG_CLBENCH
extern void clbench_add_object(unsigned long i_ino, long long ppos);
#endif
■追加ここまで■
static void do_generic_file_read(struct file *filp, loff_t *ppos,
		read_descriptor_t *desc, read_actor_t actor)
{
	struct address_space *mapping = filp->f_mapping;
	struct inode *inode = mapping->host;
	struct file_ra_state *ra = &filp->f_ra;
	pgoff_t index;
	pgoff_t last_index;
	pgoff_t prev_index;
	unsigned long offset;      /* offset into pagecache page */
	unsigned int prev_offset;
	int error;

	index = *ppos >> PAGE_CACHE_SHIFT;
	prev_index = ra->prev_pos >> PAGE_CACHE_SHIFT;
	prev_offset = ra->prev_pos & (PAGE_CACHE_SIZE-1);
	last_index = (*ppos + desc->count + PAGE_CACHE_SIZE-1) >> PAGE_CACHE_SHIFT;
	offset = *ppos & ~PAGE_CACHE_MASK;

	for (;;) {
		struct page *page;
		pgoff_t end_index;
		loff_t isize;
		unsigned long nr, ret;

		cond_resched();
find_page:
		page = find_get_page(mapping, index);
		if (!page) {
			page_cache_sync_readahead(mapping,
					ra, filp,
					index, last_index - index);
			page = find_get_page(mapping, index);
			if (unlikely(page == NULL)){
■追加ここから■
#ifdef CONFIG_CLBENCH
				clbench_add_object(inode->i_ino, *ppos);
#endif
■追加ここまで■
				goto no_cached_page;
			}
		}

ここまでやってカーネルをビルド&起動します。起動したらmknode(1)でデバイスファイルを作成しclbench_listenerを起動するとイベントの計測が開始されます。

イベント計測を終了したい時にclbench_listenerにSIGTERMシグナルを送ります。そうするとclist内に残っているオブジェクトを読みきって、終了します。左がclbench_listenerの画面で右がカーネルログです。

f:id:shimada-k:20111228163919p:image

clbench_listenerが正常に終了するとオブジェクト列が書き出されたファイルが作成されます。今回の場合output.clbenchという名前です。

イベント計測中に動かすプログラムについてやoutput.clbenchの解析は別エントリで書こうと思います。一応今回はなんとか動くものを作りました、ということで。