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も動作自体は簡単ですがそれを組み合わせることで、機能を実現しているのが驚きです。

2011年3月21日月曜日

BeagleBoard xMでブートスクリプトの作成

BeagleBoard xMでSDカードの第1パーティションにMLO、U-BOOT、カーネル
第2パーティションにユーザランドを書き込んで、SDカードからブートすればAndroidが起動します。

実際には、U-BOOTに対してパラメータを設定します。

シリアルポートでTeraTerm等で接続して電源を入れると以下のようなメッセージが表示されるはずです。

Texas Instruments X-Loader 1.4.4ss (Aug 19 2010 - 02:49:27)→X-Loader(MLO)が起動
Beagle xM Rev A
Reading boot sector
Loading u-boot.bin from mmc→MMC(SDカード)からu-boot.binをロード

U-Boot 2010.03-dirty (Aug 20 2010 - 20:50:46)→U-Bootが起動

(省略)
Hit any key to stop autoboot: 0→自動起動する前にエンターキーを叩く
OMAP3 beagleboard.org #→プロンプトが表示されて、U-Boot上で操作が出来ます。

上記のように途中でブートを止めてU-BOOTでの作業を行います。
具体的には以下の設定を行います。

setenv bootargs console=ttyS2,115200n8 noinitrd root=/dev/mmcblk0p2 rootfstype=ext3 rw rootdelay=1 init=/init

setenv mmcboot 'mmc init;fatload mmc 0 0x80300000 uImage;bootm 0x80300000'

setenv bootcmd run mmcboot

そして、コマンドプロンプトでbootを実行します。

これでAndroidが起動します。
ただ、これを毎回打つのは面倒です。以前ですと「saveenv」コマンドで保存できたのですが、xMはNANDが無くなったせいか、確かエラーになったはずです。

ということで、ブートスクリプトを作ってSDカードに置いておけば、毎回の入力から開放されます。



●ブートスクリプトの書き方

1、元ファイルの準備
以下の内容を適当なファイル(例:boot.txt)に保存します。
setenv bootargs console=ttyS2,115200n8 noinitrd root=/dev/mmcblk0p2 rootfstype=ext3 rw rootdelay=1 init=/init

setenv mmcboot 'mmc init;fatload mmc 0 0x80300000 uImage;bootm 0x80300000'

setenv bootcmd run mmcboot

2、ファイルの変換
以下のコマンドで変換します。
ブートスクリプトの名前は「boot.scr」で固定です。

$ mkimage -A arm -O linux -T script -C none -a 0 -e 0 -n "beagle-xm" -d ./boot.txt ./boot.scr
Image Name: beagle-xm
Created: Tue Feb 1 13:50:45 2011
Image Type: ARM Linux Script (uncompressed)
Data Size: 223 Bytes = 0.22 kB = 0.00 MB
Load Address: 0x00000000
Entry Point: 0x00000000
Contents:
Image 0: 215 Bytes = 0 kB = 0 MB
出来たファイルをfileコマンドで確認すると
$ file ./boot.scr
./boot.scr: u-boot/PPCBoot image

3、ブートスクリプトのコピー
SDカードをマウントして、第1パーティションにCopyコマンドでコピーします。

4、ブート
BeagleBoard xMにSDカードを挿して電源を入れます。
途中でエンターキーを押下せずに放っておきます。
boot.scrが自動で読み込まれて、Androidが起動するはずです。

U-Boot 2010.03-dirty (Aug 20 2010 - 20:50:46)

OMAP3630/3730-GP ES1.0, CPU-OPP2, L3-165MHz,
OMAP3 Beagle board + LPDDR/NAND
省略
Beagle xM Rev A
Die ID #353600001bf00000015739ea0701a021
Hit any key to stop autoboot: 0
mmc1 is available
The user button is currently NOT pressed.
reading boot.scr

手動で試して問題ないことを確認してから、上記手順でブートスクリプトを作成しておけば以降は入力が不要になります。

2011年3月13日日曜日

第13回 V7から始めるUNIX講座 復習とまとめ(シグナル)

第13回 シグナル

