プロジェクト演習 2 linux プログラミング - sicyamaken/docs/linux.pdf · linux...

58
プロジェクト演 2 Linux プログラミング 2014 3 この授業について これま ,ほ Windows ってきた.Windows さまざま OS があるが, に多く されている UNIX ある.UNIX ある 一つ OS い.さまざま があり,こ げる Linux UNIX 一つ ある. Apple Macintosh OS (MacOS X) UNIX ある. Linux みシステムから大 サーバーま ,さまざまに されている.こ Linux んだシステム し, きるように るこ する. より ,以 ある. Linux がそれ りに える shell スクリプトが れる Linux みシステム クロス プログラム きる プロセス ドライバ きる ソケット いしドライバについてある 大きさ プログラムを れるように 「プログラミング」 違い ? これま ,プログラミング するため だった. するだけ プログラミング きるよう 題を いてきた. printf だけを っていれ だった. しかし, プログラミング まったく違う.多く ,一つ システム ある一 っていくこ る.OS してグラフィクスを いたり,他 アプリケーション をコピー・ペースト にしたり,他 アプリケーション したり いったこ められる.こ ために ,システム し, API (Application Program Interface) つけ,そ るこ に多く す. また,他 いたプログラムを いうこ よくある. において りたい った きに, けに プログラムが いうこ い.そ 楽に だが,そ わりそ プログラムを けれ い. いたプログラムを し,それをアレンジしてい くこ ,プログラムを させる. に, さまざま ツールがあり, うこ きる.しかし, ために かをよく がかえって悪 する. って, してい くこ ある. あるソフト ェア ( アプリまた ドライバ) るこ を演 していく. 授業の受け方: から いたり わったりし がら, めていくこ イメージしている.す てを っているが, させたり, させたり,一 したこ を大 したりしている. 1

Upload: others

Post on 18-Oct-2019

1 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

プロジェクト演習 2 Linuxプログラミング

山崎

2014年度 第 3版

この授業について

これまで授業では,ほとんどWindowsを使ってきた.Windows以外にもさまざまな OSがあるが,中でも特

に多く利用されているものが UNIX である.UNIXとはある特定の一つの OSのことではない.さまざまな種類

があり,この授業で取り上げる Linuxも UNIXの一つである.例えば,Apple社のMacintosh用の OS (MacOS

X) も UNIXの一種である.

Linuxは,組込みシステムから大規模なサーバーまで,さまざまに利用されている.この授業では,Linuxを組

込んだシステムの開発方法を理解し,自分でも簡単な開発ができるようになることを目標とする.

より具体的な到達目標は,以下の通りである.

• Linuxがそれなりに使える

• shellスクリプトが作れる

• Linux組込みシステムのクロス開発環境の設定やプログラム作成手順を理解し実行できる

• プロセスやドライバの動作を理解し説明できる• ソケットないしドライバについてある程度の大きさのプログラムを作れるようになる

「プログラミング」などの授業との違いは何か? これまでは,プログラミング言語を習得するための授業だった.

言語を習得するだけでプログラミングできるような問題を解いてきた.例えば,文字の表示は,printf だけを

知っていれば十分だった.

しかし,実際のプログラミングはまったく違う.多くの場合,一つのシステムの中のある一部分を作っていくこ

とになる.OSを呼び出してグラフィクスを描いたり,他のアプリケーションとの間で文字をコピー・ペースト可

能にしたり,他のアプリケーションと同時並行に動作したりといったことが求められる.このためには,システム

の全体の基本的な動作を習得し,必要な API (Application Program Interface) を見つけ,その利用法を理解す

ることに非常に多くの時間を費やす.

また,他人の書いたプログラムを読むということもよくある.現在においては,何かやりたいと思ったときに,

助けになる元のプログラムが存在しないなどということはほとんどない.その分自分は楽になるのだが,その代

わりそのプログラムを読まなければならない.今回は,山崎の書いたプログラムを理解し,それをアレンジしてい

くことで,プログラムを完成させる.

更に,現在ではさまざまなツールがあり,個々の作業を簡単に行うことができる.しかし,何のために何を使う

のが効果的かをよく考えないと,作業効率がかえって悪化する.目的意識をもって,自分で作業環境を工夫してい

くことも,とても重要である.

今回は,組み込み開発環境の中であるソフトウェア (通信アプリまたはドライバ) を作ることで,上記のすべて

を演習していく.

授業の受け方: この授業は,会社で先輩や上司から話を聞いたり教わったりしながら,仕事を進めていくことを

イメージしている.すべてを口頭で説明できないので文書になっているが,故意と情報を散在させたり,情報を不

足させたり,一回説明したことを大幅に省略したりしている.

1

Page 2: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

ノートをつける: 先輩 (TA) や上司 (山崎) に同じことを 2回聞かないようにする (これも訓練).そこで自分で

ノートを作る.社会に出たら,きちんとノートを作ることは必須である.作業の意味を考えながら,どのようにま

とめたら良いかも考える.このノートは採点対象である.

グループについて: この授業は,前述のように新たに習得すべき技術や調べることがとても多いため,グループ

内で相談したり手分けしたりしても構わない.しかし,プログラムやレポートは一人で作成する.

前提となる知識: Cプログラミングについては,2年前期のプログラミング演習程度の能力を前提にしている.忘

れてしまった者や履修してない者は,特に構造体とポインタについてしっかり自習すること.この演習が完了す

れば,社会に出てようやく C のプログラミングができると言っても恥ずかしくないレベルになるので頑張って欲

しい.

また,Linuxの最低限の知識は説明するが,上にも書いたように自分が実行したコマンドの意味を一つ一つ確認

してノートを作っていくこと.

備え付けの書籍: この演習では自分で調べることが重要である.Webも便利だが,そもそも何を調べたら良いか

も分からない状態のときは,本を読んで理解するしかない.この演習用に,以下の書籍を本棚に用意している.4

冊ある本は一つの机に一冊ずつ置くことを想定している.2冊だけの本は利用したら本棚に戻すこと.

• 武藤: Debian GNU/Linux徹底入門第 3版, 翔泳社 [4冊]

Debianという Linuxの基本的な使い方と管理方法を紹介した本.3世代前の本だが基本的なところは変わ

りない.マニュアルというより読むための本.

• 山下: UNIXシェルスクリプトコマンドブック第 2版, ソフトバンククリエイティブ [4冊]

シェルスクリプトについてだけ説明した本.マニュアルとしても使える.

• 平田: Linuxデバイスドライバプログラミング, ソフトバンククリエイティブ [4冊]

デバイスドライバの作り方について解説した本.一部古いが,今回の演習でデバイスドライバを作る人には

とても役に立つ.

• 高橋: 詳解 Linuxカーネル 第 3版, オライリー・ジャパン [4冊]

Linuxのカーネルのソースコードの解説.ある関数の意味を知りたいときなどに,マニュアル的に使う.

• スティーブンス: 詳解 UNIXプログラミング, ピアソンエデュケーション [2冊]

UNIX特有のプログラミングについての説明.特にマルチプログラミングなどは分かりにくいので,本を

一回読んで整理すると良い.

• スティーブンス: UNIXネットワークプログラミング Vol.1 第 2版, ピアソンエデュケーション [2冊]

他のコンピュータと通信をするためのプログラミングについての解説.

授業の流れと提出物:

1. Linuxの設定,Cを使った簡単なプログラムの作成

2. ソケットによるプログラミング

3. ドライバの動作の理解 [中間レポート提出]

4. ソフトウェアの設計,コーディング

5. デバッグ・テスト,最終レポートの作成 [ノートチェック,最終レポート提出]

毎回の授業では,授業中に小問題を出題し,これについては授業中に回答を説明する.授業終了直前にやや長い

課題を出題するので,次回までに解いてくること.オフィスアワーは,木曜の 6限である.木曜の 6限は残れる

ように時間を空けておくことが望ましい.

提出の必要があるのは,第 3回の中間レポートと第 5回の最終レポートだけである.ただし,小問題と課題は

次の回に使うので,必ず完成させること.また同じ理由から,作成したプログラムは必ず保存しておくこと.

採点基準:

2回のレポートの内容とノートチェックで判断する (1回目レポート:30%,ノートチェック 10%,2回目レポー

ト:30%,2回目プログラム 30%).

2

Page 3: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

1 第 1回

今回の内容

• PCの設定と Linuxのブートができるようになる [0.5時間]

• Linuxの簡単な設定 (特にネットワーク) ができるようになる [0.5時間]

• Linux に慣れる.shellスクリプトを使えるようになる [1.5時間]

• Cプログラミングの復習.Cによるプロセスの生成.[1.5時間]

1.1 Linuxマシンの使い方

1.1.1 立ち上げ方

(ネットの設定が終わっていないので) LANケーブルは接続せず,それ以外を接続する.外部 HDDの電源を入

れる.ノート PC (以降ではホストと言う) の電源ボタンを押し,すぐに F12を押す.(F12が間に合わなかった

らWindowsが立ち上がるので,Windowsをシャットダウンして,また最初から.) しばらくすると,ブートす

るディスクを選択する画面が表示される.ノート PCは 2種類あり,それぞれ異なる画面が表示される.ここか

ら先は授業中に説明する.特に enshu-608-9~13は少しややこしいので注意.

babababababababababababababababababababab

Linuxの種類:

Linuxには種類がある.今回の演習のホスト側の Linuxは,Debian Linux (バージョン 6) である.す

べての Linux では OS のカーネル部分は共通だが,カーネルだけでは何もできない.コマンド類等を

セットにしたものは distributionと呼ばれる.distributionには Debian,Ubuntu,Fedora,Redhat,

Vine (PC実習室の PCに入っている) などがある.元々が同じ Linuxであり,コマンドなどはかなり

共通している.(もちろん,MacOSなどの他 UNIXとも似ている.) ただし,管理者として OSの設定

をしようとすると distributionの違いは大きい.本を買って勉強する場合,一般利用者用の本はどれを

買ってもある程度は使える.一方,管理者用の本はどの distribution用の本かをよく見て買う.

1.1.2 自分のアカウントを作る

enshuでログインする (パスワードは授業中に伝える).自分のアカウントを作るために次を実行する.

「アプリケーション」→「アクセサリ」→「端末」を起動する.

$ su

# adduser 自分のユーザ名

# gpasswd -a ユーザ名 sudo

# exit

$

suのパスワードは口頭で伝える.また,adduserの中でいろいろ聞かれるが,パスワード以外は Enterキーで良

い.最後だけ Yとする.これで,自分のアカウントが作成された.(このアカウントを実際に使うのは,もう少し

後で.)

Linuxにおいては,システムの管理を行うユーザを特権ユーザと呼ぶ.代表的な特権ユーザは rootである.一

方,enshuや上記で作成した自分のアカウントは一般ユーザである.上の suは一般ユーザが rootになるための

コマンドである.

suのたびに毎回パスワードを入力するのは面倒である.そのために sudoというコマンドもある.sudoの後に

3

Page 4: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

任意のコマンドを書くと,そのコマンドは特権モードで実行される.もちろん,誰でも sudoが実行できたら,特

権ユーザを分けている意味がない.上の gpasswdで,ユーザを sudo可能にしている.

以降では,#のプロンプトで始まるコマンドは,suして実行しても良いし,sudoで実行しても良い.

1.1.3 ネットワークの設定と再起動

ホスト名を確認する.

$ hostname

(PC本体表面のマシン名と異なる名前が表示された人だけ以下を実行:)

# echo enshu-608-XX > /etc/hostname ← XXはマシン毎に違う

$ cat /etc/hostname

IPアドレスを設定・確認する.

$ ifconfig eth0

eth0は,LANを接続するためのハードウェア (デバイス)の名前である.表示される IPアドレス (inetアドレ

スという行) は次の通りでなければならない.

表 1 IPアドレス割当て

マシン名 IPアドレス

enshu-608-0 172.28.34.60

enshu-608-1 172.28.34.61

enshu-608-2 172.28.34.62

...

enshu-608-13 172.28.34.73

74~99は欠番

armadillo-0 172.28.34.100

armadillo-1 172.28.34.101

...

armadillo-13 172.28.34.113

もし違っていたら,/etc/network/interfacesを修正する (授業中に指示する).

$ cat /etc/network/interfaces

(次は,指示があったときだけ実行する.普通は不要.)

# rm /etc/udev/rules.d/70-persistent-net.rules

1.1.4 落とし方

いきなり電源を切ったりしないこと.次のコマンド (または GUIの「システム」のシャットダウン) を実行して

終了する.

# shutdown -h now

設定確認ができたので,LAN ケーブル*1を接続し,再度電源を入れる.今度は自分のアカウントでログイン

する.

*1 LANケーブルのほかに Etherケーブル,Ethernetケーブル,100base-Tケーブルなどの呼び方がある.

4

Page 5: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

1.1.5 ファイルの保存について

これから作成するプログラムなどのファイルは,外付けディスクに保存される.ハードディスクは動作中に振動

などの衝撃を与えると容易に故障し,ファイルはすべて読めなくなってしまう.実際,毎年 1人は出ている.自

己責任でファイルを必ず保存しておくこと.MyVolumeへ保存する (付録 A) か,自分の USBメモリに保存する

とよい.後で説明する scpを使って,隣の人のコンピュータに保存したりするのも一案である.

1.1.6 コマンド入力端末の設定

アプリケーション→アクセサリ→「端末」を右クリック→「このランチャをデスクトップへ追加」

デスクトップにアイコンができるので,これをクリックする.これで「端末」アプリケーションが表示される.

この中でコマンドを入力することが,Linuxの操作の基本である.Linuxでは,GUI (Graphical User Interface)

を使うことはあまりない.

矢印キーの上 (↑)を押すと過去に入力したコマンドが表示される.→←を使って移動してコマンドを修正でき

る.Enterを押すと修正後のコマンドが実行される.

ファイル名を途中まで入力して,TABを押すとファイル名を自動補間してくれる.

1.1.7 マニュアルの引き方

Linuxのコマンドが分からないときは,次の 3つのコマンドが便利である.

• man: そのキーワードのマニュアルを引く

• whatis: そのキーワードのマニュアル一覧を表示する

• apropos: そのキーワードを含むすべてのマニュアル一覧を表示する

特に manは頻繁に使うので,以下の例を実行すること.manから抜けるには qを入力する.このことをよく忘

れるので,必ず一度練習しておく.使用例:

$ man ls

1.1.8 エディタ

著名なエディタには,以下のようなものがある.

• gedit

• vi

• emacs

geditはWindowsのメモ帳のようなもので,誰でも簡単に使える.本格的なエディットには,viか emacsを使う

(簡単な説明は付録 B).どちらも有名なエディタで,解説したWebページなどもたくさんあるので,自分で勉強

する.実に多くのコマンドがあり,使いこなせると作業効率が劇的に向上する.将来,ソフトウェア関係の職業に

つくつもりであれば,この機会にまともなエディタに慣れておくことを強くお勧めする.

なお,日本語を入力するときは,「半角/全角」キーを押す

babababababababababababababababababababab

viと emacs:

emacsの方が高機能であり,できないことはないというくらい強力なエディタである.一方,viはどん

な Linux (というよりもすべての UNIX) にも入っているので利用範囲は広い.実際,今回のターゲット

マシンには emacsはなく viしか入っていない.

5

Page 6: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

1.1.9 その他

パッドのタップによってマウスクリックをしたい場合は,システム→設定→マウスの中に設定がある.

ウェブブラウザは,アプリケーション→インターネット→ iceweaselウェブブラウザ.これは,中身は Firefox

というブラウザ.

1.2 shellプログラム

コマンド入力を受けそれを実行するプログラムを,UNIXでは shellと言う.shellには沢山の種類 (sh, bash,

csh, tcsh等) があるが,この演習では bash (多くの Linuxのデフォルトの shell) を使う.

まず,shellの基本的な使い方を説明する (詳細は,C.1,C.10を参照).

shellの特徴の一つは,自分でコマンドを作れるということである.次のような内容の l(小文字のエル)という

ファイルをエディタで作る.

#!/bin/bash

ls -l

これを保存し,中身を確認する.

$ cat l

次を実行する.

$ ls -l

$ chmod a+x l

$ ls -l

$ ./l

chmodの a+xの意味は,このファイルの属性を「全員 (all) が実行可能 (executable) 」なように設定せよ,とい

う意味である.a-x なら,全員が実行不可能という意味である.(これ以外の指定については,自分で調べる.)

ls -lを実行すると,ファイルの一覧が属性付きで表示される.

上の lのような shell用のプログラムは,スクリプトと呼ばれることが多い.shellの各種機能や shellスクリプ

トについては,付録 C (C.6など) を参照のこと.

スクリプトを作っても,いちいち./lと実行するのではコマンドらしくない.実は,~/binの下にあるファイ

ルはコマンドとして実行することになっている.このディレクトリを作って,lをそこに置く.

$ mkdir ~/bin

$ mv l ~/bin

lというコマンドが実行できるか試してみる.

小問題:

• lコマンドはアルファベット順に lsで表示している.これを参考に,タイムスタンプ順に表示するコマンド

ltを作りなさい.

• findはファイルを探すコマンドである.例えば,

$ find . -name ファイル名 -print

などとする.しかし,これは長くて面倒なので,

$ f ファイル名

とするだけでファイルを探してくれるコマンド fを作りなさい.このためには,引数にアクセスする特殊

変数$1を使う (C.7参照).

6

Page 7: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

1.3 Cプログラミング

1.3.1 復習

Cのプログラムを復習するために,次のようなプログラム test.cを作成する.何をするプログラムかは見れば

分かると思うが,(Cのプログラムがまったく思い出せない人は,/home/enshu/c/の下に幾つかプログラムが置

いてあるので,これを見て思い出す.)

#include <stdio.h>

int main()

{

char buff[100];

scanf("%s", buff);

printf("%s\n", buff);

}

このファイルを test.cに保存して,以下を実行する.

$ cc test.c

$ ./a.out

ccで Cコンパイラを実行する.これにより,Cプログラムから実行可能ファイルが生成される.実行可能ファイ

ルは,何も指定しなければ a.outという名前である.2番目の行は,このディレクトリにある a.outを実行せよ,

という意味である.(ついでに,ls -l を実行して,xを確認してみる.ccが勝手に xを立ててくれるので,実行

できるのである)

1.3.2 ファイル記述子を使ったプログラム

ここからが新しいプログラムである.ファイルから文字を入力して,それを画面に表示するプログラムを作る.

