6.828: Operating System Engineering (15)
Exercise 8 ですが 'x' をパクれば良いのかな。
// (unsigned) hexadecimal case 'x': num = getuint(&ap, lflag); base = 16; number: printnum(putch, putdat, num, base, width, padc); break;
getuint は lflag の値で型を云々してるのみな模様。
// Get an unsigned int of various possible sizes from a varargs list, // depending on the lflag parameter. static unsigned long long getuint(va_list *ap, int lflag) { if (lflag >= 2) return va_arg(*ap, unsigned long long); else if (lflag) return va_arg(*ap, unsigned long); else return va_arg(*ap, unsigned int); }
なんというか va_list とか忘却の彼方です。実装的には以下かなぁ。
// (unsigned) octal case 'o': putch('0', putdat); num = getuint(&ap, lflag); base = 8; number: printnum(putch, putdat, num, base, width, padc); break;
とりあえず
Be able to answer the following questions:
な 6 つの質問を確認してみます。
そのいち
printf.c と console.c の i/f が云々。これって putch 手続きで使ってる cputchar ってことで良いのかどうか。
static void putch(int ch, int *cnt) { cputchar(ch); *cnt++; }
cputchar は console.c で定義されてて以下。
void cputchar(int c) { cons_putc(c); }
cons_putc 手続きは private な扱いなのかな。
// output a character to the console static void cons_putc(int c) { serial_putc(c); lpt_putc(c); cga_putc(c); }
シリアルとプリンタ (?) とモニタに出してるように見えます。このあたりの手続きを掘っていると例えば serial_putc だと以下。
static void serial_putc(int c) { int i; for (i = 0; !(inb(COM1 + COM_LSR) & COM_LSR_TXRDY) && i < 12800; i++) delay(); outb(COM1 + COM_TX, c); }
IO ポートの 0x3F8+5 が Line Status Register で COM_LSR_TXRDY になってりゃ (あるいは i が 12800 超過したら) 書き込み可能なので、0x3F8+0 (Transmit buffer) なポートに c を出力する、と。
あるいは lpt_putc 手続きだと
static void lpt_putc(int c) { int i; for (i = 0; !(inb(0x378+1) & 0x80) && i < 12800; i++) delay(); outb(0x378+0, c); outb(0x378+2, 0x08|0x04|0x01); outb(0x378+2, 0x08); }
IO ポート的には ココ によると
03F8-03FF Serial Port - COM 1
とか
0378-037A Parallel Port *
を踏襲してるんですね。シリアルもステイタス確認して制御コードを出力しておるのでしょうか。で、2. の問題のソレが cga_putc 手続きに出てきます。
このモニタ周りのナニは非常に興味深いですね。console.h の定数マクロのあたりが以下。
#define MONO_BASE 0x3B4 #define MONO_BUF 0xB0000 #define CGA_BASE 0x3D4 #define CGA_BUF 0xB8000 #define CRT_ROWS 25 #define CRT_COLS 80 #define CRT_SIZE (CRT_ROWS * CRT_COLS)
あとは cga_putc 手続きの冒頭で制御コードとかの切り分けをして
// if no attribute given, then use black on white if (!(c & ~0xFF)) c |= 0x0700; switch (c & 0xff) { case '\b': if (crt_pos > 0) { crt_pos--; crt_buf[crt_pos] = (c & ~0xff) | ' '; } break; case '\n': crt_pos += CRT_COLS; /* fallthru */ case '\r': crt_pos -= (crt_pos % CRT_COLS); break; case '\t': cons_putc(' '); cons_putc(' '); cons_putc(' '); cons_putc(' '); cons_putc(' '); break; default: crt_buf[crt_pos++] = c; /* write the character */ break; }
crt_buf がモニタなバッファなんスかね。定義を見てみると上にある模様。
/***** Text-mode CGA/VGA display output *****/ static unsigned addr_6845; static uint16_t *crt_buf; static uint16_t crt_pos;
あと cga_init 手続きで諸々の初期設定してますね。
static void cga_init(void) { volatile uint16_t *cp; uint16_t was; unsigned pos; cp = (uint16_t*) (KERNBASE + CGA_BUF); was = *cp; *cp = (uint16_t) 0xA55A; if (*cp != 0xA55A) { cp = (uint16_t*) (KERNBASE + MONO_BUF); addr_6845 = MONO_BASE; } else { *cp = was; addr_6845 = CGA_BASE; }
cp にはまづ KERNBASE に CGA_BUF を加えた値が代入されるようなのですが KERNBASE は以下 (inc/memlayout.h にて定義) で
#define KERNBASE 0xF0000000
CGA_BUF が
#define CGA_BUF 0xB8000
ってなってて、ここはメモリマップによれば VGA の領域ですね。ここに試しに書いてみて書けてたらそのまま使って、書けてなかったら
#define MONO_BUF 0xB0000
という形にしてます。で最終的に crt_buf は
crt_buf = (uint16_t*) cp;
cp の値がナニ。あるいはモノラルなら
#define MONO_BASE 0x3B4
というアドレスが IO ポートのベースアドレスになってますが、これは
03B0-03BB MDA, EGA and VGA Video Display Adaptor (only 03B0 to 03BB used)
ということみたいですね。いやはや。
で、件の以下の処理ですが、
// What is the purpose of this? if (crt_pos >= CRT_SIZE) { int i; memmove(crt_buf, crt_buf + CRT_COLS, (CRT_SIZE - CRT_COLS) * sizeof(uint16_t)); for (i = CRT_SIZE - CRT_COLS; i < CRT_SIZE; i++) crt_buf[i] = 0x0700 | ' '; crt_pos -= CRT_COLS; }
一画面分 (CRT_SIZE の定義は (CRT_ROWS * CRT_COLS) になってます) バッファに出力したら云々の処理なのか。一行分ずらしてるんですね。
残りは別途、ということにて。
追記
ええと、'o' の場合ですが、間違えてましたね。正しくは以下じゃないかな。
// (unsigned) octal case 'o': putch('0', putdat); num = getuint(&ap, lflag); base = 8; goto number;
number: なソレを複数作っちゃ駄目ですね。