●スタックマシン
IBMのSYSTEM360はFORTRAN,COBOLの処理に向けて設計されており、Pascal, ALGOLなどが前提としたスタックの観念を持たない。
SYSTEM360については以下を参照
http://ja.wikipedia.org/wiki/System/360

画像


スタック・マシンはPDP-11で広まりました。
設計したのは、ゴードン・ベル
http://www.ijinden.com/_c_02/Gordon_Bell.html

画像


スタックマシンについては以下を参照して下さい。
http://ja.wikipedia.org/wiki/%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF
http://ja.wikipedia.org/wiki/%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%83%9E%E3%82%B7%E3%83%B3



●本日の話題の前にマニュアルの話題
V7の良いマニュアルが見つかったので紹介します。
以下からダウンロードできます。
http://plan9.bell-labs.com/7thEdMan/bswv7.html

・v7vol1.pdf
マニュアルです。
Section1がコマンド
Section2がシステムコール

・v7vol2a.pdf
Supplementary Documents(補足資料)とありますが、UNINに関する論文です。
D. M. Ritchie, K. Thompson, Brian W. Kernighanなどが執筆しています。
是非読んだ方が良いでしょう。
・The UNIX Time-Sharing System:Unix に関する最初の論文です。
↓
上記の発表以前の Unix の開発状況を説明した論文がありました。幻のシステムである PDP-7 Unix の仕様について言及しています。
http://cm.bell-labs.com/cm/cs/who/dmr/hist.html

・UNIX For Beginners:UNIXの初心者向けの資料

・Editor:この当時のエディタは当然「ed」です。

・Shell:シェルの使い方

・UNIX Programming:C言語のマニュアルです。

論文は読んだ方が良いですが、英語の敷居が高い人は図書館で共立出版の「bit」を探してみるのも手です。
80年代のバックナンバーには、これらの論文を翻訳したものがあるはずです。

・v7vol2b.pdf
ツールの説明です。
Yacc: Yet Another Compiler-Compiler

RATFOR:Rational Fortran(構造化FORTRAN)
FOTRANで構造化(分岐、ループ)が出来るようにしたプリプロセッサ

SED、AWK
今でも使える。テキスト処理に便利です。
例えば、100個のファイルの頭に特定の文字を埋め込むとか簡単に出来る。
知ってれば簡単に、知ってないと泣きながら手でファイル開いて埋め込んで残業する羽目に><
http://itref.fc2web.com/unix/awk.html



●SIGNAL

SIGNALはUNIXで非同期で通信する手段ですが、UNIXの中で最も互換性がありません。
最近はPOSIXのSIGNALに準拠しているようです。

SIGNALの外部仕様


1、特定のプロセスにシグナルを送る
kill(2)

2、シグナルハンドラを登録する
singal(2)
プロセスはシグナルを受信した場合のデフォルトの動作が決まっていますが、signalを使うとデフォルトの動作を
任意の処理に変更することが出来ます。

SIGNALはソフトウエア割り込みです。
LIONS本でも第13章 ソフトウエア割り込みで説明されています。
今までの(ハードウエア)割り込みの概念をそのまま使っています。

v7vol1.pdfマニュアルのP249のエラーの以下については重要なので、ピックアップしておきます。

4 EINTR Interrupted system call
An asynchronous signal (such as interrupt or quit), which the user has elected to catch, occurred
during a system call. If execution is resumed after processing the signal, it will appear as if the
interrupted system call returned this error condition.

要約すると
システムコールを実行中にSIGNALを受信した場合は、割り込みが発生してSIGNALを実行する場合があります。
割り込まれたシステムコールはエラー4を返します。

Wikipediaにも下記の記述があります。
シグナルは処理中のシステムコールを中断することがあり、アプリケーションは非透過的な再実行をしなければならない。
つまり、実行中のシステムコールはEINTRというエラーを返し、要求した処理はシグナル受信によって中断されて結果を得られていないことを示す。
そのため、処理を続行するには再度同じシステムコールを実行しなければならない。

マニュアルのシステムコールを見てみましょう。

・kill(2)
PDFマニュアルの231ページです。
send signal to a process
kill(pid, sig);
kill(2)は、プロセスにシグナルを送ります。

