2011年5月3日火曜日


// 円クラス
abstract class Shape{
abstract void Display();
}

// 円クラス
class Circle extends Shape implements Area{
int x;
int y;
int r;


Circle(int x, int y, int r ){
this.x = x;
this.y = y;
this.r = r;
}

void Display(){
System.out.println("x=" + x + ", y=" + y + ", r=" + r);
}


// 面積を計算する
public double getArea(){
return r*r*Math.PI; // Math.PI:円周率
}
}

// 長方形クラス
class Rect extends Shape implements Area{
int x1;
int y1;
int x2;
int y2;


Rect(int x1, int y1, int x2, int y2 ){
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
}

void Display(){
System.out.println("x1=" + x1 + ", y1=" + y1 + ", x2=" + y2 + ", y2=" + y2);
}


// 面積を計算する
public double getArea(){
// Math.abs : 絶対値を求めるメソッド
return Math.abs(x1-x2) * Math.abs(y1-y2);
}
}

てst

http://youtu.be/9WF6M1hy0EE









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

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と呼ぶそうです。

画像

2011年3月3日木曜日

やっときたGingerBread(2.3.3)

いや、やっと我が家のNexusOneにもGingerBreadが来ました。
2.3.3
昨日あたりからTLでもざわざわしてたので、そろそろかと思ってました。
夕方休憩時間に見てみると、おお来てる!!
ということで休憩時間にアップデートしました。

画像


バージョンは2.3.3
ビルド番号はGRI40

画像


APIデモとかで遊んでみよう!!

2011年2月27日日曜日

第11回 V7から始めるUNIX講座 復習とまとめ(IO割り込みとDISKドライバ)

第11回 IO割り込みとDISKドライバ

●callout
前回、何に使うのか(functionに登録するのは、何か)という話題がありました。
特定の時間が経過したら実行する処理として、何を思い浮かべるでしょうか?

そう、sleep(3)です。
カーネルの内部で使う、sleep,wakeupではなくsleep(3)です。
Section3ですので、ライブラリ(システムコールではない)です。

man 3 sleepを実行します。

最後に
SEE ALSO
alarm(2), pause(2)

と表示されます。
これは、sleep(3)がalarm(2), pause(2)の素材から出来ていることを示します。
alarm(2), pause(2)はシステムコールです。

sleep(3)とsleep(2)は全く別物なので注意が必要です。

alarm, pauseシステムコールのソースファイルは以下です。
http://tamacom.com/tour/kernel/unix/S/101.html#L357


354 /*
355 * alarm clock signal
356 */
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 }
371
372 /*
373 * indefinite wait.
374 * no one should wakeup(&u)
375 */
376 pause()
/* */
377 {
378
379 for(;;)
380 sleep((caddr_t)&u, PSLEP);
381 }


sleep(3)のソースを見てみます
http://minnie.tuhs.org/cgi-bin/utree.pl?file=V7/usr/src/libc/gen/sleep.c


#include <signal.h>
#include <setjmp.h>

static jmp_buf jmp;

sleep(n)
unsigned n;
{
int sleepx();
unsigned altime;
int (*alsig)() = SIG_DFL;

if (n==0)
return;
altime = alarm(1000); /* time to maneuver */
if (setjmp(jmp)) {
signal(SIGALRM, alsig);
alarm(altime);
return;
}
if (altime) {
if (altime > n)
altime -= n;
else {
n = altime;
altime = 1;
}
}
alsig = signal(SIGALRM, sleepx);
alarm(n);
for(;;)
pause();
/*NOTREACHED*/
}

static
sleepx()
{
longjmp(jmp, 1);
}


ざっくり言いますと、sleepはpauseで眠っています。
calloutで、N秒後にSIGALRMを発生させます。
これでsleepから復帰するはずです。

(備考)
alarm(n)は、自分自身にN秒後にSIGALRMを通知する機能です。
http://ja.wikipedia.org/wiki/%E3%82%B7%E3%82%B0%E3%83%8A%E3%83%AB_(%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2)

alarmについては、ちょっと不明なので次回再度取り上げます。



●DISKドライバ

