プログラマのためのSQL 読書会(18)に参加
22章,23章あたりを読みました。
- IN述語の中には列名を書ける
- NOT INのサブクエリにNULLが含まれる場合、常に結果は無しになる。
- MySQLではcheck制約の代わりにenum,setなどが使われている
- VALUES句はINSERT句以外にも使える
- SQL ServerのINCLUDE
- 量化比較述語 ALL,ANY
- Aの上下逆さ記号とEの左右反対記号
- EXISTSをCHECK句 vs REFERENCE句
- IS NOT FALSEとIS TRUE
- 23.1にある1歳年上のSQL上での表現
- 量化子SOME
- この本の人物録を作るのは極めて困難
IN述語の中には列名を書ける
これは結構以外に思われる方が多いのではないでしょうか?
例えばテストの点数をscore1~score5でもつEcaminationテーブルについて、 score1~score5のどれか一つでも100点を取っている生徒、という条件抽出を試してみます。
PostgreSQLで例示します。 SELECT文発行対象のテーブル*1
DROP TABLE Examination; CREATE TABLE Examination( student_id char(4) NOT NULL PRIMARY KEY, score1 integer, score2 integer, score3 integer, score4 integer, score5 integer ); insert into Examination(student_id,score1,score2,score3,score4,score5) values('0001',90,90,60,53,0); insert into Examination(student_id,score1,score2,score3,score4,score5) values('0002',40,20,70,51,30); insert into Examination(student_id,score1,score2,score3,score4,score5) values('0003',60,90,10,43,100);
実行
SELECT * FROM Examination WHERE 100 in(score1,score2,score3,score4,score5);
結果
postgres=# SELECT * FROM Examination postgres-# WHERE 100 in(score1,score2,score3,score4,score5); student_id | score1 | score2 | score3 | score4 | score5 ------------+--------+--------+--------+--------+-------- 0003 | 60 | 90 | 10 | 43 | 100 (1 row)
NOT INのサブクエリにNULLが含まれる場合、常に結果は無しになる。
以前このテーマを題材にQiitaに記事を書きました(懐かしい)
- みなさんNOT INはあまり使わないようです。
MySQLではcheck制約の代わりにenum,setなどが使われている
- 後述しますが、MySQLにはcheck制約のようなものはないようです。
- 方言がある。使用実績は少ないのが実情。
- トリガーで値をチェックすることもある。
- ググるとMySQLでcheck制約を頑張って実現しようとする投稿が多く引っかかる。
VALUES句はINSERT句以外にも使える
これはちょっと衝撃的でした。 ちょっとした検証用の一時表を作るときは
- 1.スカラ式のみのSELECT句を作る
- 2.UNIONで繋げる
- 3.WITHで囲って一時表とする
このテクニックはこのQiitaの投稿で利用しています。
WITH students as( SELECT 1 AS sid, '高橋太郎' AS sname FROM DUAL UNION ALL SELECT 2 AS sid, '山田幸助' AS sname FROM DUAL UNION ALL SELECT 3 AS sid, '鈴木美恵子' AS sname FROM DUAL UNION ALL SELECT 4 AS sid, '織田信長' AS sname FROM DUAL ) SELECT * FROM students ORDER BY sid
このやり方を使わずともVALUES句でUNIONを使わずに一時表が作れたようです。
SELECT * FROM (VALUES(1,'高橋太郎'),(2,'山田幸助'),(3,'鈴木美恵子'),(4,'織田信長')) AS students(sid,sname);
PostgreSQLでの実行結果
postgres=# select * from postgres-# (values(1,'高橋太郎'),(2,'山田幸助'),(3,'鈴木美恵子'),(4,'織田信長')) as students(sid,sname); sid | sname -----+------------ 1 | 高橋太郎 2 | 山田幸助 3 | 鈴木美恵子 4 | 織田信長 (4 rows)
Mysqlではできない。
SQL ServerのINCLUDE
- インデックスのリーフにINCLUDEで値を入れられる。
- INCLUDEで指定した値のみの問い合わせで条件でインデックスが使われる場合、インデックスオンリースキャンになる。
- 同等の仕組みがDB2にもある。
MySQLのセカンダリーインデックス
MySQLのセカンダリーインデックス(PK以外のインデックス)は必ず主キーを保持しているので 主キーの一部のみの問い合わせを、セカンダリーインデックスで行なった場合、 インデックスオンリースキャンにできる。
量化比較述語 ALL,ANY
初めて見ました。 http://jutememo.blogspot.jp/2010/11/sql-3-all-any.html 初出現が22章ですが、23章で説明が入ります。(さすが難しい本。厳しい)
Aの上下逆さ記号とEの左右反対記号
離散数学とか論理学の話でよく出てくる記号ですが
- :ALLのA
- :EXISTSのE
が元らしいです。こういうのとりあえず暗記させられてたからルーツとかちょっと意外でした。
EXISTSをCHECK句 vs REFERENCE句
制約の対象となる親レコード
CREATE TABLE ZipCodes (state_code CHAR(2) NOT NULL PRIMARY KEY); --データ投入 insert into ZipCodes (state_code) values('JP'); insert into ZipCodes (state_code) values('US');
REFERENCE句
DROP TABLE Addresses; CREATE TABLE Addresses( addresses_name CHAR(25) NOT NULL PRIMARY KEY, street_addr CHAR(25) NOT NULL, city_name CHAR(20) NOT NULL, state_code CHAR(2) NOT NULL REFERENCES ZipCodes(state_code) ); INSERT INTO Addresses (addresses_name,street_addr,city_name,state_code) values('俺の家','1','1','JP'); INSERT INTO Addresses (addresses_name,street_addr,city_name,state_code) values('トランプの家','1','1','US'); INSERT INTO Addresses (addresses_name,street_addr,city_name,state_code) values('金正恩の家','1','1','KP'); --三行目でエラー(親側のテーブルにKPが登録されていないため) --ERROR: insert or update on table "addresses" violates foreign key constraint "addresses_state_code_fkey" --DETAIL: Key (state_code)=(KP) is not present in table "zipcodes".
CHECK制約
DROP TABLE Addresses; CREATE TABLE Addresses( addresses_name CHAR(25) NOT NULL PRIMARY KEY, street_addr CHAR(25) NOT NULL, city_name CHAR(20) NOT NULL, state_code CHAR(2) NOT NULL, CONSTRAINT valid_state_code CHECK( EXISTS( SELECT * FROM ZipCodes AS Z1 WHERE Z1.state_code = Addresses.state_code )) ); --上記のDDLはpostgresqlでは実行できない。 --ERROR: cannot use subquery in check constraint
- REFERENCE句の方がカスケードを使えるというメリットがある。
- このCHECK句が使える実装(RDBMS)がない?
オマケ:MySQLのReference句はINSERTに対して制約を書けない?
帰宅後、自宅で検証してちょっと不思議な現象が発生したのでメモです。
mysql> CREATE TABLE Addresses( -> addresses_name CHAR(25) NOT NULL PRIMARY KEY, -> street_addr CHAR(25) NOT NULL, -> city_name CHAR(20) NOT NULL, -> state_code CHAR(2) NOT NULL REFERENCES ZipCodes(state_code) -> ); Query OK, 0 rows affected (0.01 sec) mysql> mysql> INSERT INTO Addresses (addresses_name,street_addr,city_name,state_code) values('俺の家','1','1','JP'); Query OK, 1 row affected (0.01 sec) mysql> INSERT INTO Addresses (addresses_name,street_addr,city_name,state_code) values('トランプの家','1','1','US'); Query OK, 1 row affected (0.00 sec) mysql> INSERT INTO Addresses (addresses_name,street_addr,city_name,state_code) values('金正恩の家','1','1','KP'); Query OK, 1 row affected (0.00 sec) mysql> mysql> select * from Addresses; +--------------------+-------------+-----------+------------+ | addresses_name | street_addr | city_name | state_code | +--------------------+-------------+-----------+------------+ | トランプの家 | 1 | 1 | US | | 俺の家 | 1 | 1 | JP | | 金正恩の家 | 1 | 1 | KP | +--------------------+-------------+-----------+------------+ 3 rows in set (0.00 sec)
ZipCodeに登録していない北朝鮮の住所が登録できてしまいました。
少し調べると衝撃的なことが書いてありました。
2 つのテーブルを結合するだけの場合は、外部キー制約は必要ありません。InnoDB 以外のストレージエンジンの場合、カラムを定義するときに REFERENCES tbl_name(col_name) 句を使用できます。これは実際の効果はありませんが、現在定義しようとしているカラムが別のテーブルのカラムを参照する予定であるという自分のメモまたはコメントとして役立ちます。この構文を使用するときは、次の点を理解しておくことが非常に重要です。
MySQL は、col_name が実際に tbl_name に存在するか (また、その tbl_name 自体が存在するか) を確認するためのどのような CHECK も実行しません。
MySQL は、tbl_name に対してどのようなアクションも実行しません。たとえば、定義しようとしているテーブルの行に実行されたアクションに対応して行を削除することなどはありません。つまり、この構文にはどのような ON DELETE 動作や ON UPDATE 動作もありません。(REFERENCES 句の一部として ON DELETE 句や ON UPDATE 句を記述することはできますが、これらも無視されます。)
この構文はカラムを作成します。どのようなインデックスやキーも作成しません。
https://dev.mysql.com/doc/refman/5.6/ja/example-foreign-keys.html
😱
IS NOT FALSEとIS TRUE
postgres=# select true IS TRUE; ?column? ---------- t (1 row) postgres=# select null IS NOT FALSE; ?column? ---------- t (1 row)
- 初めて知りました。
- 普通は使わないらしい
23.1にある1歳年上のSQL上での表現
INTERVAL 365 DAY
は1歳年上としては不適切なのでは?
- 閏年
- 365日先に生まれたから1歳上と言えるのか?
ドメインルールによると思いますが、教育関係の場合は学年での差分?をとるといいと思うのですが、 例えば
- 1991/03/11 (1990/4/2~1991/4/1のグループ)
- 1991/04/11 (1991/4/2~1992/4/1のグループ)
この2人の場合は1歳(1学年)差があるという扱いをする気がしますね ちなみに学年グループが4月2日からはじまる理由を知らない人はこちらを参照*2
あんまりSQL関係ありませんね。
量化子SOME
ANYと同じ意味
この本の人物録を作るのは極めて困難
しかし、アレックス・ドーフマンは1つのサブクエリで解く方法を見つけた。
アレックス・ドーフマンって誰?検索してもこの本しか出てこないんだけど…*3
この本はニュースグループというのでしょうか?ネット上の草の根SQLコミュニティの投稿なども多数紹介されており、このドーフマンさんのように出典が一切書かれていないものも多くあります。主催の方は人名録的の作成を諦められたようです。
- 作者: ジョー・セルコ,Joe Celko,ミック
- 出版社/メーカー: 翔泳社
- 発売日: 2013/05/24
- メディア: 大型本
- この商品を含むブログ (16件) を見る
30日OS自作本8日目
8日目の内容になります。 前回マウスを動かすための割り込み処理を完成させたので、 受信したデータを使ってマウスカーソルを動かす内容になります。
動画
動画を撮ってみました
マウスの受信データの解説
マウス入力は3バイトをワンセットとして送られてくるようです。意味のある入力として読み取るためにmdec.phase
という変数でこの3バイトの受信を同期させるようなことをやっています。
if (mdec->phase == 0) { /* マウスの0xfaを待っている段階 */ if (dat == 0xfa) { mdec->phase = 1; } return 0; } if (mdec->phase == 1) { /* マウスの1バイト目を待っている段階 */ if ((dat & 0xc8) == 0x08) { /* 正しい1バイト目だった */ mdec->buf[0] = dat; mdec->phase = 2; } return 0; } if (mdec->phase == 2) { /* マウスの2バイト目を待っている段階 */ mdec->buf[1] = dat; mdec->phase = 3; return 0; } if (mdec->phase == 3) { /* マウスの3バイト目を待っている段階 */ mdec->buf[2] = dat; //以下バッファの内容を加工してmdecにデータを詰めるコードが続く --(省略)-- return 1; }
バッファから構造体へ変換
mdec->btn = mdec->buf[0] & 0x07; mdec->x = mdec->buf[1]; mdec->y = mdec->buf[2];
sprintf(s, "[lcr %4d %4d]", mdec.x, mdec.y); if ((mdec.btn & 0x01) != 0) { s[1] = 'L'; } if ((mdec.btn & 0x02) != 0) { s[3] = 'R'; } if ((mdec.btn & 0x04) != 0) { s[2] = 'C'; }
受け取った3バイトのデータは以下のように対応しています。
- 1バイト目:下位3bitがクリックに関する情報
- 2バイト目:マウスカーソルの水平方向の動き
- 3バイト目:マウスカーソルの縦方向の動き
[]
の中身ですが左から1バイト目、2バイト目、3バイト目となります。
確かにこの3バイトをただ画面に表示しているharib05bなどを見ると、 限りなく縦にマウスを動かした場合は3バイト目しか反応せず、限りなく横だけにマウスを動かせば2バイト目が変化しているように見えます。
しかし、なぜかカーソルを動かしている際に1バイト目が反応することがあるようです
調べましたがこの辺の参考情報はWikiには乗っていないようです。*1
マウスはどのように制御するのですか? -- 名無しさん 2005-03-08 (火) 19:42:26
ご質問ありがとうございます。マウス関係はそのうちページを作りたいと思います。 -- K 2005-03-10 (木) 13:15:21
http://oswiki.osask.jp/?cmd=read&page=%28AT%29keyboard&word=keyboard
ちょっと割り込みの知識が整理しきれていないので、別途整理して日記に書こうと思います。
ちなみにMacbook Proのトラックパッド単体で右クリックすることはできないようです。 macOSで右クリックに相当するダブルタップもLcRとして認識されませんでした。 commandキーを押しながらクリックすることで右クリックとして認識されました。 ただしこの動作はqemu内のhariborteOSのみで右クリックとして扱われているようで、macOS上でやっても特に右クリックメニューとかは出ません。
プロテクトモード
day3に書いてある内容、セグメントレジスタESとBXを使わない方法。 GDTを代わりに使うようです。 保護(プロテクト)モードにも種類があって16bit,32bitがあるそうです。 今回利用するのは32bitの保護モードです。
感想
後半の32ビットモードへの道に書いてあることは省略(お預け)されていることも多く、よくわからなかった。 とにかく、このOSかBIOSか何かはデフォルトで16ビットの非保護モードで起動してしまうので、起動直後に32ビットモードに切り替えてGDT,IDTを作り直すということをやっているのだと思う。
*1:Kさんは著者の川合さん
30日OS自作本7日目
7日目は6日目で作った割り込みを少しカスタマイズしました。 マウスからの入力が感知できるようになりましたが、まだ動かす部分は作っていません。
割り込み処理の中身を小さく
- ハンドラの関数内部にVRAMにピクセルを書き込む処理(キー入力の文字表示)
- ハンドラの中で行う処理としては重すぎる
マウス操作にも使えるバッファに
struct FIFO8 { unsigned char *buf; int p, q, size, free, flags; };
- FIFO8という名前の構造体に。
- 構造体の定義はbootpack.hに
- 処理の定義はfifo.cに
- リングバッファの制御用の変数
p
:読み出しの位置q
:書き込みの位置
- 配列の長さ->無制限に
unsigned char *buf;
size
で長さを外部から設定できるようにする
- 空き容量を保存する
free
- バッファ溢れの情報を保持する
flags
マウスを使うための設定
- マウス制御回路の有効化
- マウス自体の有効化
http://oswiki.osask.jp/?cmd=read&page=%28AT%29keyboard&word=keyboard
#define PORT_KEYDAT 0x0060 #define PORT_KEYSTA 0x0064 #define PORT_KEYCMD 0x0064 #define KEYCMD_WRITE_MODE 0x60 #define KBC_MODE 0x47 void init_keyboard(void) { /* キーボードコントローラの初期化 */ wait_KBC_sendready(); io_out8(PORT_KEYCMD, KEYCMD_WRITE_MODE); wait_KBC_sendready(); io_out8(PORT_KEYDAT, KBC_MODE); return; } #define KEYCMD_SENDTO_MOUSE 0xd4 #define MOUSECMD_ENABLE 0xf4 void enable_mouse(void) { /* マウス有効 */ wait_KBC_sendready(); io_out8(PORT_KEYCMD, KEYCMD_SENDTO_MOUSE); wait_KBC_sendready(); io_out8(PORT_KEYDAT, MOUSECMD_ENABLE); return; /* うまくいくとACK(0xfa)が送信されてくる */ }
マウスの設定値の根拠みたいなの、ググりまくってもよくわからない。まぁ見つかってもこの位置のbitはXXXを有効かするフラグです、 みたいなのが見つかるだけだと思うので、ちょっとここをググるのはやめよう。
この辺が情報元なのかな?
データ受け取り
マウス制御回路はキーボード制御回路の中にあるらしく、データの受け取り箇所はキーボードと全く同じらしい。 ので割り込み番号で判別して扱う。
if (fifo8_status(&keyfifo) != 0) { i = fifo8_get(&keyfifo); io_sti(); sprintf(s, "%02X", i); boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31); putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s); } else if (fifo8_status(&mousefifo) != 0) { i = fifo8_get(&mousefifo); io_sti(); sprintf(s, "%02X", i); boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 47, 31); putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s); }
感想
ネットに転がっている感想を読み漁って気になるものがありました。
http://www.makisima.org/wiki/wiki.cgi?HariboteOS
そうなの?OSを作るような人は資料の無い一つ一つのハードウェアにオシロスコープとか使ってリバースエンジニアリングでもして仕様を掘り当てるのかな?大変そう(小並感)。
30日OS自作本6日目
6日目は割り込みハンドラの実装&呼び出しを行った。
GDTの仕組み
GDTRという48ビットのレジスタにGDTを配置した番地(開始地点)とリミット(開始地点からどこまでGDTか)を書き込む
- GDTRの最初の2バイトはリミットを表す
- 残りの4バイトでGDTが置いてある番地を表す
0xFFFF00002700
で0x270000~0x27ffff
までGDTとして扱うという意味なる。- GDT自体のサイズは8バイトであるからこの場合GDTは8192個設定可能となる。
構造体の解説
struct SEGMENT_DESCRIPTOR { short limit_low, base_low; char base_mid, access_right; char limit_high, base_high; };
上記の構造体の説明。文章だけだとこんがらがったので画像に起こして理解した。画像とコードの対応がちょっとおかしいが、C言語は4bit長の変数がないらしく、limit_highの上位4bitにセグメント属性を書き込むようになっている。最終的にGDTに書き込む長さとしてはlimit_highは実質4bit,access_rightは実質12bitとなっている。
構造体上での表現 63 55 47 39 31 23 15 7 0 +--------+--------+--------+--------+--------+--------+--------+--------+ |base_hi |limit_hi| ar |base_mid| base_low | limit_low | +--------+--------+--------+--------+--------+--------+--------+--------+ CPUの仕様上のデスクリプタ ※limit_highの上位4bitはアクセス属性の続きになります。だから実際はCPUは上の構造体を以下の形式で扱います。 63 55 47 39 31 23 15 7 0 +--------+----+----+--------+--------+--------+--------+--------+--------+ |base_hi |ar |l_hi| ar |base_mid| base_low | limit_low | +--------+----+----+--------+--------+--------+--------+--------+--------+ ※入りきらない変数名は省略しています
アクセス権の設定値
arの中身です。
設定値 | hex | 意味 |
---|---|---|
00000000 | 0x00 | GDTが未使用であることを示す |
10010010 | 0x92 | システム専用readのみ。実行不可 |
10011010 | 0x9a | システム専用read可能&実行可能 |
11110010 | 0xf2 | アプリケーション用readのみ。実行不可 |
11111010 | 0xfa | アプリケーション用read可能&実行可能 |
セキュリティ
CPUにはアプリケーションモードとシステムモードがある。
- アプリケーションモードの時にLGDTの実行を制限(監視)する
- アプリケーションモードの時にシステム専用のセグメントの読み書きを制限(監視)する
実行中のプログラムが0x9a,0xfaどちらのセグメントに配置されているかでモードの判別を行う。 アプリケーションモード時にLGDTの実行を制限しているのはGDTの再設定ができてしまうから。*1
割り込み
割り込みにはGDTとIDTの初期化に加えて、PICの初期化が必要
PICとは?
programmable intrrupt controllerの略
CPUは単独では一つしか割り込みを扱えない。 複数(8個)の割り込みを一つの割り込み信号にまとめる装置がPIC
割り込み信号をIRQといい0~8の数字が振られPICの各ピンに対応している
本中のPICは2連結(マスター:スレーブ)になっているらしく、15個のIRQが扱える*2
- マウス:IRQ12
- キーボード:IRQ1
はここに記載があるものと一緒。何かの標準規格?
http://d.hatena.ne.jp/wocota/20090302/1235985661
ハードウェア的にはIntel8259と呼ばれるものと同一?
PICの接続をマスターの2番ピンにさしてるのはIBMのおじさんが決めた、的な下りがあるけど、 IBMのおじさんが決めた規格があってInterl人もそれに添って決めているということなのかな。
PICのレジスタ
全て8bit
- IMR:innterrupt mask register
- 1が立っている位置に対応しているIRQの割り込みを無視する。
- ICW:1~4の合計4バイトがあるが何に使っているかよくわからない。プログラムの設定値以外は設定できないらしい。
- ICW3:スレーブが刺さってるマスタのピンを登録。つまり
0b00000100
で固定。- 下位ビットからpin 0,pin 1,pin 2,...という対応だと思う。
- 他の値は設定できない。
- ICW2:IRQ0~15をINTのどれで受け取るかを指定。以下に設定例を示す
- ICW3:スレーブが刺さってるマスタのピンを登録。つまり
io_out8(PIC0_ICW2, 0x20 ); /* IRQ0-7は、INT20-27で受ける */ io_out8(PIC1_ICW2, 0x28 ); /* IRQ8-15は、INT28-2fで受ける */
割り込みの設定
キーボード(IRQ1)の例
- IDTに0x21bの割り込みの祭に呼び出す関数
_asm_inthandler21
(アセンブリ)を設定 _asm_inthandler21
からC言語の関数inthandler21
を呼び出す- キーボードが押された際に画面に出力
感想
第14回 セキュリティ共有勉強会に参加
5月10日開催。テーマは認証
新しい知見や感想を箇条書き
パスワードマネージャー
Slackのエンタープライズ版の課題
- SAMLのidPが1つしか使えないらしい。
- 複数のワークスペースの認証情報を管理することができないという問題がある。
- keycloakというOSSを仲介に使う。
- あまり関係ないがApple Watchでの2段階認証かっこいい。
- PCのWEB画面でパスワード入力後、Apple Watchに通知が来る
- Slackの社内利用を推したいけど、こういう課題があるのを知れたのは良かった。*3
- SAMLのidPが1つしか使えないらしい。
FIDO認証がすごい。
CAPTCHA(開発会社の方がLTをされました)
パスワード定期変更の是非
インフラ勉強会
- リモートでやってるやつ
- 羽目をはずすとログに残る
- 地方の人は都内の勉強会とか羨ましい感じらしい
- 地方転勤が辛いらしい
- リモートでやってるやつ
他
- アメリカ人の好きな食べ物はだいたいピザらしい
- 秘密の質問「好きな食べ物は?」が…
- 認可と認証を混同してはいけない。
- 自衛隊の誰何は3回聞いて返事が帰ってこなかったら撃っていいらしい。
http://kamiya-masanari.com/JSDF_Word/index.html返事がなく、3度誰何しても答えない場合は「捕獲するか、刺・射殺」します。
- アメリカ人の好きな食べ物はだいたいピザらしい
*1:人間が覚えるという意味での運用コスト
*2:https://www.trendmicro.com/ja_jp/forHome/products/pwmgr.html
*3:多分1社でほそぼそと運用するには問題にならないだろうけど。
*4:生体情報は個人情報なのでサーバー側で管理するのはリスクらしい
30日OS自作本5日目
5日目は文字とマウスカーソルの表示をやりました。
文字をどう扱うか
8 × 16のピクセルでAを表現することを考えます。 1行を1バイトの
□ □ □ □ □ □ □ □ □ □ □ ■ ■ □ □ □ □ □ □ ■ ■ □ □ □ □ □ □ ■ ■ □ □ □ □ □ □ ■ ■ □ □ □ □ □ ■ □ □ ■ □ □ □ □ ■ □ □ ■ □ □ □ □ ■ □ □ ■ □ □ □ □ ■ □ □ ■ □ □ □ ■ ■ ■ ■ ■ ■ □ □ ■ □ □ □ □ ■ □ □ ■ □ □ □ □ ■ □ □ ■ □ □ □ □ ■ □ ■ ■ ■ □ □ ■ ■ ■ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □
//16バイトで1文字を表現。 //※char型=1バイト static char font_A[16] = { 0x00,// 00000000 0x18,// 00011000 0x18,// 00011000 0x18,// 00011000 0x18,// 00011000 0x24,// 00100100 0x24,// 00100100 0x24,// 00100100 0x24,// 00100100 0x7e,// 01111110 0x42,// 01000010 0x42,// 01000010 0x42,// 01000010 0xe7,// 11100111 0x00,// 00000000 0x00 // 00000000 };
もちろん全ての文字に対してC言語でいちいち配列を作ってられないから、DB命令でアルファベットなど主要な文字の群を実行バイナリに直接書き込み、Cからアドレス経由で指定する。
文字を配列のインデックスに利用する
//ASCIIと同じ並びで1文字あたり16バイト格納。16バイト*265文字=4096バイトぶんのcharが必要。 //※char型=1バイト extern char hankaku[4096]; putfont8(binfo->vram, binfo->scrnx, 8, 8, COL8_FFFFFF, hankaku + 'A' * 16);
putfont8の最後の引数hankaku + 'A' * 16
がミソ。文字データを格納している配列hankaku
の先頭アドレスから0x41 * 16 バイト先に16バイト分の'A'
を表す文字のデータが格納されている。配列 + 文字のポイント演算をデータ参照のインデックスに利用しているというのが高等なテクニックに感じた。*1
//定義 void putfont8(char *vram, int xsize, int x, int y, char c, char *font) { //for分の中は一行ごとの処理。i<16からわかる通り、16行分行う。 for (i = 0; i < 16; i++) { d = font[i]; ・・・(略) //VRAMに位置ピクセルごとに書き込み * 8回行う if ((d & 0x80) != 0) { p[0] = c; } if ((d & 0x40) != 0) { p[1] = c; } ・・・(略) if ((d & 0x02) != 0) { p[6] = c; } if ((d & 0x01) != 0) { p[7] = c; } } return; }
マウスカーソルを動かすためにはセグメンテーションと割り込みが必要。
マウスカーソルを動かすにはGDTとIDTの説明が必要らしい。 5日目にしてはちょっと難しくなるよ、と書かれている。
セグメンテーション
- ORG命令で読み込む番地を指定する
- だが競合が起きると大変。
- メモリを切り分けて好きなブロックの最初の番地を0番地にする。
以下の設定が必要
- セグメントのサイズ
- セグメンの開始番地
- セグメントの管理用属性
セグメントでは0~8191番号が使える(8192個のセグメントが使える)
8192個のセグメントが定義できるわけで、だから設定にはその8倍の65536バイト(=64KB)が必要になります
この8倍っての1つのセグメントの設定に必要なサイズだと思うのだけれど、どこから出てきたのだろう?*2
とにかくこの64KBをGTD:global (segment) descriptor tableという。 使い方としては並んだセグメントの設定の先頭と有効設定個数をCPUのGDTRというレジスタに登録すれば完了。
割り込み
- IDTはinterrupt descriptor tableの略
- ポーリングと割り込みの比較。が入っている。コンピュータは監視対象が多すぎるのでポーリングでOSを作るのは難しい。
- IDTでは割り込み番号0~255を設定可能(つまり割り込みは256種類までしか登録出来ない?)
- 番号と関数を対応付けた表のようになっているらしい。
セグメントと割り込みのコード
データ構造は以下のようになる
//GDTの8バイトの中身 struct SEGMENT_DESCRIPTOR { short limit_low, base_low; char base_mid, access_right; char limit_high, base_high; }; //IDTの8バイトの中身 struct GATE_DESCRIPTOR { short offset_low, selector; char dw_count, access_right; short offset_high; };
GDTとIDTのメモリ割り当てはコード上ではこんな感じ。
void init_gdtidt(void) { struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) 0x00270000; struct GATE_DESCRIPTOR *idt = (struct GATE_DESCRIPTOR *) 0x0026f800;
関係ないけど上のコードをatomで書いたらmarkdownのシンタックスハイライトがバグった。(Markdown上の**強調表現**
とC言語のシンタックスハイライトが衝突してる?)
以降のコードの詳細。(GDTとIDTに実際に値を割り当てて割り込みとセグメンテーションを設定している箇所)は説明が放棄されている?(明日に廻す)ためか中途半端になっているようですのでday6の記事に書きます
思い出
- 昔割り込みハンドラに登録して云々?ということをやった記憶がある。
- ISRとかいうのを使った覚えがある。
わからないこと
hariboteの開発環境では2進数リテラルが使えない?
char test = 0b01100100;
この行をbootpack.cの66行に入れてmake run
すると以下のようなエラーが入る。
bootpack.c:66: invalid suffix on integer constant
うーん以下のプログラムはgccでコンパイルして実行もできるけどhariboteの開発環境のコンパイラは特殊?
#include <stdio.h> int main(void){ char test = 0b01100100; printf("%c\n",test); //dが出力される }
前回失敗したuchanさんの「OS自作入門*3」
起動ディスクの選択画面まではいけたが、その先が真っ黒で特にhelloworldしなかった。 打ち間違いがあったのかな?時間が無くなったので今日はこの辺でおしまい。
あるブログでこれを実験してくれた人がいて、上手くいかなかったみたいなので、家帰ったらちょいと再実験してみよう。
— OS自作 uchan_nos (@uchan_nos) 2018年5月8日
修正していただいたようなので、再度チャレンジしたものの昨日と状況は変わらず、起動ディスク選択画面まで行くが画面が表示されない。 USBを何本か変えてみたり、FATの種類?*4を変えてみたりしたが上手くいかなかった。 ちょっと特殊な環境でやっているため*5何が原因なのかよくわからない。別のPCを使える機会があれば再度試してみようと思う。
感想
大学時代にAVRマイコンを扱う授業の自由課題として8*8のマトリックスLEDに美咲フォントを表示する要素を作った作品を作ろうと試したことがあった。*6*7今回の内容はC言語でピクセル形式のデータをVRAMで扱うためのノウハウが詰まっていた1章であり、件の実験でやりたかったことに近い内容だったように思う。
当時は美咲フォントのデータを上手く扱うことができず、また時間がなかったため諦めたが、事前にこの本を読んでいれば出来ていた気がする。
まぁ件に限らず、技術書を読んで引き出し増やすのは重要だと思う。
SQLのCOUNT,MAX関数はソートを発生させるのか PostgreSQL編
この記事の続きてです 今回も同じようなデータを使います。
id | age | country |
---|---|---|
0001 | 18 | JP |
0002 | 23 | US |
0003 | 56 | SK |
0004 | 99 | SK |
0005 | 11 | US |
0006 | 34 | JP |
create table people( id char(4) not null primary key, age integer not null, country char(2) not null ); -- people INSERT INTO people(id, age, country) VALUES('0001', '18', 'JP'); INSERT INTO people(id, age, country) VALUES('0002', '23', 'US'); INSERT INTO people(id, age, country) VALUES('0003', '56', 'SK'); INSERT INTO people(id, age, country) VALUES('0004', '99', 'SK'); INSERT INTO people(id, age, country) VALUES('0005', '11', 'US'); INSERT INTO people(id, age, country) VALUES('0006', '34', 'JP');
PostgreSQLでのソート
PostgreSQLではソートが入るとSort演算子が実行計画に表示される
postgres=# explain select * from people order by age; QUERY PLAN ----------------------------------------------------------------- Sort (cost=88.17..91.35 rows=1270 width=36) Sort Key: age -> Seq Scan on people (cost=0.00..22.70 rows=1270 width=36) (3 rows)
count関数
postgres=# select country, count(*) from people group by country; country | count ---------+------- US | 2 JP | 2 SK | 2 (3 rows) postgres=# explain select country, count(*) from people group by country ; QUERY PLAN ----------------------------------------------------------------- HashAggregate (cost=29.05..31.05 rows=200 width=20) Group Key: country -> Seq Scan on people (cost=0.00..22.70 rows=1270 width=12) (3 rows)
- Sort確認できず
max関数
postgres=# select country, max(age) from people group by country; country | max ---------+----- US | 23 JP | 34 SK | 99 (3 rows) postgres=# explain select country, max(age) from people group by country ; QUERY PLAN ----------------------------------------------------------------- HashAggregate (cost=29.05..31.05 rows=200 width=16) Group Key: country -> Seq Scan on people (cost=0.00..22.70 rows=1270 width=16) (3 rows)
- sort確認できず
集約なしmax関数
postgres=# explain select max(age) from people; QUERY PLAN ---------------------------------------------------------------- Aggregate (cost=25.88..25.89 rows=1 width=4) -> Seq Scan on people (cost=0.00..22.70 rows=1270 width=4) (2 rows)
- sort確認できず
結論
実行計画を見たところsortが発生している箇所はなさそうです。
おうちで学べるデータベースの基本はMySQLを念頭に置いているので、
本書では、実際にいくつかの章(特に後半)で「MySQL」というデータベースを操作することで、「データベースとは何か?」ということをわかりやすく解説しています。
MySQLの動作としてMax,Countを集約関数と同時に使うと ソートが発生するという記載は適当なのだと思います。
- 作者: ミック,木村明治
- 出版社/メーカー: 翔泳社
- 発売日: 2015/02/13
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る