それが僕には楽しかったんです。

僕と MySQL と時々 MariaDB

全てのプログラマに捧げるScala入門 トレイト

はじめに

Scalaで規模の大きいプログラムを作成する場合にモジュール化するのは非常に重要なことである。
そこでモジュール化のための機能をココでは紹介する。
JavaにおけるInterfaceと同等の機能を持つtraitである。

トレイト

トレイトとは次の様に宣言する。

trait Name{
}

宣言自体はtraitというキーワードを使うだけなので宣言は比較的簡単である。
それでこのトレイトで何が出来るかというとメソッドとフィールドの定義を持つことができる。
ScalaのトレイトはほとんどJavaのInterfaceと同じであるが、決定的に違う点がある。

それはinterfaceの様な機能を持ちながら、メソッドが宣言のみで処理を持たないものだけでなく
実装をもったメソッドも含めることが出来るという点である。

実装をもつメソッドも含めるということはそれだけ機能を抽象化するだけでなく
カプセル化できるという利点がある。

では実際に自身がTwitter4SというScalaで書いたTwitterAPIのラッパーライブラリ内で使用しているトレイトを例に挙げてみよう。

trait StatusBase {

  /**
    * @param text
    * @return response json as Status
    */
  def updateStatus(text: String): Status

}

実際に処理がない宣言のみのメソッドを持っているがこれはエラーにならずトレイトの要素として認められる。
上記のメソッドには実装がないため、このトレイトをミックスインしたクラスでオーバーライドしなければならない。


それではクラスとほとんど変わらないのでは?と思うかも知れないが
クラスともまた違う点がある。それは次の点である。

最初の2つはともかくとして、ミックスインはモジュール化やトレイトを使う上で重要なので説明していく

ミックスイン

クラスにトレイトを実装する場合やトレイト自身に別のトレイトを継承(厳密にはこれもミックスインというが)することをミックスイン合成という。

どのように使うかというと次のように使う。

trait TraitA

class Sample extends TraitA //これはミックスイン
class SubSample extends Sample with TraitA // withキーワードでもミックスインとなる

//traitにtraitをミックスインする場合
trait TraitB extends TraitA
trait TraitC extends TraitA with TraitB

1つめでミックスインするならextends、ふたつ目以降はwithキーワードを使う。
ただしクラスを継承する場合は優先的にextendsを使わないとエラーとなる。

ここまで聞くと、インスタンス生成やコンストラクタを持てないクラスが多重継承できると思うが(クラスは多重継承できない)
実際に多重継承でなくても、実質的に多重継承の様な機能を持っていることになる。


この性質がもたらす問題について別の記事で解説していく。
詳しくはドワンゴScalaドキュメント等を見て欲しい。

おわりに

ここまでで、大規模なコードを書く上で重要なモジュール化の役割を果たすトレイトについて解説した
次は、ラムダや高階関数、カリー化についてまとめる。

全てのプログラマに捧げるScala入門 クラスとオブジェクト、ケースクラス

はじめに

前回の続きで、今回はクラスとオブジェクトについてまとめていく。
その前に、メソッドについて触れていなかったのでそこもまとめる。

メソッド

Scalaにおけるメソッドとは次の形をとる。

(private or protected [this or package_name]) def methodName(arg0: DataType, arg1: DataType, ... argN: DataType): ResultDataType = ???

これだけではわかりにくいのでざっくりと解説すると
アクセス修飾子やthis, package_name等は必要に応じて任意でつける。
defがメソッドを宣言するキーワードとなり、0個以上の引数を持つ。
最後に戻り値のデータ型、つまり結果型を記述し=の後に式を書いていく。
式ということなので{}式を用いて複数の式を入れることも可能。

privateやprotectedをつけない場合はpublicとみなされる点はJavaと異なるので注意が必要である。
[this]とすればそのクラス内だけから、[package_name]とすると同一パッケージ内のみからのアクセスが可能となる。
よく使うのは次のような形となる。

private [this] def getXXX(): Int = 0