ただし,fscanfは OSの機能を直接使えないので,ここでは利用しない.今後のために,ここではファイル記述

子という OSの機能を直接利用する方法を習得する.ファイル記述子を使ってファイルから入力を行うプログラ

ムは,次のようになる.

#include <fcntl.h>

int main(){

int fd;

char buff[100];

fd = open("test.txt", O_RDONLY);

read(fd, buff, 10);

write(1, buff, 10);

close(fd);

}

ファイルをオープンしてファイル記述子を作る関数は openである.ファイル記述子から読み込むには read関数

を使う.これによりファイルからデータが buffに読み込まれる.

このプログラムでは fdがファイル記述子 (file descriptor)である.見ての通り整数である.ファイル記述子の

0, 1, 2は特別である.0は標準入力 (つまりキーボード),1 は標準出力 (つまり画面),2は標準エラー出力 (これ

も画面)のためのもので,この 3つのファイル記述子は最初から用意されている.

read(0, buff, 10);

7

Page 8: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

などとすると,キーボードからの入力を buffに読み込むことができる.

上の open,read,writeのように新しいシステムコールやコマンドが出て来たら,manコマンドを使って自分

で調べる.ただし,コマンド名,システムコール名,ライブラリコール名が重複しているときには,うまく探せな

い.例えば,システムコールの openを調べようとして,man openとしても,実はコマンドにも openがあるの

で,そちらの説明が表示されてしまう.whatis openの結果を見ると,次のようになっている.

open (1) - start a program on a new virtual terminal (VT).

open (2) - open and possibly create a file or device

括弧の中が種類を表しており,1はコマンド,2はシステムコール,3はライブラリコールである.

$ man 2 open

とやると,システムコールの openのマニュアルが表示される.

babababababababababababababababababababab

システムコールとライブラリコール:

システムコールとライブラリコールは,Cのプログラムから呼び出す関数のことである.システムコー

ルは OSが用意している機能だが,ライブラリコールは OSとは関係なく,Cが用意している関数であ

る.ライブラリコールは普通の Cで書かれており,自分が書いたプログラムと一緒にリンクして使って

いる.一方システムコールは,中身は OSの中にある.例えば,printfはライブラリコールなので普通

の関数であり,その中で writeシステムコールを呼び出している.

プログラムを作る上で両者の違いはあまり意識する必要はないが,前述のように manのマニュアルは 2

がシステムコール,3章はライブラリコールと別れている.

小問題:

次の 2 つの修正をして,上のプログラムを file.c として完成させなさい.1 つ目の修正は,1 文字だけを読

み込んで表示するようにすることである.2 つめは,エラーをチェックきちんとすることである.open,read,

write, closeを manで調べて,そのエラーに対処する.エラーのチェックについては,perrorという便利な関数

がある (D.3を参照).このプログラムは後で使うので,きちんと作っておくこと.

1.3.3 プロセスを作る

Linuxでは,複数のプログラムを同時に動かすことができる.一つ一つのプログラムが動いている状態を「プロ

セス」と呼ぶ.プロセスを作るには fork関数を呼ぶ.

pid = fork();

fork関数を呼ぶと,今実行中のプロセスをコピーした新しいプロセスが作られる.元から実行していたプロセス

を親,新しく作られたプロセスを子と呼ぶ.子は親のコピーなので,親と子はまったく同じプログラムであるが,

forkの戻り値だけが異なる.親には子供のプロセス番号が返され,子には 0が返される.これを使うと,自分が

親なのか子なのかを区別できる.

プロセスをコピーすると変数もすべてコピーされる.親と子の変数は,プロセスの丸ごとコピーにより同じ値

になるが,変数としては別である.従って,どちらかが変数の値を変えても,もう一方にはその値は伝わらない.

forkを調べると,プロセス番号 (上の pid) は pid_tというデータ型だが,これは実際は intである.

課題:

forkして次のことをするプログラム fork.cを作成せよ.forkのエラーもチェックすること.

8

Page 9: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

• 子は,”child”と表示しては 2秒スリープすることを 10回繰り返す.

• 親は,”parent”と表示しては 3秒スリープすることを 10回繰り返す.

余力があれば,次のようなプログラムも作ってみる.子ごとに違う値とする部分がポイントである.

• 親は,n個の子プロセスを作る (とりあえずは n=2でもよい).

• n番目の子は,nの値を表示しては 1秒スリープすることを 10回繰り返す.

• 親は子プロセスを n個作った後,”parent”と表示しては 1秒スリープすることを 10回繰り返す.

子が計算した結果を親が必要な時にはどうしたらよいか.値が 0~255 (つまり 8 ビット) の範囲で良ければ,

親が wait関数で待つことで子から値を貰うことができる.(manで wait関数を調べてみる.) 更に余力がある者

は,上のプログラムを拡張して,各プロセスが nの値を親に返すようにする.親はそれを受け取り,一つずつ表

示する.

なお,もっと様々なデータを親と子の間でやりとりするには,プロセス間通信を行う必要があり,後の演習で取

り上げる.

babababababababababababababababababababab

プロセスを終了させる方法:

forkを使ったプログラムにバグがあると,作成したプロセスが終了しなくなることがある.このような

時は,まず psコマンドを実行してプロセス番号を調べる.そして,次のコマンドでプロセスを殺す.

kill プロセス番号

babababababababababababababababababababab

コマンドでプロセスを作る方法:

これには & (C.5) を使う.コマンドの後に&を付けると,そのコマンドは別のプロセスで実行される.こ

れを通称「バックグラウンド実行」と呼ぶ.普通のコマンド入力の裏側 (バックグラウンド) で実行する

からである.普通のコマンドとバックグラウンドのコマンドは同時に実行されることになる.

2 第 2回

今回の内容

• 組込み開発の基礎 [1.5時間]

• シグナルによるプロセス間通信 [1.5時間]

• タイマーとシグナル [1.5時間]

2.1 ターゲットの設定

2.1.1 コンピュータ間のファイル転送

コンピュータ Aから Bにファイルを転送するには,scpコマンドを使う.scpコマンドは次のように書く.

$ scp コピー元 コピー先

コピー元とコピー先が同じホストなら,cpと同じである.別ホストのファイルを指定するときは,ホスト名:ファ

イル名と書く.さらに次のように書けば,あるホストのあるユーザのあるファイル名という指定もできる.

ユーザ名@ホスト名:ファイル

9

Page 10: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

scp の仕組みを説明する前に,まずサーバーとクライアントの定義を口頭で説明する.scp のための scp サー

バーが各ホストで動作している.scp コマンドを実行する側が,scp クライアントになる.クライアントとサー

バーの関係と,ファイルを転送する方向とは何の関係もないので注意.

scpクライアントを実行すると,別ホストの scpサーバーにログインする必要が出てくる.このため,scpはパ

スワードを聞いてくる.どのホストの scpサーバーに何という名前でログインしようとしているのか考えて,適

切なパスワードを入力する.

実際に本日の演習に必要なファイルをノート PCに転送する.ノート PCを scpクライアントとして動作させ,

172.28.34.hh (hhの部分は授業中に指示) という scpサーバーから,enshu.tar.gz というファイルを取得せよ.ア

カウントは enshuを使う (パスワードは既に知っているはず).

取得したファイルは,沢山のファイルを一つにまとめたものなので,次のコマンドで解凍する.

$ tar zxvf enshu.tar.gz

これによって,p6というディレクトリが作成される.

2.1.2 組込みシステムのブート

まず,ターゲットとホストという組込み開発の用語を口頭で説明する.

普通の PCには必ずハードディスクが付いており,そこからプログラムを読み込んでブートする.しかし,組

込みシステムのハードウェアはさまざまなため,ブートの方法も多様である.

Armadillo (以降はターゲットと呼ぶ) には,ブートモードが 3つある (オート,保守,UART).現在は,オー

トモードに設定済みである (詳細は付録 Eを参照のこと).オートモードでは,Linuxを立ち上げるために,カー

ネルイメージ (linux.bin.gz) とファイルシステムイメージ (romfs.img.gz) が必要である.前者は OSのプログラ

ムそのものであり,後者は OSが立ち上がるために必要な最小限のファイルをまとめたものである.

これらのイメージを取得する方法としては,以下の 3つがある.

• 内蔵フラッシュブート: 内蔵フラッシュメモリに置かれたイメージを使う

• microSDブート: microSDに置かれたイメージを使う

• tftpブート: サーバー (ホスト)上に置いたイメージをネットワーク経由で入手する

この演習では,オートモードの tftpブートを使う.tftpブートに設定するには,保守モードで setbootdeviceコ

マンドを使う.これについては 2.1.4で説明する.

2.1.3 tftpサーバー側の設定

tftp ブートにおいても tftp 要求を出す側を tftp クライアント,要求を受ける側を tftp サーバーと呼ぶ.tftp

サーバーとしての基本設定は既に完了している.このためやるべきことは,tftpブートに使う 2つのファイルを

/srv/tftpというディレクトリに置くことだけである.

$ cd ~/p6/atmark-dist/images

# cp linux.bin.gz romfs.img.gz /srv/tftp

babababababababababababababababababababab

scp サーバーや tftp サーバーなど,通信要求を受け取る側のプログラムはどのように起動されるので

あろうか.実は,/etc/inetd.confに書いてある.このファイルを見て行くと,tftp dgram udp4 ...

という行がある.これは tftp要求が来たら/usr/sbin/in.tftpd を動かせという意味である.つまり,

in.tftpdが tftpサーバーのプログラムなのである.

また,tftpという要求は何番のポートを使うかということは,/etc/inetd.confというファイルに書

いてある.これを見ると,tftpは 69番ポートであることが分かる.

10

Page 11: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

2.1.4 minicomの設定

見ての通り,ターゲットにはキーボードがない.ターゲットにキーボードからコマンドを入力するには,シ

リアル (本当は RS-232C と言う) インタフェースから行う.ターゲットとホストをシリアルケーブルで接続す

る.ホスト側では,ホストのキーボード (と文字表示) をシリアルに入力するソフトを動かす必要がある.これが

minicomというソフトである.

ただし,minicomを直接動かすと日本語表示がうまくできないので,mcというコマンドを使う.mcは Linux

のコマンドではなく,山崎が作成したファイルである.これをコマンドとして実行できるように,自分で設定

せよ.

いよいよターゲットを立ち上げてみる.ただし,まだ LANケーブルは接続しないこと.

mcコマンドを実行する.次に,保守モードでターゲットをブートする.保守モードでブートするためには,メ

イン基板のボタンを押しながら電源を入れる.ボタンは,minicomの画面に文字が出たら離して良い.画面には,� �hermit>� �

と出ているはずである.この hermit>という文字列は,ターゲットが表示した文字がシリアルを通ってホストに

送られ,それをmcが画面に表示しているのである.

2.1.5 ターゲットの設定の確認

まず,tftpによるブートの設定を確認する.まず,現在の設定を見るには,単に setbootdeviceと入力する.

よく見て設定を確認する.

設定が正しくなかったときには,次のように入力する.� �hermit> setbootdevice tftp 172.28.34.tt 172.28.34.hh --kernel=linux.bin.gz --userland=romfs.img.gz� �

”tftp”の後の最初の IPアドレスは,自分 (ターゲット)の IPアドレスである.次は,tftpサーバ (ホスト)の IP

アドレスである.その次は,2つのイメージファイルの指定であり,このまま書く.

設定が正しかったら,tftpでブートするための準備をする.まず,ハブの親側のケーブル (床下から立ち上がっ

て来ているケーブル) を抜く.これは,間違った IPアドレスのパケットが,大学のネットワークに流れ出て行か

ないようにするためである.そして,ターゲットとハブを LANケーブルで接続する.

リセットボタンを押すと,minicom画面に Linuxがブート過程のメッセージが出るはずである.立ち上がった

ら,rootでログインする (パスワードは授業中に伝える).

次に,各マシン毎に正しい IPアドレスが設定されているかを確認する.� �# ifconfig eth0� �

IPアドレスが表示されるはずなので,それが正しいかを確認する.正しい IPアドレスが設定されていたら,ハ

ブを大学のネットワークに接続して良い.(まだ,他の人が IPアドレスの設定中かもしれないので,全員が設定

完了してから接続すること.)

正しい IPアドレスでなかった時は,以下を行う.

11

Page 12: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

� �# vi /etc/config/interfaces

下記の内容を手で入力

auto lo eth0

iface lo inet loopback

iface eth0 inet static

address 172.28.34.tt

netmask 255.255.255.0

gateway 172.28.34.1

# cat /etc/config/interfaces

念のため目で見て確認

# flatfsd -s

リセットボタンを押す.� �babababababababababababababababababababab

flatfsdコマンドの仕組み:

Linuxの IPアドレスはファイルに書いてある.ということは,romfs.img.gzが同じだと,すべてのマシ

ンの IPアドレスが同じになってしまう.しかし,一台ずつ個別に romfs.img.gzを作るのは手間である.

そこで,armadilloでは,個々のマシン毎に設定を個別に保存する機能として,flatfsdコマンドがある.

