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: なソレを複数作っちゃ駄目ですね。