クラス

Scalaのもつクラスはコンストラクタの記法を除いてほとんどJavaと同じである。
というのもコンストラクタを特に使用しない場合は次の様に記述するからです。

//これはScalaのクラス
class ClassName{
}

これでコンストラクタを使う場合は次のようになる。

class ClassName(param1: DataType, ... ,paramN: DataType){
}

()で囲んだパラメータリストをクラス名の横に付与するとそれがコンストラクタになる。
例として、コンストラクタの値をフィールドに代入するコードを上げると次のようになる。

class SampleClass(num1: Int, num2: Int){

    val number1: Int = num1
    val number2: Int = num2
}

このようなクラスはJavaインスタンスを生成するようにnewでインスタンスを生成することができる。
コンストラクタに対しての引数の渡し方も同様に次のようにできる。

val inst: SampleClass = new SampleClass(1, 2)

継承

ScalaのクラスはほとんどJavaと同じわけなので継承に関しても次のようになる。

class SubClass extends SuperClass{
}

見かけは同じなのだが少しだけ違いがある。
クラスにおいて継承した既存のメソッドをオーバーライドする場合、Javaでは@Overrideというアノテーションを付与したが
Scalaにおいてはoverrideキーワードになる。

なので、仮にtoStringメソッドをオーバーライドするときは次のようになる。
overrideキーワードをつけるのは継承したメソッドだけでなくオーバーライドする全ての場合に適用される。

override def toString(): String = ???

補助コンストラクタ

ここまでの内容を把握してみて、ひとつ疑問に思うことはないだろうか。
それはコンストラクタのオーバロードをどうするかという問題である。

それに関しては解決策があり、補助コンストラクタを用いるものである
これはJavaでいうコンストラクタのオーバロードと似ているが決定的にことなる部分がある。

まずは補助コンストラクタの使い方から見てみよう。
例はコップ本にある分数を扱うクラスを参考に一部改変し、簡略化したものを使う。

object Sample{
  def main(args: Array[String]):Unit = {

    val sample: Rational = new Rational(1,2)// 1/2

    val ratinal: Rational = new Rational(2)

  }
}

class Rational(val n:Int, val d:Int){

  //コンストラクタから値を受け取り初期化
  val numer: Int = n
  val denom: Int = d

  def this(n:Int) = {
    this(n,1)
    //ここに処理があってもいい
  }

  //toStringメソッドをオーバーライド
  override def toString(): String = numer + "/" + denom

}

このコードにおいて、補助コンストラクタは次の部分である

def this(n:Int) = {
    this(n,1)
    //ここに処理があってもいい
  }

このようにdef thisというメソッドが補助コンストラクタとなる。
しかしこの補助コンストラクタというものを使うときには
thisを使い必ず先頭でそれより前に定義されたコンストラクタを呼び出す必要がある。

オブジェクト

Scalaにおいて全てはクラスであり、値はオブジェクトとして存在しメソッドは何らかのオブジェクトとなっている。
その状態を考えるとクラスに属するstaticなものを生成することができない。

しかし、その状態は不便なのでobjectキーワードがそれを解決する。
objectがあると何が嬉しいのかというと、objectで指定したシングルトンオブジェクトを生成することが出来る点である。
シングルトンオブジェクトであるが故に、そのオブジェクト固有のメソッドやフィールドを扱うことができる。

シングルトンオブジェクトを作ることができるなら、それによる恩恵を受けることができる。
そうなると、ユーティリティメソッドやオブジェクトのファクトリメソッドなども扱うことができる様になる。

それでは早速使ってみる。
といっても宣言から継承などほとんどクラスと変わらない上に、実はもう一度登場している。
それはHelloWorldを出力した時である。

object Sample{
    def main(args: Array[String]): Unit = {
        println("hello,world")
    }
}


この時にすでに使っている。
これによりmainメソッドがJavaでいうところのstaticメソッドになっている。
唯一といってもいい大きな違いはコンストラクタとしてパラメータを受け取れない点にある。