flatfsd -s コマンドを実行すると,内蔵フラッシュに /etc/config/* を保存する.ブートの時には

/etc/init.d/flatfsd が呼ばれる,その動作は次のようなものである.

1. flatfsd -r をして内蔵フラッシュから/etc/config/* を復活する.

2. /etc/config/*の内容を,/etc/default/*に上書きする.

3. /etc/default/*を /etc/config/* に名前付け替えをする.

2.1.6 終了と再起動の方法

ターゲットは ROMだけで動いているので,いきなり電源を抜いても問題ない.しかし,正しくは haltコマン

ドを実行する.電源を落として良いというメッセージが表示されるので,電源アダプタを抜く.

また,再起動するにはリセットボタンを押しても良いが,正しくは rebootコマンドを実行する.

minicomの終了は,^A,z,q,(Yesか聞かれるので) Enterと順番に入力する.

2.2 クロスコンパイル

簡単なプログラムを作り,これをターゲット用にクロスコンパイルし,ターゲットに転送して実行するという一

連の手順を実行してみる.ホストとターゲット間の転送は,やはり scpを使う.ただし,ターゲットは scpサー

バにはなれないことに注意.

ホストには,既に ARM用クロスコンパイラ arm-linux-gnueabi-gccがインストール既みである.クロスコ

ンパイルしたいファイルが main.c だとすると,

$ arm-linux-gnueabi-gcc main.c

とやってみる.a.outができたはずである.

$ file a.out

と実行すると,ARM 用のコードが出来ていることが分かる.(普通の cc でコンパイルした a.out にも file をし

12

Page 13: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

て,その結果を比較してみるとよい.)

小問題:

1~10億までの数を加えるプログラムを作り,main.cとしなさい.これをまずホストで実行して,実行時間を

測定する.(実行時間を測定するには timeコマンドを使ってもよい.) 次に,同じファイルをクロスコンパイルし

て,実行可能ファイルをターゲットに転送し実行せよ.ターゲットでの実行時間はどの程度になったか.

(また,ccでコンパイルした実行可能ファイルをターゲットで実行すると,何が起きるか.)

2.3 シグナルを使ったプロセス間通信

プロセスは独立した存在であって,互いに干渉することは基本的にはできない.プロセス間で何らかのやり取

りをするには,プロセス間通信と呼ばれる仕組みを使う.プロセス間通信には様々な種類があるが,今回は「シグ

ナル」を使う.

シグナルとは,ソフトウェアだけで行う割り込みのことである.割り込みとは,あるプロセスがある処理をして

いるときに,強制的に別の処理を実行させる仕組みである.( 「コンピュータアーキテクチャ」の授業では,割り

込みはハードウェアが掛けるものであったが,シグナルはソフトウェアが掛ける割り込みである.)

シグナルには種類があり,番号で分けられている.シグナル番号の一覧は,/usr/include/bits/signum.hの

80行目前後にある.

シグナルを受け取る側は,例えば次のように書く*2.

void sig_handler(int signum){~

}

int main(){signal(SIGUSR1, sig_handler);~

}

signalを実行すると,ある番号のシグナル (SIGUSR1は既に定義済みの番号なのでこのまま使えばよい) が送ら

れてきた時に実行される関数 (上では sig_handler) を登録する.このような関数をシグナルハンドラ(または

ハンドラ)と呼ぶ.mainの~の部分を実行中にシグナルが発生すると,ハンドラが実行される.ハンドラは,1

引数 (データ型は int)で戻り値は voidの関数でなければならない.なお,引数はシグナル番号である.

あるプロセスへシグナルを送るには,次のように書く.

kill(プロセス番号, シグナル番号);

勝手に使ってよいシグナル番号は決まっており,SIGUSR1と SIGUSR2である.今回は,SIGUSR1 (実際は 10) を

使う.(なお今回は使わないが,自分自身にシグナルを送るときには,raise関数を使う.)

小問題:

上の説明を参考にして,シグナルを送信する側のプログラム sig.c,シグナルを受信する側のプログラム rec.cを

作りなさい.幾つか注意が必要である.2つの異なる実行可能ファイルを作るには,次のようにする.

$ cc -o sig sig.c

$ cc -o rec rec.c

ここで-oは,実行可能ファイルの名前指定である.

rec.cのメインは次のようなプログラムとする.

*2 この書き方は実は古い.今は sigaction という関数を使うことが推奨されているが,使い方が大変なので今回はこちらを使う.もちろん,sigactionを自分で調べて使っても良い.

13

Page 14: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

for (;;) {

printf("z\n");

sleep(1);

}

すなわち,1秒に一回”z”を表示する.

recを別の端末画面で動かし,もう一方で次のコマンドを実行する.

$ ps u

PIDの欄を見ると recのプロセス番号が分かる.この番号を sigに与える必要がある.これには 2つの方法があ

る.一つは,プロセス番号を scanfで読み込む方法である.もう一つは,次のような形でコマンドのオプション

で指定する方法である(1234はプロセス番号の例).

$ ./sig 1234

このようにコマンドオプションの値を使う方法は,D.2 を参照のこと.文字列の数字を整数の数値に直すには

atoiが便利である.

sig.cではシグナルを送った旨を,また rec.cでは受け取った旨を表示するだけでよい.

babababababababababababababababababababab

CUIだけで実験する方法:

ここでは端末画面を複数開いて複数のプロセスを実行した.一つの端末画面の CUIの中だけで開発した

い場合は,前回述べた&を使う.

$ ./rec &

これにより./recは別プロセスで実行される.この時,そのプロセス番号が表示されるので,これを利用

すれば良い.

余力がある人用:

1. signalが SIG_ERRを返したらエラーなので,これをチェックする (このチェックはデータ型が少し難しい

ので,分からなかったら聞く).

2. システムコール getpidを呼び出して,自分のプロセス番号を表示する機能を付ける.(これで psコマンド

で番号を調べる必要がなくなる.)

3. 例えば,次のような簡単なプログラムを考えてみる.

while(1){

oldv = extv;

newv = extv;

if (oldextv != newextv) printf("error\n");

}

ただし,extvは外部変数,oldvと newvは自動変数である.これに対して,シグナルハンドラを追加する.

シグナルハンドラの中では,extv++を実行するだけである.次にこのプロセスに対して,外部から繰返し

シグナルを送信してみる.何が起きるだろうか.errorが表示されないようにするには,どのようにしたら

良いだろうか.

14

Page 15: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

2.4 シグナルとタイマー

ある指定した時間待つプログラムはどのように書けば良いか.一つの方法は,forループを何回も回すことであ

る.しかし,計算機にただ回数を数えるという仕事をさせるのはもったいないし,電力も消費する.タイマーを使

うと,計算機は別の仕事をしたりスリープしたりできる.

タイマーを使うには,次のようにする.詳細は,manで調べること.

struct itimerval timval;

timval.it_interval.tv_sec=1;

timval.it_interval.tv_usec=0;

timval.it_value.tv_sec=1;

timval.it_value.tv_usec=0;

setitimer(ITIMER_REAL, &timval, NULL);

~ // 別の仕事

タイマーが発火すると,SIGALRMというシグナルが発生する.(参考までに SIGALRMは実際は 14である.) シ

グナルハンドラーを書けば,タイマーの発火を知ることができる.

小問題:

タイマーを使って次のようなプログラム timer.cを作れ.必ずエラーチェックをすること.なお,別の仕事が特

に何もない時は,無限ループにすれば良い.(sleep関数を実行しても良い.)

• 1秒経過するたびに,何らかのメッセージを出力するプログラムを作れ.

• (少し余力のある人用) クロスコンパイルして,armadilloで実行せよ.先ほどと違い,CPUの性能に関係

なく同じタイミングでメッセージが表示されるはずである.

課題:

シグナルとタイマーを組み合わせて,ストップウォッチを遠隔操作するプログラムを作成する.ストップウォッ

チのメインとなるのは watch.cで,次の動作をする.

• SIGUSR1が送られてきたら,ストップウォッチを起動する.

• SIGUSR2が送られてきたら,ストップウォッチを停止する.

• ストップウォッチ動作中は 1秒に 1回,経過時間を表示する.

もちろんストップウォッチの実装は,sleep ではなく setitimer を使うこと.(メインで何も処理しないために

sleepを使うのは構わない.)また,ストップウォッチ動作中の SIGUSR1,停止中の SIGUSR2に対しては適切に

処理する.

ストップウォッチを別画面から起動・停止するプログラムは,sig.cを改造して用いる.とりあえずは,SIGUSR1

を送るだけの sig1.cと SIGUSR2用の sig2.cを別々に作れば良い.余力があれば,次のような形で起動と停止がで

きるようにする(1234はプロセス番号の例).

$ ./sig start 1234

$ ./sig stop 1234

3 第 3回

今回の内容

15

Page 16: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

• クロス開発環境でのドライバとアプリケーションの作成方法を理解する [1時間]

• makeを理解する [1時間]

• サンプルドライバを動かして動作を理解する [1.5時間 +レポート]

前回,enshu.tar.gzを解凍したときに,p6/pdfというディレクトリが作成されているはずである.この下にあ

るファイルについて説明する.これらは,これから使う組込みコンピュータ armadillo-440に添付されていたド

キュメントである.組込みシステムには,このように詳細な情報が付いてくるのが普通である.これまでの授業

の知識を総動員すれば,ほとんど理解できるはずである.

• Armadillo-440 液晶モデル開発セットスタートアップガイド: ハード・ソフトの基本的な使い方.アプリ

ケーションソフトの書き方.

• Armadillo-400シリーズハードウェアマニュアル: ターゲットのハードウェアに関するマニュアル.演習に

関連があるのはジャンパーや位置や意味など.

• Armadillo-400シリーズソフトウェアマニュアル: クロス開発環境の説明.ソフトウェアをカスタマイズす

るための情報.ブートローダの情報.

• atmark-dist開発者ガイド: ターゲットの OSを本格的に開発するための情報.

• Armadillo-400リビジョン情報: 今回は使わない.

3.1 ターゲット用 Linuxの作成

ターゲットマシン (armadillo) では,普通の Linux ではなく,uClinux (http://www.uclinux.org/) とい

う組込み機器用の Linux が動作する.前回は,カーネルイメージ (linux.bin.gz) とファイルシステムイメージ

(romfs.img.gz) について,提供されたものをそのまま利用したが,今回は,それをソースコードからコンパイル

して作ってみる.

babababababababababababababababababababab

Linuxと uClinuxの違い:

Linuxは汎用の OSであり,仮想記憶が可能である.これにより,実メモリよりも大きなプログラムを

実行できる.一方,組込み装置では,それほど巨大なプログラムを動作させることはないため,組込み用

CPU には仮想記憶のためのハードウェア MMU がないものもある.uClinux は,MMU のない CPU

でも動作するのが特徴である.

3.1.1 ソースファイルのコンパイル

カーネルをソースからコンパイルするには,数々のオプションを指定する必要がある.それを実際に体験し

てみる.また,後でドライバーを自分たちで作成する前準備として,標準で入っているドライバー (下の GPIO

Buttons)を削除しなければならない.それについてのオプション設定も行う.

$ cd ~/p6/atmark-dist

$ make menuconfig

メニュー画面が開く.この中では,Enterキーで選択,SPACEキーで ON/OFF.

Vendor/Product Selectionを選択

Vendorを選択

「AtmarkTechno」を選択 (かなり上の方にある)

自動的に画面が戻る

2行下の「AtmarkTechno Products」を選択

Armadillo-440を選択

16

Page 17: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

自動的に画面が戻る

(右矢印キーを押して) Exitを選択

自動的に画面が戻る

Kernel/Library/Defaults Selectionを選択

Default all settingsを ON

Customize Kernel Settingsを ON

Exitを選択

もう一回 Exitを選択

Do you wish to save~ に対して Yesを選択

いろいろやった後でもう一回メニューが出てくる

Device Driversを選択

Input device supportを選択

Keyboardsを選択

GPIO Buttonsを OFF

Exitを 4回

Do you wish to save~ に対して Yesを選択

しばらく時間がかかる.何もエラーメッセージが出ていなかったら次へ進む.

$ make

OS全部をコンパイルするので,かなり (15~20分) 時間がかかる.待っていても仕方ないので,ほっておいて

次の節に進む.

以降は,エラーなく終わったのを確認してからやる.images/の下に以下の 4つのファイルができていることを

確認する.

linux.bin, linux.bin.gz, romfs.img, romfs.img.gz

ファイル名の最後に gz とついているのは,圧縮されたファイルである.つまり,linux.binを圧縮したものが

linux.bin.gzである.(圧縮については man gunzip)ターゲットをブートするためには,前回のように linux.bin.gz

と romfs.img.gzを tftp用のディレクトリに置く.そして,ターゲットの電源を入れる.� �# cat /proc/sys/kernel/version� �

と実行すると,現在のカーネルがいつコンパイルされたものかが分かる.

babababababababababababababababababababab

大量のメッセージが出力される場合の対処方法:

makeは大量のメッセージを表示するので,次のようにしてメッセージをファイルに一旦保存するのが普

通である.(今回は,エラーは出ないはずなので,画面にたれ流しにしている.)

$ make >log 2>&1

ここで 2>&1というのは,2番出力を 1番出力と一緒にせよという意味である.通常,2番出力はエラー

メッセージ,1番出力は通常メッセージである.両方とも同じファイル (log)に記録するために,上記

のようにする.

17

Page 18: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

babababababababababababababababababababab

/procの仕組み:

/procというディレクトリから下は実は本当のファイルではない.OSの内部状態をファイルの形で見

せているだけである.例えば,/proc/1など数字のついたディレクトリがある.これは,そのプロセス

番号の情報が入っている.そのディレクトリに行って,cat で見てみよう.また,書き込むことで OS

の設定を変えることができるファイルもある.

3.1.2 make

OSのように大きなプログラムは,膨大な数のソースファイルと includeファイルから構成される.その中の一

部を修正した時に,何をコンパイルすべきかを人手で管理するのは大変だし間違いやすい.makeは,これを自動

化してくれる.ここでは時間がないので,既に用意してあるファイルを使って,makeが何をしてくれるのかを簡

単に説明する.まず,ファイルの中身を確認する.

$ cd ~/p6/maketest

$ cat m.c

$ cat f.c

$ cat g.c

コンパイルして,実行してみる.

$ cc f.c g.c m.c

$ ./a.out

次に,Makefileを見てみる.

m: f.o g.o m.o

f.o: f.c

g.o: g.c

m.o: m.c

Makefileとは,コマンド makeが参照するファイルで,実行可能ファイルの作り方が書いてある.上の 1行目は,

「mは f.o,g.o,m.oに依存している」という意味であり,「mは f.oと g.oとm.oから作れる」という意味でもあ

る..oというファイルは,.cという Cプログラムから作られる中間ファイルである.次の行は,f.oは f.cに依存

しているという意味である.(実は,最初の一行以外は省略できる.C.11を参照のこと.)

小問題:

まず makeを実行して,必要なファイルを全部コンパイルして,mを作ってくれることを確認する.次に,エ

ディタを使って f.cをエディットする (スペースを足す,あるいはメッセージを書き換えるなど).もう一度 make

を実行して,何がコンパイルされるかを確認する.その他,m.c をエディットしたり,m や f.o を削除したりし

て,その都度makeを実行してみる.これにより,必要最小限の動作しかしないことを確認する.

余力のある人は,次を行う.Makefileを次のように修正する.

m: f.o g.o m.o

cc -o m f.o g.o m.o

f.o: f.c

cc -c f.c

g.o: g.c

cc -c g.c

18

Page 19: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

m.o: m.c

cc -c m.c

ここで,ccの左はスペースではなく,TABである (重要).TABで始まる行は,その上の行の依存関係が満た

されなかったら,TABの右を実行せよという意味である.最初に実験したように,TABで始まる行がなくても,

makeは適当に作ってくれる.しかし,特殊なことをする場合は,作成方法を TABの行で指定しなければならな

い.上のMakefileを元に,クロスコンパイルするためのMakefileを作成せよ.

(余力のある人) makeを使って,2つのファイルをコンパイルすることもできる.このためには,Makefileの 1

行目を次のように書く.

target: sig rec

説明したように,makeは 1行目のコロンの左側を作成しようとする.そのためには,sigと recが必要だという

のが,このルールの意味である.しかし,targetの作り方が 2行目に書いてないので,永久に targetは作られな

い.つまり,makeは実行されるたびに,sigと recを作るしかないのである.

3.2 ターゲットにファイルを置くには

ターゲットは電源を入れるたびに,ホストから OSとファイル群 (romfs.img.gz) を受け取ってブートする.こ

のため,ターゲットのファイルを変更しても,電源を切ると元に戻ってしまう.ターゲットのファイル状態を永続

的に変更するには,romfs.img.gzそのものを変更するしかない.その方法を説明する.

実は,ホスト側の~/p6/atmark-dist/romfsディレクトリの下のファイル構成が,そのまま romfs.img.gzに

なる.実験してみるために,viなどを使って romfs/rootというディレクトリの下に testfileというファイルを作

る.romfs.img.gz を作るには,先程のように make を実行しても良いが,romfs.img.gz だけを作り直すときは,

次のようにするとずっと早い.

$ cd ~/p6/atmark-dist

$ make image

imagesの下の romfs.img.gzを適切に設定し,ターゲットを立ち上げてみる.ターゲットの /rootの下に testfile

ができているはずである.(時間がある時は実際にやってみる.)

この機能を使って,scpを便利にしてみよう.scpのパスワードを毎回入れるのは面倒だが,実はパスワードの

代わりに「鍵ファイル」を使うとこれを回避できる.次のようにする.

$ ssh-keygen

こうすると,~/.ssh の下に id_rsa と id_rsa.pub という 2 つの鍵ファイルができる.前者を scp クライアン

ト,後者を scpサーバに置けば,パスワードを入力せずに scpが実行できる.ssh-keygenをホストで実行したと

する.サーバに置く鍵のファイル名は authorized keysなので,次のようにする.

$ cd ~/.ssh

$ mv id_rsa.pub authorized_keys

これでサーバ側は設定完了である.

次に id_rsaを scpクライアントに設定する.具体的には,このファイルがクライアント (つまりターゲット)

の/root/.ssh/id_rsaというファイルになるようにすれば良い.ただし,ターゲットの電源が切れてもファイル

が残るようにするには,上のような手順を実行する必要があるのだった.(後は考える.)

クライアントで scpを実行してみると,パスワードが聞かれなくなっているはずである.

19

Page 20: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

3.3 サンプルドライバの作成とアプリケーションの作成

~/p6の下には,bmpread,fbwrite,tactswの 3つのディレクトリが存在する.bmpreadは,BMPファイル

を読み出すプログラム,fbwriteはターゲットの画面にグラフィック表示をするプログラム,tactswはサンプルド

ライバのプログラムである.

まずドライバを用意する.ドライバのプログラムは,tactsw.cという名前でなければならない.サンプルとし

て,tactsw sample.cという山崎が書いたプログラムがあるので,これをコピーする.

$ cd ~/p6/tactsw

$ cp tactsw_sample.c tactsw.c

ドライバを作成する.

$ make modules

エラーメッセージのようなものが出るが,ls -l で tactsw.ko の日付けを見て,今作成されたものであれば問題

ない.

.koというファイルは何か.Linuxでは,カーネルの一部分 (例えばドライバ) は,カーネルを動かしたままで

組み入れたり,カーネルから外したりできる.これをカーネルモジュール (略称は kmでなく,kernel objectで

ko) と言う.今回のドライバもカーネルモジュールの一つである.

tactsw.koを scpでターゲット側に転送する.次に,カーネルモジュールのドライバを今動作しているカーネル

に組み込んでみる.これには,insmod (insert module) コマンドを使う.� �# insmod tactsw.ko� �

(なお,ドライバモジュールの削除は,rmmod tactsw.koである.新しくドライバを作ったときには,rmmodを

してから insmodする.)

このときに,このドライバに割り振られたMajor番号が表示される.251のはずである.これで 251番という

ドライバがカーネルに入ったのだが,このドライバーに対して,openや readをするには,どうしたら良いだろう

か.Linuxでは,ドライバも一つのファイルのように見せることで,これを可能とする.

ドライバとやりとりするための疑似的なファイルは,「デバイス」と呼ばれる.次のコマンドで,251番ドライ

バにデバイスとしてのファイル名を付与する.� �# mknod /dev/tactsw c 251 0� �

と実行する.cというのはドライバの種類で,character型と block 型がある.今回は cである.(キーボードのよ

うに入力したデータは 1回限りのデータで消え去っていくタイプがキャラクタ型デバイスで,ハードディスクの

ように同じデータに何回もアクセスできるタイプがブロック型デバイスである.) 最後の 0は子番号である.251

番ドライバの中に子供のデバイスを作れるが,今回はこの機能は使わないので 0である.

小問題:

/dev/tactswをオープンし,5回だけ文字を readするプログラムを書き,サンプルドライバが動作しているこ

とを確認せよ.ファイル名は read.cとする.(file.cを参考にせよ.)

readする前に,ボタンを複数回押したときの動作と,readした後で押したときの動作の違いは何か?

file.cを作成したときに,readや closeの戻り値を調べていない人は,この機会に行う.

3.4 ドライバーの動作の理解

サンプルドライバのソースコードの詳細については,授業中に説明する.

20

Page 21: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

ドライバは,大きくトップハーフとボトムハーフに分けて構成される.ボトムハーフは,割り込みハンドラとそ

れに関連する部分を言う.トップハーフは,ユーザプロセスからのシステムコールに対する処理をする部分であ

る.*3

トップハーフ中心で考えると,ドライバの動作は次のように 2つのパターンがある.

• readシステムコールで呼ばれて,もう入力済みの文字があったので,すぐ戻る.

• read システムコールで呼ばれて,まだデータがないので待ちに入る.その後,入力があったら read から

戻る.

ボトムハーフ中心で考えると,次のような 2つがある.

• 割り込みが入ったときに,それを待っているプロセスがいない.• 割り込みが入ったときに,それを待っているプロセスがいる.

後述の課題では,それぞれについてどういう順番で動作しているのかを調べてみる.

ドライバを設計するに当たっては,クリティカルリジョンに注意する必要がある.クリティカルリジョンとは,

プログラム上のある一部分のコードであって,その部分を同時に実行してはならない部分である.

サンプルコードでは,msg[]と mlenという配列をトップハーフとボトムハーフが共有しているため,これに関

連するデータ更新には注意が必要である.

一般論としては,注意すべきは次のパターンである.

• トップハーフはプロセススイッチする可能性がある.つまり,2 つのトップハーフが同時に実行される可

能性がある.(今回のドライバは,これがないように作られている.それはどこで保証されているのだろう

か?)

• シングルコアのプロセッサでは,ある瞬間に実行しているコードは一つだけである.しかし,マルチコアの場合は,異なるプロセッサが同じコードを本当に同時に実行する可能性がある.プロセススイッチは禁止す

ることができるが,物理的に同時に実行されることは防止できない.プロセススイッチからのクリティカル

リジョンの保護と,マルチコア実行からの保護は手段が異なる.マルチコアについては,スピンロックで待

つことが有効である.

• 割り込みはいつでも入ってくる可能性があるため,非同期に割り込みハンドラが走行する.ボトムハーフとトップハーフが同じデータを共有する場合は,上記と同様の注意が必要である.

• クリティカルリジョンとは関係ないが,割り込みハンドラの記述には,もう一つ注意が必要である.割り込みハンドラの中では,待ちに入る可能性がある関数を呼んではならないという点である.割り込みハンドラ

は,他の割り込みを禁止して走っているので,その中で待ちに入ると何の割り込みも受けられなくなる.

サンプルドライバを見ると分かるように,msg[]と mlenにアクセスするときには,単純にそのまま操作するこ

とはしていない.これらのデータは,ドライバのトップハーフとボトムハーフが共有しており,同時に操作すると

データが破壊されたり,誤った値を参照したりするためである.

工夫の一つは,次の方法である.

ret = wait_event_interruptible(tactsw_info.wq, (tactsw_info.mlen != 0) );

これは,第 2引数 (tactsw_info.mlen != 0)が正しくなるまで待ち (スリープ) に入るという関数である (実

際は関数ではない).普通に Cで書けるような気もするかもしれないが,tactsw_info.mlenが 0であることを

検知してから,スリープに入る間に mlen!=0になると,そのプロセスは 2度と目を覚まさなくなってしまうだろ

う.つまり,mlenを調べてから寝るまでの間がクリティカルリジョンなのである.

wait_event_interruptibleは,ある工夫によってクリティカルリジョンが 2重に実行されないようになって

いる.なお,そのプロセスがスリープした情報は,tactsw_info.wqに入るので,そのプロセスを起こしたい場

*3 Linuxドライバにおいては,これとは違う意味でトップハーフ,ボトムハーフという言葉を使うこともあるようだが,一般的なドライバではここで述べた意味で使う.

21

Page 22: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

合は,次を実行する.

wake_up_interruptible(&(tactsw_info.wq));

wake_up_interruptibleは起こすだけなので,この呼び出しが待ちに入ることはない.したがって,割り込

みハンドラの中で使っても構わない.

もっと一般的に msg[]や mlenを操作したいときはどうするか.この時には,ロックを使う.ロックを取れた

プログラムだけが,そのデータを操作できるようにすることで,安全に操作可能となる.ここでは,ロックの一つ

であるスピンロックを使っている.

spin_lock_irqsave(&(tactsw_info.slock), irqflags);

spin_unlock_irqrestore(&(tactsw_info.slock), irqflags);

spin_lock_irqsaveは 2つのことを行う.今実行中のプロセッサの割り込みの禁止と,スピンロックの取得であ

る.詳細は,F.5を参照のこと.~の部分がクリティカルリジョンであり,これが 2重に実行されることはない.

irqflagsは,禁止前の古いCPU状態を入れる変数なので,後で使う.slockを取得できれば,もう誰も関連する

コードを実行することはできない (そのようにプログラムを作る).仕事が終わったら,spin_unlock_irqrestore

でロックを解放する.

クリティカルリジョンの実行は,いろいろな意味でシステムに悪影響を与えるので,できるだけ短い時間で実

行しなければならない.また,クリティカルリジョンの中でプロセスがスリープするような関数を呼んではいけ

ない.

課題:

ドライバの動作を解析し,レポートにまとめる.これは採点対象である.少なくとも以下について動作解析を

すること.

• 問 1. 各関数 (tactsw ioctlを除く) がどのようなときに呼ばれるかを調べなさい.

「a.outを実行すると呼ばれる」とかは駄目.(自分で作ったプログラムでしょう?)

• 問 2. あらかじめキー入力がない時に readを実行すると,そのプロセスは待ちに入る.この「待ち」はド

ライバーのプログラムのどの部分で実現されているか調べなさい.まず,どのように調査したかについて述

べ,その後で動作説明をすること.(調査の試行錯誤の過程まで書かないこと.以下同様.)

• 問 3. 問 2の状態に続けてキー入力が入ると,そのプロセスは待ち状態が解け,実行中となる.何 (ドライ

バー中のプログラムのどの部分) によってプロセスが起こされるのか.そのプロセスが本当に動作を再開す

るのはどのタイミングなのかを調べなさい.まず,どのように調査したかについて述べ,その後で動作説明

をすること.

• 問 4. あらかじめキー入力がある場合において,readを実行したときの動作を調べなさい.まず,どのよう

に調査したかについて述べ,その後で動作説明をすること.

ソースコードは必須ではないが,調査方法の説明等のために付けてもよい.

調査方法のヒント: アプリケーションでは printf,ドライバーでは printk (F.1 を参照) を入れて追い掛けて

いく.

22

Page 23: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

babababababababababababababababababababab

【重要】printfとバッファリング:

printfは,実はストリーム (FILE *) に対して出力をしており,中身は fprintf(stdout, ...);であ

る.ストリームに出力すると,OSは,出力回数をできるだけ減らして無駄を防ごうとする.つまり,出

力した文字を OSの中にギリギリまで溜めこむ (これをバッファリングという).printfした文字がすぐ

画面に表示されるとは限らないのである.しかし,デバッグでは,直ちに文字を表示したいことが多い.

一つの方法は,改行を付けることである.多くの場合はこれで表示されるが,必ず表示されるとは限ら

ない.

端末への表示においては,次のようにすると良い.

printf(~);

tcdrain(1);

ここで tcdrainは,バッファリングされた文字が端末に表示されるまで待つ関数である.

なお,printkは,見かけは printfに似ているが,ストリームを使っていないので,直ちに表示される.

4 第 4回

今回の内容:

• 中間レポートの回答を簡単に説明.• ソケットによる通信プログラム• 最終製作プログラムの説明

4.1 TCP/IPとソケット

TCP/IP の解説を行う (授業内で説明).コンピュータアーキテクチャの授業で既に簡単に説明したし,情報

ネットワークではもっと深い説明があるので,ソケットを理解するためのごく簡単な説明である.

以下のキーワードを理解すること.

• サーバ• クライアント• IPアドレス

• ポート• ソケット (5つ組, 5-tuple)

4.2 ソケットによるプロセス間通信

プロセス間通信の一つであるシグナルについては 2.3 で行った.今回は,別のプロセス間通信方法として,ソ

ケットを取り上げる.これまでと同様に,ソケット通信を待ち受ける側をサーバ,通信要求を発行する側をクライ

アントと呼ぶ.サーバ側のやるべきことをプログラムの骨格だけ書くと次のようになる (エラーチェック等いろい

ろと省略している).

#include <arpa/inet.h>

struct sockaddr_in serv_addr;

23

Page 24: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

// ソケットを作る

sockfd = socket(PF_INET, SOCK_STREAM, 0);

// アドレスを作る

bzero(&serv_addr, sizeof(struct sockaddr_in));

serv_addr.sin_family = PF_INET;

serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

serv_addr.sin_port = htons(ポート番号);

// ソケットにアドレスを割り当てる

bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr_in));

// コネクション要求を待ち始めるよう指示

listen(sockfd, 5);

// 要求があったらそれを受け付ける (なければ待つ)

new_sockfd = accept(sockfd, NULL, NULL);

// クライアントからデータを受け取る

read(new_sockfd, buff, 128);

// 1秒待ってソケットを終了する

sleep(1);

close(new_sockfd);

close(sockfd);

一方,クライアント側のプログラムは,次のようになる.

// ソケットを作る

sockfd = socket(PF_INET, SOCK_STREAM, 0);

// サーバのアドレスを作る

bzero(&serv_addr, sizeof(struct sockaddr_in));

serv_addr.sin_family = PF_INET;

serv_addr.sin_addr.s_addr = inet_addr("172.28.34.~");

serv_addr.sin_port = htons(ポート番号);

// コネクションを張るための要求をサーバに送る

connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr_in));

// 128バイトのデータをソケットで送る

write(sockfd, buff, 128);

// ソケットを終了する

close(sockfd);

各システムコールは次のような意味である.

• socket: ソケット (ファイル記述子)の生成

• bind: ソケットにアドレスをつける

• listen: コネクションの要求を待つ

• connect: コネクションを張る要求を送る

• accept: コネクションの要求を受け入れる

• write, read: データの送受信

24

Page 25: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

• close: ソケットを閉じる

各ライブラリ関数の詳細な使い方は,自分で調べること.closeの手順には注意が必要である.クライアントが

closeしてからサーバが closeしなければならない.正しく closeする方法については,D.5.1を必ず参照すること

(重要).ここでは,最も手抜きの方法として,サーバが 1秒待ってから closeしている.

小問題:

まず,クライアント (ターゲット) から任意の数字をサーバに送り,サーバでそれを表示するプログラムを作成

せよ.

必ずすべてのシステムコールに対して,エラーチェックを入れること.エラーの時にどのような値が返される

かは,manで調べる.

クライアント用プログラムは cli.c,サーバ用プログラムは serv.cとする.ポートは,10000番を使うものとす

る.クライアント・サーバ間で通信する数字は,最初は charで良い.(charも数字だったことに注意.ただし,範

囲は-128~127.) つまり,上のプログラムで言えば,buff[0]だけを使ってデータを通信する.

ターゲットに実行可能ファイルを毎回転送するのは大変なので,まず,ホストの上でサーバとクライアントの両

方のプログラムを作り,デバッグまで行う.完成したら,クライアントをクロスコンパイルし,実行可能ファイル

をターゲットに転送して,動作を確認する.

余力がある人は,以下について考えてみる.

• クライアント (ターゲット) から任意の数字をサーバに送り,サーバ (ホスト) 上でそれに 1を加えてクラ

イアントに戻すようなプログラムを作成せよ.

• サーバが一回で終了せず,何回もクライアント接続が可能なようにする.• 上のようにするとサーバーが終了しなくなってしまうので,ある特殊なデータ (0や-1) を送ったら,サー

バーが終了するようにする.

• クライアントとサーバ間の通信を 1バイトでなく,4バイト (int)にする.ただし,ソケットで正しい送受

信が保証されるのはバイト列だけである.つまり,buff[0], buff[1], buff[2], buff[3]という 4つのバイトか

ら intを作り出す関数と,intを 4つのバイトに分解して buff[0]~buff[3]に入れる関数を自分で作る必要が

ある.(これを毎回やるのは面倒なので,htonlや ntohlという関数がある.これを自分で調べて使っても

良い.)

• (更に余力がある人へ) 整数や文字列を混在して送信できるようにするには,どうしたら良いだろうか.

4.3 最終課題

2つのコース,通信アプリコースとドライバーコースがあり,各コース複数の問題がある.どれか一問を選択し

て作成する.自分のオリジナルな機能は勝手に入れてもよい.

4.3.1 通信アプリコース

通信アプリケーションは,複数のマシン上の互いに通信するプロセスによって構成される.現在では,一般のア

プリケーションで通信をしないものなどほとんどないが,今後は組込みシステムでも通信は重要である.既にソ

ケットを使った簡単な通信は実験したが,これをベースにして機能を考えていく.

具体的には,以下のアプリケーションを作成する (どれか一つ).

1. デジタルフォトフレームを作る (難易度低):

サーバー上に置いてある画像ファイル (bmpファイル) をターゲットの画面に表示する.以下のプログラム

を読んで理解すれば,後は組合せるだけでできるはずである.これらのプログラムについては付録 Gを参

照.(特に色の扱いが armadilloは独特なので,必ず読むこと.)

• bmpread.c: ホスト上で bmpファイルを読み込むプログラム

25

Page 26: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

• fbwrite.c: ターゲットに画像を表示するプログラム

• serv.c, cli.c: 今回の演習で作成した,サーバー・クライアント通信プログラム

最初の表示が完成したら,次のようにフォトフレームらしい表示の方法を工夫する.まず,クライアントが

繰返し画像を要求しても良いようにする.つまり,サーバーが一回で終了しないようにする.複数の bmp

ファイルを用意し,リクエストのたびに異なる画像が表示されるようにする.また,下から表示する,右か

ら表示する,ディゾルブ (少しずつ)で表示するなどのパターンを考えてみるのも面白い.

2. 2つ以上のソケットの同時処理 (難易度高):

今回の演習で作成したサーバー・クライアント通信プログラムにおいて,複数のクライアントが同時接続し

ても対応できるようなサーバーを作成する.ただし,このクライアントはすぐ終了してしまうので,同時に

は動かせない.そこで,クライアントを次のように修整する.値を送信し,サーバーが 1加算した値を返

して来たら,1秒スリープして,またサーバーに値を送信することを 10回ほど繰り返す.つまり,実行に

10秒かかるようにする.後は,端末を沢山開いてクライアントを次々と起動すれば良い.

このプログラムは見かけは面白くないが,サーバープログラミングの基本中の基本である.もっと面白くし

たい人は,例えば複数の利用者間でチャットをすることなどを考えてみても良い.

ヒント: ホスト側の受信プログラム (メインのプログラム) では,selectや pollという関数を使う.

4.3.2 ドライバコース

ドライバコースでは,サンプルドライバに機能を加える.組込みシステムにおいては,新たな独自ハードを接続

することが多い.その場合には,必ずドライバーを作成する必要がある.しかし,ドライバー作成は,OS自身に

手を入れることを除けば,最高難度のプログラミングの一つと言われており,なかなか自力ではマスターできな

い.ここでは入門レベルではあるが,ドライバープログラミングを体験する.

具体的には,現在のサンプルドライバに,以下のいずれかの機能を追加する.複数の機能を実装しても構わな

い.全ての機能は,一つのドライバ内に共存させることができる.

1. ioctlによる状態取得 (難易度低):

今のタクトスイッチの状態を取得するための ioctlを用意する.ioctlのコマンドは適当に自分で定義する.

readでは,スイッチが押されるまで待ってしまうが,この機能があれば,キーの状態を瞬時に知ることが

できる.ioctlについては,D.4, H.6 を参照のこと.これは簡単にできるはずなので,次の機能と組み合わ

せて作って欲しい.

2. キーの組合せによる文字入力 (難易度低~中):

複数のタクトスイッチの組合せを検知して,それぞれに異なる文字を出すようにする.例えば,スイッチを

押す個数に応じて,’2’, ’3’, ’4’などとするのでも良いし,スイッチの組合せを文字に対応させるのでも良

い.3つのスイッチの組合せで 7種類の文字を入力できるはずである.(意図的に行うのは極めて困難だが,

ほぼ同時に 2つのキーを押した場合,割り込みが 2回でなく 1回になることがありうる.一つ前の割り込

み処理が完了していないうちに入った次の割り込みは無視されるためである.これを完全に対処するには

工夫が必要で,その場合は難易度高.)

3. シグナル化 (難易度高):

タクトスイッチが押されたらプロセスに signalをかけるような機能を実装する.ioctlを使って,signalを

掛けるように設定する.また,その設定を解除する ioctlも作る (F.7).ioctlについては,D.4, H.6 を参照

のこと.

4. オートリピートドライバ (難易度高):

オートリピートというのは,キーボード (今回はスイッチ) を長く押していると,連続して文字が入力され

ることを言う.この機能を実装する.これには,スイッチが押され,ある一定時間のうちに離されなかった

ら,文字が入力されたことにすれば良い.時間検出のためには,カーネルタイマーを使う.キーが押された

らタイマーを起動し,タイマーがタイムアウト (時間切れ) になったら,文字が押されたことにする.この

タイマーをスイッチが離されるまで,繰り返し設定すれば,オートリピートが可能となる.(カーネルタイ

26

Page 27: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

マーの使い方は,F.9を参照すること.)

タイムアウト処理を含めて,トップハーフとボトムハーフで共有データを操作するコードが多くなる.ロッ

クがいい加減でもだいたいは動作するが,厳密に正しい実装にしようとすると,難易度は「超高」である.

いずれも,Linuxカーネルが用意する関数の使い方を深く理解する必要がある.一部については,付録に記載し

た.また,準備している本「LINUXデバイスドライバ」,「Linuxデバイスドライバプログラミング」を参照した

り,Webを使って自分で調べる.

babababababababababababababababababababab

割り込みハンドラの中で時間のかかる仕事をする方法:

割り込みハンドラの中では,待つ可能性のあることは一切できず,またできるだけ早く終了しなければ

ならない.そこで,ハンドラから抜けた後で仕事をする方法が幾つか用意されている.一つは,カーネ

ルタイマーである.これは,ある時間経過後に関数を実行するためのものであり,その間は別の仕事が

できる.時間指定でなくて,割り込みハンドラから抜けた後,いつでも (暇なときに) 関数を実行させる

こともできる.これがタスクレットである.タスクレットは,適当なタイミングで実行されるカーネル

タイマーのようなものである.

カーネルタイマーもタスクレットも,割り込みハンドラと同様に厳しい制約がある.つまり,その中で

待つことはできないし,またユーザ空間とのデータ転送もできない.もっと自由に何でもやりたいとき

は,ワークキューを使う.ワークキューは,独立したカーネル内スレッドで実現されている.なお,今

回の演習では,タスクレットもワークキューも使う必要はない (使いたければ使っても良い).

まず設計をする.関数を単位として,引数と戻り値の決定をする.また,外部変数を中心としてデータ設計を

し,そのデータを参照する関数と修正する関数を決定する.通信アプリについては,ターゲットとホスト間にどの

ようなデータの通信が発生するかもまとめる.

実装において,よく分からない関数があったら,それを呼び出す簡単なプログラムを作るなどして実験して理解

する.特にカーネル関連の関数は,適当に呼んで動くというものではない.一つ一つきちんと理解して進める.

教員・TAへの説明

これは必須ではないが,設計方針が決まったら,教員または TAにそれを説明してみるとよい.自分のシステ

ムを大雑把に人に分かるように説明できることは,とても重要である.また,根本的な間違いがあることもあるの

で,それを早期に知るためにもお勧めする.

よくあるバグ

症状: ターゲットで./a.outを実行すると,いきなり Killedと表示された.

原因: 配列のサイズが大き過ぎる.組み込みシステムでは,メモリに限界があり,また uClinuxは仮想記憶ももっ

ていない.配列のサイズを減らす.

症状: ターゲットで./a.outを実行すると,いきなり Segmentation Faultと表示された.

原因: これは,いろいろな原因が考えられる.もしかしたら,自動変数で大きなサイズの配列を宣言しているため

かもしれない.

症状: まっ白い画面が表示された.

原因: 表示しようとしている free1.bmpは,画像の上下に白い帯が入っている.その部分を表示してしまった可

能性がある.なぜ,その部分が表示されてしまったのかは,自分で考えてみること.

27

Page 28: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

babababababababababababababababababababab

ソースコードを探して読んでみよう:

ある関数の使い方が分からなかったら,インターネット検索も一つの方法であるが,一番確かなのは

コードを見ることである.例えば,add_timerという関数を使っているコードを見つけたいときは,

$ cd ~/p6/linux-2.6.26-at15/drivers

$ grep add_timer */*.c