see also signal(2)となっているのでsignal(2)から出来ていることがわかります。

・signal(2)
PDFマニュアルの252ページです。
signal – catch or ignore signals
(*signal(sig, func))()
(*func)();
signalのパラメータは2つ
シグナルとファンクションです。

シグナルに設定できるのは16種類(以下参照)です。
このシグナルを受信した時に実行するfunctionを登録することが出来ます。

SIGHUP 1 hangup
SIGINT 2 interrupt
SIGQUIT 3* quit
SIGILL 4* illegal instruction (not reset when caught)
SIGTRAP 5* trace trap (not reset when caught)
SIGIOT 6* IOT instruction
SIGEMT 7* EMT instruction
SIGFPE 8* floating point exception
SIGKILL 9 kill (cannot be caught or ignored)
SIGBUS 10* bus error
SIGSEGV 11* segmentation violation
SIGSYS 12* bad argument to system call
SIGPIPE 13 write on a pipe or link with no one to read it
SIGALRM 14 alarm clock
SIGTERM 15 software termination signal
16 unassigned

SIGNALを受信した時のデフォルトの動作(死ぬ、コアを吐く)を変えることが出来ます。



では次にソースコードを見てみます。

●signal(2)
カーネルで実行するのは、ssigになります。
http://minnie.tuhs.org/cgi-bin/utree.pl?file=V7/usr/src/libc/sys/signal.s
によればsignalは48番です。

http://www.tamacom.com/tour/kernel/unix/S/102.html#L115
を見ると48番は「ssig」になります。
カーネルに「signal」はありますが別の機能です。

ssigのソースは以下です。
http://www.tamacom.com/tour/kernel/unix/S/101.html#L273

281行目:u構造体からsignalのパラメータを取り出して構造体aにキャストしています。
282行目:シグナルを取り出して
283行目:シグナルが不正(0以下あるいはNSIG(17)以上)、SIGKILの場合はエラーを格納してリターンしています。
SIGKILをチェックしているのは、SIGKILを上書きして変更してしまうと、外部から終了(kill -9 pid)が出来なくなるので変更を許可していません。
288行目:シグナルハンドラをu構造体に代入しています。
289行目:該当するシグナルのビット値を0にします。


273 ssig()
/* */
274 {
275 register a;
276 struct a {
277 int signo;
278 int fun;
279 } *uap;
280
281 uap = (struct a *)u.u_ap;
282 a = uap->signo;
283 if(a<=0 || a>=NSIG || a==SIGKIL) {
284 u.u_error = EINVAL;
285 return;
286 }
287 u.u_r.r_val1 = u.u_signal[a];
288 u.u_signal[a] = uap->fun;
289 u.u_procp->p_sig &= ~(1<<(a-1));
290 }




●kill(2)
http://www.tamacom.com/tour/kernel/unix/S/104.html#L34

ちょっとややこしいので、図で概要を説明します。
まず、kill(2)では、シグナルを設定するだけで、シグナルハンドラの実行はまた別になります。

1、killの処理
killは送信先のプロセスのプロセス構造体(u.p_sig)のシグナルビットを立てて、送信先プロセスを起こすのがメインです。
killの実行後に、outへGOTOで飛びます。
issigでカレントプロセスが実行すべきシグナルハンドラを保持していれば、psig()を実行します。
画像


2、psig
不動小数点レジスタが必要なら保存して
fsig()でプロセス構造体(u.p_sig)のシグナルビットをチェックして
該当するシグナルのシグナルハンドラが有効なら、sendsig()を実行します。

画像


3、sendsig
スタックを4(2個分)増分して、その領域を確保してから、suwordでカーネル空間からユーザ空間へ保存します。
保存するのは、ユーザの現在のPCとPSです。
保存しておいてから、PCを実行すべきシグナルハンドラーに書き換えます。SPも書き換えます。
PSからTBITを落としていますが、目的不明です。

画像


4、実装に関して
つまり、非同期通信という言葉とイメージが一致しませんが、実際には、送信先のプロセスのu構造体とproc構造体の中身を書き換えて、送信先のプロセスが実行を再開した際に書き換えた内容で動作するようです。
シグナルハンドラの実行が終わると、本来の開始するはずだった命令へのポインタ(PC)がスタックに保存されているので、それを戻して、何もなかったように処理が続行します。