ケースクラス

最後に、クラスでもオブジェクトでもない特別なクラスを紹介する。
それはケースクラスというものでこの後に登場するパターンマッチという機能にも関連するものである。


ケースクラスとは次の様に宣言する。

case class User(name: String, age: Int)

これを見ると早いのだが、classの宣言の前にcaseキーワードが付いただけである。

一見なんら変化が無いように思えるがケースクラスには次のような利点がある

  • コンストラクタ引数にvalが付いた状態で自動的にそのクラスのメンバとして宣言される
  • パターンマッチに使える
  • 便利なメソッドが自動生成される

今回はこれの特に自動生成されるメソッドについて、とくにapplyについて触れる。

applyメソッド

実際、自動生成されるメソッド群のなかでこれが一番使われていると思う。
applyメソッドはそのケースクラスのインスタンスを生成する。
ということはこれを使うことでnewをする必要がなくなる。

上記のUserクラスをそのまま例で使う。
caseクラスのインスタンス生成の仕方は以下の通りである

val user :User = new User("scala", 20) //これでも生成できる
val user :User = User.apply("scala", 20) //こっちでもok 

おわりに

ここまでで、一通りクラス、オブジェクト、ケースクラスについては解説した。
次はJavaでいうinterfaceに類似した機能をもつtrait(トレイト)について解説する。

全てのプログラマに捧げるScala入門 Scalaの基本編

はじめに

Scalaを入門する上でこれは外せないというものを紹介していく。
ここでは主に、Scalaのプログラムの基本形式や変数、データ型の扱い
ifやforなどの制御構文についてまとめていく。

Scalaでコードを書く


Javaでコードを書く時の最小単位は次の様なものを書くだろう。

public class Hello{
  public static void main(String... args){
    System.out.println("Hello,World!!");
  }
}

クラス名とファイル名が一致し、mainメソッドを持つお馴染みの形である。
これがScalaだとどうなるかと言えば、次のようになる。

object Hello{
  def main(args: Array[String]): Unit = {
    println("Hello,World!!")
  }
}

classではなくobjectというキーワードを使い(これは後述する)、
mainメソッドをdefで定義し標準出力はprintln()という様に短くなる。
そして文末のセミコロンもいらない。しかし任意につけることは出来る。
Unitなどは後の、データ型で詳しく説明する。

ScalaにはREPLがあるので、それを使うともっとコードは短くなる。


これがREPLを使わない時のScalaプログラムの基本形である。

変数

Javaで変数の宣言をするときは次のような形式を取る

int i = 0;
String str = "Hello";

つまりは

(アクセス修飾子)[データ型] [変数名];

or

(アクセス修飾子)[データ型] [変数名] = [初期値];

と言った形である。

これに対してScalaは次のような形式を取る。

val str: String = "Hello"
var i: Int = 0

var number = 1L //val number:Long = 1L

つまりは

(アクセス修飾子) [val or var] [変数名] : (データ型) = [初期値]

となっている。

ここで重要なのがval,varとデータ型である。
変数宣言の先頭にある、valとvarで何が違うのかというと再代入が可能かどうかで
Javaでいうところのfinalが付与される動作に似ているのがvalで、非finalな変数がvarとなる。

実際に次のようにvalに値を再代入するとエラーが発生し、再代入が出来ないことを確認できる。

scala> val index: Int = 0
index: Int = 0

scala> index = 1
<console>:8: error: reassignment to val
       index = 1
             ^

これに対してvarは再代入ができることを確認するのが次のREPLになる

scala> var index: Int = 0
index: Int = 0

scala> index = 1
index: Int = 1

var,valでどちらを使うのが良いかと思うかも知れないが、Scalaでは基本的にvalを使用することが多い。
というのも関数型的な側面を活かすには変数等はイミュータブルの方が望ましいからである。
ネットでは何がなんでもvalを使えという過激派な意見も見受けられるが、実際にScalaの標準ライブラリの実装では
ミュータブルな変数やコレクションなんかが多用されているので一概にどちらかだけを使えとは言えない。