1、磁気DISK
まずは、磁気DISKをイメージして下さい。
磁気DISKは円板(プラッタ)が複数毎構成になっており、磁気ヘッドでデータアクセスします。以下のような図です。
画像


Cylinder head sector(CHS シリンダ・ヘッド・セクタ)で磁気ディスクにアクセスします。
(参考)
http://www.it-license.com/memory/harddisc.html
基本情報技術者の資料、そういや昔やった覚えが

http://ja.wikipedia.org/wiki/Cylinder_head_sector

小から大に説明すると
・セクタ:トラックを分割したもの

・トラック:セクタの集まったもの、データの記録単位で円の1周分
⇒陸上のトラックを思い浮かべると理解しやすいかな?

・シリンダ:同じトラックの集合、同じトラックを縦に見ると、筒のように見えることからシリンダと呼ぶようです。
同じシリンダ上のデータは磁気ヘッド動かずにデータの読み書きが出来ます。

よって、特定のデータを読み書きするには、
・シリンダにより磁気ヘッドの移動
・ヘッドによりどのプラッタにアクセスするか
・セクタによって、トラック内のどの部分か
により、実現します。

(備考)LBA
初期のハードディスクもこの方式でアクセスを行っていたが、容量が増大するにつれ、内側と外側のトラックでセクタ数が異なる等の理由で内部で適当な値に変換してアクセスするようになった。
どうせ変換するであれば、先頭から何番目のセクタであるかのみを指定してアクセスできた方があらゆる点で便利であるため、現在ではLogical Block Addressing(LBA)によるアクセスが主に用いられている。

2、DISKアクセスの実現
Lions本をお持ちの方は第16章RKディスクドライバを参考にして下さい。

DISKアクセスも割り込みです。
DISKドライバに読み込み、書き込みを依頼します。処理が終わると割り込みで教えてくれます。
割り込みですからベクターテーブルに記述があるはずです。
Lions本のP336に割り込みベクタの一覧があります。





ベクタ位置周辺デバイス割り込み優先度プロセス優先度
220RKディスクドライブ55


では、お馴染みのベクターテーブルの220を見てみます。
http://tamacom.com/tour/kernel/unix/S/7.html


41 . = ZERO+220
42 rkio; br5


rkioは


68 .globl _rkintr
69 rkio: jsr r0,call; jmp _rkintr


callで色々準備してから、最終的に_rkintrにジャンプします。
callでの詳細処理は前回のまとめを参照して下さい。

_rkintr(C言語のrkintr)を見てみましょう。
http://tamacom.com/tour/kernel/unix/S/40.html#L96


96 rkintr()
/* */
97 {
98 register struct buf *bp;
99
100 if (rktab.b_active == NULL)
101 return;
102 dk_busy &= ~(1 << DK_N);
103 bp = rktab.b_actf;
104 rktab.b_active = NULL;
105 if (RKADDR->rkcs < 0) { /* error bit */
106 deverror(bp, RKADDR->rker, RKADDR->rkds);
107 RKADDR->rkcs = RESET|GO;
108 while((RKADDR->rkcs&CTLRDY) == 0)
109 ;
110 if (++rktab.b_errcnt <= 10) {
111 rkstart();
112 return;
113 }
114 bp->b_flags |= B_ERROR;
115 }
116 rktab.b_errcnt = 0;
117 rktab.b_actf = bp->av_forw;
118 bp->b_resid = 0;
119 iodone(bp);
120 rkstart();
121 }