画像




図の概要を元にソースコードを見ていきます。

以前やったシステムコールを思い出して下さい。
システムコールのパラメータはtrapの中で「fuiword」を使って読み出して
u構造体のu_apに格納されています。
u_apはパラメータのリストです。

302行目でu.u_apをa構造体にキャストしています。
311行目からfor文で全てのプロセスを舐めまわしています。
312から316行目まではエラーチェックです。
312行:プロセスのステータスがNULL
314行:対象のプロセス(a)ではない
316行:不明
318行:ユーザがスーパーユーザではなく、ユーザ識別が一致しない。

321行目まで来たら、正しくパラメータとして渡されたプロセスIDが取得できているはずです。
プロセス構造体とプロセス番号を渡して、psignalを呼びます。


292 kill()
/* */
293 {
294 register struct proc *p, *q;
295 register a;
296 register struct a {
297 int pid;
298 int signo;
299 } *uap;
300 int f, priv;
301
302 uap = (struct a *)u.u_ap;
303 f = 0;
304 a = uap->pid;
305 priv = 0;
306 if (a==-1 && u.u_uid==0) {
307 priv++;
308 a = 0;
309 }
310 q = u.u_procp;
311 for(p = &proc[0]; p < &proc[NPROC]; p++) {
312 if(p->p_stat == NULL)
313 continue;
314 if(a != 0 && p->p_pid != a)
315 continue;
316 if(a==0 && ((p->p_pgrp!=q->p_pgrp&&priv==0) || p<=&proc[1]))
317 continue;
318 if(u.u_uid != 0 && u.u_uid != p->p_uid)
319 continue;
320 f++;
321 psignal(p, uap->signo);
322 }
323 if(f == 0)
324 u.u_error = ESRCH;
325 }




●psignal
http://www.tamacom.com/tour/kernel/unix/S/95.html#L55

60行目:シグナル番号がNSIG(17)以上の場合はリターンします。
62行目:シグナル番号が0以外だったら
63行目:シグナルはビット列なので、p->p_sigとORを取って該当ビットを立てます。
64行目:プロセスの優先度がPUSER(50)より大きければ、PUSERにします。
66行目:プロセスがスリープ状態で優先度がPZERO(25)より大きければ
67行目:setrunで該当のプロセスを実行状態にします。
→つまり、該当プロセスがスリープ状態であっても、SIGNALを送れば起きて処理を実行します


51 /*
52 * Send the specified signal to
53 * the specified process.
54 */
55 psignal(p, sig)
/* */
56 register struct proc *p;
57 register sig;
58 {
59
60 if((unsigned)sig >= NSIG)
61 return;
62 if(sig)
63 p->p_sig |= 1<<(sig-1);
64 if(p->p_pri > PUSER)
65 p->p_pri = PUSER;
66 if(p->p_stat == SSLEEP && p->p_pri > PZERO)
67 setrun(p);
68 }




●setrun
http://www.tamacom.com/tour/kernel/unix/S/96.html#L140

コメントにあるようにプロセスを実行状態にします。
該当プロセスがスワップアウトされていても、必要ならスワップインします。
つまり必ず実行されます。

144、145行目:プロセスの状態が0かゾンビならパニック
151行目:コメントをそのまま読むと、競合状態のためwへの割り当てが必要?
152行目:該当プロセスのpではなく、w(p->p_wchan)を起こします。
155行目:該当プロセスの状態をSRUN(実行中)に変更
156行目:該当プロセスをランキューに入れます
157行目:該当プロセスの優先度が現在実行中のプロセスの優先度より高ければ
158行目:再スケジューリングフラグを立てて
159行目:実行待ちのプロセスがなくて該当プロセスがメモリ上にない(スワップアウトされている)場合は
161行目:0番(スケジューラ)を起こします。

(備考)
runrun:先度の高い実行待ちプロセスがある
runin:実行待ちプロセスあり
runout:実行待ちプロセスなし(非ゼロは実行可能状態でかつディスクへスワップアウトされたプロセスがないことを示す)