または

$ find . -name "*.c" -exec grep add_timer \{\} \; -print

などとする.一方,add_timerの関数定義の方を見たいときには,最も簡単な方法は,

$ cd linux-2.6.6-at14

$ find . -name "*" -exec grep "add_timer(" \{\} \; -print

とする方法である.ただし,沢山出てくる.もう少し本格的にソースコードを読む場合は,

$ cd linux-2.6.6-at14

$ ctags -R

と実行する.かなり時間がかかるが,tags というファイルができる.これをエディタや less で見て,

add_timerを探す.すると,include/linux/timer.h に定義されていることが分かるはずである.(tags

とエディタを連携させて,もっと簡単にファイルを閲覧することもできるが,後は自分で調べる.)

5 第 5回

今回の内容

• ノートチェック• 実装継続• レポートの書き方

5.1 ノートチェック

どのようにノートを付けているかを確認し,採点する.質問するので,それに関連するノートのページを見せ

る.ノートなど不要という人もいるかもしれないが,その場合は実際に手作業してもらい,そのスムーズさを見て

採点する.

5.2 ドライバの拡張機能の実装とデバッグ・テスト

引き続き,コーディングならびにデバッグをする.ある機能の実装が順調にできたら,別の機能にも挑戦してみ

る.ないしは,データ表現を工夫して高速化したり,プログラムを簡潔にしたりできないか考えてみる.