100 if (rktab.b_active == NULL)
b_activeはb_bcountです(#defineしてる)ので転送カウント?がNULLならリターンします(通常はここは通らないはず)

105から115まではエラー処理です。
105 if (RKADDR->rkcs < 0) { /* error bit */
エラービットが立っていないかのチェックです。
110から113は10回のリトライ処理です。10回やっても駄目ならb_flagsにB_ERRORを設定します。
ここで実行している、 rkstart()がデバイスに対してIOを発行する部分になります。

●ディスクコントローラのレジスタアドレス定義
ディスクコントローラのレジスタのアドレスがRKADDR(0177400)でdefineされていて、レジスタのdevice構造体にマッピングされています。
このマッピングされているものはrkstartのところで出てきます(後述)

●疑問
ディスクコントローラのレジスタアドレスに対して処理を行うことでディスクIOを行っているようですが、
これは、いわゆる「メモリマップドIO」ということでしょうか?
ディスクコントローラのレジスタアドレスがメモリ空間上にマッピングされているので、ディスクコントローラの仕様に従って操作することでディスクIOが出来る?

メモリマップドIO
http://ja.wikipedia.org/wiki/%E3%83%A1%E3%83%A2%E3%83%AA%E3%83%9E%E3%83%83%E3%83%97%E3%83%89I/O


RKADDR
12 #define RKADDR ((struct device *)0177400)

27 /*
28 * Monitoring device bit
29 */
30 #define DK_N 1
31
32 struct device
33 {
34 int rkds;
35 int rker;
36 int rkcs;
37 int rkwc;
38 caddr_t rkba;
39 int rkda;
40 };


正常ならば
116以降、buf構造体に値を設定して、iodone、rkstartを実行
以下にコードを示します。


●iodone
http://tamacom.com/tour/kernel/unix/S/18.html#L374

・b_flagsをB_DONE(転送完了)に設定
・b_flagsにB_ASYNC(非同期:IOの完了を待たない)が立っていたらだったらbpを開放
・B_ASYNCが立っていない場合はB_WANTED(誰かが使っているので欲しいという印)を落として、wakeupでこのbpが開放されるのを待っているプロセスを起こす。

以前第8回のキャッシュバッファのところで出てきました。
http://xiangcai.at.webry.info/201102/article_1.html

2、キャッシュヒットしたが誰かが使っている。
getblkで、そのバッファが欲しいという印を付けて、スリープして、待つ。

この待っているのを起こす作業になると思います。


370 /*
371 * Mark I/O complete on a buffer, release it if I/O is asynchronous,
372 * and wake up anyone waiting for it.
373 */
374 iodone(bp)
/* */
375 register struct buf *bp;
376 {
377
378 if(bp->b_flags&B_MAP)
379 mapfree(bp);
380 bp->b_flags |= B_DONE;
381 if (bp->b_flags&B_ASYNC)
382 brelse(bp);
383 else {
384 bp->b_flags &= ~B_WANTED;
385 wakeup((caddr_t)bp);
386 }
387 }




●rkstart
http://tamacom.com/tour/kernel/unix/S/40.html#L68


68 rkstart()
/* */
69 {
70 register struct buf *bp;
71 register com;
72 daddr_t bn;
73 int dn, cn, sn;
74
75 if ((bp = rktab.b_actf) == NULL)
76 return;
77 rktab.b_active++;
78 bn = bp->b_blkno;
79 dn = minor(bp->b_dev);
80 cn = bn/12;
81 sn = bn%12;
82 RKADDR->rkda = (dn << 13) | (cn << 4) | sn;
83 RKADDR->rkba = bp->b_un.b_addr;
84 RKADDR->rkwc = -(bp->b_bcount>>1);
85 com = ((bp->b_xmem&3) << 4) | IENABLE | GO;
86 if(bp->b_flags & B_READ)
87 com |= RCOM; else
88 com |= WCOM;
89 RKADDR->rkcs = com;
90 dk_busy |= 1 << DK_N;
91 dk_numb[DK_N] += 1;
92 com = bp->b_bcount>>6;
93 dk_wds[DK_N] += com;
94 }


・転送カウントをインクリメント
・bn(block number)を取得
・dn(デバイスのマイナー番号)を取得
(補足)
bp->b_devはメジャー番号(8ビット)+マイナー番号(8ビット)が入っています。
⇒同じく第8回で出てきました。

Wikipediaのスペシャルファイルの解説
http://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%9A%E3%82%B7%E3%83%A3%E3%83%AB%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB

一般にメジャー番号がデバイスドライバの識別に使われ、マイナー番号がそのドライバが制御する個々の機器の識別に使われる。この場合、システムはドライバに対して引数としてマイナー番号を渡す。

まさにこれのためにマイナー番号を取得していると思います。

・cn(cylinder number)はbnを12で割った値
・sn(sector number)はbnを12で割った余り
1トラック当たりのセクタが12ということだと思います。
Lions本のP382に
セクタ/トラック 12
と記載があります。

Lions本のP383には、データ転送を開始する場合、RKDA、RKBA、RKWCを設定し、その後RKCSを設定する。とあります。

●RKDA、RKBA、RKWC


82 RKADDR->rkda = (dn << 13) | (cn << 4) | sn;
83 RKADDR->rkba = bp->b_un.b_addr;
84 RKADDR->rkwc = -(bp->b_bcount>>1);


・RKDAにマイナー番号、シリンダ番号、セクタ番号を設定しているようです。
・RKBAにbp->b_un.b_addrを設定。読み書きするバッファのアドレス?
⇒buf構造体はヘッダみたいなものでバッファの実体は別にあるはず?
・RKWCには-(bp->b_bcount>>1)を設定。転送カウントを二分の一してマイナスを付けた値?

●RKCS


85 com = ((bp->b_xmem&3) << 4) | IENABLE | GO;
86 if(bp->b_flags & B_READ)
87 com |= RCOM; else
88 com |= WCOM;
89 RKADDR->rkcs = com;


RKCSにはコマンドを記述するようです。
ReadならRCOM、WriteならWCOM
これで、実際にデバイスに対してReadあるいはWriteが実行されるはずです。

ということでrkstartをざっくりまとめると
RKDAで指定したブロック(メジャー番号、シリンダ、トラックに変換してアクセス)に対して、RKBAで指定したバッファで読み書きするってことになるかと思います。

●疑問点

rkintrは、依頼した読み書きが終わった段階で割り込みとして呼ばれるという認識です。
しかしrkintrの最後にrkstartを実行しています。
rkstartは実際にDISKに対してコマンドを送信している処理だと思うのですが、ここで実行している理由は?

readを例にして、流れを書いて見ました。(バッファキャッシュ上にデータがない場合)
この認識で合っていますか?
画像

2011年2月20日日曜日

第10回 V7から始めるUNIX講座 復習とまとめ(割り込みPart2)

第10回 クロック割り込み
今回で10回目です。あっという間でしたね。

今回はクロック割り込みです。

汎用OSでは、どんなプログラムが動くか分かりません。
中には無限ループ(while(1), for(;;))するような、プログラムがいるかもしれません。
そういうプログラムがいてもUNIXは他のプロセスの実行が出来ます。

システムコールの時には自発的に「割り込み」を発生させるわけですが、一定時間ごとに割り込みを発生させる機能もあります=インターバルタイマ割り込み。
Lions本の第11章にはクロック割り込みと記述があります。以降「クロック割り込み」と記述します。

クロック割り込みは「clock」で実装されています。
Lions本のP346に電源周波数クロックかプログラマブルリアルタイムクロックのどちらからの割り込みを処理するとあります。
クロックの呼び出しはベクターテーブルに記載があります。
http://tamacom.com/tour/kernel/unix/S/7.html

34 . = ZERO+100
35 kwlp; br6
36 kwlp; br6


ベクタアドレスが100(34行目)がベクタテーブルです。
Lions本のP336の表を見ると、





ベクタ位置周辺デバイス割り込み優先度プロセス優先度
100電源周波数クロック66
104プログラマブルクロック66


となっています。
どちらも割り込みも、PCはkwlpを実行します。
PSWはbr6(300)、8進数の300なので、11000000
5,6,7がプロセッサ優先度を示すので、110⇒6となります。上記のP336の割り込み優先度と一致しています。
kwlpはもう少し下に定義されています。


64 .globl _clock
65 kwlp: jsr r0,call; jmp _clock


jsr r0,call; jmp _clock
jsrはサブルーチンを実行する命令です。
実行すると以下のようになります。
PC=call サブルーチン:callへ分岐します。
r0=サブルーチンからの戻り先、つまりjsrの次の命令を示します。
この場合、;で命令を続けて記述しています。
よって、r0=jmp _clockとなります。



1、callの実行
callは、m40.sに定義されています。
http://tamacom.com/tour/kernel/unix/S/1.html


114 call:
115 mov PS,-(sp)
116 1:
117 mov r1,-(sp)
118 mfpi sp
119 mov 4(sp),-(sp)
120 bic $!37,(sp)
121 bit $30000,PS
122 beq 1f
123 jsr pc,(r0)+ ⇒前モードがユーザモード
124 tstb _runrun
125 beq 2f
126 mov $12.,(sp) / trap 12 is give up cpu
127 jsr pc,_trap
128 2:
129 tst (sp)+
130 mtpi sp
131 br 2f
132 1:
133 bis $30000,PS
134 jsr pc,(r0)+ ⇒前モードがカーネルモード
135 cmp (sp)+,(sp)+
136 2:
137 mov (sp)+,r1
138 tst (sp)+
139 mov (sp)+,r0
140 rtt


115から順番に
・現在のPSをスタックに積む
・現在のr1をスタックに積む
・前モードのspをスタックに積む
・さっき積んだ現在のPSW(スタック1回で2なので2個前)をもう一度スタック積む







スタック
現在のPS(マスクされた)
前モードのsp
現在のr1
現在のPS


120行目
スタックに乗せた現在のPSに対してマスクを掛けています。
bic命令はソースの非ゼロビットに対応するディスティネーションの各ビットを0クリアします。
37(8進数)は、011111。!でひっくり返しているので、1111111111100000
非ゼロを0にするので、PSWの下位5ビット以外を0クリアすることになります。

121行目
bit命令はソースとディスティネーションの論理積(AND)なので、$30000とPSの論理積
$30000(8進数)は2進数で、11000000000000です。
つまりPSWの12,13桁(前モードの状態を示すビット)をチェックします。
カーネルモードの場合は1fへ分岐します。



●前モードがカーネルモードの場合
132行以降へ分岐

133行目
PSに$30000を設定しています。(ユーザモード)

134行目
jsrでサブルーチンジャンプします。ジャンプ先はr0です。
先ほど書いたようにr0は(jmp _clock)です。

これで_clockつまりC言語のclockがコールされることになります。

●clockの実行
http://tamacom.com/tour/kernel/unix/S/84.html#L28

クロック割り込み(clock)は以下のブログが凄く詳しいので、こちらをどうぞ
http://d.hatena.ne.jp/takahirox/20110211/1297403488

以下に概要を示します。


28 clock(dev, sp, r1, nps, r0, pc, ps)

このパラメータは、こういう形で呼んでくれるようです。Linons本のP344参照
dev, sp, r1, npsは、callでスタックに積んでいました。

「タイマ割り込みのまず最初にやることは次の割り込みの設定。」

41 lks->r[0] = 0115;


callout、p2->c_timeが0より大きい最初のエントリのc_timeをデクリメントします。


54 if(callout[0].c_func == NULL)
55 goto out;
56 p2 = &callout[0];
57 while(p2->c_time<=0 && p2->c_func!=NULL)
58 p2++;
59 p2->c_time--;



callout構造体の定義は以下の通り
時間とルーチン、ルーチン用の引数を格納しています。
http://tamacom.com/tour/kernel/unix/S/53.html#L17


11 struct callo
12 {
13 int c_time; /* incremental time */
14 caddr_t c_arg; /* argument to routine */
15 int (*c_func)(); /* routine */
16 };
17 struct callo callout[NCALL];


優先度を5に設定します。
73~77でc_timeが0以下のもののc_funcを実行します。
78~84で既に実行したものは不要なので後ろから前へつめています。


71 spl5();
72 if(callout[0].c_time <= 0) {
73 p1 = &callout[0];
74 while(p1->c_func != 0 && p1->c_time <= 0) {
75 (*p1->c_func)(p1->c_arg);
76 p1++;
77 }
78 p2 = &callout[0];
79 while(p2->c_func = p1->c_func) {
80 p2->c_time = p1->c_time;
81 p2->c_arg = p1->c_arg;
82 p1++;
83 p2++;
84 }
85 }
86


ユーザモードで動作しているなら、ユーザの時間をインクリメントする
カーネルモードで動作しているなら、システム時間をインクリメントする


93 if (USERMODE(ps)) {
94 u.u_utime++;
95 if(u.u_prof.pr_scale)
96 addupc(pc, &u.u_prof, 1);
97 if(u.u_procp->p_nice > NZERO)
98 a += 8;
99 } else {
100 a += 16;
101 if (pc == waitloc)
102 a += 8;
103 u.u_stime++;
104 }


clockからcallへ戻り、135行以降を実施します。
・まず2回ポップしてspから2ワード取り除きます。
なぜcmpしてるかは、その方が高速だからだろうとV6の読書会で話題になりました。
・r1をspから戻す
・PSを取り除いて
・r0をspから戻す
・割り込みからリターンする


133 bis $30000,PS
134 jsr pc,(r0)+
135 cmp (sp)+,(sp)+
136 2:
137 mov (sp)+,r1
138 tst (sp)+
139 mov (sp)+,r0
140 rtt




●前モードがユーザモードの場合
基本はカーネルモードと同じですが、clock実行後に優先度の高いプロセスがあれば、そちらに切り替えます。
つまり、割り込まれたルーチンが再開するとは限りません。

callルーチン
123行以降を実行
123行目で_clockへジャンプします。これは前モードがカーネルモードと同じなので省略

_clockからcallへ戻り、124行以降を実施します。
runrunで優先度の高いプロセスが実行可能かどうかのチェック

なければ、スタックの内容を戻して、140行目で割り込みからリターンする

優先度の高いプロセスが存在する場合
V6だと_swtchを呼ぶだけですが、V7はちょっと複雑です。


124 tstb _runrun
125 beq 2f
126 mov $12.,(sp) / trap 12 is give up cpu
127 jsr pc,_trap


126,127行目
10進数の12をスタックに積んで、_trap(C言語のtrap)へ分岐します。

●trap
trapのswitch文の206でいきなりgoto out;です。


203 /*
204 * Allow process switch
205 */
206 case USER+12:
207 goto out;


230、231でrunrunをチェックして、qswtch()を呼びます。


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);


●qswtch
http://tamacom.com/tour/kernel/unix/S/96.html#L329
qswtchは、swtchを呼んでいるだけです。
setrqは実行するプロセスの選択?


329 qswtch()
/* */
330 {
331
332 setrq(u.u_procp);
333 swtch();
334 }


●swtch
setrqのすぐ下にswtchがあります。
プロセスの切り替えのために、現在のプロセスの状態をsaveで保存し、切り替えるプロセスの状態をresumeで復帰させます。


347 swtch()
/* */
348 {
349 register n;
350 register struct proc *p, *q, *pp, *pq;
351
352 /*
353 * If not the idle process, resume the idle process.
354 */
355 if (u.u_procp != &proc[0]) {
356 if (save(u.u_rsav)) {
357 sureg();
358 return;
359 }
360 if (u.u_fpsaved==0) {
361 savfp(&u.u_fps);
362 u.u_fpsaved = 1;
363 }
364 resume(proc[0].p_addr, u.u_qsav);
365 }


●プロセス状態の保存先
プロセスの保存先はu.u_rsavのようです。
http://tamacom.com/tour/kernel/unix/S/80.html#L19


19 label_t u_rsav; /* save info when exchanging stacks */


コメントにあるとおり、(カーネル)スタックを交換する場合に情報を保存する場所だと思います。

●saveとresume
http://tamacom.com/tour/kernel/unix/S/1.html
saveとresumeは、以下のようにレジスタの値を保存したり、戻したりしています。


713 .globl _save
714 _save:
715 mov (sp)+,r1
716 mov (sp),r0
717 mov r2,(r0)+
718 mov r3,(r0)+
719 mov r4,(r0)+
720 mov r5,(r0)+
721 mov sp,(r0)+
722 mov r1,(r0)+
723 clr r0
724 jmp (r1)
725
726 .globl _resume
727 _resume:
728 mov 2(sp),r0 / new process
729 mov 4(sp),r1 / new stack
730 bis $HIPRI,PS
731 mov r0,KISA6 / In new process
732 mov (r1)+,r2
733 mov (r1)+,r3
734 mov (r1)+,r4
735 mov (r1)+,r5
736 mov (r1)+,sp
737 mov $1,r0
738 bic $HIPRI,PS
739 jmp *(r1)+