2011年4月24日日曜日

XOOMが我が家にやってきた

仕事帰りに秋葉原へ行ったら、まだやってましたね。
XOOM+Pocket Wifiのセット販売

画像


Data05は要らないですが、Pocket Wifiとのセットならお得(25,100円)でポイントが10%付くので良いかなと思って買っちゃいました(^^;)
最後までOptimasPadと迷ったんですが、OptimasPadはサイズ的に中途半端な感じです。
通信もデータ通信が出来てテザリング出来るのは魅力的なのですが、Wifiルータ代わりに持ち歩くのはちょっと...
USBがホストモードに対応していて、キーボードとか繋げて遊べるのでいいのですが、そこはBTで良いかなと
ということで、最終的にはXOOMにしました。
今までWifeルータ持ってなかった(NexusOneで代用)ので、Pocket Wifi単独で使えるのも良いですね。

REGZAタブレットも6月末に予定されていますが、ちょっとそこまで待てない、そこまで購買意欲が続かない。
一応まがりにもAndroid関連の仕事することになったので、1台くらい3.0の現物持っておきたいなと。

待てる方は、夏以降の国産品を待った方が良いでしょう。
NexusTabletが出るという噂もありますし。

●開封の儀

外箱はこんな感じ
画像


ぱかっ
画像


アクセサリ
画像


サイズ比較
B5Vaioより少し小さめ
画像

XOOMのデバイスドライバーインストール

XOOMをPCに接続してみましょう。

接続後にPCのウイザードに従って、AndroidSDK配下のフォルダ(android-sdk-windows\extras\google\usb_driver)を指定してもインストールできません。

XOOM用のデバイスドライバは以下のサイトからダウンロードしてインストールします。
http://www.motorola.com/Support/US-EN/Support-Homepage/Software_and_Drivers/USB-and-PC-Charging-Drivers

画像



現在のバージョンは4.9.0

インストーラを起動します。
画像


ライセンス
画像


インストール
画像


インストール完了
画像


PCから見えるようになります。
EclipseからもDDMSで中身が見えるようになります。

XOOMでBluetoothキーボード接続

前に買ったBTキーボードがころがってたのでXOOMとつなげてみました。

設定⇒無線とネットワーク⇒Bluetooth設定
でScanして、ぺリングしてやれば、あっさり繋がりました。

画像


こんな感じ
ノートPCに比べれば軽いので、勉強会の時のメモとかネット接続とか、これで良いかも
ただ、入力が英語配列になってしまうのが難点
画像

2011年4月22日金曜日

第16回 V7から始めるUNIX講座 復習とまとめ(システムコール落穂拾い)

第16回 V7から始めるUNIX講座(システムコール落穂拾い)

今回で16回目になります、早いものです。
ずいぶん続きましたが、諸事情により、第16回でいったん区切りとさせて頂く事になりました。
私自身も非常に勉強になりました、まごろくさん及び聴いていただいた方々に感謝いたします。

●前回の補足

exit
339行目:FDは全てクローズ
⇒UNIXでのポイント
ここで必ずクローズするので、exitするならFDをCloseする必要はありません。
342行目:inodeのリファレンスカウントをデクリメント。inodeのリファレンスカウントが1ならinodeを開放してパイプをアンロック
⇒ここもポイント。プロセス毎にカレントディレクトリを握っています。そのためここで参照を外しています。
348行目:acctでアカウンティングファイル?に書き込みをしている
⇒acct()が課金情報の書き込み。大昔のコンピュータは高価だったので、CPUの使用時間で従量制の料金を払ったそうなので、その元になる情報を書き込んでいます。

wait
waitはゾンビになったプロセスの後始末をします。
413行目:u_procp(アドレス)を指定してスリープします。
誰かがwakeupするまで、ずっとここで寝ています。

413 sleep((caddr_t)u.u_procp, PWAIT);

バックグラウンド”&” で起動したプロセスの終了はSIGCHILDのsignalが来るのでその時にwaitすることで、後処理をします。



「システムコール番号の歴史」
@oracchaさんのまとめられた、システムコール番号の歴史を元に振り返ってみます。
http://tiki.is.os-omicron.org/tiki.cgi?c=v&p=%A5%B7%A5%B9%A5%C6%A5%E0%A5%B3%A1%BC%A5%EB%C8%D6%B9%E6%A4%CE%CE%F2%BB%CB

表の見方、
UNIX v7、MINIX 1.0、Linux 2.4.18、FreeBSD 4.5の1から100番まで
空欄はUNIX v7と同じという意味です。
番号が若いものは昔からあるシステムコール

ざっとですので、説明が抜けているところもありますが、ご容赦下さい。

exit 第15回プロセスの停止でやりました。
fork 第1回でやりました。
read ファイルシステムの辺りで軽くやりました。★
write ファイルシステムの辺りで軽くやりました。★
open ファイルシステムの辺りで軽くやりました。★
close ファイルシステムの辺りで軽くやりました。★
wait 第15回プロセスの停止でやりました。
creat ファイルシステムの辺りで軽くやりました。★
link ファイルシステムの辺りで軽くやりました。★
unlink ファイルの削除
exec 第2回でやりました。
chdir カレントディレクトリの変更
time プロセスの実行時間
mknod 特殊ファイルや通常のファイルを作成する
chmod パーミッションの変更
chown オーナーの変更
break BSSの下の端を下げます。メモリの使用領域の境界を下げる(だけ。)その当時malloc 自分で作るモノだったようです。
stat スーパーブロックの情報です(du, dfはボリューム全体の情報を示す)
seek File構造体のoffsetの値を変更する
getpid プロセスIDの取得
mount ファイルシステムでやりました。
umount ファイルシステムのアンマウント
setuid プロセス生成時にファイルの所有者(通常はroot)の権限を得られる
getuid ユーザIDを取得
stime
ptrace デバッガで使います
alarm
fstat 現在オープンしているファイルの情報を表示します
pause
utime
stty sttyシステムコールの実体、機能の多くはioctlに吸収されました。
gtty
access
nice プロセスの優先度の重み付けに使います。
ftime
sync ダーティなキャッシュをディスクに書き出します。sync;sync;syncで電源ブチが正式な作法(^^;)
kill 第13回シグナルでやりました
switch
tell
dup 第14回パイプでやりました
pipe 第14回パイプでやりました
times
prof
setgid
getgid
sig 第13回シグナルでやりました
acct アカウント
phys ?
lock ファイルロック
ioctl
mxpchan
exece
umask デフォルトでのファイル作成時のパーミッションを指定
chroot 特定のディレクトリをROOTと見なす