関数型でありオブジェクト指向であるので、そのあたりは臨機応変に使い分けることが必要になる。

データ型

ScalaにはJavaの様なプリミティブ型が存在しない。
ではなにが存在するかといえば、全て参照型でありつまりはクラスの値なのだ。
Scalaは拡張性が高くスケーラブルであるという最大の要因であると言える。

つまりはJavaからプリミティブが消えて、それに対応しているラッパークラスのみが残った状態と言える。

ここまでで、そのように説明されてもいまいちしっくりこないだろう。
そこで次の例を用いてInt型について考えてみる。ここでのIntは前述の通りクラスである。
まだその点において疑問を持つ人は次のリンクを参照してみるといい。
github.com

val a: Int = 1
val b: Int = 2
println(a + b) //out -> 3

気になるのが

a + b

という構文である。
Int型がクラスであるというなら、通常このように書くはずであると考えるし
そもそも+という名前のメソッドを書けるのかと疑問に思うだろう。

a.+(b)

Scalaをさわったことが無い人なら誰でもそのように感じるだろう。
その考えは全て正しいが、Scalaにおいては全てがクラスとそのメソッドなので+という演算子そのものが
言語レベルでサポートされているわけではなく、あくまでもそのクラスのひとつのメソッドにすぎない。

今までの言語と似た感じで使用できるのは、メソッドの呼び出しで.や()を省略できる場合があるからである。

ここまで話して、Scalaにおいてのデータ型とは参照型を指すということがわかるだろう。
また演算等についても自身で作成することができるのでデータ型を自作できるという面で非常に拡張性が高くなっている。

Scalaのデータ型とはそういうものなのだ。

制御構文

ここまでで変数とデータ型について触れたので、基本的なifやwhile、forといった制御構文についてここでは触れようと思う。

{}式

これについてはあまり触れられていないが、if文やwhile文ではなくif式やwhile式であるScalaにおいて複数の式を
記述する場合にこれは非常に重要な役割を持つ。{}式は次のようにいくつかの式を含む。

{ exp1; exp2; .... expn;}

これは順番に1~nの順で評価される。

if式

これはほとんどjavaのif文のように使えるが、それに馴染みすぎた人にはすごく奇妙な性質を持つ。
というのを、次のかなり有名な例を基に考えてみる。

Javaでは、年齢を数値として考えるとき次の様にすることが多い。

int age = 21;
String res;
if(age < 20){
    res = "未成年";
}else{
    res = "成人";
}

System.out.println(res); //out -> "成人"

if~else文で評価式に応じて変数に値を代入しているがScalaでは次のように書くことが多い。

val res:String =  if(age < 20) "未成年"  else "成人" 
println(res) //out -> 成人

この処理を見てもらうとわかるのだがif式が式であるがゆえに値が返っている。
また{}式も併用も可能であり、else if,elseも使うことができる。

while式

while式もif式同様に値を返すが適切な値がない(これは書いてみるとわかると思う)ためUnit型の()が返る
基本的にはjavaのwhile文と同じなので省略。

for式

ここまでのif,whileは基本的にJavaの制御構文と同様に使えるがforはかなり奇妙な使い方をする。
一般的にJava等の言語でfor文と言えば次を想像するだろう。

for(int i = 0; i < 10; i++){
    System.out.println(i);
}

しかしこれをScalaで書くと次のようになる。

for(i <- 0 until 10)
    println(i)

どちらかというとPythonに近い感じで書ける。
untilでなくtoも存在するが、これに共通しているのはループ変数の増減ではなくコレクションを対象に行うことである。
もちろんListやMap等でも同じようにループを回せる

Scalaのfor文は次のようにも書ける。
これは多重ループのように機能する。

 for( x <- 1 to 5; y <- 1 to 5; if x != y)
      println(s"x : y = ${x} : ${y}")
