ブロックデバイスの登録
Rev.2を表示中。最新版はこちら。
ブロックデバイスはVFSからの要求を、実際の物理デバイスに対して入出力を行うものです。ブロックデバイスはstruct gendisk構造体で表します。ここには、セクタ数/セクタサイズ等のデバイス情報はもちろん、入出力をつかさどるコールバック関数等を設定していくことになります。ブロックデバイスが動作する流れは、alloc_disk関数でstruct gendisk構造体を取得し、そこにそのブロックデバイスの情報(メジャー番号、ディスク名、セクタサイズ、セクタ数等)を設定すると同時に、要求キューと称するstruct request_queue構造体をセットします。そしてこの要求キューにはそのデバイスの実際の入出力を行うコールバック関数を設定します。そしてIOスケジューラからこのコールバック関数がコールすることで、セクタ単位での入出力を行うわけです。
hd_init関数はIDEデバイスの処理です。register_blkdev関数はMAJOR_NRが既に登録されていないかチェックし、登録してなければhdという名で登録します。これはメジャー番号をハッシュキーとして静的変数major_names[]にそのメジャー番号が登録されているかチェックするだけです。デバイス動作にかかる処理と、直接関係ありません。
次にblk_init_queue関数で要求キーを作成します。同時にhd_queue->request_fn = do_hd_request;と設定されます。ここでの処理は重要です。これで物理デバイスの入出力を行う時do_hd_requestをコールされ、セクタ単位でIO処理を行うことができるようになるからです。
blk_queue_max_sectors/ blk_queue_hardsect_size関数で、要求キューの上限数およびセクタサイズを設定し、デバイスタイマーとして init_timer関数でカーネルタイマを設定します。これは入出力時のタイムアウト処理を行うようです。
for (drive = 0 ; drive < NR_HD ; drive++)で、struct gendisk構造体を作成します。NR_HDはデバイスの数で、システム起動時BIOSから渡される引数か、プログラム的にdefineしているかで設定されるようです。IDEについてはマスタとスレーブということで、1ないし2という事ではと思います。
alloc_diskの引数64は、そのデバイスのパーティションが、64個まで持てるようにstruct gendiskを作成するということですが、要はstruct gendisk->part情報のメモリを、64個獲得することのようで、この時点では物理デバイスのパーティション情報は反映されていません。そしてプログラムにあるようなメジャー番号を初めとするデバイス情報を設定し、先に作成した要求キューをdisk->queue = hd_queueとして設定します。これでこのデバイスに対して要求キューが紐づきました。すなわち入出力のコールバック関数do_hd_requestとも紐づいたというわけです。
struct gendisk作成毎にsprintf(disk->disk_name, "hd%c", 'a'+drive);で、デバイス名が付けられます。デバイスファイルがhda/hdbとなる所以です。なおNR_HDを2とした場合(たぶんマスタとスレーブ)hdaとhdbのメジャー番号は同じだということで、hda,hdbおよよびそれらのパーティションのIO処理は同じだということです。
追記
セカンドIDEのサポートはどうなっててるんでしょうか?少なくとも割り込み番号が違ういうことで本処理内でインプリメントできないはずで・・・それらしいファイル名を探してみましたが見当たりません。セカンドIDEにつては主流がSCSIということでサポートされなくなったんでしょうか?request_irq関数でそのデバイスの割り込み番号に、そのハンドラとして、hd_interrupt関数を登録しています。入出力処理はblk_init_queue関数で登録したdo_hd_requestコールバック関数で行うのですが、この関数は、デバイスにコマンドとしてIO処理をするだけで、実際のデータの処理は行いません。データの処理は、ここで設定した割り込みハンドラhd_interrupt関数で行うことになります。
request_region関数はこのデバイスで使うIOを、他のデバイスが使うことがないように予約しているだけです。
そして最後にadd_disk関数で、作成した struct gendisk *diskをカーネルに登録します。これでこのデバイスは動作することになります。なお、add_disk関数ではデバイス名からデバイスファイルを作成し、デバイスのMBRを読んで実際のパーチィション情報を再設定し、そのデバイスファイルを作成しています。
static int __init hd_init(void) { int drive; if (register_blkdev(MAJOR_NR, "hd")) return -1; hd_queue = blk_init_queue(do_hd_request, &hd_lock); if (!hd_queue) { unregister_blkdev(MAJOR_NR, "hd"); return -ENOMEM; } blk_queue_max_sectors(hd_queue, 255); init_timer(&device_timer); device_timer.function = hd_times_out; blk_queue_hardsect_size(hd_queue, 512); if (!NR_HD) { printk("hd: no drives specified - use hd=cyl,head,sectors" " on kernel command line\n"); goto out; } for (drive = 0 ; drive < NR_HD ; drive++) { struct gendisk *disk = alloc_disk(64); struct hd_i_struct *p = &hd_info[drive]; if (!disk) goto Enomem; disk->major = MAJOR_NR; disk->first_minor = drive << 6; disk->fops = &hd_fops; sprintf(disk->disk_name, "hd%c", 'a'+drive); disk->private_data = p; set_capacity(disk, p->head * p->sect * p->cyl); disk->queue = hd_queue; p->unit = drive; hd_gendisk[drive] = disk; printk("%s: %luMB, CHS=%d/%d/%d\n", disk->disk_name, (unsigned long)get_capacity(disk)/2048, p->cyl, p->head, p->sect); } if (request_irq(HD_IRQ, hd_interrupt, IRQF_DISABLED, "hd", NULL)) { printk("hd: unable to get IRQ%d for the hard disk driver\n", HD_IRQ); goto out1; } if (!request_region(HD_DATA, 8, "hd")) { printk(KERN_WARNING "hd: port 0x%x busy\n", HD_DATA); goto out2; } if (!request_region(HD_CMD, 1, "hd(cmd)")) { printk(KERN_WARNING "hd: port 0x%x busy\n", HD_CMD); goto out3; } /* Let them fly */ for (drive = 0; drive < NR_HD; drive++) add_disk(hd_gendisk[drive]); return 0; : : }do_hd_request関数を割り込みを禁止してhd_request関数がコールします。ここでは実際のIO処理が行われます。if (do_hd)はまだ先の入出力のデータ読み込みが終了していない事を意味します。この理由は割り込み処理を見ると分かります。
req = CURRENTは処理する要求キューを取得し、そこから読み書きするセクタ/セクタ数およびディスク情報を取得し、要求サイズがディスクサイズを超えてないかチェックします。OKならそれをセクタ、トラック、ヘッド、シリンダと分解し、読み/書きに応じて、hd_out関数をコールします。
#define CURRENT elv_next_request(hd_queue) static void hd_request(void) { unsigned int block, nsect, sec, track, head, cyl; struct hd_i_struct *disk; struct request *req; if (do_hd) return; repeat: del_timer(&device_timer); local_irq_enable(); req = CURRENT; if (!req) { do_hd = NULL; return; } if (reset) { local_irq_disable(); reset_hd(); return; } disk = req->rq_disk->private_data; block = req->sector; nsect = req->nr_sectors; if (block >= get_capacity(req->rq_disk) || ((block+nsect) > get_capacity(req->rq_disk))) { printk("%s: bad access: block=%d, count=%d\n", req->rq_disk->disk_name, block, nsect); end_request(req, 0); goto repeat; } if (disk->special_op) { if (do_special_op(disk, req)) goto repeat; return; } sec = block % disk->sect + 1; track = block / disk->sect; head = track % disk->head; cyl = track / disk->head; if (blk_fs_request(req)) { switch (rq_data_dir(req)) { case READ: hd_out(disk, nsect, sec, head, cyl, WIN_READ, &read_intr); if (reset) goto repeat; break; case WRITE: hd_out(disk, nsect, sec, head, cyl, WIN_WRITE, &write_intr); if (reset) goto repeat; if (wait_DRQ()) { bad_rw_intr(); goto repeat; } outsw(HD_DATA, req->buffer, 256); break; default: printk("unknown hd-command\n"); end_request(req, 0); break; } } }hd_out関数でデバイスにコマンドを送ります。それが一連のoutb_p関数です。実際のデータの処理はSET_HANDLER(intr_addr);です。intr_addr関数は hd_request関数からのコール時に指定された引数です。読み込みなら読み込み処理の、書き込みなら書き込み処理をする関数です。hd_out関数ではコマンドを送るだけで、データの処理はSET_HANDLER(intr_addr);で設定する割り込み関数に委ねるわけです。なおSET_HANDLER(intr_addr)マクロは、スタティック変数do_hdにそのアドレスをセットするだけです。
static void hd_out(struct hd_i_struct *disk, unsigned int nsect, unsigned int sect, unsigned int head, unsigned int cyl, unsigned int cmd, void (*intr_addr)(void)) { unsigned short port; if (reset) return; if (!controller_ready(disk->unit, head)) { reset = 1; return; } SET_HANDLER(intr_addr); outb_p(disk->ctl, HD_CMD); port = HD_DATA; outb_p(disk->wpcom >> 2, ++port); outb_p(nsect, ++port); outb_p(sect, ++port); outb_p(cyl, ++port); outb_p(cyl >> 8, ++port); outb_p(0xA0 | (disk->unit << 4) | head, ++port); outb_p(cmd, ++port); }デバイスの割り込みがあがると、hd_interruptがハンドラとしてコールされます。do_hdはhd_out関数で読み込みならread_intr関数が、書き込みならwrite_intr関数が設定されています。この処理では結果的にこの関数どちらかをコールすることになるわけです。なおこの時do_hd = NULLとしています。これはデバイスの不具合等で、IOコマンドを送ってないのに割り込みが発生した場合の処理だと思います。この場合unexpected_hd_interrupt関数がコールされるようになっています。
hd_request関数で「if (do_hd)はまだ先の入出力のデータ処理が終了していない。」まだその割り込みがあがってきていないと。いう事なのです。
static irqreturn_t hd_interrupt(int irq, void *dev_id) { void (*handler)(void) = do_hd; do_hd = NULL; del_timer(&device_timer); if (!handler) handler = unexpected_hd_interrupt; handler(); local_irq_enable(); return IRQ_HANDLED; }