★ファイルシステムは排他が細かくて読むのが大変

補足
V7にはmkdirがありません、mknodとlinkを組み合わせていたが問題がありました。
mknod とlinkの間でシステムがダウンあるいはCtrl+Cするとでは不整合が起き得るので後にアトミックなmkdir が用意されました。

UNIX以前(ジュラ紀)はファイルひとつ作るのも大変でした。UNIXはファイル名ひとつでファイルを生成できます。
ジュラ紀のファイルシステムはバイトストリームではありませんでした。
⇒ISAMファイルに近い(構造を持つ)形式だったようです。

ファイルシステムの違い
「UNIX以前のファイルは構造を持つのが普通。UNIXは構造を待たないバイトストリーム。」

この「構造を持つ必要のあるファイル」はデータベースに進化しました。

すでに絶版の名著、「UNIXシステムコールプログラミング」と「UNIXプログラミング環境」などを写経するのが、昔ながらの学習方法のようです。

主要な内容はV7で学ぶUNIX講座で取り上げており、これを元に細かく見たりあるいはLinuxの実装を確認したりするには十分な内容だった(システムを俯瞰するという意味で)と思います。

2011年4月21日木曜日

ACTION_BOOT_COMPLETEDによるアプリケーションの自動起動

この前、中文の記事を翻訳しました。
http://xiangcai.at.webry.info/201104/article_4.html

Androidの起動の手順に関してです。
以下の記述がありました。

ブート後に自動起動されるアプリケーション
ActivityManagerService内でアクティビティのアイドルが決定したとき、ActivityManagerServiceは最初にACTION_BOOT_COMPLETEDインテントをブロードキャストします。

なので、これでブート後にアプリケーション(Androidアプリ)を自動的に起動できるんだろうなと思っていたら、今日TL上で話題に上がっていたのでメモしておきます。

BroadCastReceiverでACTION_BOOT_COMPLETEDインテントを受けとれば良いようです。
ただし、onReceiveの実行には時間制限があるので、ここで処理はせずに目的の処理を起動するようにした方が良いようです。
http://developer.android.com/guide/practices/design/responsiveness.html

@itogさんのブログに詳細が書いてあります。
http://d.hatena.ne.jp/itog/20100611/1276218418

amコマンドを使ったIntentの投げ方も参考になります。
http://d.hatena.ne.jp/itog/20100622/1277174913

2011年4月18日月曜日

[翻訳] Androidの起動工程

twitterでAndroidの起動に関する文章が紹介されていました。
http://www.huomo.cn/developer/article-b95a.html(中文)

なかなか良い資料だったのでざっくり翻訳してみました。
中文と途中から英文でしたが、何とか大意は取れました。
↓以下は中国語で火魔网の記事を翻訳しましたという説明(念のため)
--------------------中文--------------------------
你好,这个文件不是我写的,翻译成而已。
原文是火魔网(www.huomo.cn)的[android启动流程]
http://www.huomo.cn/developer/article-b95a.html
这个文件的内容很好,我想给日本朋友介绍一下。
所以翻译成日语。
--------------------------------------------------



Androidの起動工程

●SystemServerの起動
frameworks/base/services/java/com/android/server/SystemServer.java: run()
その中で、ActivityManagerService.installSystemProviders()を使用します。
最後には、frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/配下の
各content providersのonCreate()関数を使用して、データベースの内容を初期化します。
画像


other ref:http://www.cnblogs.com/jacktu/archive/2010/07/02/1769939.html

AndroidはLinuxシステム起動の4つの手順からなります。

(1) initの起動

(2) Nativeサービスの起動

(3) System Server,Androidサービスの起動

(4) Homeの起動

全体の起動の枠組みは図のようになります。

画像


・第一歩:initial工程(system\core\init)
initを実行します。initはカーネルが起動するユーザプログラムの1つです。
カーネルが自分で起動(既に搭載されたメモリ、運行開始、さらに既に初期化された全てのプログラム、データベースなど)の後、1つのユーザプログラムinitの起動を通じて、工程の完了を導きます。initは第一番目の工程です。

Init.rc

Init.marvell.rc
画像


Initはinit.rcとinit.xxx.rcスクリプトにより、いくつかの基本的なサービスを起動します。

servicemanamger
zygote
。。。

最後にInitは終了しないだけでなく、さらにproperty serviceの機能を担当します。

1.1スクリプトファイル
init@System/Core/Init

Init.c: parse_config_file(Init.rc)

@parse_config_file(Init.marvel.rc)

スクリプトファイルの解析:Init.rcとInit.xxxx.rc(ハードウエアプラットフォームに応じて)

Init.rcはAndroidが自分で規定した初期化スクリプトです(Android Init Language, System/Core/Init/readme.txt)

そのスクリプトは4つのタイプがあります。

Actions
Commands
Services
Options.

1.2 サービス起動のメカニズム
Initがどのようにrcファイルを解析してサービスを始めるのか見てみましょう。

(1)rcファイルをオープンして、ファイルの内容を解析@ system\core\init\init.c

serviceの情報をservice_listに格納します。@ system\core\init parser.c

(2)restart_service()@ system\core\init\init.c

service_start

execve(…).serviceプログラムの起動。

・第二歩 Zygote
ServicemanagerとzygoteはAndroidの基礎を決定します。Zygoteこのプログラムによって、真のAndroid実行空間を獲得します。
初期化されたサービスは全てNavtive serviceでrcスクリプトファイル中のzygoteの記述です。

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server

そのため、Zygoteはmain(…)@frameworks\base\cmds\app_process\app_main.cppから開始します。

(1) main(…)@frameworks\base\cmds\app_process\app_main.cpp