x : y = 1 : 2
x : y = 1 : 3
x : y = 1 : 4
x : y = 1 : 5
x : y = 2 : 1
x : y = 2 : 3
x : y = 2 : 4
x : y = 2 : 5
x : y = 3 : 1
x : y = 3 : 2
x : y = 3 : 4
x : y = 3 : 5
x : y = 4 : 1
x : y = 4 : 2
x : y = 4 : 3
x : y = 4 : 5
x : y = 5 : 1
x : y = 5 : 2
x : y = 5 : 3
x : y = 5 : 4


この時点でも、かなり奇妙だがScalaのfor式はyieldというキーワードを使うことで
コレクションの要素を加工し新たなコレクションとして返すことができる。

for(i <- List(1, 2, 3, 4, 5)) yield {
    i + 1
}
//return List(2, 3, 4, 5, 6)

最後に

ここまでがScalaの基本となるあれこれである。
次回はクラスとオブジェクトについてまとめようと思う

全てのプログラマに捧げるScala入門 ラムダと高階関数、カリー化

はじめに

Scalaをああだこうだ使っているけど、いまいちココらへんが理解できていないので
なんとなくまとめて整理してみる。

ラムダ式

上の参考サイトを元に値を引数で一つ受け取って、それに1加算して返す関数を考える。
通常の書き方で書くと次のようになる。

def inc(x: Int): Int = x + 1

inc(1) // return => 2

これをラムダで書き換えてみる。

すると次のようになる。

val inc: Int => Int = x => x + 1
inc(1) // return => 2

ただこちらは関数型でない言語を使ったことがない人には直感で理解がしにくいと思う。
上記のコードを次の用にするとわかりやすいかもしれない。

val inc: (Int => Int) = (x :Int) => x + 1

(Int => Int)が変数incの型であり、Int型の引数をとりInt型の戻り値を返すという意味になっている
(Int => Double)ならInt型の引数をとり、Double型の戻り値を返すという意味合いになる。
引数を2つとり、それらを加算して返す関数は次のように書ける。

val plus: ((Int,Int) => Int) = (x: Int, y: Int) => x + y

変数の型(今回ならInt => Int)を省略することができて、その場合は次のようになる。

val inc = (x: Int) => x + 1

この場合だと引数を二個以上もつ関数もラムダで書きやすい。
上記と同様に引数を2つ受け取り、それを加算する関数を書くと次のようになる。

val plus = (x: Int, y: Int) => x + y
plus(5,6) // return => 11

ここまで書いた右辺のことをラムダ式という。

無名関数

ラムダ式は名前のない関数、つまり無名関数として定義でき
無名関数は変数に束縛されていると言える。

高階関数

一言で言うなら、関数を引数にとる関数のことで次のように書ける。
ラムダ式のことがわかっていればさほど難しい話ではない。

def calc(plus: (Int,Int) => Int, minus: Int) = plus(2,3) - minus
val plus_func: (Int, Int) => Int = (x, y) => x + y

calc(plus_func, 5) //return => 0

引数だけでなく、戻り値として関数を返すこともできるのが高階関数である。

def add(x: Int): (Int => Int) = (y : Int) => x + y
add(2)(3) // return => 5

この様な高階関数はあまり意識しなくても使っていることが多く
よく使う場面ではScalaのコレクションにおいて、mapやforeach,filterなどのメソッドを使うときである

(1 to 100).filter(x => x % 2 == 0)

カリー化

カリー化とは(Int, Int) => Intのように複数の引数をとるラムダにおいて Int => Int => Intのように引数を分割し、
ひとつ引数を取ったあとに、その引数をメソッドチェインのようにチェインさせて書く方法のことを指す。

関数型言語ではよく使われており、Haskellでは複数の引数をとると自動でカリー化されるらしい。

さっきのplus関数で例を挙げてみる。

先に書いたカリー化していない場合は

val plus: (Int, Int) => Int = (x: Int, y: Int) => x + y

となるが

これをカリー化すると次のようになる

val plus: Int => Int => Int = (x: Int) => (y: Int) => x + y
plus(2)(3) // return => 5