テストもすること.基本的な動作を確認したら,さまざまなケースについて確認する.もちろんプログラムは,

各種のエラーを判定するようになっていなければならない.システムコールやライブラリコールのエラー判定は

必須である.テスト手順をきちんとメモしておき,最終報告書にて示すこと.

また,時間があったら性能測定もしてみると良い.特に通信アプリについては,例えば,1 バイトずつ送った場

合と,3バイトずつ送った場合などを測定してみる.時間の測定は,Cの中で自分で時間測定関数 (times, clock)

を呼ぶ方法と,shellレベルで timeコマンドを使う方法がある.

28

Page 29: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

ドライバについては,速すぎて測定は難しい.また,テストも網羅的に行うのは難しい.微妙なタイミングをす

べて再現することはできないためである.少なくとも 3.4節でやったように,待ちが入るときと,待ちが入らない

ときはテストすべきである.また,msg配列が溢れないかのテストや,キーを同時に押した時のテスト,キーを

高速に連打した時のテストなどは簡単にできる.その他にもテストができないか考えてみる.

以上の過程を便利にする工夫ができないかも自分で考える.とにかく楽をするように考える.例えば,scpを含

む一連の手順を実行する一つのコマンドを作ってみるなどである.自分が考えて行った工夫もレポートに書いて

良い.

babababababababababababababababababababab

逆アセンブルの方法:

コンパイルされた実行可能ファイルは機械語であり,人間が見ても分からない.これをアセンブリプロ

グラムに戻すことを逆アセンブルと言う.

$ arm-linux-gnueabi-objdump -d ファイル名

とすれば,全逆アセンブルが一気にできる.

デバッガ gdbを使うこともできる.(ただし,クロスデバッグではなく,単にソースを見るだけ.)

$ arm-linux-gnueabi-gdb tactsw.ko

逆アセンブルには,

disass 関数名

と入力する.

5.3 最終報告書

最終報告書を書く.最終報告書は txt, pdf, docのいずれで提出すること.(txtをWindowsにコピーする場合

は,geditで shift JIS, Windows改行の設定をする必要がある.授業中に指示する.)

Linux には OpenOffice というソフトが入っているので,これを使っても良い.メニュー→オフィス→

OpenOffice.org Writer を選ぶ.「エキスポート」を使えば pdfに変換できる.

ワードを使いたい場合は,PC実習室の PCを使う.(ただし,勝手に PC演習室に行かないで,まず山崎に許

可を得ること.)

babababababababababababababababababababab

整形文書の作成:

Linux において,きちんと整形された文書を書くには,TeX というソフトを使うと良い.この文書も

TeXを使って書かれている.研究論文も TeXのことが多いので,大学院に進む予定の人は,知ってお

いて損はない.この機会に自分で TeXを勉強するのも良いだろう.

最終報告書は例えば次のような章構成とする.A4で 5ページ前後とする.

表紙

【学籍番号,氏名,提出年月日】

1. はじめに

【このレポートの目的を書く】

2. 組込み開発環境の概要

29

Page 30: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

【今回の組込みシステム開発のための機器,ソフト,役割,手順などについて書く】

3. 動作の概要

【どちらのコースについても複数のプログラムから構成されている】

【これらがどのように連携しているかの概要を書く】

【通信アプリの場合,サーバ側ソフトとクライアント側ソフトの役割と通信について】

【ドライバの場合,アプリケーションとドライバの役割と連携動作について】

4. システムの詳細

【自分の作ったプログラムを掲載し,その説明を書く】

【テストについても書くこと】

【プログラム以外にもツールの使い方の工夫など,自分で考えたことを何でも書く】

【プログラムは全部載せる必要はないが,自分が手を入れた部分は載せること】

5. おわりに

【この演習で習得した知識や技術などを簡単に書く】

【この演習の感想も書く (普通の技術レポートでは感想は書かないが,これは大学の授業なので特別)】

すべてが完了したら,自分のファイルをすべて消去するため,以下を行う.まず,必要なファイルを MyVolume

などに保存する.ログアウトして,enshuでログインする.(自分がログインしたままだと自分は消去できない.)

# userdel -r 自分のアカウント名

30

Page 31: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

付録 A MyVolumeへの保存

MyVolumeのアクセスを準備する

まず,AMI (https://ami.sic.shibaura-it.ac.jp/) にアクセスして,自分のMyVolumeのサーバ名を確認してお

く.どの部分がサーバ名かよく見ること (yfx3.sic.shibaura-it.ac.jpなどの名前).

以下の手順を行う.

「場所」→「サーバへ接続」をクリック

サービスの種類を「Windows共有」にする

サーバ名: (AMIで確認したサーバ名)

共有する場所: 空欄のまま

フォルダ: 学籍番号/win/Desktop

ユーザ名: 学籍番号

ドメイン名: sic.shibaura-it.ac.jp

ブックマークを追加するにチェック

ブックマーク名は,「MyVolume」などとする

「接続する」をクリック

パスワードを入力

「ログアウトするまでパスワードを記憶する」をクリック (「期限なし」でも良い)

「接続する」をクリック

2回目以降は,ブックマークからすぐに開くことができる.

なお,授業資料のある ShareFolders にアクセスするには,上のサーバ名のところを「yshare.sic.shibaura-

it.ac.jp」とし,フォルダを,「ShareFolders」とすれば良い.

付録 B エディタ: viと emacs

B.1 vi

viはファイルを修正するときに起動し,修正が終わったら viを終了するという使い方をする.

$ vi ファイル名

viには,入力モードとコマンドモードという 2つのモードがあり,今どのモードなのかを意識しなければなら

ない.viを立ち上げたときにはコマンドモードである.コマンドモードにおける主なキーは以下の通り.

h j k l (または←↓→↑):それぞれ左下上右の移動

x (または DELETE):一文字削除 (改行は削除できない)

dd:一行削除

a:入力モードにする (入力モードからコマンドモードにするには ESC)

i:入力モードにする (入力モードからコマンドモードにするには ESC)

ZZ:保存して終了

aと iの違いは,新たな文字を,今いる文字の右に入れるか左に入れるかである.

B.2 emacs

emacsは,viと異なり立ち上げっぱなしにしておくのが普通である.

$ emacs &

31

Page 32: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

とすると,emacs用のウィンドウが新たに作られる (&の意味は C.5を参照).そちらのウィンドウに移り,^X^F

でファイルをオープンし,^X^Sでセーブするという形でファイルを修正する.

emacsは viと異なりモードはない.キー入力すると,いつでもそれに対応する動作をする.以下で,^はコン

トロールキーの同時押しを意味する.

^X^F:エディットしたいファイル名を指定する

^B ^P ^N ^F (または←↓→↑):それぞれ左下上右の移動

^D (または DELETE):一文字削除 (改行も削除できる)

^X^S:保存

^X^C:終了 (終了する必要はない)

付録 C shellプログラミング

本章では,shellでのプログラミングに関して,基本的な考え方を説明する.詳細にはついては自分で調べる.

C.1 ファイル名の指定

shellの基本は,「コマンド ファイル名」である.コマンドについては C.10を参照せよ.ファイル名の指定方法

の基本は,

/home/yamazaki/read.c

である.これは,ルート (/)の下の homeの下の yamazakiの下の read.cというファイルである.

D2/F

のように”/”で始まらないときは,今いるディレクトリが開始点となる.つまり,今のディレクトリにある Dとい

うディレクトリの下の Fである.

その他,以下のような記法がある.

. 今いるディレクトリ (ピリオド)

.. 一つ上のディレクトリ (ピリオド 2つ)

~ ホームのディレクトリ

複数のファイルを指定したいときには,?と*を使う.?は任意の一文字,*は任意の文字列という意味である.幾

つか例を挙げる.

* そこにあるファイルすべて

*z zという文字で終わるファイル

a* aという文字で始まるファイル

*m* 中に mという文字を含むファイル

? 任意の 1文字のファイル

??c 3文字のファイルで最後は cで終わるファイル

C.2 コマンドの実行

コマンドを入力すると,shellはどのようにしてそれを実行するのだろうか.

$ echo $PATH

と実行すると,コロン (�:)で区切られたディレクトリ名が表示される.shellは,これらのディレクトリを順番に

訪問し,入力されたコマンドと同じ名前のファイルがあるかを探す.もし,あればそれを実行する.このコマンド

を調べていくディレクトリ群を実行パスと呼ぶ.Linuxのコマンドがどれだけあるのか知りたかったら,実行パ

32

Page 33: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

スのディレクトリを一つ一つ lsしてみる.(演習用の debianでは 1899個ある.)

なお,

$ /home/enshu/a.out

$ ./a.out

などのようにディレクトリ名も付けて入力した場合は,実行パスとは関係なく,そのファイルをそのまま実行

する.

C.3 リダイレクション

コマンドの入力と出力をキーボードや画面でなく,ファイルに対して行うことを,リダイレクションと言う.リ

ダイレクションの一般形は,

$ コマンド <入力ファイル >出力ファイル

である.

|を使うと,コマンド1 の出力をコマンド2 の入力へリダイレクションできる.このようなリダイレクションの

ことを,パイプと言う.

$ コマンド1 | コマンド2

C.4 コマンドの便利な入力法

↑を入力すると,昔のコマンドが表示され,それを実行できることは本文で説明した.

キー入力の途中で TABを押すと候補となるファイルを教えてくれる.候補が一つしかないときは,TABを押

すとそのままそのファイルが表示される.候補が複数あるときは,TABを 2回押すと一覧が表示される.(この

ように,途中まで入力すると後を補ってくれる機能を自動補間 (コンプリーション) と呼ぶ.)

C.5 プロセス制御

$ ./aaa

と実行すると,bashは aaaを自分の中から関数のように呼び出して,それが終了するまで待っている.もし,aaa

が無限ループになってしまったりして止めたくなったときには,^Cを押す (これは Controlキーと Cキーを同時

に押すという意味).

aaaを実行したまま,bashが待たないようにすることもできる.

$ ./aaa &

と実行すると,aaaは bashと並行して動作する別プロセスとして実行される.これを裏 (background) での実行

という.bashは bashで動作しているので,別のコマンドを実行したりできる.裏で実行されるプロセスは,キー

ボードとは切り離されているので,何も入力できないし,^Cもできない.

裏のプロセスを前面にもってくるには,fgというコマンドを使う (fgは foregroundの略).逆に bgというコ

マンドもある.

プロセスにシグナルを送るには,プロセス番号に対して,

$ kill -シグナル番号 プロセス番号

を実行する.プロセス番号は,psコマンドで見ることができる.

練習: psコマンドで bashのプロセスを見つけて,それに対して,9番シグナルを送ってみよう.

33

Page 34: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

C.6 shellスクリプト

本文で述べたように,端末アプリで手で入力する内容をそのままファイルに書けば,shellスクリプトとして実

行できる.ただし,以下のことが必要である.

• スクリプトの 1行目に#!/bin/bashと書く.これにより,沢山種類がある shellのどれを使うかが指定さ

れる.2行目移行に実行したいコマンドを書く.

• chmod a+xなどでファイルを実行可能にする.

• 一般コマンドのように使いたい場合は,~/binに置く.

C.7 変数と制御

普通のプログラミング言語と同様に変数があり,繰返し実行や条件分岐もできる.これは特に shellスクリプト

を書くときに重要である.

変数への代入は =を使う.また,変数の値を取り出すには$を付ける.

x=3

echo $x

コマンドの引数に書いた値は,第 1引数は$1という特殊変数で参照できる (以下同様に$2, $3である).

echo $1 $2 $3

のようなスクリプトを作って実験してみる.

繰返しは forを使う.例えば,全ファイルを表示するには,次のように入力する.ここで,*は正規表現で,す

べてのファイルを指定している (正規表現は「データ構造とアルゴリズム」の授業で説明した).

for f in *; do

echo $f

done

条件分岐は ifを使うが,特殊な書き方なので,なかなか使いにくい.ここでは例を挙げるので,後は自分で調

べる.

if [ $x = $y ]; then

echo same1

elif [ $x -eq $z ]; then

echo same2

else

echo diff

fi

最初の ifは文字列としての比較,2番目の elifは数値としての比較である.スペースも正確に上のように書かなけ

ればならない.

C.8 shellスクリプトの例題

shellスクリプトの勉強には,例題を読んでみるのが一番てっとり早い.Linuxのコマンドの中には,shellで書

かれたものが沢山ある.

$ cd /bin

34

Page 35: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

$ file * | grep shell

こうすると,shellで書かれたコマンドが見つかる./usr/binや/etcの下などにも沢山ある.

C.9 .bashrcと alias

bashは立ち上がるときに,~/.bashrcというファイルを読み込む.自分独自の設定はここに書いておく.

特に重要なのは aliasである.aliasというコマンドを使うと,自分の好きなようにコマンドをカスタマイズで

きる.例えば,.bashrcに

alias ls=’ls -F’

と書いておくと,lsというコマンドを入力すると,自動的に-Fオプションが付いて実行される.

C.10 (この授業で) よく使う Linuxのコマンド一覧

(*)がついているのは超重要コマンド.

• adduser: 新たなユーザアカウントの追加

• alias: 別名コマンドの設定

• cat: ファイルの中身を見る (*)

• cd: ディレクトリを移動する (*)

• chmod: ファイルの状態を変更する (*)

• cp: ファイルをコピーする (*)

• echo: 画面に文字を表示する

• exit: shellを終了する

• file: ファイルの種類を表示する

• find: ファイルを探す

• ftp: ファイル転送

• gpasswd: グループの管理をする

• grep: ファイルの中から文字列を探す

• ls: ファイルの一覧を表示する (*)

• mkdir: ディレクトリの作成 (*)

• mv: ファイルを移動する (*)

• patch: パッチを当てる

• ps: プロセスの一覧を見る (*)

• pwd: 今いるディレクトリの名前を表示する (*)

• rm: ファイルを削除する (*)

• rmdir: ディレクトリを削除する (*)

• scp: マシン間でのファイル転送

• strace: コマンド実行中のシステムコールを表示する

• tar: ファイル群を一つのファイルにする/元に戻す

• which: コマンドがどこにあるか教えてくれる

C.11 makeとMakefile

Makefileの書き方は,かなり複雑である.基本だけ説明する.Makefileは,次のような記述を一つのまとまり

として,それを続けて書いていく.

35

Page 36: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

ターゲット : 依存ファイル1 依存ファイル2 依存ファイル...

<TAB>コマンド 1

<TAB>コマンド 2

<TAB>コマンド ...

これはターゲットのファイルは依存ファイル (群) に (時間的に) 依存しているという意味である.この依存関係

が満たされないとき,つまり依存ファイルのどれかがターゲットより新しいとき,コマンドが実行される.(コマ

ンドの頭には,必ずタブ <TAB>が必要である.よく忘れるので注意すること.) コマンドの行は幾つ書いても

良い.

makeコマンドを実行すると,Makefileの中の一番上 (最初) の行のターゲットを作ろうとする.(それよりも下

の依存関係は直接は関係ない.)

コマンドの行は省略できることもある.典型的なターゲットの接尾辞については,makeは何のコマンドを実行

すべきか知っているからである.例えば,”*.o” を ”*.c” から作るには ccを実行すべきということは知っている

ので,依存関係の行だけを書けば良い.

ターゲットは,存在しないファイルでも良い (依存ファイルも存在しないファイルでも良い).例えば,次の

Makefileを考える.

a: b c

b: b.o

c: c.o

aは bと cに依存するという意味なので,bと cを作ろうとする.bは b.oに依存している.”*.o”から実行ファ

イルを作る方法を makeは知っているので,これで b を作ってくれる.cも同様である.

しかし,aをどう作るかは分からない.bも cも一般的なファイルなので,aの作り方はユーザが指定しないと

makeには分からないのである.しかし,上のMakefile にはコマンドの行がない.つまり,一番最初の依存関係

は永久に満たされないことになる.したがって,makeを実行するたびに,bと cを作ろうとする.これは複数の

ターゲットを作る基本的なテクニックである.

makeの使い方

make -n と実行すると,どのようなコマンドを実行するかが表示される.実際には実行されないので,Makefile

のテストに便利である.

単に makeと実行すると,一番最初のターゲットを作ろうとする.一方,

make ターゲット

とすると,指定したターゲットを作ろうとする.よくあるのは,

clean:

rm *.o

という行を Makefile に入れておく.そして,make clean と実行する.これにより,すべての”*.o”が消され

る.(上の依存関係には依存ファイルが存在しないので,いつでも必ずコマンドを実行する.)

付録 D Cプログラミング

本章では,アプリケーションを Cでプログラミングする際に知っておきたい幾つかの技術を説明する.

D.1 Cの超基本

Cの構造体とポインタを忘れてしまった人へ.

構造体のメンバへのアクセスは,「構造体変数.メンバ名」と書く.例えば,

36

Page 37: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

x.member

ポインタの宣言,ポインタの取得,ポインタの指す先への代入

int i, *p;

p = &i; // pは iを指すポインタ

*p = 1; // pの指す先に 1を代入

*p = *p + 1; // pの指す先の値に 1を足して代入

ポインタが構造体を指している場合のメンバのアクセス

struct my_struct{

int x;

};

struct my_struct *p;

p->x = 1; // (*p).x = 1; と同じ

p->x = p->x + 1;

これだけでは思い出せない人は,かなりヤバイです.自習して下さい.

D.2 コマンドへの引数

$ ./a.out 100 200 300

のように,コマンドに引数を渡すことができる.これには mainを次のように書く.

int main(int argc, char *argv[])

{

printf("argc=%d,", argc);

printf("argv[0]=%s,", argv[0]);

printf("argv[1]=%s\n", argv[1]);

}

argcは,引数の個数+1が入っている.上の例なら 4である.「argvは配列で,その要素はポインタであり,char

を指す」のだった (「プログラミング」でさんざんやったはず).つまり,argv[0]や argv[1]は文字列である.

上の実行結果は,argc=4,argv[0]=./a.out,argv[1]=100

1個目の引数は argv[1]に入っているので,これを atoi()に与えれば intになる.

D.3 システムコール/ライブラリコールのエラー

システムコール/ライブラリコールを manで見ていると,「エラーの理由は,errnoに設定される」というよう

な記述がある.システムコール/ライブラリコールでエラーが起きると,その詳細な理由が errnoという外部変数

に代入される.値の意味は,/usr/include/asm-generic/errno-base.h等に書いてあるが,これをいちいち調べる

のは大変である.

perrorを使うと,見易い形でエラー理由を表示してくれる.

val = システムコール名 (~);

if(val < 0){

perror("コメント:");

exit(1);

}

37

Page 38: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

などとやると,とても簡単にエラーの理由が表示できる.

D.4 デバイス関連のシステムコール (特に ioctl)

デバイスに対しては,open, read, write, closeなどのシステムコールが実行できる.一つ一つの意味は明らか

と思われるが,ioctlは何に使うのかよく分からない.ioctlとは,io controlの略で,デバイスを制御するための

関数である.

ioctl(ファイル記述子, コマンド, コマンドパラメタ);

のようにして呼び出す.コマンドは,デバイスごとに独自に決められている.要するに readや writeではできな

い,「その他なんでも」のための関数である.

D.5 ソケットプログラミング

基本的な説明は本文で行った.ここでは,それ以外の点を幾つか説明する.

D.5.1 ソケットの close

ソケットはどちらから閉じても良いが,クライアントが close をしてから,サーバが close をする方が好まし

い.クライアントが closeしたことをサーバが知るには,readの戻り値が 0かを見ればよい.それを判定してか

らサーバが closeをする.(readの返す値は,自分で manで確認してみること.)

しかし,デバッグしている最中などは,プログラムがその手順で動作するとは限らない.すると,次回の bind

がエラーとなってしまう.これは,まだそのソケットが使用中と OSが判断してしまうからである.実は,そのま

ま数分待つと自動的にソケットが終了するので,また使えるようになる.しかし,それではデバッグに時間がかか

るので,次のようにすると良い.

int flag=1;

setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(int));