Java Runtimeの開始
runtime.start("com.android.internal.os.ZygoteInit", startSystemServer);
(2) runtime.start@frameworks/base/core/jni/AndroidRuntime.cpp

仮想マシンの起動:startVM(...)
com.android.internal.os.ZygoteInit:main関数の実行
(3)main()@com.android.internal.os.ZygoteInit//本当のZygote。

registerZygoteSocket();//Listenソケットの登録
startSystemServer();
Zygoteサービスフレームワークに入ります。
このいくつかの手順を通して、Zygoteの起動がうまくいけば、Socket通信を利用してActivityManangerServiceの要求を受け取って、アプリケーションプログラムをForkします。

・第三歩 System Server
1つのプログラムのforkにおいて:com.android.server.SystemServer.つまりSystemServer@(SystemServer.java)が実行されます。
Androidの全てのサービス、循環フレームワーク?はSystemServer@(SystemServer.java)上で実行されます。
SystemServer.javaにおいて循環構造は見えません、init2の実現関数のみが見えます、サービスを実行し、さらにAddServiceによってservice Managerにアクセスします。

main() @ com/android/server/SystemServer

init1();

Init1()はNative空間で実現されています(com_andoird_server_systemServer.cpp)。この関数は見ればすぐ分かります。init1->system_init() @System_init.cpp

system_init()において、循環を閉じるための管理フレームワークを見ることが出来ます。

Call "com/android/server/SystemServer", "init2"

…..

ProcessState::self()->startThreadPool();

IPCThreadState::self()->joinThreadPool();

init2()@SystemServer.javaの中でAndroidの全ての必要とするサービスを実行します。

このinit2()はNew ServiceとAddServiceによってサービスを起動します。

・第4歩 Home起動
後半において、システムが全てのAndroidサービスの起動が完了するところを見ることが出来ます、このような一連の動作によって

(1)xxx.systemReady()を使用して各サービスに準備完了を通知

(2)ActivityManagerService.systemReadyに対しては特別です

Widget.wallpaper,imm(インプットメソッド)等のready通知。

HomeはActivityManagerService.systemReady()の通知の過程で実行します。下のActivityManagerService.systemReady()の仮ソースコードです。

systemReady()@ActivityManagerService.java

resumeTopActivityLocked()

startHomeActivityLocked();//もし第1の起動がHomeActivityであれば

startActivityLocked(。。。)CATEGORY_HOME android起動の工程
このトピックにおいて、Androidのプロセス管理に関してのいくつかの情報を学ぶことが出来る。最初にAndroidのブートを通じてプロセスの起動を見てみましょう。

USER PID PPID VSIZE RSS WCHAN PC NAME

root 1 0 264 176 c00acc6c 0000c36c S /init

root 28 1 724 308 c0051354 afe0c4cc S /system/bin/sh

system 30 1 796 248 c026516c afe0b74c S /system/bin/servicemanager

root 31 1 1824 316 ffffffff afe0b50c S /system/bin/mountd

root 32 1 652 248 c02976e0 afe0c0bc S /system/bin/debuggerd

radio 33 1 5344 664 ffffffff afe0bdbc S /system/bin/rild

root 34 1 71028 18828 c00ad308 afe0b874 S zygote

media 37 1 16812 3456 ffffffff afe0b74c S /system/bin/mediaserver

root 39 1 788 288 c02f9ae4 afe0b50c S /system/bin/installd

system 86 34 187756 21836 ffffffff afe0b74c S system_server

radio 118 34 103476 13896 ffffffff afe0c824 S com.android.phone

app_4 124 34 117848 19248 ffffffff afe0c824 S android.process.acore

app_5 139 34 98672 11516 ffffffff afe0c824 S com.android.mms

app_3 151 34 92096 10976 ffffffff afe0c824 S com.android.alarmclock

app_6 161 34 94436 12616 ffffffff afe0c824 S com.android.calendar

app_9 173 34 93248 11728 ffffffff afe0c824 S android.process.media

app_15 182 34 91848 9764 ffffffff afe0c824 S com.android.voicedialer

app_16 190 34 94524 10812 ffffffff afe0c824 S android.process.im

これらは3種類に分けることが出来ます。

(1)Root Process
initはカーネルがブートした後の最初のプロセスです。主な仕事は以下を実行することです。

・init.rcとinit.%hardware%.rcを解析して実行する
・/dev配下のデバイスノードを自動的に生成する
・ログサービス、プロパティサービスを開始する
・デバイスのためにプロパティを設定する、そして子プロセスの終了イベントを監視する

(2)Native Application Process
init.rcにより、initは以下のネイティブアプリケーションプロセスをforkする

console: シェルをスタートする

servicemanager: binder IPC service managerを起動する

mountd: もし起動後、いずれかのファイルシステムへローカルソケットを通じてコマンドを受信したら、/system/etc/mountd.confで定義された全てのファイルシステムをマウントする。

debuggerd: debug systemを開始する

rild: radio interface layer daemonを開始する

zygote: Android Java VM Runtimeとsystem serverを開始する. これらは最も重要なプロセスです

mediaserver: AudioFlinger, MediaPlayerService, CameraServiceを開始する.

installd: install package daemonを開始する

(3)JAVA Application Process
全てのJavaアプリケーションプロセスはzygoteプロセスからForkされます。system_serverは特別なJavaプロセスで、zygoteから直接Forkされます。
その他のJavaプロセスはActivityManagerService(system_serverプロセス上で実行)から生成されます。例えばこのように

int pid = Process.start("android.app.ActivityThread",

mSimpleProcessManagement ? app.processName : null, uid, uid,

gids, ((app.info.flags&ApplicationInfo.FLAG_DEBUGGABLE) != 0), null);

Process.javaがzygoteと通信する間はUNIXドメインソケットを使用します。全体像を以下の図に示します。

画像


System Server
zygoteによって起動される最初のJavaアプリケーションです。System Serverは、コアとなるAndroidサービスを開始します、例えばActivityManager, WindowManager, PackageManagerなどなど。System ServerはAndroidのコアエンジンです。

Persistent Application(永続的アプリケーション)
ブート中にActivityManagerService.systemReadyは、全ての永続的アプリケーションを開始します。