これが使えるとなにが嬉しいかというと、filterなどの高階関数を扱うあれこれにおいて
片方の値を定数にして、もう片方を動的に処理した場合などに非常に便利になる。

val myFilter: Int => Int => Boolean = (x: Int) => (y: Int) => x < y 
(1 to 100).filter(myFilter(50))
res10: scala.collection.immutable.IndexedSeq[Int] = Vector(51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100)

最速のEchoサーバーを目指して、LinuxKernelモジュールを作っていく part2

はじめに

前回はカーネルモジュールで出力をやるところまでやった。
rabbitfoot141.hatenablog.com
今回は、気合でカーネルスレッドを扱えるようにする。

実装

EchoサーバではTCPを使用する、それをスレッドでああだこうだして複数接続を可能にしたい。

その目的のために今回は頑張ってカーネルスレッドを実装する。

kernel moduleでカーネルスレッド


参考にしたのはまつもとりーさんの次の記事。
hb.matsumoto-r.jp

linux/kthread.hとlinux/sched.hいうヘッダーファイルにある関数を使う。

kthread.hがカーネルスレッドに関するあれこれで、sched.hがカーネルで扱うプロセスやそれを扱うtask_structという構造体を使うためにある。

それでは早速コードを晒す。

#include <linux/kthread.h>
#include <linux/sched.h>

struct task_struct *task;

static int kthread_cb(void *arg){

    printk("[%s] running as kthread\n", task->comm);

    while(!kthread_should_stop()){
        schedule();
    }

    return 0;

}

グローバル変数としてtask_structを宣言している。
これにプロセスとして起動したいあれこれを代入して使う。

kthread_cb関数ではカーネルスレッドとしての動かしたい処理を書いている。

それを次のモジュール本体のコードで次のように使う。

static int fastecho_init_module(void){

        printk("Fastest Echo Server Start!!");

        //make kernel thread
        task = kthread_create(kthread_cb, NULL, "lrf141:fastecho");
        
        printk("[%s] wake up as kthread\n", task->comm);

        //launch task
        wake_up_process(task);
        
        return 0;
}

static void fastecho_cleanup_module(void){

        printk("Fastest Echo Server is unloaded!");
        printk("[%s] stop kthread\n", task->comm);
        kthread_stop(task);

}

まず前のコードで宣言したtask_structにkthread_create関数の戻り値を代入する
これでスレッドが使える様になるので、printkの次でwake_up_process関数を呼び出し、プロセスとして起動する

rmmod時にはそれを止める処理を書いている。


これを前回のモジュール本体に組み込んで次のようにする。

$ make
$ sudo insmod fastecho.ko
$ ps auwx | grep "\[lrf141:faste
cho\]"
root     30557 98.3  0.0      0     0 ?        R    16:49   0:05 [lrf141:fast
echo]
$ dmesg
[176532.823327] Fastest Echo Server Start!!
[176532.826178] [lrf141:fastecho] wake up as kthread
[176532.827453] [lrf141:fastecho] running as kthread
[176586.304842] Fastest Echo Server is unloaded![lrf141:fastecho] stop kthrea
d
$ sudo rmmod fastecho.ko

おわりに

まつもとりーさんの記事のおかげでなんとかカーネルスレッドを動かすことはできたので
次はネットワークの部分を実装していきたい。

最速のEchoサーバーを目指して、LinuxKernelモジュールを作っていく part1

はじめに

これから諸事情でLinux Kernel moduleとして動作する最速(?)のEchoサーバを作ることになったので
Echoサーバを作る過程をまとめていく。
今回はとりあえず、環境構築からKernel moduleで「Hello,World」的なことをするまでをやる

開発環境構築

参考にしたのは次の2つ
前者では主に、開発環境の面を、後者でコード周りを参考にした。

カーネルモジュール作成によるlinuxカーネル開発入門 - 第一回 hello world - Qiita
Linux kernel module の作成 - Qiita