136 /*
137 * Set the process running;
138 * arrange for it to be swapped in if necessary.
139 */
140 setrun(p)
/* */
141 register struct proc *p;
142 {
143 register caddr_t w;
144
145 if (p->p_stat==0 || p->p_stat==SZOMB)
146 panic("Running a dead proc");
147 /*
148 * The assignment to w is necessary because of
149 * race conditions. (Interrupt between test and use)
150 */
151 if (w = p->p_wchan) {
152 wakeup(w);
153 return;
154 }
155 p->p_stat = SRUN;
156 setrq(p);
157 if(p->p_pri < curpri)
158 runrun++;
159 if(runout != 0 && (p->p_flag&SLOAD) == 0) {
160 runout = 0;
161 wakeup((caddr_t)&runout);
162 }
163 }


(参考)
p->p_flagには以下の状態をとります。


39 /* flag codes */
40 #define SLOAD 01 /* in core */
41 #define SSYS 02 /* scheduling process */
42 #define SLOCK 04 /* process cannot be swapped */
43 #define SSWAP 010 /* process is being swapped out */
44 #define STRC 020 /* process is being traced */
45 #define SWTED 040 /* another tracing flag */
46 #define SULOCK 0100 /* user settable lock in core */




●setrq
http://www.tamacom.com/tour/kernel/unix/S/96.html#L118

runqは実行しているプロセスを保持しているキューです。
該当のpをランキューに入れます。


112 /*
113 * when you are sure that it
114 * is impossible to get the
115 * 'proc on q' diagnostic, the
116 * diagnostic loop can be removed.
117 */
118 setrq(p)
/* */
119 struct proc *p;
120 {
121 register struct proc *q;
122 register s;
123
124 s = spl6();
125 for(q=runq; q!=NULL; q=q->p_link)
126 if(q == p) {
127 printf("proc on q\n");
128 goto out;
129 }
130 p->p_link = runq;
131 runq = p;
132 out:
133 splx(s);
134 }


●疑問
setrunの152行目
競合が発生しているときは、別のwを起こしてリターンしていますが、元々起こそうとしていたプロセスへの処理は
中断されるってことでしょうか?

setrunで161行目が実行した後は
スケジューラが起動して、ランキューに該当プロセスが入っている。
該当プロセスの優先度が現在のプロセスより高いのでプロセススイッチが発生して、該当プロセスが実行状態になるという理解で正しいですか?



●プロセスハンドラの実行
killはプロセス番号の設定と送信先プロセスを起こすことを行っていました。

プロセスハンドラの実行は別で行っています。
killの実行が終わると、outへGOTOで飛びます。

http://www.tamacom.com/tour/kernel/unix/S/104.html#L227

226行目:issigでカレントプロセスが実行すべきシグナルハンドラを持っているかをチェックします。
227行目:シグナルハンドラがあれば、psigを実行します。


225 out:
226 if(issig()) {
227 psig();
228 }
229 curpri = setpri(u.u_procp);
230 if (runrun)
231 qswtch();
232 if(u.u_prof.pr_scale)
233 addupc((caddr_t)pc, &u.u_prof, (int)(u.u_stime-syst));
234 if (u.u_fpsaved)
235 restfp(&u.u_fps);
236 }


●psig
http://www.tamacom.com/tour/kernel/unix/S/95.html#L128

148行目:実行すべきシグナルハンドラの実行はsendsigを呼び出します
151行目以降:実行すべきシグナルハンドラがないときは、デフォルトの動作Coreを吐く)をします。