List apps = ActivityThread.getPackageManager().getPersistentApplications(PackageManager.GET_SHARED_LIBRARY_FILES);

if (apps != null) {
int N = apps.size();
int i;

for (i = 0; i < N; i++) {

ApplicationInfo info = (ApplicationInfo)apps.get(i);

if (info != null && !info.packageName.equals("android")) {

addAppLocked(info);

}
}
}


現在では、Phone applicationのみが永続的アプリケーションとしてAndroidManifest.xmlに以下のように登録されています。

<application android:name="PhoneApp"

android:persistent="true"

android:label="@string/dialerIconLabel"

android:icon="@drawable/ic_launcher_phone">

そのため、ブート中はphone applicationのみが自動的に起動されます。それは"com.android.phone”プロセスです。

The First Activity
最初のアクティビティはActivityManagerServiceからIntent.CATEGORY_HOMEインテントを送信することで起動されます。

Intent intent = new Intent(mTopAction,mTopData != null ? Uri.parse(mTopData) : null);

intent.setComponent(mTopComponent);

if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
intent.addCategory(Intent.CATEGORY_HOME);
}

ActivityInfo aInfo = intent.resolveActivityInfo(mContext.getPackageManager(), PackageManager.GET_SHARED_LIBRARY_FILES);

if (aInfo != null) {
intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));

// Don't do this if the home app is currently being

// instrumented.

ProcessRecord app = getProcessRecordLocked(aInfo.processName,

aInfo.applicationInfo.uid);

if (app == null || app.instrumentationClass == null) {
intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);

startActivityLocked(null, intent, null, null, 0, aInfo,null, null, 0, 0, 0, false);

}
}

それは、“android.process.acore”プロセスです。(プロセス名は、AndroidManifest.xmlに定義されています)

ブート後に自動起動されるアプリケーション
ActivityManagerService内でアクティビティのアイドルが決定したとき、ActivityManagerServiceは最初にACTION_BOOT_COMPLETEDインテントをブロードキャストします。


if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {

// Tell anyone interested that we are done booting!

synchronized (this) {

broadcastIntentLocked(null, null, new Intent(Intent.ACTION_BOOT_COMPLETED, null), null, null, 0, null, null, android.Manifest.permission.RECEIVE_BOOT_COMPLETED, false, false, MY_PID, Process.SYSTEM_UID);
}
}


現在、MMS, AlarmClock, Calendar, MediaProvider, VoiceDialer と IMがACTION_BOOT_COMPLETEDインテントのレシーバとしてAndroidManifest.xmlに登録されています。そのため、それらのは自動的に起動されます。(これはJavaプロセスが残ることを意味します)

EmailもACTION_BOOT_COMPLETEDのレシーバとしてAndroidManifest.xmlに登録されています。しかし、android:enable=”false”と定義されているので起動しません。


<receiver android:name=".service.BootReceiver" android:enabled="false">

<intent-filter>

<action android:name="android.intent.action.BOOT_COMPLETED" />

</intent-filter>

<intent-filter>

<action android:name="android.intent.action.DEVICE_STORAGE_LOW" />

</intent-filter>

<intent-filter>

<action android:name="android.intent.action.DEVICE_STORAGE_OK" />

</intent-filter>

</receiver>


DownloadProviderもACTION_BOOT_COMPLETEDのレシーバとしてAndroidManifest.xmlに登録されています。しかし、android:enable=”false”と定義されているので起動しません。


<receiver android:name=".DownloadReceiver" android:enable="false">

<intent-filter>

<action android:name="android.intent.action.BOOT_COMPLETED" />

<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />

</intent-filter>

</receiver>


Javaプロセスの背後
system_serverは特別な事例です。system_serverはActivityThread.javaのsystemMain static functionを呼び出します、それはActivityThreadのインスタンスを生成します。
ActivityThreadはそれから、アプリケーションスレッド、アプリケーション、アプリケーションコンテキストのインスタンスを生成します、

その他の全てのJavaプロセスは、異なった方法で動作します。それはzygoteにより、Forkされたsystem_serverによって制御されます。
system_server以外のいずれかのJavaプロセスがzygoteからForkされた時、そのJavaプロセスは、自動的にActivityThread.javaのmain static functionを呼び出します(Process.javaと以下のコードの切れ端を見てください)

try {
ZygoteInit.invokeStaticMain(cloader, className, mainArgs);
} catch (RuntimeException ex) {

logAndPrintError (newStderr, "Error starting. ", ex);

}


ActivityThread.javaのmain関数は、ActivityThreadのインスタンスを生成します。ActivityThreadはそれから、ApplicationThreadのインスタンスを生成します。
ApplicationThreadはsystem_server内のActivityManagerServiceと共に情報を交換するためのIBinder objectとして動作します。
新しいプロセスは、この時は、system_serverからのIPCコールの待ちうけ以外何もしません。
アプリケーションとアプリケーションコンテキストは、この段階では生成されません。事実、プロセスが本当に動作するときまで延期されます、
例えば、アクティビティの開始、インテントの受信あるいはサービスの開始などなど

例えば、アクティビティが開始するとき、ActivityManagerServiceはどのプロセスが開始するべきか知っています、
そのためRPCはそのプロセス内で新しいアクティビティを起動するために、ApplicationThreadのscheduleLaunchActivityを呼び出します。
ApplicationThreadはそれから、ActivityThreadに対してアクティビティを起動する必要があることを知らせるためにメッセージを送信します。
ActivityThreadはそれからアプリケーションとアプリケーションコンテキストを生成します。その後、Instrumentationを呼び出します。
Instrumentationはそれから、最後に、本当にアクティビティのJavaオブジェクトを生成するためにJAVA dalvik VMを呼び出します。

2011年4月14日木曜日

Multiple substitutions specified in non-positional

Androidの開発本を見ながら、ぼちぼちと

ソースも公開されているので、ダウンロードしてプロジェクトにインポートしたら
大量のエラーが!!
プロジェクトのクリアをしても解決せず。
R.javaが生成されていないためでした。

経験的にこういう場合はリソース系のレイアウトとかStringとかに間違いがあって生成されないですね。
ということで見てみると、「string.xml」でエラーが発生していました。