VM的なのを使いたいなら特に前者を、そうでなくてよくてバグを踏んだ瞬間OSが死んでいいなら後者で十分だと思う。
これがメインではないので関連記事を参照して欲しい。

カーネルモジュールの作成

とりあえずCで開発するのでMakefileを作る
今までこういったものまともに書いたことないので今のところはふたつ目の参考記事のものをパクる。


KERNEL_DIR = /lib/modules/$(shell uname -r)/build
BUILD_DIR := $(shell pwd)
VERBOSE   := 0

obj-m := fastecho.o
fastecho-objs := fastecho_module.o


all:
	make -C $(KERNEL_DIR) SUBDIRS=$(BUILD_DIR) KBUILD_VERBOSE=$(VERBOSE) modules

clean:
	rm -f *.o *.ko *.mod.c *.symvers *.order .fastecho*
	rm -fr .tmp_versions

次に、カーネルモジュール本体のコードを書く

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

MODULE_DESCRIPTION("The fastest echo server in kernel module");
MODULE_AUTHOR("lrf141");
MODULE_LICENSE("MIT");


static int fastecho_init_module(void){
  
  printk("Fastest Echo Server!!");

  return 0;
}


static void fastecho_cleanup_module(void){

  printk("Fastest Echo Server is unloaded!");

}

module_init(fastecho_init_module);
module_exit(fastecho_cleanup_module);

カーネルモジュールを書く上で、一行目のlinux/module.hは必ず必要となる。
include文の次に来る、大文字のあれこれはそれぞれモジュールの情報で
上から、そのモジュールの説明文、作者、ライセンスとなっている。

そのあとにはモジュールとして呼び出す関数が2つあるが、その両方で使用しているprintkはstdio.hを使った時のprintfのような機能がある。

末尾二行にあるmodule_init()関数ではカーネルモジュールがロードされた際に引数で渡した関数を呼び出す。
引数として渡す関数はintを返り値としている。

module_exit()関数では、カーネルモジュールがアンロードされた時に引数の関数を実行する。

ソースコードがかけたらとりあえずmakeする。


そのあとこれらモジュールの情報は次のようにすると見れる。

$ modinfo fastecho.ko
filename:       /home/lrf141/cProject/fastest_echo/fastecho.ko
license:        MIT
author:         lrf141
description:    The fastest echo server in kernel module
srcversion:     B5F9EFB5EC209D9B991796D
depends:        
vermagic:       4.4.0-98-generic SMP mod_unload modversions 

モジュールをロードするときは次のようにする

$ sudo insmod fastecho.ko

モジュールをアンロードするときはこれ。

$ sudo rmmod fastecho.ko

ただこれではprintkで出力された文字列が見れない。
ということでここまで終わった段階でdmesgコマンドでカーネルのバッファ出力内容をみる
ただ見るだけでは見づらいのでgrepも組み合わせる。

$ dmesg | grep "Fast"
[46074.578777] Fastest Echo Server!!
[46079.485027] Fastest Echo Server is unloaded!

するとprintkした内容が見えている。

最後に

今回は簡単なモジュールの制作をやったので次回はこれにネットワークの機能を突っ込みたい

Hadoopを使ってみた

はじめに

インフラに興味をもってから、その中でも特に分散システムや分散並列処理に興味を持ったので有名な分散処理フレームワークであるHadoopを使ってみる。

今回は以下の記事を参考にして自分のUbuntu 16.04上で動かす。

Apache Hadoop 2.5.0 セットアップ手順 その1 – ローカル実行からシングルノードクラスター起動まで – hrendoh's tech memo

Hadoopのインストール

Hadoop ver 2.7以降のものはJava7の環境下で動く。 Linuxを使ってる人でJavaScalaを使ったことがある人ならわかると思うがJavaの実行環境と言ってもOpenJDKとOracle JDK/JREの2つが存在する。 しかし、Hadoopはこの両方をサポートしているため、いずれかがインストールされていればいい。

詳しくは以下のHadoop wikiに書いてある。

HadoopJavaVersions - Hadoop Wiki

