研究室でLinuxのソースを触ることになったのだが、そこには生粋のVB厨C++厨であるどようびにとっては信じられない世界が広まっていた!
既に絶滅したかに思われていたさまざまなコードがどようびを襲う!
そして、カーネルいじりでもC++使えたらなぁ~。と思いぐぐってみたら次の記事が引っかかった!
Linux: C++ In The Kernel? | KernelTrap
Linus曰く
The fact is, C++ compilers are not trustworthy. They were even worse in 1992, but some fundamental facts haven't changed:
つまり、例外とnew/deleteを使わなければ良いんじゃね?良いよね!!!!new,deleteも自前で定義すればいいよね!
次のLinuxカーネルのコードを見てくれ!
(include/linux/device.h)
struct device {
struct device *parent;
struct device_private *p;
(略)
void *platform_data; /* Platform specific data, device
(略)
};
任意のデータをvoid*で受けるのはC++erとしてはあんましやりたくない。というか型エラーの
チェックはできるだけ全てコンパイラにやらせたいのがC++erの真理だと思う。(void*)にキャスト
しちゃうと目でチェックするしかない。
で、この場合platform_dataの子クラスのみ格納できるようにしたい。
実際にデバイスドライバ側で件のデータを使うときは、『platform_data ⇒ その子クラス』ダウンキャストになるが、
polymorphic_downcast という神キャストが存在するのでそれを使うべし!
デバッグ時とリリース時で違う動きするなだと!?、両方で使えるテストを書こう!!!
もう継承でやっちゃえよ!直したいー!!
(include/linux/fs.h)
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
(略)
};
↓直したい!!!
class file_operations {
module *owner;
virtual loff_t llseek(file *, loff_t, int) {
....
}
(略)
};
次のコードを見てくれ......
(kernel/sched.c)
static void migrate_nr_uninterruptible(struct rq *rq_src)
{
struct rq *rq_dest = cpu_rq(cpumask_any(cpu_active_mask));
unsigned long flags;local_irq_save(flags);
double_rq_lock(rq_src, rq_dest);
rq_dest->nr_uninterruptible += rq_src->nr_uninterruptible;
rq_src->nr_uninterruptible = 0;
double_rq_unlock(rq_src, rq_dest);
local_irq_restore(flags);
}
次の関数が対応し、割り込み無効化/有効化、ロック/アンロックを行う
local_irq_save/local_irq_restore
double_rq_lock/double_rq_unlock
こういうセットで使う関数は、C++erとしては、RAII化せざるを得ない。
コードのメンテ時に、片方の位置を変えたらもう片方の位置も変える、とかめっちゃめんどくさい
RAIIの続きになるが、件の対応する関数があると、どうしてもgoto文に頼らざるを得なくなる
(kernel/sched.c)
static int init_rootdomain(struct root_domain *rd, bool bootmem)
{
gfp_t gfp = GFP_KERNEL;
memset(rd, 0, sizeof(*rd));
if (bootmem)
gfp = GFP_NOWAIT;if (!alloc_cpumask_var(&rd->span, gfp))
goto out;
if (!alloc_cpumask_var(&rd->online, gfp))
goto free_span;
if (!alloc_cpumask_var(&rd->rto_mask, gfp))
goto free_online;if (cpupri_init(&rd->cpupri, bootmem) != 0)
goto free_rto_mask;
return 0;free_rto_mask:
free_cpumask_var(rd->rto_mask);
free_online:
free_cpumask_var(rd->online);
free_span:
free_cpumask_var(rd->span);
out:
return -ENOMEM;
}
やっぱしC++erとしてはgoto文を使わないで、RAIIやscope_exitで固めた状態で
途中returnだよね!!!goto文に比べて、コードを目で追っかける量が減るよ!
みんな待ってたforeach、GCC4.6で遂に実装!wktk,カーネル内には以下のような
foreachがあったけど、できれば標準のを使いたいよね!
(mm/page_alloc.c)
static void drain_pages(unsigned int cpu)
{
unsigned long flags;
struct zone *zone;for_each_populated_zone(zone) {
struct per_cpu_pageset *pset;
struct per_cpu_pages *pcp;local_irq_save(flags);
pcp = &pset->pcp;
pset = per_cpu_ptr(zone->pageset, cpu);
free_pcppages_bulk(zone, pcp->count, pcp);
pcp->count = 0;
local_irq_restore(flags);
}
}
他にも
for_each_migratetype_order(order, ...)
list_for_each(curr, ...)
for_each_zone_zonelist_nodemask(zone, ...)
とか。
こんなコードがあった
(mm/slab.c)
static __always_inline int index_of(const size_t size)
{
extern void __bad_size(void);if (__builtin_constant_p(size)) {
int i = 0;
if (size <= 32) return i;
i++;
if (size <= 64) return i;
i++;
if (size <= 128) return i;
i++;
(略)
if (size <= 131072) return i;
i++;__bad_size();
} else {__bad_size();
}
return 0;
}
1. 最適化オプションをつけてコンパイル
2. __bad_size関数をわざと定義しないでおく
上記の条件を満たし、かつsizeが定数でないか131072より大きい場合は
__bad_sizeがundefined refereceとなるリンク時エラーが起きる。という関数。
当然のことながら、訓練されたC++erとしては、static_assertを使わざるを得ない
とりあえずは、カーネルモジュールのキャラクタデバイスを作ってみます。
具体的には、
cat /dev/hellocxx
コマンドを打つと、
Hello, C++
と表示させるものを。
とりあえずカーネルモジュールを作る準備をしなければいけませんね!
hellocxx.c がカーネルモジュール向けにコンパイルされるMakefileになります!
KERNEL_DIR = /root/linux
VERBOSE = 0
ARCH = x86EXTRA_CFLAGS := -isystem $(KERNEL_DIR)/include -isystem $(KERNEL_DIR)/arch/$(ARCH)/include
obj-m := hellocxx.o
CFLAGS_hellocxx.o := -x c++ -std=gnu++0x -fno-operator-names -fno-rtti -fpermissive -fno-exceptionsall:
make -C $(KERNEL_DIR) M=$(PWD) V=$(VERBOSE) modules
ダウンロードはこちら>> https://gist.github.com/760992
基本事項はぐぐってください!詳しいサイトがいっぱいあります!
重要なオプション解説
*** -fpermissive
Linuxのヘッダ内に、C++では許されないポインタ同士の変換がいっぱいあるため、
そいつがエラーにならないようにするオプション。正直いきなりくじけそうだッ
*** -fno-operator-names
andとかがC++の予約語とかぶるので、それを抑制
*** -isystem
warningがうざすぎるので、linuxのヘッダのものに関しては全部抑制
*** -fno-exception
例外を使おうとするとC++ランタイムが必要になるっぽくてめんどくさそうなので使わないように
*** -fno-rtti
rttiもめんどくさそうなので切る
*** -x c++
このソースはC++だッ
*** -std=gnu++0x
C++0xに、gnuの拡張を入れたやつだと思う
C言語用に書かれたヘッダファイルをC++で読もうとすると、いろいろ不都合があるよね!
1. 予約語が.....
2. 名前の修飾が.....
ということで、とりあえず以下のようになる
extern "C" {
#define alignof alignof_REPLACED
#define bool bool_REPLACED
#define catch catch_REPLACED
.....#include <linux/kernel.h>
#undef alignof
#undef bool
#undef catch
......}
これは基本だね!
とくに凶悪だったコードはこちら
(include/linux/kernel.h)
/* Force a compilation error if condition is true, but also produce a
result (of value 0 and type size_t), so the expression can be used
e.g. in a structure initializer (or where-ever else comma expressions
aren't permitted). */
#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
#define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); }))
#define BUILD_BUG_ON_ZERO(e) ((size_t)0)
#define BUILD_BUG_ON_NULL(e) ((void*)NULL)
しかし、マクロ置換だけでは対応できない個所が多々あることが解ってしまった。
とくに多かったのがこういうやつ
static inline foo(int ret)
{ return (pte_t) { .pte = ret };
}
中カッコを使って構造体の右辺値を、メンバの値を設定しつつ作る奴。
この書き方はC++でも採用して欲しかったなぁ。
そこでなんだが、どおおおおおしても僕の実力では対応しきれないため、カーネルソースの
static inlineな関数の中身を変えるだけなら、今動いているカーネルを変更しなくてもいいはず!
と踏み、書き換えに走ることにした。だいたいもうすでに#define文でlinuxのヘッダの内容はだいぶかわっちゃってるしね
まぁ、仕方がないと自分を納得させる。
手元のカーネルのバージョンは2.6.32、おおむね以下のような書き換えになりました
- return (pte_t) { .pte = ret };
+ {
+ pte_t r = {0};
+ r.pte = ret;
+ return r;
+ }
まぁ、修正前と後は処理内容的には同じです。
変更内容全体はこちら>> https://gist.github.com/761000
しかし、カーネルのソースいじくった時点でもう負けているのだあおあいaoidsjjkfks('A`)......
** カーネルモジュール書きます!
さっき書いたヘッダファイル、さすがにひどくてあんまり見たくないので stdafx.h (なにかとトラウマなヘッダファイル名) として分離
そして、カーネルモジュール本体を書き始める。キモは次の関数
static ssize_t hellocxx_read(struct file* f, char* buf, size_t count, loff_t* pos)
{
if (*pos != 0) {
return 0;
}
char message[] = "Hello, C++\n";
ssize_t copy_count =
count < sizeof(message) ? count : sizeof(message);
copy_to_user(buf, message, copy_count);
*pos += copy_count;
return copy_count;
}
ユーザーから読み込みのリクエストがあったら、"Hello, C++"という文字列を返す
全体はこちら>> https://gist.github.com/761004
ドライバのメジャー番号は適当に204にしました。
おおっ、予想通り今動いてるカーネルも変更なしで動いた!
platform_device化も行けそうです!ひどいコードばかりだし、カーネルのコード変えちゃったけど、まぁ動いたから良しとしましょう。
ここまで読んでいただき、ありがとうございました!
これらのファイルをまとめたモノはこちら >> http://dl.dropbox.com/u/458517/20101231/hellocxx.zip
トラックバックURL: http://www.lunaport.net/mt/mt-tb.cgi/29
へー。foreachをC/C++がサポートするんだ。自分的にはラベルつきbreakが欲しかったな。あとは多重ポインタであっても機能するアロー演算子(T** pに対してp-->xというようなことをしたい)があればなぁと思ってます。
リンク時static assertとか-cで作られたら最後まで分からない・・・最強魔法リンカスクリプトで回避!(orz
そういえばrange based forはまた議論されてるっぽいのでGCCで実装したからと言って油断できない・・・
あけましておめでとうございます
> hoge
ラベル付きbreakは僕もたまに欲しくなります。結局、goto怖いので終了条件の変数を新設して対応しちゃってるけど。あとは、-->があるんだったらもう『-』が一個増えるごとに逆参照一回増やすとかにしたいw
> Flast
せめてBOOST_STATIC_ASSERTの結果みたいに、変数名を__argument_is_bad_size_or_not_constant みたいにしてくれれば、わかりやすかったんすけどねー
>> range based forはまた議論
マジすか.... まだ当分はboost foreachに頼る日々か..