Multiple substitutions specified in non-positional format
という良く分からないエラー
ぐぐって見ると
http://blogs.yahoo.co.jp/bearrolling/23838548.html

同じですね。
%でフォーマットしていますが、引数が2つのところがエラーに
なので%の後ろに「順番$」と追記したら消えました。

何とも解せない話ですが、まずは解決したのでよしとしますか

2011年4月10日日曜日

第15回 V7から始めるUNIX講座 復習とまとめ(shellのなぞ、そしてプロセスの停止処理)

第15回 shellのなぞ、そしてプロセスの停止処理

●前回の補足

・forkとexecのなぞ
forkしてexecというのは知っていましたが何故というのはずっともやもやでした。
forkしてからexecしないことはないので最初から目的とするプロセスを起動すれば良いのにと。
前回でパイプをやりましたがそれでやっと分かりました。
forkして分身を作ってからexecする間に標準入出力をパイプに切り替えるために、分かれているんですね。

・dup2について

dup2(fildes, fildes2)

fildesをfildes2にコピーします。
fildesは既にオープンしているファイルのFD(コピー元)です。
fildes2がfildesと等しくなく、既にオープンしているファイルのFDの場合、まずクローズされます。

なので

close(fildes2)
dup(fildes)

と同じです。


fd = open("file", O_RDWR);
dup2(fd, 1)

とすれば、標準出力がファイルに切り替わります。

・パイプの実体
メモリ上に構築されていると思っていたのですがソースを辿っていくと
http://www.tamacom.com/tour/kernel/unix/S/8.html#L72


70 int rootdev = makedev(0, 0);
71 int swapdev = makedev(0, 0);
72 int pipedev = makedev(0, 0);


makedev(0, 0)としていますので通常のファイルシステムと同じように見えます。
これは今後の課題ということで



●プロセスの停止処理

shell上で
$ sleep 10
と実行した場合には、10秒経ってからプロンプトが表示されます。

$ sleep 10 &
と実行した場合には、すぐにプロンプトが表示されます。

この違いは何でしょうか?
通常の場合、親プロセスはforkした後でwaitで子プロセスの終了を待ちます。
&を付けた場合はwaitをしないので即時にコマンドプロンプトへ戻ってきます。

画像



プロセスの停止で参照すべきシステムコールは、exitとwaitになります。


●処理の概要
プロセスには親子関係があるため、プロセスが停止する場合には自分の子(図では孫プロセス)と親(図では親プロセス)に対して適切な処理をしないと資源が開放されずに残ったり、プロセスの行き場がなくなったりします。
概要としては
・自分の資源は自分で出来るところまで解放
・自分の子プロセスの親を1(init)にして引き継ぎ
・出来るところまでやったら、後は親プロセスに依頼

画像





●exit
マニュアルのP222

exit - terminate process
exit(status)

Exit is the normal means of terminating a process. Exit closes all the process’s files and notifies the
parent process if it is executing a wait. The low-order 8 bits of status are available to the parent process.
This call can never return.


(要約)
Exitはプロセスを終了する通常の手段です。
もし、親プロセスがwaitを実行している場合、Exitは全てファイルをクローズして親に通知します。
ステータスの下位8ビットは親プロセスが利用できます。このコールはリターンしません。

ソースファイルを見てみます。
http://www.tamacom.com/tour/kernel/unix/S/98.html#L325

基本はプロセスを破棄するために各種資源を初期化して、ゾンビ状態になります。

まずは自分の資源の解放
331行目:u構造体をたどってプロセス構造体をpに代入
332行目:プラグを落として
333行目:クロックタイムを0に初期化
334~335行目:シグナルu_signalの配列を全て1で初期化(0じゃないの?)
338行目:FDを格納している配列:u_ofileを全てNULLで初期化
339行目:FDは全てクローズ
341行目:パイプをロックして
342行目:inodeのリファレンスカウントをデクリメント。inodeのリファレンスカウントが1ならinodeを開放してパイプをアンロック
347行目:xfreeでプロセスの共有テキストセグメントの使用を破棄します
348行目:acctでアカウンティングファイル?に書き込みをしている
349行目:mfreeでユーザ空間を開放
350行目:プロセスの状態をゾンビ(SZOMB)に設定
351行目:xp_xstatにパラメータとして受け取ったExit番号を格納(これがwaitに渡ります)
352行目:ユーザタイムを計算します
353行目:システムタイムを計算します

子プロセスへの処理です。
354~360行:自分の子プロセスを探して後処理をします。プロセス構造体は最初からなめます。
 355行目:プロセスの親プロセス番号が自分のプロセス番号と等しい(つまり自分の子プロセス)
 356行目:プロセス番号1(init)を起こします
 357行目:子プロセスの親プロセスを1(init)に変更します
 358、359行:子プロセスがSTOPしているときは実行(RUN)状態にします

ここまでやれることは全部やって、最後の処理は自分で出来ないので親を起こしてお願いします
361~366行:自分の親プロセスを捜して後処理を依頼します。プロセス構造体は最初からなめます
362行目:プロセスの番号が自分の親プロセス番号と等しい(つまり自分の親プロセス)
363行目:親プロセス(q)を起こします。
364行目:プロセスをスイッチします。
プロセススイッチするので、Exitはリターンしません。



318 /*
319 * Release resources.
320 * Save u. area for parent to look at.
321 * Enter zombie state.
322 * Wake up parent and init processes,
323 * and dispose of children.
324 */
325 exit(rv)
/* */
326 {
327 register int i;
328 register struct proc *p, *q;
329 register struct file *f;
330
331 p = u.u_procp;
332 p->p_flag &= ~(STRC|SULOCK);
333 p->p_clktim = 0;
334 for(i = 0; i < NSIG; i++)
335 u.u_signal[i] = 1;
336 for(i = 0; i < NOFILE; i++) {
337 f = u.u_ofile[i];
338 u.u_ofile[i] = NULL;
339 closef(f);
340 }
341 plock(u.u_cdir);
342 iput(u.u_cdir);
343 if (u.u_rdir) {
344 plock(u.u_rdir);
345 iput(u.u_rdir);
346 }
347 xfree();
348 acct();
349 mfree(coremap, p->p_size, p->p_addr);
350 p->p_stat = SZOMB;
351 ((struct xproc *)p)->xp_xstat = rv;
352 ((struct xproc *)p)->xp_utime = u.u_cutime + u.u_utime;
353 ((struct xproc *)p)->xp_stime = u.u_cstime + u.u_stime;
354 for(q = &proc[0]; q < &proc[NPROC]; q++)
355 if(q->p_ppid == p->p_pid) {
356 wakeup((caddr_t)&proc[1]);
357 q->p_ppid = 1;
358 if (q->p_stat==SSTOP)
359 setrun(q);
360 }
361 for(q = &proc[0]; q < &proc[NPROC]; q++)
362 if(p->p_ppid == q->p_pid) {
363 wakeup((caddr_t)q);
364 swtch();
365 /* no return */
366 }
367 swtch();
368 }




