xv6如何写入终端?
How does xv6 write to the terminal?
printf
函数调用 write
(关于 forktest.c):
void printf ( int fd, char *s, ... )
{
write( fd, s, strlen(s) );
}
将 1
作为 fd
写入控制台(因为 1
映射到标准输出)。但是 write
是在哪里定义的呢?我只在 user.h.
中看到它的声明
int write ( int, void*, int );
我假设它以某种方式被重定向到 file.c 中的 filewrite
。
int filewrite (struct file *f, char *addr, int n )
{
int r;
if ( f->writable == 0 )
return -1;
if ( f->type == FD_PIPE )
return pipewrite( f->pipe, addr, n );
if ( f->type == FD_INODE )
{
// write a few blocks at a time to avoid exceeding
// the maximum log transaction size, including
// i-node, indirect block, allocation blocks,
// and 2 blocks of slop for non-aligned writes.
// this really belongs lower down, since writei()
// might be writing a device like the console.
int max = ( ( MAXOPBLOCKS - 1 - 1 - 2 ) / 2 ) * 512;
int i = 0;
while ( i < n )
{
int n1 = n - i;
if ( n1 > max )
n1 = max;
begin_op();
ilock( f->ip );
if ( ( r = writei( f->ip, addr + i, f->off, n1 ) ) > 0 )
f->off += r;
iunlock( f->ip );
end_op();
if ( r < 0 )
break;
if ( r != n1 )
panic( "short filewrite" );
i += r;
}
return i == n ? n : -1;
}
panic( "filewrite" );
}
并且 filewrite
调用 writei
,它在 fs.c.
中定义
int writei ( struct inode *ip, char *src, uint off, uint n )
{
uint tot, m;
struct buf *bp;
if ( ip->type == T_DEV )
{
if ( ip->major < 0 || ip->major >= NDEV || !devsw[ ip->major ].write )
return -1;
return devsw[ ip->major ].write( ip, src, n );
}
if ( off > ip->size || off + n < off )
return -1;
if ( off + n > MAXFILE*BSIZE )
return -1;
for ( tot = 0; tot < n; tot += m, off += m, src += m )
{
bp = bread( ip->dev, bmap( ip, off/BSIZE ) );
m = min( n - tot, BSIZE - off%BSIZE );
memmove( bp->data + off%BSIZE, src, m );
log_write( bp );
brelse( bp );
}
if ( n > 0 && off > ip->size )
{
ip->size = off;
iupdate( ip );
}
return n;
}
这一切如何导致终端显示字符?终端怎么知道读取fd 1进行显示,到哪里去找fd 1呢? fd 1 的格式是什么?是标准吗?
fd==1
指的是 stdout
或标准输出。这是类 Unix 操作系统的一个共同特征。内核知道它不是真正的文件。写入 stdout
映射到终端输出。
以下是从 printf
到终端的完整路径。要点是最终,xv6 将字符写入 CPU 的串口。
QEMU 使用标志 -nographic
或 -serial mon:stdio
进行初始化,这些标志告诉它使用终端向 CPU 的串行端口发送数据或从中接收数据。
步骤 1) printf
在 forktest.c
void printf ( int fd, const char *s, ... )
{
write( fd, s, strlen( s ) );
}
void forktest ( void )
{
...
printf( 1, "fork test\n" );
...
}
步骤 2) write
在 usys.S
.globl write
write:
movl $SYS_write, %eax
int $T_SYSCALL
ret
步骤 3) sys_write
在 sysfile.c
int sys_write ( void )
{
...
argfd( 0, 0, &f )
...
return filewrite( f, p, n );
}
static int argfd ( int n, int *pfd, struct file **pf )
{
...
f = myproc()->ofile[ fd ]
...
}
之前在系统初始化期间,init.c 中的 main
被调用,其中 stdin (0),stdout (1) 和 stderr (2) 创建文件描述符。这是 argfd
在查找 sys_write
.
的文件描述符参数时发现的内容
int main ( void )
{
...
if ( open( "console", O_RDWR ) < 0 )
{
mknod( "console", 1, 1 ); // stdin
open( "console", O_RDWR );
}
dup( 0 ); // stdout
dup( 0 ); // stderr
...
}
stdin|out|err 是 T_DEV 类型的索引节点,因为它们是使用 sysfile.c
中的 mknod
创建的
int sys_mknod ( void )
{
...
ip = create( path, T_DEV, major, minor )
...
}
用于创建它们的 1
的主设备号映射到控制台。参见 file.h
// Table mapping major device number to device functions
struct devsw
{
int ( *read )( struct inode*, char*, int );
int ( *write )( struct inode*, char*, int );
};
extern struct devsw devsw [];
#define CONSOLE 1
步骤 4) filewrite
在 file.c
int filewrite ( struct file *f, char *addr, int n )
{
...
if ( f->type == FD_INODE )
{
...
writei( f->ip, addr + i, f->off, n1 )
...
}
...
}
步骤 5) writei
在 fs.c
int writei ( struct inode *ip, char *src, uint off, uint n )
{
...
if ( ip->type == T_DEV )
{
...
return devsw[ ip->major ].write( ip, src, n );
}
...
}
调用devsw[ ip->major ].write( ip, src, n )
变成 devsw[ CONSOLE ].write( ip, src, n )
.
之前在系统初始化期间,consoleinit
将此映射到函数 consolewrite
(参见 console.c)
void consoleinit ( void )
{
...
devsw[ CONSOLE ].write = consolewrite;
devsw[ CONSOLE ].read = consoleread;
...
}
步骤 6) consolewrite
在 console.c
int consolewrite ( struct inode *ip, char *buf, int n )
{
...
for ( i = 0; i < n; i += 1 )
{
consputc( buf[ i ] & 0xff );
}
...
}
步骤 7) consoleputc
在 console.c
void consputc ( int c )
{
...
uartputc( c );
...
}
步骤 8) uartputc
在 uart.c.
out
汇编指令用于写入 CPU 的 serial port.
#define COM1 0x3f8 // serial port
...
void uartputc ( int c )
{
...
outb( COM1 + 0, c );
}
步骤 9) QEMU 通过 -nographic
或 -serial mon:stdio
标志配置为使用串口在 Makefile 中进行通信。 QEMU使用终端向串口发送数据,并从串口显示数据。
qemu: fs.img xv6.img
$(QEMU) -serial mon:stdio $(QEMUOPTS)
qemu-nox: fs.img xv6.img
$(QEMU) -nographic $(QEMUOPTS)
printf
函数调用 write
(关于 forktest.c):
void printf ( int fd, char *s, ... )
{
write( fd, s, strlen(s) );
}
将 1
作为 fd
写入控制台(因为 1
映射到标准输出)。但是 write
是在哪里定义的呢?我只在 user.h.
int write ( int, void*, int );
我假设它以某种方式被重定向到 file.c 中的 filewrite
。
int filewrite (struct file *f, char *addr, int n )
{
int r;
if ( f->writable == 0 )
return -1;
if ( f->type == FD_PIPE )
return pipewrite( f->pipe, addr, n );
if ( f->type == FD_INODE )
{
// write a few blocks at a time to avoid exceeding
// the maximum log transaction size, including
// i-node, indirect block, allocation blocks,
// and 2 blocks of slop for non-aligned writes.
// this really belongs lower down, since writei()
// might be writing a device like the console.
int max = ( ( MAXOPBLOCKS - 1 - 1 - 2 ) / 2 ) * 512;
int i = 0;
while ( i < n )
{
int n1 = n - i;
if ( n1 > max )
n1 = max;
begin_op();
ilock( f->ip );
if ( ( r = writei( f->ip, addr + i, f->off, n1 ) ) > 0 )
f->off += r;
iunlock( f->ip );
end_op();
if ( r < 0 )
break;
if ( r != n1 )
panic( "short filewrite" );
i += r;
}
return i == n ? n : -1;
}
panic( "filewrite" );
}
并且 filewrite
调用 writei
,它在 fs.c.
int writei ( struct inode *ip, char *src, uint off, uint n )
{
uint tot, m;
struct buf *bp;
if ( ip->type == T_DEV )
{
if ( ip->major < 0 || ip->major >= NDEV || !devsw[ ip->major ].write )
return -1;
return devsw[ ip->major ].write( ip, src, n );
}
if ( off > ip->size || off + n < off )
return -1;
if ( off + n > MAXFILE*BSIZE )
return -1;
for ( tot = 0; tot < n; tot += m, off += m, src += m )
{
bp = bread( ip->dev, bmap( ip, off/BSIZE ) );
m = min( n - tot, BSIZE - off%BSIZE );
memmove( bp->data + off%BSIZE, src, m );
log_write( bp );
brelse( bp );
}
if ( n > 0 && off > ip->size )
{
ip->size = off;
iupdate( ip );
}
return n;
}
这一切如何导致终端显示字符?终端怎么知道读取fd 1进行显示,到哪里去找fd 1呢? fd 1 的格式是什么?是标准吗?
fd==1
指的是 stdout
或标准输出。这是类 Unix 操作系统的一个共同特征。内核知道它不是真正的文件。写入 stdout
映射到终端输出。
以下是从 printf
到终端的完整路径。要点是最终,xv6 将字符写入 CPU 的串口。
QEMU 使用标志 -nographic
或 -serial mon:stdio
进行初始化,这些标志告诉它使用终端向 CPU 的串行端口发送数据或从中接收数据。
步骤 1) printf
在 forktest.c
void printf ( int fd, const char *s, ... )
{
write( fd, s, strlen( s ) );
}
void forktest ( void )
{
...
printf( 1, "fork test\n" );
...
}
步骤 2) write
在 usys.S
.globl write
write:
movl $SYS_write, %eax
int $T_SYSCALL
ret
步骤 3) sys_write
在 sysfile.c
int sys_write ( void )
{
...
argfd( 0, 0, &f )
...
return filewrite( f, p, n );
}
static int argfd ( int n, int *pfd, struct file **pf )
{
...
f = myproc()->ofile[ fd ]
...
}
之前在系统初始化期间,init.c 中的 main
被调用,其中 stdin (0),stdout (1) 和 stderr (2) 创建文件描述符。这是 argfd
在查找 sys_write
.
int main ( void )
{
...
if ( open( "console", O_RDWR ) < 0 )
{
mknod( "console", 1, 1 ); // stdin
open( "console", O_RDWR );
}
dup( 0 ); // stdout
dup( 0 ); // stderr
...
}
stdin|out|err 是 T_DEV 类型的索引节点,因为它们是使用 sysfile.c
中的mknod
创建的
int sys_mknod ( void )
{
...
ip = create( path, T_DEV, major, minor )
...
}
用于创建它们的 1
的主设备号映射到控制台。参见 file.h
// Table mapping major device number to device functions
struct devsw
{
int ( *read )( struct inode*, char*, int );
int ( *write )( struct inode*, char*, int );
};
extern struct devsw devsw [];
#define CONSOLE 1
步骤 4) filewrite
在 file.c
int filewrite ( struct file *f, char *addr, int n )
{
...
if ( f->type == FD_INODE )
{
...
writei( f->ip, addr + i, f->off, n1 )
...
}
...
}
步骤 5) writei
在 fs.c
int writei ( struct inode *ip, char *src, uint off, uint n )
{
...
if ( ip->type == T_DEV )
{
...
return devsw[ ip->major ].write( ip, src, n );
}
...
}
调用devsw[ ip->major ].write( ip, src, n )
变成 devsw[ CONSOLE ].write( ip, src, n )
.
之前在系统初始化期间,consoleinit
将此映射到函数 consolewrite
(参见 console.c)
void consoleinit ( void )
{
...
devsw[ CONSOLE ].write = consolewrite;
devsw[ CONSOLE ].read = consoleread;
...
}
步骤 6) consolewrite
在 console.c
int consolewrite ( struct inode *ip, char *buf, int n )
{
...
for ( i = 0; i < n; i += 1 )
{
consputc( buf[ i ] & 0xff );
}
...
}
步骤 7) consoleputc
在 console.c
void consputc ( int c )
{
...
uartputc( c );
...
}
步骤 8) uartputc
在 uart.c.
out
汇编指令用于写入 CPU 的 serial port.
#define COM1 0x3f8 // serial port
...
void uartputc ( int c )
{
...
outb( COM1 + 0, c );
}
步骤 9) QEMU 通过 -nographic
或 -serial mon:stdio
标志配置为使用串口在 Makefile 中进行通信。 QEMU使用终端向串口发送数据,并从串口显示数据。
qemu: fs.img xv6.img
$(QEMU) -serial mon:stdio $(QEMUOPTS)
qemu-nox: fs.img xv6.img
$(QEMU) -nographic $(QEMUOPTS)