次はJDK/JRE以外で必要なものをインストールする。 公式ドキュメントにはssh,rsyncが必要と書いているがこれらはUbuntuにデフォルトで入っているため省略。

いよいよ、Hadoop本体をインストールする。 今回は次のリンクからhadoop-2.8.2をダウンロードした。

Index of /software/apache/hadoop/common

それらのファイルを展開したら/etc/profileに次のコードを末尾に追加する

export PATH=/<展開したディレクトリ>/hadoop-2.5.0/bin:$PATH

その次に展開したディレクトリ内の/etc/hadoop/hadoop-env.shにJAVA_HOMEのパスを通す。

それが完了したなら、コマンドを実行する。

$ hadoop
Usage: hadoop [--config confdir] [COMMAND | CLASSNAME]
  CLASSNAME            run the class named CLASSNAME
 or
  where COMMAND is one of:
  fs                   run a generic filesystem user client
  version              print the version
  jar <jar>            run a jar file
                       note: please use "yarn jar" to launch
                             YARN applications, not this command.
  checknative [-a|-h]  check native hadoop and compression libraries availability
  distcp <srcurl> <desturl> copy file or directories recursively
  archive -archiveName NAME -p <parent path> <src>* <dest> create a hadoop archive
  classpath            prints the class path needed to get the
                       Hadoop jar and the required libraries
  credential           interact with credential providers
  daemonlog            get/set the log level for each daemon
  trace                view and modify Hadoop tracing settings

Most commands print help when invoked w/o parameters.

このあと参考にした記事ではスタンドアロンモードで起動して動作確認しているがそれは省略。

擬似分散モード

通常はいくつかのサーバなどにあれこれを分散させて処理を行うがそれが難しい場合もあるので今回は擬似分散モードという HDFSデーモンやDataNode、yarnなどを全て同一のサーバ上で実行するモードを使う。

設定ファイルの編集

etc/hadoopディレクトリに設定ファイルが存在する。

まずはcore-site.xmlに次を追加

<configuration>
    <property>
        <name>fs.defaultFS</name>
        <value>hdfs://localhost:9000</value>
    </property>
</configuration>

次にhdfs-site.xmlにプロパティdfs.replicationを1にセットしたproperty要素を追加。

<configuration>
    <property>
        <name>dfs.replication</name>
        <value>1</value>
    </property>
</configuration>

これが終わったなら次の段階に移行する

hdfs(hadoop distributed file system)をフォーマット

初回はデーモンを起動する前にファイルシステムのフォーマットが必要になる。

$ hdfs namenode -format

hdfsデーモンの起動

hdfsのデーモンを起動するにはhadoopを展開したディレクトリ直下のsbin/start-dfs.shを実行する。

実行後にjpsコマンドを叩いて、NameNodeとDataNodeがあれば正常に動作している。

$ cd hadoop-2.8.2
$ sbin/start-dfs.sh
$ jps
15538 SecondaryNameNode
15301 DataNode
24793 Jps
15182 NameNode

Webインターフェースの確認

50070番ポートの通信をファイヤーウォールの設定から許可するとlocalhost:50070でwebインターフェースが起動していることが確認できる。

MapReduceを実行する

MapReduceで使うディレクトリをHDFS(Hadoop Distributed File System)上に作成する

$ hadoop fs -mkdir /user
$ hadoop fs -mkdir /user/<username>

この時のusernameは現在自分の使用しているアカウント名と同一にするといい。

次に、ディレクトリ内に格納されている既存のサンプルを動作させる。

$ hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-2.8.2.jar pi 10 10000
.....
Estimated value of Pi is 3.14120000000000000000

今回はHDFS上で円周率を求めるサンプルがあったのでそれを実行した。 調べてみるとWordCountなどもあるらしい。

おわりに

今回はHadoopをインストールし、HDFS上でサンプルを動かすところまでやった。 ただし、HDFS上で実行したためジョブがローカル実行されている。 次回はそれを分散実行するためのyarnについて触れていこうと思う。