●wait
マニュアルのP263

wait – wait for process to terminate

wait(status)
int *status;
wait(0)

Wait causes its caller to delay until a signal is received or one of its child processes terminates. If any
child has died since the last wait, return is immediate; if there are no children, return is immediate with
the error bit set . The normal return yields the process ID of the terminated
child.
If (int)status is nonzero, the high byte of the word pointed to receives the low byte of the argument of
exit when the child terminated. The low byte receives the termination status of the process.
A special status(0177) is returned for a stopped process which has not terminated and can be restarted.
If the parent process terminates without waiting on its children, the initialization process (process ID =
1) inherits the children.

(要約)
Waitはシグナルを受信するかそのプロセスのいずれかが終了するまで、呼び出し元に遅延を発生させます。
もし最後のwait以降に子プロセスが死亡した場合は、ただちにリターンします。
もし子プロセスが存在しない場合はエラービットを設定してただちにリターンします。
もしstatusが0でない場合、WORDの上位バイトはexitが終了したときのexitの引数下位バイトを示します。
下位バイトプロセスの終了ステータスを受け取る。

特別なステータス(0177)は終了しておらず再スタートできる状態の停止しているプロセスに対して返されます。
もし、親プロセスが子プロセスを待たずに終了した場合、初期化プロセス(プロセスIDが1)が子供を継承します。


waitソースを見てみます。
exitで子プロセスが出来るところまで自分でやって親を起こしたので、これは親プロセス側の後処理になります。

385~411目:プロセス構造体は最初からなめます
386行目:プロセスの親プロセス番号が自分のプロセス番号に等しい(つまり自分の子プロセス)
388行目:子プロセスの状態がゾンビ状態だったら以下を実行
389行目:システムコールのリターン値1に子プロセスのプロセス番号を格納
390行目:システムコールのリターン値2にxp_xstat、exitのパラメータで指定した番号を格納
→exitの351行目でパラメータ:rvをxp_xstatに代入している。
391行目:ユーザタイムを足しこみます。
392行目:システムタイムを足しこみます。
393~398行:プロセス番号、親プロセス番号などを0に初期化していきます。
399行目:プロセスの状態をNULLに設定
→これで子プロセスの後始末が終わりました。
400行目:リターンします

402行目:子プロセスの状態がSTOPの場合(マニュアルの0177の説明参照)
403行目:プロセスのフラグがSWTED?ではない場合
404行目:フラグにSWTEDを立てて
405行目:システムコールのリターン値1に子プロセスのプロセス番号を格納
406行目:システムコールのリターン値2に0177を立てて
407行目:リターン

412行目:fが0以外になるのは、for文の子プロセスが見つかった場合、387行目でインクリメント
ここにくるのは、子プロセスは見つかったがゾンビでも停止でもない場合
413行目:一定時間スリープして
414行目:loop(384行目)へGOTOします。

416行目:ここにくるのは子プロセスが存在しない場合、ECHILDをエラーに設定して関数を抜けます。



370 /*
371 * Wait system call.
372 * Search for a terminated (zombie) child,
373 * finally lay it to rest, and collect its status.
374 * Look also for stopped (traced) children,
375 * and pass back status from them.
376 */
377 wait()
/* */
378 {
379 register f;
380 register struct proc *p;
381
382 f = 0;
383
384 loop:
385 for(p = &proc[0]; p < &proc[NPROC]; p++)
386 if(p->p_ppid == u.u_procp->p_pid) {
387 f++;
388 if(p->p_stat == SZOMB) {
389 u.u_r.r_val1 = p->p_pid;
390 u.u_r.r_val2 = ((struct xproc *)p)->xp_xstat;
391 u.u_cutime += ((struct xproc *)p)->xp_utime;
392 u.u_cstime += ((struct xproc *)p)->xp_stime;
393 p->p_pid = 0;
394 p->p_ppid = 0;
395 p->p_pgrp = 0;
396 p->p_sig = 0;
397 p->p_flag = 0;
398 p->p_wchan = 0;
399 p->p_stat = NULL;
400 return;
401 }
402 if(p->p_stat == SSTOP) {
403 if((p->p_flag&SWTED) == 0) {
404 p->p_flag |= SWTED;
405 u.u_r.r_val1 = p->p_pid;
406 u.u_r.r_val2 = (fsig(p) << 8) | 0177;
407 return;
408 }
409 continue;
410 }
411 }
412 if(f) {
413 sleep((caddr_t)u.u_procp, PWAIT);
414 goto loop;
415 }
416 u.u_error = ECHILD;
417 }



疑問
&はwaitしないとのことでしたが、子プロセスが終了した後の後始末はどうなるんでしょうか?
マニュアルにあったように、initがやってくれるのでしょうか?

2011年4月3日日曜日

第14回 V7から始めるUNIX講座 復習とまとめ(パイプ)

第14回 V7から始めるUNIX講座 復習とまとめ(パイプ)

前回の補足というか余談

System360ではアドレスは24bitしかなかった。データは32bit
上位の8ビットをタグに使っていたりしたが、ハードウエアが変わったらプログラムが動かなくなった。

68000でもアドレスは24bitしかなかったので、上位の8bitをタグに使ったりしていたプログラムがあったけど、それは68020になったときに動かなくなった。
→あるものは使うというのは共通のようです(^^;)