121 /*
122 * Perform the action specified by
123 * the current signal.
124 * The usual sequence is:
125 * if(issig())
126 * psig();
127 */
128 psig()
/* */
129 {
130 register n, p;
131 register struct proc *rp;
132
133 rp = u.u_procp;
134 if (u.u_fpsaved==0) {
135 savfp(&u.u_fps);
136 u.u_fpsaved = 1;
137 }
138 if (rp->p_flag&STRC)
139 stop();
140 n = fsig(rp);
141 if (n==0)
142 return;
143 rp->p_sig &= ~(1<<(n-1));
144 if((p=u.u_signal[n]) != 0) {
145 u.u_error = 0;
146 if(n != SIGINS && n != SIGTRC)
147 u.u_signal[n] = 0;
148 sendsig((caddr_t)p, n);
149 return;
150 }
151 switch(n) {
152
153 case SIGQUIT:
154 case SIGINS:
155 case SIGTRC:
156 case SIGIOT:
157 case SIGEMT:
158 case SIGFPT:
159 case SIGBUS:
160 case SIGSEG:
161 case SIGSYS:
162 if(core())
163 n += 0200;
164 }
165 exit(n);
166 }


●sendsig
http://www.tamacom.com/tour/kernel/unix/S/87.html#L141

146行目:カレントプロセスのスタックを4増やして
147行目:スタック領域を確保して
148行目:現在のPSをスタックに保存
149行目:現在のPCをスタックに保存
150行目:スタックの位置を変更後(4増やした)に設定
151行目:PSからTBITを落とす
152行目:PCをp(シグナルハンドラの関数ポインタ)に設定


138 /*
139 * Let a process handle a signal by simulating an interrupt
140 */
141 sendsig(p, signo)
/* */
142 caddr_t p;
143 {
144 register unsigned n;
145
146 n = u.u_ar0[R6] - 4;
147 grow(n);
148 suword((caddr_t)n+2, u.u_ar0[RPS]);
149 suword((caddr_t)n, u.u_ar0[R7]);
150 u.u_ar0[R6] = n;
151 u.u_ar0[RPS] &= ~TBIT;
152 u.u_ar0[R7] = (int)p;
153 }

2011年3月6日日曜日

第12回 V7から始めるUNIX講座 復習とまとめ(メモリ再配置)

第12回 メモリ再配置

放送当日のG2Mの調子が悪くて前回の復習が中心になりますがご了承下さい。


sleepについては、問題ないですがsleep(2)はないので(システムコールではない)の区別するなら「sleepk」と言ったほうが良い

疑問
メモリマップドIOで良いか?
→良い。

PDP-11だと、RKADDRがメモリマップドIOの領域になり、CPUとDISK Controllerの両方から見える。
RDADDRを構造体deviceとしてアクセスすることにより、IOの指示を行う。
下図を参照して下さい。

画像


○参考
最近の組み込みCPUはだいたいMemory mapped I/Oが多い
ARM、モトローラ、System360、PDP-11などもMemory mapped I/O

また、ポートマップドI/Oという方式もあり、これは、専用の命令(例:INTELだとIN命令、OUT命令)と別のアドレス空間によりIOを実現している。

iodone
→bufをロックしているので、ここでアンロックする。

bp->b_dev
major(0-255)、minor(0-255)でそれぞれ8ビットづづの値を保持する。
majorで装置の種類を判別し、minorで何番目の装置かを示す。


●疑問点
rkintrは、依頼した読み書きが終わった段階で割り込みとして呼ばれるという認識です。
→OK
しかしrkintrの最後にrkstartを実行しています。
rkstartは実際にDISKに対してコマンドを送信している処理だと思うのですが、ここで実行している理由は?
→rkstartは1度に1つの処理しか出来ない、遅延書き込みが発生したり、読み込みが続けて発生した場合を考えて次の処理用にrkstartを実行している。
キュー上にIOのリクエストが溜まっているので掃けるまで続く、なくなったらrkstartが失敗して元のルートに戻るはず。

図は、ファイルシステムの場合だと名前からi-Nodeを取得する処理が抜けている。
そうでなければ問題なし。



●前々回のCalloutからの宿題



357 alarm()
/* */
358 {
359 register struct proc *p;
360 register c;
361 register struct a {
362 int deltat;
363 } *uap;
364
365 uap = (struct a *)u.u_ap;
366 p = u.u_procp;
367 c = p->p_clktim;
368 p->p_clktim = uap->deltat;
369 u.u_r.r_val1 = c;
370 }



この辺のp_clktimを追っかけてみます。

clockに突き当たります。
http://tamacom.com/tour/kernel/unix/S/84.html#L122