bind(~);

setsockoptは,ソケットにオプション設定をする.上のオプション SO_REUSEADDRは,そのアドレスが使用中で

あっても,構わず bindせよという指示である.この後 bindを実行すれば,bindはエラーにならない.ただし,

このオプションはデバッグが終わったら外した方が良い.

forkすると,ソケットもコピーされ,どのプロセスもそのソケットを読み書きできるようになる.そのような

ソケットを closeする時は注意が必要である.ソケットが forkによりコピーされた場合,すべてのプロセスでそ

のソケットを closeする必要がある.一つでも残っていると,ソケットは開いたままになる.つまり,一番最後の

closeを実行した時に,本当の close処理が実行される.

なお,closeに似たシステムコールに shutdownがある.forkでコピーされたソケットでもいきなり閉じること

ができるが,詳細は自分で調べる.

D.5.2 複数要求の受け付け

演習の例では,acceptした後にすぐ sockfdを closeしていた.listenの引数に渡したソケットは,クライアン

トからの要求を受けつけるためだけのソケットである.acceptは,要求があると新しくソケットを生成しそれ (例

題における new_sockfd) を戻す.実際の通信は new_sockfdに対して行う.

元の sockfdはどうなったのか.実は,コネクション要求は,一つだけでなく複数受けつけることができる.先

の例のように,close(sockfd);とせず,もう一度 acceptを呼べば,次の要求を受け付けることができる.

38

Page 39: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

D.5.3 複数のファイル記述子の待ち

acceptを複数回呼び出して,複数の要求を受け付け,複数のソケットが張られたとしても,複数のマシンと通

信できるわけではない.

while(1){

read(new_sockfd1, buff1, 100);

read(new_sockfd2, buff2, 100);

}

などとしたとすると,もし new_sockfd1にデータがなければ,read はそこで待ちに入る.従って new_sockfd2

に来ているデータはずっと読み込まれないことになる.

これを回避する方法の 1つは,一つのソケットに対して一つプロセスを作り,それぞれのプロセス (D.6参照)

がそれぞれのソケットを readするようにすれば良い.

もう一つの方法は poll (または select) を使う方法である.簡単に説明すると,poll を使うと,複数のソケッ

ト (あらかじめ配列に入れておく) のどれかに入力があったのかを,一度に調べることができる.入力があったソ

ケットだけを readするようにすれば,待ちに入らずにすべての入力を処理できる.

D.6 マルチプログラミング

複数の仕事を並行して実行する方法によってプログラムを作ることを,マルチプログラミングと呼ぶ.Linuxで

は,複数のプロセスを同時に実行することができる.

注意 1: ターゲットの Linux (uClinux) は,forkでなく vforkを使うなどの制限がある.ここに書いてあるこ

とは,ホスト PCの Debian等の普通の Linuxでのマルチプログラミングの方法である.

注意 2: 一つのプロセスの中で,複数のスレッドというものを動かすこともできるが,今回は説明しない.

pthreadなどのキーワードで調べてみる.

D.6.1 プロセスの生成

プロセスの生成には,forkを使う.forkは不思議な動作をするので,簡単に説明する.典型的なプログラムは

次のようになる.

pid=fork();

if (pid == 0) {

子の仕事

} else {

親の仕事

}

form()を実行すると,親プロセス (今実行中のプロセス) から子ができるが,子というのは実は親とまったく同

じである.同じプロセスが 2つ走ることになる.ただ一つ違うのが,forkからの戻り値である.子には 0が戻さ

れ,親には子のプロセス番号が戻される.従って上のプログラムでは,「子の仕事」と書いた部分と,「親の仕事」

と書いた部分が並行して動作することになる.

なお,子プロセスは親の完全なコピーなので,その時の変数の値などもそのままコピーされる.ただし,子プロ

セス側で変数に代入したりすると,それが親に伝わるわけではない.その代入は,子プロセスの中だけで有効であ

る.つまり,あくまでもコピーされるだけなのである.

D.6.2 プロセスの終了

子プロセスがmainから returnすれば,プロセスは終了する.(_exit()を呼んだ方が良いときもあるので,こ

の辺は自分で調べる.)

39

Page 40: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

親が,子供が終了したことを確認したいときや,子が main から return した値を知りたいときは,wait を使

う.上の「親の仕事」の中で,

int ret, w_pid;

w_pid = wait(&ret);

とする.retには子が返した値が入る.w_pidには,終了した子のプロセス番号が入る (親は沢山の子をもてるた

め,これが必要である).

D.6.3 別プログラムの実行

forkでは,プログラムの中に「子の仕事」として書いてあることしか実行できない.自分と関係ない別のコマ

ンドを fork して実行するにはどうしたら良いか.これには,execve (その他 execl, execv など沢山ある) を

使う.

forkした後で,子プロセスの中で execveの第一引数にファイル名を指定と呼び出す.すると,今のプロセス

がそのファイルのプログラムで置き換わり,実行が始まる.(execveを実行すると,プログラムが置き換わるの

で,execveはエラーのとき以外はリターンしない.)

付録 E Armadilloのブートの詳細

計算機のブートの仕組みは,ハードウェアによって少しずつ違うが,大雑把には同じである.電源を入れると,

ROMに入っているごく簡単なプログラムが動き始める.このプログラムがブートローダというプログラムを実行

する.Armadilloでは,ブートローダは内蔵フラッシュメモリに入っている.また,Armadillo のブートローダは

hermitという.このブートローダには 3種類のブートモードがある.モードは下記のジャンパで選ぶ.

表 2 ジャンパーピンの設定

JP1 JP2 モード

OPEN OPEN オートブートモード

OPEN SHORT 保守モード

SHORT - UARTモード

保守モードは,ブートローダだけを立ち上げて,ハードが壊れていないかをテストしたり,手動でブートしたり

するためのモードである.

UARTモードは,ブートローダそのものが壊れてしまった時の最後の手段であり,hermitを内蔵フラッシュに

書き込むことができる.

オートブートモードは,一番普通の方法である.ブートローダはオートブートモードで立ち上がると,Linuxを

立ち上げるために,カーネルイメージ (linux.bin.gz) とファイルシステムイメージ (romfs.img.gz) を取得する.

前者は OSのプログラムそのものであり,後者は OSが立ち上がるために必要な最小限のファイルをまとめたもの

である.

付録 F ドライバプログラミング

この章では,ドライバをプログラムする場合に,よく用いられる概念や技術を説明する.

F.1 デバッグ

カーネル (ドライバ) の中では,printfは使えないが,printkという関数が使える.使い方は,printfと同

じである.

ドライバーの中では,とにかくエラーチェックを徹底的にやる.ありえないと思われることでも,一応チェック

40

Page 41: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

をしておく.絶対にありえない条件が成り立ったときは,次のように書く.

if (ありえないけど念のためチェック) panic("tactsw: ~");

panicを呼ぶと,CPUのレジスタ情報などを表示して,ただちに OSが停止する.

F.2 GPIO

General Purpose IOの略である.要するに,信号ピンの現在の電圧を入力 (5Vなら 1,0Vなら 0) したり,信

号ピンに電圧を出力したり (5Vや 0V) するような IOのこと.

GPIOは頻繁に使うのでドライバを書く人のためのライブラリがある.サンプルドライバもこれを使っている.

このため,ハードの IOレジスタにアクセスする部分は,実は見えない.それをするには,かなりハードを理解し

ないと難しいので,今回は GPIOライブラリを使うことにする.

このライブラリを使うと,各デバイスの個々の信号ピンに割り当てられた gpio 番号という番号さえ分かれば,

そこへの入出力はライブラリ関数を呼ぶだけでよい.

• gpio_to_irq(GPIO): GPIO番号に対応した IRQ番号 (割り込み番号) を返す

• gpio_get_value(gpio): GPIO番号に対応する信号ピンの現在の状態を返す (1 or 0)

• gpio_request(gpio, "tactsw"): この GPIO番号の利用開始."tactsw"はデバッグやログのために使

うだけで余り意味はない.

• gpio_direction_input(gpio): この GPIO番号を入力として使うよう設定

• gpio_free(gpio): この GPIO番号の使用終了

F.3 待ち方 (=寝方),起こし方

[プロセスの待ち方 (=寝方)]

ドライバの中で待ちに入る基本関数は sleep_on().しかし,

while(条件) sleep_on()

だと,条件を見てから寝るまでの間に条件を見落とす可能性がある.

従って,普通は wait_event()を使う.これは,まずプロセスを仮の待ち状態にしてから条件を見るような方

法で,条件見落としを防いでくれる.書き方は,次の通りである (これを見ると分かるように,wait_event は関

数ではない).

wait_event(キュー, 条件);

wait_eventには,名前に_interruptibleが付いたものと,_timeoutが付いたものがある._interruptible

が付いた関数で待ちに入ると,signalがあった時に起きあがる.これを付けないと signal (例えば^C) が無視され

る._timeout が付いた関数で待ちに入ると,指定時間経過すると起きる (今回は使わない).

wait_eventなどを,割り込みハンドラの中で使ってはならない.また,割り込みを禁止した状態で使ってはな

らない.

[プロセスの起こし方]

プロセスを起こすには,wake_up()を使う.引数は,キューである.wake_up()は,キューの中で寝ているプ

ロセスのうち,non-interruptibleも interruptibleも含めて一つだけを起こす.wake_up_interruptible()は,

interruptibleで sleepしたプロセスを一つ起こす.

F.4 割り込み

request_irqの最後の引数と free_irqの最後の引数の値が一致するように注意すること.

41

Page 42: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

F.5 スピンロック

プロセスと割り込みハンドラがデバイス構造体の情報などを同時書き込みしないように,ロックを取ってから

書き込みをするという手順を守るようにする.ロックを取れるのは一つのプロセスだけなので,同時書き込みは

発生しない.実は,シングルコアであれば割り込みを禁止するだけで良いが,マルチコアのプロプロセッサでは,

物理的に複数のコアが走るので,これも対処しなければならない.このためのロックがスピンロックである.

このロックを取るのに,spin_lock_irqsave()を使う.

spin_lock_irqsave(&(tactsw_info.slock), irqflags);

spin_lock_irqsaveは,ロックを取るまで busy waitで待ち,取ったら今そのコードを実行しているプロセッ

サの割り込み (IRQ) を禁止する.(割り込み禁止前の状態は irqflagsに入る.) busy waitで待つのは,物理的に

別のプロセッサが,その slockをロックしているかもしれないからである.そのプロセッサ極短時間でロックを

解放するはずなので,解放されるまでループしながら待つ.解放されたら,自分がロックを取る.これで,もう別

のプロセッサがそのロックを取ることはできなくなった.しかし,自分自身に割り込みが入って,割り込みハンド

ラが動き,その中でロックが必要になるかもしれない.そのような事態を避けるために,割り込みを禁止するので

ある.

終了したら,

spin_unlock_irqrestore(&(tactsw_info.slock), irqflags);

これによってロックを解放する.(これにより,別のプロセッサでスピンロックしながら待っていたコードが実行

を開始するかもしれない.) そして,irqflagsを使って,CPUを元の状態に戻する.(これによって,割り込み

が許可されるので,割り込みハンドラが動き出したりするかもしれない.)

いつスピンロックを使うかだが,複数のプロセスがある一つのデータ構造を操作する可能性があるときは,いつ

でも使わなければならない.例えば,ドライバ内に何バイトデータが溜ったかを保持するカウンタなどは,割り

込みハンドラがカウンタを増やし,read時にはカウンタが減る.また,同時に各種ポインタをいじることもある.

そのような操作は,ロックを取って行う必要がある.

なお,countを読み出したり,単にインクリメントしたりする場合などまで,スピンロックをするのは面倒であ

る.その場合は,atomic_incや atomic_readを使うべきである.ただし,32ビット境界にあるデータのリード

は,多くのハードでは不可分で実行されるのでロックしなくても良い.サンプルプログラムでもしていない.

F.6 ユーザ空間とのデータのやり取り

ドライバのトップハーフは,ユーザプロセスのまま特権モードで動作している.つまり,トップハーフのメモリ

はユーザ空間の状態である.一方,キーボード (今回はスイッチ) からの入力は,割り込みハンドラによって取得

されるが,割り込みハンドラはカーネルの中にあるので,取得したデータはカーネル空間に入ってくる.カーネル

空間とユーザ空間は分離されており,カーネル空間からユーザ空間へは簡単にデータをコピーすることはできな

い.これを行う関数が,copy_to_userである.引数は以下の通り.

copy_to_user(コピー先アドレス, コピー元アドレス, バイト数);

ただし,コピー先アドレスはユーザ空間のアドレス,コピー元アドレスはカーネル空間のアドレスである.

また,今回は行わないがデータを出力 (write) することを考えると,書き込みたいデータはユーザ空間にあ

り,ドライバからデータを出力させるには,カーネル空間にデータをコピーする必要がある.これを行うのが,

copy_from_userである.

copy_to_userも copy_to_userも,「待ち」になる可能性のある関数である.従って,割り込みハンドラの中

で使ってはならない.

42

Page 43: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

なぜ copy_to_userや copy_from_userは,「待ち」になる可能性があるのだろうか.コピー元やコピー先と

して指定したユーザ空間アドレスが,仮想記憶によってページアウトされている可能性があるからである.その

場合は,ページをメモリに読み込む必要があり,これにはかなりの時間がかかる.その間,そのプロセスはスリー

プし,別のプロセスが実行される.

F.7 シグナル

カーネル (ドライバ) からユーザプロセスにシグナルを送るには,kill_pid()を使う.

kill_pid(pid番号, シグナル番号, 1);

pid番号については F.8で述べる.シグナル番号は,SIGIO, SIGUSR1などを使うと良い.第 3引数が 1のとき

は,シグナル元がカーネルであることを示す.0のときは,シグナル元がユーザであることを示す.

ドライバからプロセスにシグナルを送る標準的な方法としては,kill_fasync がある.ioctl で FASYNC オプ

ションを指定して設定する.kill_fasyncは利用手順が複雑なので今回は避けたが,kill_pidをこのような形

で利用するのは,実は余り適切ではない.

参考: kill_pid, kill_fasyncの他に,send_sigという関数もある.これは,struct task_struct *に直

接シグナルを送れる.ただし,相手が受け取れる状態にない (既に exit していたなど) と kernel panic になる.

カーネル関数にはさまざまなものがあり,利用状況にあわせて最適なものを利用する必要があるが,かなりの知識

を要する.

F.8 pid

カーネル (ドライバ) の中でプロセスを指示するには,幾つかの方法がある.

• pid_t

• struct task_struct *

• struct pid *

である (他にもあるかもしれない).

pid_tは,実体は int であり,プロセス番号そのものである.これはアプリケーションレベルではよく使われ

る.psコマンドを実行すると見ることができる.実は,プロセス番号は 32767個しかないので再利用される.最

悪の場合,昔の pid_t pidが指すプロセスは,今指しているプロセスとは別という可能性もある.(ドライバプ

ログラミングだけでなく,アプリケーションにおいてもプロセス番号だけを信じるのは実は危険である.)

struct task_struct (include/linux/sched.h) は,プロセスを表現する巨大なデータ構造である.(ここでは

タスクとプロセスは,ほぼ同じだと考えて良い.) struct task_struct *は,そこへの直接のポインタである.

pid_t のように再利用によって問題が生じることはない.しかし,そのプロセスが (exit などして) 存在しなく

なっても,ポインタとしては残る.例えば send_sig で直接 struct task_struct * にシグナルを送るとき,

カーネルは送信先のプロセスが存在するかのチェックをしないので,kernel panicになる可能性がある.カーネル

の中の信頼できるプログラムが使う関数なので,そんなチェックはしないのである.struct task_struct *に

よって指し示すことは,よく分かってやらないと危険である (カーネル内は全部そうだが).

struct pid (include/linux/pid.h) は,pid のハッシュ表の一部である.プロセス番号からプロセス本体の