シグナルを送る命令が「kill」というのは
→おそらくsignalという機能はプロセスを殺すためだけにあったのだろう。その後に色々な種類が追加されたと思われます。

signalは実行中のシステムコールを中断することがある。そのときシステムコールはEINTRを返す。
→なのでシステムコールを呼ぶような処理を書くときはEINTRが返ってくる場合の再実行を実装する必要があります。
例として以下のサイトを参考にして下さい。
http://kzk9.net/column/write.html

シグナル(非同期通信)という言葉から、プロセス同士が何かやりとりしているようなイメージを抱きますが
実際は相手のプロセスのu構造体の中身を書き換えているだけ。対象のプロセスが再開するときにシグナルがあったことを認識して処理を行います。
→もともとプロセス間通信とはそういうものだそうです。



●パイプ
さて、今回は目からうろこが落ちまくりのパイプです。

例えば
$ ls | wc -l
$ sort file | uniq

というパイプ"|"でコマンドの入力と出力を接続することが出来ます。
こういった考え方が生まれたのはメモリ空間が小さいため、大きなものが作れないため、小さいものをつなぐ形になったと考えられます。

・MS-DOSの擬似パイプ
MS-DOSにもパイプの機能はありますが、これは後ろでこっそり一時的な中間ファイルを介しています。

・ファイルディスクリプタ(FD)
Openシステムコールを実行するとFile Descriptor(FD)と呼ばれる小さな整数(V7の場合は0~19)が返ってきます。
これらはプロセス単位で管理しています。
u構造体の中に20個の配列で保持しています。これが最大20個までのファイルを指しています。

user構造体
http://www.tamacom.com/tour/kernel/unix/S/80.html

53行目:NOFILEは20です。struct file(ファイル構造体)でOpenしているファイルを管理しています。



53 struct file *u_ofile[NOFILE]; /* pointers to file structures of open files */



file構造体
http://www.tamacom.com/tour/kernel/unix/S/58.html#L8

file構造体は以下のような構造です。
12行目にinode構造体がありますので、これで小さな整数(0~19)とファイル(実体はinode)の結びつけを行っています。
詳細は後述します。


8 struct file
9 {
10 char f_flag;
11 char f_count; /* reference count */
12 struct inode *f_inode; /* pointer to inode structure */
13 union {
14 off_t f_offset; /* read/write character pointer */
15 struct chan *f_chan; /* mpx channel pointer */
16 } f_un;
17 };



標準入力、標準出力、標準エラー出力
FDは0~19までですが、0,1,2は既に割り付けられています。
割付はinitとgettyで行います





種類普通の接続先CFD
標準入力キーボードstdin0
標準出力画面stdout1
標準エラー出力画面stderr2





●リダイレクション

$ ls > file
とすればlsの結果がfileに書き込まれます。

親プロセス(Shell)がforkで子プロセスを生成して子プロセスがexecでlsになります。
execの前に子プロセスの標準出力(1)をCloseして、fileをOpenするとFDの1番はfileを指します。
ls自体は標準出力(1)に出力しているつもりですが、実際はfileに書き込まれます。

画像


●ファイルディスクリプタとファイル構造体とinodeの関係
Unixのread, writeシステムコールはシーケンシャルアクセスを前提としていてoffsetを明示的に指定しません。
今どこまで読み書きしたかという情報はFile構造体の中に格納されています。

先ほど出てきたfile構造体のf_offsetがoffsetになります。
lseekはこのoffsetの値を返します。


14 off_t f_offset; /* read/write character pointer */



file構造体にinodeを格納しています。

以下のような関係になります。

画像


●pipe
pipe(2)システムコールを見ていきます。
まずはマニュアルを
http://plan9.bell-labs.com/7thEdMan/bswv7.html
v7vol1.pdf
P245です。

pipe ? create an interprocess channel
pipe(fildes)
int fildes[2];

int型の配列(要素数が2)を渡します。

The pipe system call creates an I/O mechanism called a pipe. The file descriptors returned can be used
in read and write operations. When the pipe is written using the descriptor fildes[1] up to 4096 bytes of
data are buffered before the writing process is suspended. A read using the descriptor fildes[0] will pick
up the data. Writes with a count of 4096 bytes or less are atomic; no other process can intersperse data.

(要約)
パイプシステムコールはpipeと呼ばれるI/Oメカニズムを作成します。リードライト操作できるファイルディスクリプタを返します。
ファイルディスクリプタ:fildes[1]に4KByte書くと止まります。
ファイルディスクリプタ:fildes[0]を使ってデータを読みます。
(4KByte読むと止まる)
書き終わる(書くものがなくなる)とEOFが返ります。

協調してリードライトが出来るわけです。

画像


ソースコードを見てみます
http://www.tamacom.com/tour/kernel/unix/S/91.html#L25

31行目:iallocでinodeを生成します。
パラメータで「pipedev」を指定しています。inodeはファイルシステム「pipedev」上に作成されます。
34行目:Read用のファイル構造体を作成します。★
39行目:Read用のFDをrに退避します。
40行目:Write用のファイル構造体を作成します。★
47行目:user構造体のシステムコールのリターン値1(Write用のFD)をリターン値2に格納します。
48行目:user構造体のシステムコールのリターン値1に退避しておいたRead用のFDを格納します。
49行目:Write用のファイル構造体のf_flagに書き込み用でパイプであることを示すフラグを立てます
50行目:Write用のファイル構造体のf_inodeに生成したinodeを格納します
51行目:Read用のファイル構造体のf_flagに読み込み用でパイプであることを示すフラグを立てます
52行目:Read用のファイル構造体のf_inodeに生成したinodeを格納します
53行目:inodeの参照カウントを2(read/write)にする
54行目:inodeのタイプをレギュラー(通常)にします
55行目:inodeのフラグに更新されたことを示すフラグを立てます

47、48行目でシステムコールのリターン値を格納する領域の
1番目:Read用のFD
2番目:Write用のFD
を格納することになります。