109行目で1秒に1回実施(HZは1秒のはず)
117行目でプロセス構造体を全部舐め回して、118行目でステータス(p_stat)をチェックして生きていてなおかつゾンビ(SZOMB)ではないものを処理します。
119行目でpp->p_timeが127でなければデクリメントしています。pp->p_timeは(resident time for scheduling)と定義されているのでスケジューラから当初与えられた時間を
デクリメントしているのではないかと思います。この時間を使い切ったら強制的にカーネルモードに戻って再スケジュールする。
121から123行目がシグナルの発生場所です、pp->p_clktimが0になったらシグナルを発生させています。
てっきり、ここでSIGALRMを発生させていると思ったのですが、「SIGCLK」でした。


109 if(++lbolt >= HZ) {
110 if (BASEPRI(ps))
111 return;
112 lbolt -= HZ;
113 ++time;
114 spl1();
115 runrun++;
116 wakeup((caddr_t)&lbolt);
117 for(pp = &proc[0]; pp < &proc[NPROC]; pp++)
118 if (pp->p_stat && pp->p_stat 119 if(pp->p_time != 127)
120 pp->p_time++;
121 if(pp->p_clktim)
122 if(--pp->p_clktim == 0)
123 psignal(pp, SIGCLK);
124 a = (pp->p_cpu & 0377)*SCHMAG + pp->p_nice - NZERO;
125 if(a < 0)
126 a = 0;
127 if(a > 255)
128 a = 255;
129 pp->p_cpu = a;
130 if(pp->p_pri >= PUSER)
131 setpri(pp);
132 }
133 if(runin!=0) {
134 runin = 0;
135 wakeup((caddr_t)&runin);
136 }
137 }


psignalを、もう少し追っかけてみます。

●psignal
http://tamacom.com/tour/kernel/unix/S/95.html#L55

63行目プロセスのp_sigにシグナルSIGCLKをセット(1引いて2倍?)
65行目で優先度セットして(PUSERより大きければPUSER)
66、67行目でプロセスが寝てて、優先度が0以上ならsetrunを実行


51 /*
52 * Send the specified signal to
53 * the specified process.
54 */
55 psignal(p, sig)
/* */
56 register struct proc *p;
57 register sig;
58 {
59
60 if((unsigned)sig >= NSIG)
61 return;
62 if(sig)
63 p->p_sig |= 1<<(sig-1);
64 if(p->p_pri > PUSER)
65 p->p_pri = PUSER;
66 if(p->p_stat == SSLEEP && p->p_pri > PZERO)
67 setrun(p);
68 }


続いて、setrun
●setrun
http://tamacom.com/tour/kernel/unix/S/96.html#L140

151行目でプロセスが実行待ちであれば直ちに起こす。
そうでなければ
156行目でプロセスのステータスをSRUN(実行中)にして
プロセスの優先度が現在の優先度より小さい(優先度が高い)ならば
プロセスをrunqueに入れて、runrun(カレントプロセスよりも優先度が高いプロセスの存在を示すフラグ)をインクリメントしてして、プロセスを起こします。
とここまで、書いてみて、「SIGALRM」がやっぱりない??
今回はここまでとしたいと思います。(残念)


136 /*
137 * Set the process running;
138 * arrange for it to be swapped in if necessary.
139 */
140 setrun(p)
/* */
141 register struct proc *p;
142 {
143 register caddr_t w;
144
145 if (p->p_stat==0 || p->p_stat==SZOMB)
146 panic("Running a dead proc");
147 /*
148 * The assignment to w is necessary because of
149 * race conditions. (Interrupt between test and use)
150 */
151 if (w = p->p_wchan) {
152 wakeup(w);
153 return;
154 }
155 p->p_stat = SRUN;
156 setrq(p);
157 if(p->p_pri < curpri)
158 runrun++;
159 if(runout != 0 && (p->p_flag&SLOAD) == 0) {
160 runout = 0;
161 wakeup((caddr_t)&runout);
162 }
163 }


●備考
プロセスの情報は以下に定義されています。
http://tamacom.com/tour/kernel/unix/S/71.html#L13