データを得る場合に,32767 のテーブルを使うと無駄であるからハッシュ表を使う.この構造の一部を指すの

が struct pid *である.これは task_struct の問題も,pid_t pid のような問題もない.struct pid *と

struct task_struct * との間は以下の関数を使って,互いに行き来できる.

• task_pid(struct task_struct *): そのプロセスの pidを返す

• pid_task(struct pid *, enum pid_type): pidに対応するプロセスを返す

43

Page 44: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

もし pidに対応するプロセスがもう存在しなくても,エラーチェックをしているので kernel panicになることは

ない.その意味で安心である.kill_pidの第一引数には,これを指定する.

ただし,pidは,その pidの利用者の数を数えて管理をしている.(利用者がいなくなったらデータを捨てる等

の処理を自動的に行う.) このため,利用するときには get_pidを呼び,使い終わったら put_pid()を呼ぶのが

ルールである.次のような使い方だけを知っていれば十分なはずである.

使い始め:struct pid *my_pid = get_pid(task_pid(current));

使い終わり:put_pid(my_pid);

なおここで,currentという変数のデータ型は struct tast_struct *の変数 (実際は変数ではないが) であ

り,今,実行中のプロセスを指す.つまり,上の一行で,今実行中のプロセスの pidを取得したことになる.

F.9 カーネルタイマー

カーネルタイマーとは,カーネルコードの中で,指定時間経過にある関数を呼び出すための機構である.一般の

アプリケーションなら,sleepを使って指定した時間だけ寝るところだが,割り込みハンドラの中では寝ることは

許されない.そこで,カーネルタイマーを使う.タイマーの宣言は,次のようにする.

struct timer_list my_timer;

関連する関数は以下の通り.

• init_timer(&my_timer) 初期化 (使う前に一回だけ呼び出す)

• add_timer(&my_timer) タイマー開始

• del_timer(&my_timer) タイマー中止

add_timerの前には,必要な設定をする.典型的には次のようになる.

mytimer.expires = jiffies + INTERVAL;

mytimer.function = my_timer_handler;

ここで,jiffiesは現在時刻 (ただしミリ秒) が入っている変数である.INTERVALは何ミリ秒後に起きたいかを

指定する.my_timer_handlerは自分で定義する関数の名前である.この関数がどのようなプロトタイプでなけ

ればならないかは,自分で調べる.

なお,同じタイマー構造体を 2回 add_timerすると,NULL pointerが何たら,という kernel panicになる.

アセンブリソースを調べて,panic が発生した PC のアドレスの少し後に,mod_timer の呼び出しがあったら,

add_timerを 2回呼んでしまったということである.(add_timer は内部で mod_timerを呼んでいるので,こう

なる.)

付録 G BMP関連ソフトの解説

G.1 bmpread.c

BMP ファイルを読み込んで画像データを取り出すプログラムである.BMP ファイルはヘッダと画像データ

の 2つのパートから構成される.ヘッダは,どのような画像が格納されているかに関する情報で,画像の縦横の

サイズなどが含まれている.関数 readbmpはこのヘッダを解析する関数である.このプログラムでは,あらゆる

BMPファイルを扱えるわけではない.例えば,圧縮がかかった BMPファイルは扱えない.readbmpは,ファ

イルを bmpbuff に読み込んだ後,自分が扱えるファイルなのかの確認,ならびに画像の縦横サイズ (height と

width)の取得を行う.

44

Page 45: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

関数 showbmpは,画像データを読み込む関数である.多くの場合は,showdotを修正すれば良いはずである.

G.2 fbwrite.c

任意の色をターゲット上の液晶ディスプレイに表示するプログラムである.ハードウェアに依存したプログラ

ムのため,このまま使うしかない部分が多い.特に openfb,closefbは修正の必要はほとんどない.openfbは,

フレームバッファ (/dev/fb0) というデバイスをオープンして,画面の情報を取得する.フレームバッファとは,

表示画像のデータがそのまま入ったメモリであり,そこに書き込めば,そのまま画面に表示される.この関数の中

で最も重要なのは,mmapである.mmapとは,memory mappingの略であり,MMU (コンピューテアーキテク

チャで聞いたはず) を操作するシステムコールである.今回は,フレームバッファというディスプレイ用のメモリ

を,プログラムから普通のポインタを使ってアクセスできるように指定している.

こうすると,後は psetの中を見ると分かるように,ポインタで色を表現するデータを書き込めば,そのまま表

示される.

通常は display関数 (場合によっては psetも) を修正すれば良いはずである.一つ注意が必要なのは,colと

いう色のデータをどう作るかである.多くの場合,色は RGBの 3原色であり,各色は 8bitで表現される.実際,

BMPファイルもそのようになっている.一方,colは unsigned short,つまり 16bitの整数である.この 16bit

の中身は,上位ビットから順に赤 5bit,緑 6bit,青 5bitとする.これはハードで決まっているビット構成なので,

このようにするしかない.8bitのデータをビットを削って 5bitや 6bitにするには,Cのビット AND (&) を使っ

て捨ててしまえば良い.また,ビット位置をずらすには,<<や>>を使う.ビット位置が確定した複数のデータを

合成するには,ビット OR (|) を使う.結局,r, g, bから colを計算するプログラムは,次のようになる.

col = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);

付録 H サンプルドライバの解説

H.1 ドライバの構成

ドライバのファイルは,tactsw.cである.このファイルは,次のような順序でプログラムが書かれている.

ドライバの各関数が共通で使う外部変数 (群)

関数定義 (群)

module_init(tactsw_init);

module_exit(tactsw_exit);

MODULE_AUTHOR("Project6");

MODULE_DESCRIPTION("tact switch driver for armadillo-440");

MODULE_LICENSE("GPL");

外部変数で最も重要なのは,struct cdevである.ドライバには,character deviceと block deviceがあるが,

今回は前者で行くので,cdevを使う.

module_init(tactsw_init);は,モジュールが insmodされたときに実行される関数を登録している.次の

行は,モジュールが削除されたときに実行される関数である.その下の 3行で,ドライバの説明を登録している

(これは必須である).

従って,tactsw_init()をまず説明する.

H.2 ドライバの初期化

まず,外部変数を説明する.tactsw_buttons[]は,このドライバが扱う複数のボタンの一覧 (配列)である.

GPIOというマクロについては,別途説明する.

tactsw_devは,OSに登録するデバイス構造体である.

tactsw_info は,各関数が使うさまざまなデータが詰め込んである.特に重要なのは,char msg[] である.

45

Page 46: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

このドライバでは,ボタンをキーボードのように取り扱っている.すなわち,ボタンが押されると文字が入力され

たことになるので,それを溜めておくのが,配列 msgである.その他のメンバーについては,その都度説明する.

tactsw_init()は,ほとんどお決まりの書き方である.大雑把に説明すると,新しいキャラクタデバイス用の

メモリを OSから allocして,major番号を一つ割当てる.cdev_initで,このデバイスに関係する関数の一覧

表 (配列)である tactsw_fopsを登録する.最後に cdev_addでデバイスを OSに登録する.これで OSに対す

る処理は終わりである.

その後,tactsw_setupを呼び出す.ここでは,このデバイス特有の初期化を行う.このドライバが管理する一

つ一つのボタンついて,以下を行う.

1. 変数 gpioに gpio番号を得る

2. gpio_request()で GPIOのモジュールの利用開始を宣言する

3. GPIOは入力も出力もできるが,今回は入力モードに設定する

4. gpio_to_irq() で,そのボタンの割り込み (IRQ) 番号を得る.その番号に対して,割り込みハンドラ

tactsw_intrを登録する.

tactsw_exit()は,さまざまな登録を逆に解除していく.例えば,gpio_request()を実行したら,終わると

きには gpio_free()を実行しなければならない.

H.3 デバイスの open

static int tactsw_open(struct inode *, struct file *);

現在の実装では 2つのプロセスが同時にオープンすることに対応していないため,これを防御している.

現在のドライバは,/dev/tactsw にデータを書き込む (writer) のは割り込みハンドラだけであり,読み出す

(reader) のは openをしたプロセス 1つだけである.これは,single writer, single reader型のソフトと呼ばれる.

writer (割り込みハンドラ)は,同時に readerが動いているかもしれないことだけに注意してプログラムする.

readerは writerだけに注意してプログラムする.

一方,同時 openが複数回可能とすると,multiple readerということになる.この場合は,readerを作るとき

には,同時に別の readerがいることにも注意を払わないといけない.具体的にプログラム上のどの部分に注意が

必要になるか,自分で考えてみて欲しい.

H.4 デバイスからのデータ読み込み

static int tactsw_read(struct file *, char *, size_t, loff_t *);

第 1引数は,fileへのポインタである.今回のドライバでは,そもそも入力の元が一つしかないので,この引数

は使わない.

第 2引数は,read関数を呼んだユーザが用意したバッファのアドレスである.このバッファに対して,readし

た結果を書き込まなければならない.

第 3引数は,入力すべきデータの最大数である.

第 4引数は,入力データのオフセットであるが,今回は使わない.

wait_event_interruptibleは,ある条件が立り立つまで,スリープする関数である.第 2引数が待つべき条

件である.今回は,tactsw_info.mlen != 0となるまで待つ.メンバー mlenは,tactsw_info.msg[]に入っ

ているデータの個数である.つまり,もし msg[]にデータがなければ,データが入ってくるまで待つということ

を意味する.

これ以降は,データが msg[]にあったので,それを取り出して,readを呼び出したユーザの返すための処理で

ある.まず,mlenを read_sizeに取り出す.

copy_to_user()は,msg[]の内容を buffに,read_size個だけコピーする.これは非常に特殊な処理なの

で,よく理解する.

46

Page 47: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

read_size分のデータをユーザに渡したので,そのデータを msg[]から削除する.通常はリングバッファアル

ゴリズムを使うが,ここでは残りのデータをそのまま左にシフトしている.

H.5 割り込みハンドラ

ボタンが押されると GPIO 割り込みが発生する.OS がこれを処理し,request_irq で登録した関数である

tactsw_intrを呼び出す.

static irqreturn_t tactsw_intr(int, void *)

このときの引数は,第 1引数は IRQ番号であり,第 2引数はデバイスの IDである.

すべてのボタンに対して,以下を行う.

1. そのボタンの gpio番号に対応する IRQ番号だったときは,以下を行う

2. gpio_get_value(gpio)によって,その GPIOハードウェアの状態を得る.

3. もし,まだ msg[]に余裕があったら,以下を行う.

4. msg[]に適切な文字コードを追加する.

5. このデバイスに入力があるのを待っているプロセスがあるかもしれないので,wake_up_interruptible

を実行する.

6. 割り込みが正しく処理された場合は,IRQ_HANDLEDを戻す.

H.6 デバイスの ioctl

int tactsw_ioctl(struct inode *, struct file *, unsigned int, unsigned long);

第 1引数は inode,第 2引数はファイルポインタで,今回は使わない.第 3引数は ioctlへのコマンドで,第 4

引数はコマンドのパラメタである.

ioctlを拡張するには,この中にプログラムを作っていく.

付録 I ソースコードの解読

この章では,カーネルソースコードを読む際の注意点,幾つかのテクニック,実際に GPIO関連の部分を追い

掛けたメモなどをまとめた.興味がある人だけ読めば良い.

I.1 Platform Device

Linux 2.6 からドライバに新たに platform device というモデルが入った (従来の character device, block

device もある).ただし,ドライバとは何かを理解するには,従来のモデルの方が都合がよい.platform device

では,いろいろな抽象化が入るのでイメージをつかみにくい.

今回の演習では使わないが,platform deviceを簡単に説明する.旧来のデバイスは,デバイス固有の動作の記

述に対して,そのデバイスをシステムがどう扱うかなどが混在していた.platform device では,デバイス,バス,

ドライバを分離して作っていく.ここでバスとは,本当のバスではなくて pseudo bus.

struct platform_device は,name, id, resource (ともちろん dev へのポインタ) だけの構造体で,これが

bus に相当する.resource構造体の中にアドレス情報などを入れる.

ドライバの中では,platform_get_resource()という関数で,resourceを取ってくる.resource->startが

最初の制御レジスタのアドレス.

バスにデバイスがぶらさがり,その先にドライバがあるという構造になる.

47

Page 48: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

I.2 割り込み

デバイスドライバにおいて,割り込みハンドラを登録するには,request_irq()を使う.irqとは,interrupt

request,割り込み要求のことである.

本当の割り込みを受け付ける,OS 本体の割り込みハンドラは実は別にある.その割り込みハンドラから,

request_irq()で登録した関数を呼び出して貰う仕組みになっている.

本物のコードは,kernel/irq/handle.cの中の__do_IRQにある.

ここで,chipの ackメンバが割り込みを受けつけ,chipの endメンバが割り込みの終了 (レベル割り込みであ

れば,信号を落とさせる) 処理をする.

また,__do_IRQの中から handle_IRQ_event()が呼ばれ,この中で,割り込みハンドラ (action->handler())

が呼ばれている.この handler()が request_irq()で登録した関数である.

なお,登録部分のコードを追い掛けるには,set_irq_handler (または set_irq_chained_handler) を見る.

参考: 元の gpio_key.cの中の request_irqの引数の IRQF_SAMPLE_RANDOMは,割り込み頻度を乱数発生源

として OSが使ってよいかどうかの指定.ドライバの機能には何の関係もない.

I.3 insmodの引数を使いたいとき

MODULE_PARAMを使うと,insmodの引数を xxx_init()に渡すことができる.

I.4 printkの出力先変更

printk (pr_err, pr_info 等でも同じ) は,dmesg用バッファに出て来ている.dmesgコマンドを使えば見る

ことができる.また,/etc/syslog.confで指定しておけば,syslogに出力することができる.(要確認)

I.5 コーディングテクニック

if(unlikely(x!=0)){...}

if(likely(x!=0)){...}

意味は,unlikelyや likelyがない時と同じである.ただし,コンパイラに対して,

• ほぼ偽となるケース (unlikely),

• ほぼ真となるケース (likely)

を伝えることができる.中身は,マクロで__builtin_expectという指示になる.

I.6 gpio関係

Documentation/gpio.txtは gpiolib を使うためのガイドなので参考になる.

また,drivers/input/keyboard/gpio_keys.cは,GPIOに接続されたタクトスイッチ用ドライバである.

一番気になるのは,物理的なハードウェアであるタクトスイッチの状態をソフトが読み取っている部分であろ

う.これは gpio_get_value()という関数が行っている.全部を説明するのは無理だが,次のような順序で呼び

出しが進む.

gpio_get_value();

#define gpio_get_value __gpio_get_value [include/asm-arm/arch-mxc/gpio.h]

と定義されているので,実は__gpio_get_valueが呼ばれる.

48

Page 49: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

__gpio_get_value() [drivers/gpio/gpiolib.c] の中で

chip->get(chip, gpio - chip->base);

を実行するが,これは mx25_gpio_get()関数を呼ぶ.

mx25_gpio_get() [arch/arm/mach-mx25/gpio.c] の中で

mxc_get_gpio_datain();

mxc_get_gpio_datain() [arch/arm/plat-mxc/gpio.c] の中で

return (__raw_readl(port->base + GPIO_DR) >> GPIO_TO_INDEX(gpio)) & 1;

マクロ__raw_readは [include/asm-arm/io.h]において次のように定義されている.

#define __raw_read(a) (__chk_io_ptr(a), *(volatile unsigned int __force *)(a))

このマクロは要するに*aと書いたのと同じことである.つまり,port->base+GPIO_DRというアドレスを読ん

でいることになる.GPIOの信号ピンは,このアドレスにメモリマップされており,これにより現在の信号ピン

の状態を取得できる.

以下はメモなので,興味がある人は読んで欲しい.

●各ファイルの概要説明arch/arm/mach-mx25:board-armadillo400.harmadillo400.c : armadillo400固有.特に初期化.armadillo400_gpio.cgpio.c: mx25_xxx という関数はここで定義 (mx25はハードの名前)devices.c: いろいろなデバイスの変数宣言と初期値

arch/arm/plat-mxc:gpio.c: mxc_xxx という関数はここで定義している

drivers/gpio/gpiolib.c以下の関数 (gpio共通関数) の定義をしている:gpio_request()gpio_direction_input()

// include/asm-arm/arch-mxc/gpio.hの中で,__gpio_get_value() // #define gpio_get_value __gpio_get_value__gpio_set_value() // #define gpio_set_value __gpio_set_value__gpio_cansleep // #define gpio_cansleep __gpio_cansleep

include/asm-arm/arch-mxc: インクルードファイル (あんまり関係ないかも)

●初期化のうち gpioに関連する部分の流れarch/.../armadillo400.cの下の方

MACHINE_START(ARMADILLO440, "Armadillo-440").init_machine = armadillo440_init,

MACHINE_END↓

armadillo440_init():armadillo400_gpio_init();mxc_gpio_init();mx25_generic_gpio_init();armadillo400_key_init();

↓以下順番に追いかける.armadillo400_gpio_init() [armadillo440_gpio.c] これはあまり関係ない

mxc_gpio_init() [plat-mxc/gpio.c]メインは_mxc_gpio_initで,全 GPIOポートについて以下を行う.データの初期化:mxc_gpio_ports[] (in devices.c) の中に GPIOレジスタの物理アドレスなどが入っている.(この計算は,IO_ADDRESSマクロ (in mx25.h) を使っている.) これを使って gpio_port[]を初期化している.この表は,GPIO番号を物理アドレスや IRQ番号に対応付ける重要な表.

ハードの初期化:割り込み禁止して,irqハンドラの設定をいろいろやっている.かなり複雑.

49

Page 50: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

最終的に登録されるハンドラは,MXC_MUX_GPIO_INTERRUPTSが定義されていたらmxc_gpio_irq_handler()で,

そうでなければ,mxc_gpio_mux_irq_handler()

なお,初期化とは関係ないが,これらの中身は:実際は,mxc_gpio_mux_irq_handlerは,multiplexをバラして,一つ一つmxc_gpio_irq_handlerを呼んでいるだけ.最後は d->handle_irq(); を実行.

これが gpiolibのキモの一つ.

mx25_generic_gpio_init() [gpio.c]gpiolib.cの中の gpiochip_add() を複数回呼び出している.addしているのは GPIOの 4つのチップで,これは配列 mx25_gpio_banks[]に入っている.chip固有 (つまりハード固有) の関数はこの構造体を経由して呼ぶことになっている.chip->get() などのような形 (plat-mxc/gpio.cを参照).以下の値が関数ポインタとして定義されている.