25 pipe()
26 {
27 register struct inode *ip;
28 register struct file *rf, *wf;
29 int r;
30
31 ip = ialloc(pipedev);
32 if(ip == NULL)
33 return;
34 rf = falloc();
35 if(rf == NULL) {
36 iput(ip);
37 return;
38 }
39 r = u.u_r.r_val1;
40 wf = falloc();
41 if(wf == NULL) {
42 rf->f_count = 0;
43 u.u_ofile[r] = NULL;
44 iput(ip);
45 return;
46 }
47 u.u_r.r_val2 = u.u_r.r_val1;
48 u.u_r.r_val1 = r;
49 wf->f_flag = FWRITE|FPIPE;
50 wf->f_inode = ip;
51 rf->f_flag = FREAD|FPIPE;
52 rf->f_inode = ip;
53 ip->i_count = 2;
54 ip->i_mode = IFREG;
55 ip->i_flag = IACC|IUPD|ICHG;
56 }


★falloc
http://www.tamacom.com/tour/kernel/unix/S/85.html#L242

247行目:user構造体のu_ofile(配列)の空いてる場所を探して、その番号(FD)を取得します。
その番号はuser構造体のu_r.r_val1(システムコールのリターン値1)に格納しておきます。
250~255行目
file構造体の配列の空いてるところを探して、u_ofileのFDが指す場所に格納します。


241 struct file *
242 falloc()
243 {
244 register struct file *fp;
245 register i;
246
247 i = ufalloc();
248 if(i < 0)
249 return(NULL);
250 for(fp = &file[0]; fp < &file[NFILE]; fp++)
251 if(fp->f_count == 0) {
252 u.u_ofile[i] = fp;
253 fp->f_count++;
254 fp->f_un.f_offset = 0;
255 return(fp);
256 }
257 printf("no file\n");
258 u.u_error = ENFILE;
259 return(NULL);
260 }


ufalloc
fallocから呼ばれます(247行目)
user構造体から空いているFDの場所を返します。

222行目:0から19までループして
223行目:u_ofileがNULLである一番最初を見つけたら
224行目:システムコールのリターン値1にi(FD)を格納
225行目:ファイルをオープンしているかのを示すu_pofileのi番目を0に設定
226行目:i(FD)を返します
228、229行目:u_ofileを全て使用している場合は、エラーを設定して(-1)を返します。


218 ufalloc()
219 {
220 register i;
221
222 for(i = 0; i < NOFILE; i++)
223 if(u.u_ofile[i] == NULL) {
224 u.u_r.r_val1 = i;
225 u.u_pofile[i] = 0;
226 return(i);
227 }
228 u.u_error = EMFILE;
229 return(-1);
230 }


●質問
pipeの実体は専用のファイルシステム(pipedev)上に生成されたinode及びデータブロックだと思って良いでしょうか?



上記でpipeの概念は分かりましたが、何故これでプロセス間で入出力を繋げられるのでしょうか?

●親子間での接続
まず親でpipeシステムコールを使用して、Read/Write用のFDを取得します。
これだと自分で書いて自分で読むだけの状態です。
この状態でforkします。
inodeは上述したように共有するのでコピーされません。
inode以外の情報はforkした子プロセスにコピーされます。(下図参照)

画像


この状態で親プロセスのRead用のFDをクローズ、子プロセスのWrite用のFDをクローズすると
親子間がパイプを通じて繋がりました。

画像





●標準出力と標準入力の接続
元々は
$ ls | wc
のようにlsの標準出力(1)とwcの標準入力(0)をつなげるという話でした。
上記の親子間の接続ではまだ繋がっていません。
それを実現するためのシステムコールが「dup」です。

では早速マニュアルを
P221です。
dup, dup2 ? duplicate an open file descriptor

dup(fildes)
int fildes;
dup2(fildes, fildes2)
int fildes, fildes2;

Given a file descriptor returned from an open, pipe, or creat call, dup allocates another file descriptor
synonymous with the original. The new file descriptor is returned.

とても簡単です。FDを引数として渡すとコピーして新しいFDを返します。
新しいFDは空いてるところの一番小さい値を返します。(これがミソ)
例えば、0,1,2を使っていたとして、dup(0)とすると3が返ります。

画像


dupはFDをコピーするだけです。これでどうしてlsの標準出力とwcの標準入力が繋がるのでしょうか?
実現するには、
・lsの標準出力(1)をpipeの書き込み用(fildes[1])に切り替え
・wcの標準入力(0)をpipeの読み込み用(fildes[0])に切り替え
を行えば実現します。

まずは標準出力の切り替えです。
1、標準出力(1)をクローズします。(これで1が空きます)
2、dup(fildes[1])を実行します。リターン値は1(最初に空いてるところを見つけるから)
→これで標準出力(1)はfildes[1]のコピーになります。つまりpipeの書き込み用を指します
3、close(fildes[1])を実行します
4、close(fildes[0])を実行します

次に標準入力側です。
1、標準入力(0)をクローズします。(これで0が空きます)
2、dup(fildes[0])を実行します。リターン値は0(最初に空いてるところを見つけるから)
→これで標準入力(0)はfildes[0]のコピーになります。つまりpipeの読み込み用を指します
3、close(fildes[0])を実行します
4、close(fildes[1])を実行します

以下の図を参照してください
画像




●リダイレクションの再説明
リダイレクションの説明のところでは、dupが出て来てなかったので詳細説明は省きました。
リダイレクションもdupを使って実現しています。

1、fileをcreateシステムコールを使用して作成(FDを得る)
2、標準出力をクローズする。(これで1が空きます)
3、dup(fd)を実行する。fdは1で取得したもの。これで標準出力(1)はfdのコピーになります。
4、close(fd)を実行する。

この作業により、標準出力の結果がファイルに書き込まれます。



●参考資料
パイプについては以下の資料を参照下さい

・UNIXカーネルの設計
P95 パイプ、P99 dup、P198 シェルについて
特にシェルについてを読むと、より分かります。

・UNIXの1/4世紀
P55 Cとパイプ:1971年から1973年
にパイプについて記載があります。パイプはThirdEdtionで実装されたようです。
パイプが動作するようになった時に、その場にいた全員がこいつはすごいと悟った
と記載があります。その当時でも素晴らしく画期的な出来事だったのが分かります。

pipeもdupも動作自体は簡単ですがそれを組み合わせることで、機能を実現しているのが驚きです。