9 struct proc {
10 char p_stat;
11 char p_flag;
12 char p_pri; /* priority, negative is high */
13 char p_time; /* resident time for scheduling */
14 char p_cpu; /* cpu usage for scheduling */
15 char p_nice; /* nice for cpu usage */
16 short p_sig; /* signals pending to this process */
17 short p_uid; /* user id, used to direct tty signals */
18 short p_pgrp; /* name of process group leader */
19 short p_pid; /* unique process id */
20 short p_ppid; /* process id of parent */
21 short p_addr; /* address of swappable image */
22 short p_size; /* size of swappable image (clicks) */
23 caddr_t p_wchan; /* event process is awaiting */
24 struct text *p_textp; /* pointer to text structure */
25 struct proc *p_link; /* linked list of running processes */
26 int p_clktim; /* time to alarm clock signal */
27 };


プロセスの状態は以下に定義されています。
http://tamacom.com/tour/kernel/unix/S/71.html#L37


31 /* stat codes */
32 #define SSLEEP 1 /* awaiting an event */
33 #define SWAIT 2 /* (abandoned state) */
34 #define SRUN 3 /* running */
35 #define SIDL 4 /* intermediate state in process creation */
36 #define SZOMB 5 /* intermediate state in process termination */
37 #define SSTOP 6 /* process being traced */




●禁断の「setjmp」「longjmp」
sleep(3)のソースを見ていて話題になったのが、「setjmp」「longjmp」です。
説明によると、GOTOが同じ関数内で別の場所に飛ぶのに対して「setjmp」「longjmp」は任意の関数にいきなり戻れるようです。
通常はA→B→Cと読んだときにスタックに上から順にC、B、Aの情報が載っています。
CがリターンすればスタックのCの情報は無効になってBへ戻り、BがリターンすればばスタックのBの情報は無効になってAへ順番に戻ってきます。

しかし、「setjmp」「longjmp」を使えば、CからいきなりAへ戻れるそうです。
setjmpでAの状態を保存しておいて、Cでその保存しておいた情報を使ってAへ戻るようです。
例外処理や初期のスレッドはこれで実装していたとのことです。
以下の説明が詳しいので参考にして下さい。
Super Technique 講座
http://www.nurs.or.jp/~sug/soft/super/longjmp.htm



●メモリの再配置
PDP-11は16ビットCPUなので、アドレスは0~64KBのメモリ空間を持ちます。
カーネルもユーザプロセスも同じです。
それぞれが仮想空間の0~64KBで動作するようになっています。
ただそのままだと全てのプロセスが同じアドレスを使用するので衝突します。
そのため、実行時にメモリの再配置(リアロケーション)が必要になります。
PDP-11ですと、メモリが18ビットなので物理メモリは256KBになります。
メモリの再配置のイメージは以下参照
画像


各プロセスは8KB単位の8つに分割します。1つをページと呼びます。つまり8ページに分割します。
ユーザモードとカーネルモードで変換表が2つあります。
この変換表により、仮想アドレス(VA)を物理アドレス(PA)に変換し各プロセスのメモリ再配置を行い衝突しないようにします。
画像


PDP-11では、PSWの上位4ビットに現在、前回のモード(カーネル、ユーザ)の状態を保持しています。
それにより、変換表の切り替えを行います。
そのため、モードが切り替わると変換表自体が変わるので前のモードから値を渡したりすることが出来ません。
以前にシステムコールでシステムコールの番号(例:getpidは20)を取得するのに、前モードのアドレスから値を取得する関数fuiwordがありましたが、こういう理由で特殊な命令で値を渡していたわけです。

画像


最近のLINUXなどは物理メモリ自体が大きいので、1GBを境界にして、カーネル空間とユーザ空間をそもそも分けています。
なので、ユーザプロセスからカーネル空間は見えているわけですが、メモリ保護機能により直接のアクセスを禁止しています。
→unix の実績を元にCPU側で保護機能を実装

ユーザプロセスからのアクセスは以前と同じようにシステムコールになります。
カーネル空間とユーザ空間は別なのでそこでのデータアクセスは実際はメモリコピーになりますが
伝統的なUNIXの用語をそのまま使用してcopyin, copyoutと呼ぶそうです。

画像