.direction_input = mx25_gpio_direction_input

.direction_output = mx25_gpio_direction_output

.get = mx25_gpio_get

.set = mx25_gpio_set

.dbg_show = mx25_gpio_dbg_showこれが gpiolibのキモのもう一つ.

armadillo400_key_init() [armadillo400.c]mxc_register_device(&mx25_gpio_key_device,&armadillo400_gpio_key_data);

ここで,mx25_gpio_key_deviceは,devices.cで定義されており,名前を見ると "gpio_keys" である.armadillo400_gio_key_dataは構造体の表で,gpioメンバー (GPIOのポート番号),codeメンバー(KEY_ENTER等のキーコード) などを定義している.gpio_keys.cの中で出てくる buttonという変数はこれ.mxc_register_deviceは,メンバー platform_data (デバイスのプライベートデータ) に上の第 2引数

(button情報) を登録する.そして第 1引数を platform_device_register する.これによって,"gpio_keys"という名前のデバイスが登録される.この名前は,gpio_keys.cの中で登録するplatform driverの名前になる.あるドライバ関数を呼ぶときの引数に platform deviceを渡すがこの対応付けを名前でやっているのだろう.なお,gpio_request(GPIO番号,"gpio_keys")という呼び出しがある.この文字列は,カーネルメッセー

ジ,および sysfsに使われるだけらしい.

●おまじないの部分module_init(xxx_init);

↓__define_initcall("6", xxx_init, 6);

↓static initcall_t __initcall_xxx_init6 __used

__attribute__((__section__(".initcall" level ".init"))) = xxx_init;

__usedは,この変数が使われなくても warningを出すなという指示.__attribute__は,__initcall_xxx_init6を".initcall6.init"というセクションに置けという指示.

".initcall6.init"という名前のセクションが無条件に呼ばれる.これは,「.initcall.initというセクションに .initcallX.initをかき集めて入れろ」という指示がリンカースクリプトとして書いてあるから.

50

Page 51: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

tactsw_sample.c

1 /**2 * Sample driver for tact switch3 * File name: tactsw.c4 * Target board: Armadillo 4405 * For Project-enshu 6 @ Shibaura Institute of Technology6 */

7 #include <linux/module.h>8 #include <linux/kernel.h>9 #include <linux/fs.h>10 #include <linux/gpio.h>11 #include <linux/string.h>12 #include <asm/uaccess.h>13 #include <linux/cdev.h>

14 #define N_TACTSW 1 // number of minor devices15 #define MSGLEN 256 // buffer length

16 static int tactsw_buttons[] = { // board dependent parameters17 GPIO(3, 30), // SW118 #if defined(CONFIG_MACH_ARMADILLO440)19 GPIO(2, 20), // LCD_SW120 GPIO(2, 29), // LCD_SW221 GPIO(2, 30), // LCD_SW322 #if defined(CONFIG_ARMADILLO400_GPIO_A_B_KEY)23 GPIO(1, 0), // SW224 GPIO(1, 1), // SW325 #endif /* CONFIG_ARMADILLO400_GPIO_A_B_KEY */26 #endif /* CONFIG_MACH_ARMADILLO440 */27 };

28 // character device29 static struct cdev tactsw_dev;

30 // Info for the driver31 static struct {32 int major; // major number33 int nbuttons; // number of tact switchs34 int *buttons; // hardware parameters35 int used; // true when used by a process,36 // this flag inhibits open twice.37 int mlen; // buffer filll count38 char msg[MSGLEN]; // buffer39 wait_queue_head_t wq; // queue of procs waiting new input40 spinlock_t slock; // for spin lock41 } tactsw_info;

42 static int tactsw_open(struct inode *inode, struct file *filp)43 {44 unsigned long irqflags;45 int retval = -EBUSY;46 spin_lock_irqsave(&(tactsw_info.slock), irqflags);47 if (tactsw_info.used == 0) {48 tactsw_info.used = 1;49 retval = 0;50 }51 spin_unlock_irqrestore(&(tactsw_info.slock), irqflags);52 return retval;53 }

54 static int tactsw_read(struct file *filp, char *buff,55 size_t count, loff_t *pos)56 {57 char *p1, *p2;58 size_t read_size;59 int i, ret;60 unsigned long irqflags;

61 if (count <= 0) return -EFAULT;

62 ret = wait_event_interruptible(tactsw_info.wq, (tactsw_info.mlen != 0) );

51

Page 52: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

63 if (ret != 0) return -EINTR; // interrupted

64 read_size = tactsw_info.mlen; // atomic, so needless to spin lock65 if (count < read_size) read_size = count;

66 if (copy_to_user(buff, tactsw_info.msg, read_size)) {67 printk("tactsw: copy_to_user error\n");68 // spin_unlock_irqrestore()69 return -EFAULT;70 }

71 // Ring buffer is better. But we prefer simplicity.72 p1 = tactsw_info.msg;73 p2 = p1+read_size;

74 spin_lock_irqsave(&(tactsw_info.slock), irqflags);75 // This subtraction is safe, since there is a single reader.76 tactsw_info.mlen -= read_size;77 for (i=tactsw_info.mlen; i>0; i--) *p1++=*p2++;78 spin_unlock_irqrestore(&(tactsw_info.slock), irqflags);

79 return read_size;80 }

81 int tactsw_ioctl(struct inode *inode, struct file *filp,82 unsigned int cmd, unsigned long arg)83 {84 int retval=0;

85 switch(cmd){86 case 1:87 // fetch current status88 case 2:89 // set signal90 case 3:91 // clear signal92 default:93 retval = -EFAULT; // other code may be better94 }95 return retval;96 }

97 static int tactsw_release(struct inode *inode, struct file *filp)98 {99 tactsw_info.used = 0;100 return 0;101 }

102 static irqreturn_t tactsw_intr(int irq, void *dev_id)103 {104 int i;105106 for (i = 0; i < tactsw_info.nbuttons; i++) {107 int gpio = tactsw_info.buttons[i];108109 if (irq == gpio_to_irq(gpio)) {110 int mlen;111 unsigned long irqflags;112 int val = gpio_get_value(gpio);113 int ch = (val == 0)? ’1’:’0’; // val=0 when key is pushed114 spin_lock_irqsave(&(tactsw_info.slock), irqflags);115 mlen = tactsw_info.mlen;116 if (mlen < MSGLEN) {117 tactsw_info.msg[mlen] = ch;118 tactsw_info.mlen = mlen+1;119 wake_up_interruptible(&(tactsw_info.wq));120 }121 spin_unlock_irqrestore(&(tactsw_info.slock), irqflags);122 return IRQ_HANDLED;123 }124 }125 return IRQ_NONE;

52

Page 53: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

126 }

127 // .XXX = という書き方は,gccという特殊な Cに独自の機能で,128 // XXX という構造体メンバだけを初期化できる.129 static struct file_operations tactsw_fops = {130 .read = tactsw_read,131 .ioctl = tactsw_ioctl,132 .open = tactsw_open,133 .release = tactsw_release,134 };

135 static int __init tactsw_setup(int major)136 {137 int i, error, gpio, irq;

138 tactsw_info.major = major;139 tactsw_info.nbuttons = sizeof(tactsw_buttons)/sizeof(int);140 tactsw_info.buttons = tactsw_buttons;141 tactsw_info.used = 0;142 tactsw_info.mlen = 0;143 init_waitqueue_head(&(tactsw_info.wq));144 spin_lock_init(&(tactsw_info.slock));

145 for (i = 0; i < tactsw_info.nbuttons; i++) {146 gpio = tactsw_info.buttons[i];

147 error = gpio_request(gpio, "tactsw");148 // 2nd arg (label) is used for debug message and sysfs.149 if (error < 0) {150 printk("tactsw: gpio_request error %d (GPIO=%d)\n", error, gpio);151 goto fail;152 }

153 error = gpio_direction_input(gpio);154 if (error < 0) {155 printk("tactsw: gpio_direction_input error %d (GPIO=%d)\n", error, gpio);156 goto free_fail;157 }

158 irq = gpio_to_irq(gpio);159 if (irq < 0) {160 error = irq;161 printk("tactsw: gpio_to_irq error %d (GPIO=%d)\n", error, gpio);162 goto free_fail;163 }

164 error = request_irq(irq, tactsw_intr,165 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,166 "tactsw", // used for debug message167 tactsw_intr); // passed to isr’s 2nd arg168 if (error) {169 printk("tactsw: request_irq error %d (IRQ=%d)\n", error, irq);170 goto free_fail;171 }172 } // end of for

173 return 0;174175 free_fail:176 gpio_free(gpio);

177 fail:178 while (--i >= 0) {179 gpio = tactsw_info.buttons[i];180 free_irq(gpio_to_irq(gpio), tactsw_intr);181 gpio_free(gpio);182 }183 return error;184 }

185 // ・staticと付けるとこのファイル内でのみ見えることの指示.186 // 付けないと OS全体から見えるようになる.シンボルテーブルが取られる.187 // OSがリブートしない限りシンボルテーブル上で邪魔になる.

53

Page 54: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

188 // ・init __init とすると .text.initセクションに入る.189 // 不要になった時 (=init終了後) に削除してくれる.190 static int __init tactsw_init(void)191 {192 int ret, major;193 dev_t dev = MKDEV(0, 0); // dev_tは単なる int

194 printk("tactsw: tactsw_init\n");

195 ret = alloc_chrdev_region(&dev, 0, N_TACTSW, "tactsw");196 if (ret < 0) {197 return -1;198 }199 major = MAJOR(dev);200 printk("tactsw: Major number = %d.\n", major);

201 cdev_init(&tactsw_dev, &tactsw_fops);202 tactsw_dev.owner = THIS_MODULE;

203 ret = cdev_add(&tactsw_dev, MKDEV(major, 0), N_TACTSW);204 if (ret < 0) {205 printk("tactsw: cdev_add error\n");206 unregister_chrdev_region(dev, N_TACTSW);207 return -1;208 }

209 ret = tactsw_setup(major);210 if (ret < 0) {211 printk("tactsw: setup error\n");212 cdev_del(&tactsw_dev);213 unregister_chrdev_region(dev, N_TACTSW);214 }215 return ret;216 }

217 // init __exit は上と同様に .text.exit セクションに入る.218 static void __exit tactsw_exit(void)219 {220 dev_t dev=MKDEV(tactsw_info.major, 0);221 int i;

222 // disable interrupts223 for (i = 0; i < tactsw_info.nbuttons; i++) {224 int gpio = tactsw_info.buttons[i];225 int irq = gpio_to_irq(gpio);226 free_irq(irq, tactsw_intr);227 gpio_free(gpio);228 }

229 // delete devices230 cdev_del(&tactsw_dev);231 unregister_chrdev_region(dev, N_TACTSW);

232 // wake up tasks233 // This case never occurs since OS rejects rmmod when the device is open.234 if (waitqueue_active(&(tactsw_info.wq))) {235 printk("tactsw: there remains waiting tasks. waking up.\n");236 wake_up_all(&(tactsw_info.wq));237 // Strictly speaking, we have to wait all processes wake up.238 }

239 printk("tactsw: exit module\n");240 }

241 module_init(tactsw_init);242 module_exit(tactsw_exit);

243 MODULE_AUTHOR("Project6");244 MODULE_DESCRIPTION("tact switch driver for armadillo-440");245 MODULE_LICENSE("GPL");

54

Page 55: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

bmpread.c

1 //2 // A sample program to read and parse BMP file3 // For Project-enshu 6 @ Shibaura Institute of Technology4 //5 #include <stdio.h>6 #include <fcntl.h>7 #include <stdlib.h>

8 // This program does not check whether the bmp file is larger than MAXSIZE9 #define MAXSIZE 1000000010 unsigned char bmpbuff[MAXSIZE];

11 // convert 4 bytes to integer12 // i: index to bmpbuff[], 1st byte of the consecutive 4 bytes13 // return: converted integer (0 ... 2^32-1)14 unsigned int get4(int i)15 {16 return bmpbuff[i+3]<<24 | bmpbuff[i+2]<<16 | bmpbuff[i+1]<<8 | bmpbuff[i];17 }

18 // convert 2 bytes to integer19 // i: index to bmpbuff[], 1st byte of the consecutive 2 bytes20 // return: converted integer (0 ... 2^16-1)21 unsigned int get2(int i)22 {23 return bmpbuff[i+1]<<8 | bmpbuff[i];24 }

25 // do something for each dot (it’s up to you)26 void showdot(int b,int g,int r, int x, int y){27 if (y<200) return;28 if (x>70) return;29 if (x==0) printf("\n");30 if (b+g+r>300) printf("X");31 else if (b+g+r>200) printf(".");32 else printf(" ");33 }

34 // check and read BMP file35 // First of all, this function checks the BMP file’s header.36 // It confirms BMP mark, Windows bmp, 24b color, uncompressed.37 // If OK, it reads the entire BMP file into bmpbuff[].38 // return value:39 // start index of image data (when confirmed)40 // -1 when incorrect file41 int readbmp(char *filename, unsigned int *height, unsigned int *width)42 {43 int fd, len;44 fd = open(filename, O_RDONLY);45 if (fd<0) {printf("Cannot open %s", filename); return -1;}46 len = read(fd, bmpbuff, MAXSIZE);47 if (len < 0) {printf("Cannot read"); return -1;}48 close(fd);

49 if (get2(0) != 0x4d42) {printf("not bmp\n"); return -1;}50 if (get4(14) != 40) {printf("not bmp\n"); return -1;}51 if (get2(28)!=24) {printf("not 24bit\n"); return -1;}52 if (get4(30)!=0) {printf("compressed file\n"); return -1;}53 *width = get4(18);54 *height = get4(22);55 return 54;56 }

57 // Show each dot in the bmp file58 // return value: 0 normal, -1 error59 int showbmp(char *filename)60 {61 unsigned int width, height;62 int x, y, i, base, r, g, b, linesize;

63 base=readbmp(filename, &height, &width);

55

Page 56: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

64 if (base<0) return -1;

65 linesize=width*3;66 i=linesize%4;67 if (i!=0) linesize=linesize+(4-i);

68 for(y=0; y<height; y++){69 for(x=0; x<width; x++){70 b = bmpbuff[base+y*linesize+x*3];71 g = bmpbuff[base+y*linesize+x*3+1];72 r = bmpbuff[base+y*linesize+x*3+2];73 showdot(b,g,r,x,y);74 }75 }76 return 0;77 }

78 int main()79 {80 if (showbmp("free1.bmp")<0) return 1;81 }

56

Page 57: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

fbwrite.c

1 //2 // A sample program to display dots on armadillo’s LCD3 // For Project-enshu 6 @ Shibaura Institute of Technology4 //5 #include <unistd.h>6 #include <stdio.h>7 #include <fcntl.h>8 #include <linux/fb.h>9 #include <linux/fs.h>10 #include <sys/ioctl.h>11 #include <sys/mman.h>12 #include <limits.h>

13 #define FBDEV "/dev/fb0" // Name of frame buffer device14 // "/dev/fb1" is also possible

15 // Color assign is red(5bit), green(6bit), blue(5bit)16 // These are sample colors17 #define RED 0xf80018 #define GREEN 0x07e019 #define BLUE 0x001f

20 struct tagctxt{21 int fd; // file descriptor for frame buffer device22 struct fb_var_screeninfo fvsi; // frame buffer information23 struct fb_fix_screeninfo ffsi; // frame buffer information24 long int screensize; // frame buffer size (used by mmap)25 char *fb_base ; // base address of mmap26 } ctxt;

27 // Open the framebuffer28 // This function initializes the external variable ctxt.29 int openfb()30 {31 int xres,yres,bpp;

32 ctxt.fd = open(FBDEV, O_RDWR);33 if (ctxt.fd<0) {34 fprintf(stderr, "Cannot open");35 return -1;36 }37 if (ioctl(ctxt.fd, FBIOBLANK, FB_BLANK_UNBLANK) != 0) {38 fprintf(stderr, "Cannot wakeup LCD");39 return -1;40 }41 if (ioctl(ctxt.fd, FBIOGET_FSCREENINFO, &(ctxt.ffsi)) != 0) {42 fprintf(stderr, "Cannot get FSCREENINF");43 return -1;44 }45 if (ioctl(ctxt.fd, FBIOGET_VSCREENINFO, &(ctxt.fvsi)) != 0) {46 fprintf(stderr, "Cannot get VSCREENINFO");47 return -1;48 }49 xres = ctxt.fvsi.xres ; // Resolution for X direction50 yres = ctxt.fvsi.yres ; // Resolution for Y direction51 bpp = ctxt.fvsi.bits_per_pixel ; // Number of bits for each pixel52 printf("%d(pixel)x%d(line), %dbpp(bits per pixel)\n",xres, yres, bpp);

53 // Calculate the size of frame buffer54 ctxt.screensize = xres * yres * bpp / 8;

55 // Map the frame buffer device to memory.56 ctxt.fb_base = (char *)mmap(0, ctxt.screensize, PROT_READ|PROT_WRITE,57 MAP_SHARED, ctxt.fd, 0);58 if ( (int)ctxt.fb_base == -1 ) {59 fprintf(stderr, "Cannot mmap");60 return -1;61 }62 return 0;63 }

57

Page 58: プロジェクト演習 2 Linux プログラミング - SICyamaken/docs/linux.pdf · Linux の種類: Linux には種類がある.今回の演習のホスト側のLinux は,Debian

64 // Close the framebuffer65 void closefb()66 {67 munmap(ctxt.fb_base, ctxt.screensize);68 close(ctxt.fd);69 }

70 // Display a dot in the screen71 // col is a color whose bit assignment is shown above.72 void pset(int x, int y, unsigned short col)73 {74 int offset;75 offset = ((x+ctxt.fvsi.xoffset) * ctxt.fvsi.bits_per_pixel / 8) +76 (y+ctxt.fvsi.yoffset) * ctxt.ffsi.line_length ;77 *((unsigned short *)(ctxt.fb_base + offset)) = col;78 }

79 // Display dots in rectangle area80 // This function is just only a sample.81 // Modify this function to display something. (it’s up to you)82 void display()83 {84 int x, y;85 unsigned short col;

86 col = BLUE;87 for (y=0; y<240 ; y++) {88 for (x=0; x<320 ; x++) {89 pset(x, y, col);90 }91 }92 }

93 int main()94 {95 if (openfb()<0) return 1;96 display();97 closefb();98 return 0;99 }

58