Xv6 I/O 与文件描述符

本文详细介绍了Xv6操作系统中的I/O系统调用,包括open、write、read、close和dup等,以及如何使用它们进行文件操作和重定向。文件描述符作为操作IO对象的标识,其读写偏移在父子进程中共享。dup系统调用则允许复制已存在的文件描述符。此外,通过示例展示了如何实现输入输出重定向以及文件偏移共享的效果。

Meaning Unknown's Head Image

Xv6 I/O 与文件描述符

参考: xv6-riscv-book 1.2 I/O and File descriptors

Xv6 I/O 系统调用

本文会使用到如下 Xv6 的 I/O 系统调用:

系统调用说明
int open(char *file, int flags)打开一个文件,flags 用来指示读or写,返回一个文件描述符
int write(int fd, char *buf, int n)从 buf 写 n 个字节到文件描述符 fd,返回写入的字节数
int read(int fd, char *buf, int n)从文件描述符 fd 读 n 个字节到 buf,返回读取的字节数或 0 表示 EOF(文件结束)
int close(int fd)释放打开的文件描述符 fd
int dup(int fd)返回一个新的文件描述符,指向与 fd 相同的文件

open 的 flags 由 kernel/fcntl.h:1-5 提供:

flag说明
O_RDONLY只读
O_WRONLY只写
O_RDWR读和写
O_CREATE不存在时新建
O_TRUNC把文件截断到 0 长度

这些都是用 bit 描述的,可以做或运算:

fd = open("/dir/file", O_CREATE|O_WRONLY);

Xv6 文件描述符

文件描述符就是一个整数,用来代表一个打开的 IO 对象(如文件),通过文件描述符就可以对 IO 对象进行读写操作。程序一开始就会被分配给如下惯例文件描述符:

文件描述符说明
0stdin 标准输入
1stdout 标准输出
2stderr 标准错误

read & write

// useio.c
// Copies data from its standard input to its standard output.

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int main() {
	char buf[512];
	int n;

	for(;;) {
		n = read(0, buf, sizeof buf);  // 0: stdin

		if (n == 0) {
			break;  // EOF
		}
		if (n < 0) {
			fprintf(2, "read error\n");  // 2: stderr
			exit(1);
		}
		if (write(1, buf, n) != n) {  // 1: stdout
			fprintf(2, "write error\n");
			exit(1);
		}
	}

	exit(0);
}

这个程序从标准输入读,写到标准输出,相当于一个简化的 cat:

$ useio > fff
123456  # 这是输入的
$ useio < fff
123456  # 这是输出的

重定向的实现

在通过如 open 的系统调用打开一个文件时,被分配给的文件描述符总是当前可用的描述符中最小的

用这个特性就可以实现输入输出的重定向。Xv6 的 Shell (user/sh.c:82) 里就是这么实现的。

下面的程序实现一个 cat < input.txt 的效果:

// uredirection.c
// A simplified version of the code a shell runs for the command `cat < input.txt`

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fcntl.h"  // define O_RDONLY

int main() {
	char *argv[2];
	argv[0] = "cat";
	argv[1] = 0;

	if (fork() == 0) {  // subprocess
		close(0);  // close stdin
		// A newly allocated file descriptor is always the lowest-numbered unused descriptor of the current process.
		open("input.txt", O_RDONLY);  // 0 => input.txt
		// exec replaces the calling process’s memory but preserves its file table.
		exec("cat", argv);
	}

	exit(0);
}

编译运行:

$ cat > input.txt
<Input something here>
$ uredirection
<what is inputted above>

fork 和 exec 分离的一个好处就是 shell 可以在 fork 和 exec 之间优雅实现重定向,如上面的程序。如果把二者合并,提供一个 forkexec 系统调用,重定向的实现就很烦了:需要多传参数;或者在调用 forkexec 前设置 shell 进程自己的描述符,然后又改回去;或者让每个程序自己去支持重定向。

共享偏移

fork 的时候会拷贝文件描述符表,但每个文件的偏移量(读/写到哪)会在父子进程间共享。

// usharedoffset.c
// Although fork copies the file descriptor table, each underlying file offset is shared between parent and child. 

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fcntl.h"

int main() {
	if (fork() == 0) {
		write(1, "hello ", 6);
		exit(0);
	} else {
		wait(0);
		write(1, "world\n", 6);
	}
	exit(0);
}

运行效果:

$ echo "" > output.txt
$ usharedoffset > output.txt
$ cat output.txt
hello world

dup

dup “复制”一个现有的文件描述符,返回的新描述符指向和原来一样的 I/O 物体(比如文件)。类似于 fork,新旧文件描述符共享 offset。

// udup.c 
//
// The dup system call duplicates an existing file descriptor, 
// returning a new one that refers to the same underlying I/O object. 
// Both file descriptors share an offset, just as the file descriptors 
// duplicated by fork do. 

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fcntl.h"


int main() {
	int fd = dup(1);
	write(1, "hello ", 6);
	write(fd, "world\n", 6);

	exit(0);
}

运行效果:

$ udup > output.txt
$ cat output.txt
hello world

利用 dup,shell 就可以实现 ls existing-file non-existing-file > tmp1 2>&1 了。 2>&1 就是 2 = dup(1),让标准错误和标准输出指向同一个文件,并且共享偏移(一直往后写)。


EOF


# By CDFMLR 2021-02-18
echo "See you.🧑‍💻"

顶部图片来自于小歪API,系随机选取的图片,仅用于检测屏幕显示的机械、光电性能,与文章的任何内容及观点无关,也并不代表本人局部或全部同意、支持或者反对其中的任何内容及观点。如有侵权,联系删除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值