Chapter 1 Operating system interfaces
一方面,我们希望界面简单,另一方面,我们可能为应用程序提供许多复杂的功能解决这种矛盾的诀窍是设计依赖于少数几种机制的界面,这些机制可以组合在一起,提供更多的通用性。
但程序需要内核操作时,调用system call (系统调用),system call 进入内核,内核执行操作后返回
内核使用CPU提供的硬件保护机制,使得进程只能访问自己的内存
Processes and memory
一些相关系统调用的使用
fork()
fork创建子进程,父进程中fork返回子进程的pid,子进程中fork返回0
fork的使用模板:
int pid;
if(pid > 0){
// 父进程
}
else if(pid == 0){
// 子进程
}else{
// error
}
exit(int)
exit(0)表示成功,exit(1)表示是失败。
*wait(int wstatus)
wait 返回当前进程的一个子进程的PID,将子进程的退出状态复制到wstatus指向的地址
使用方法:wait(&status)
- 当前进程没有子进程退出,等待其中一个退出
- 当前进程没有子进程,return -1
如果不关心子进程的退出状态,则使用 wait(0)
exec()
The exec system call replaces the calling process’s memory with a new memory image loaded
from a file stored in the file system
exec 将调用进程的内存替换为从文件系统中加载的新内存映像
该文件必须具有特定的格式,该格式指定文件的哪个部分包含指令,哪个部分是数据,从哪个指令开始等。Xv6 使用 ELF 格式。exec执行成功不会返回调用程序,而是从ELF头文件中声明的入口点开始执行
exec 有两个参数:包含可执行文件的文件名和字符串参数数组
// 当前进程执行 echo hello
char *argv[3];
argv[0] = "echo";
argv[1] = "hello";
argv[2] = 0;
exec("/bin/echo", argv);
printf("exec error\n");
shell执行命令的过程:main循环通过withgetcmd读取命令,调用fork创建子进程,父进程调用wait,子进程调用exec执行命令
sbrk()
内存不足的进程可以调用sbrk(n)来分配n个字节的新内存
I/O
一个进程可以通过打开文件、文件夹、设备、创建管道等方式获取fd
fd接口将抽象化文件、管道、设备,让它们看起来都是字符流
xv6中fd作为每个进程中 ofile[] 的索引,每个进程都有自己的 ofile[] ,索引从0开始
struct proc{
...
struct file *ofile[NOFILE]; // Open files
...
}
一般的,进程从 0(标准输入) 读取,向 1(标准输出) 写入,向 2(标准错误) 写入错误信息
read()
read(fd, buf, n) 从fd读取最多n个字节到buf,返回读取的字节数
每一个文件fd都有一个关联的偏移量,read从偏移量位置读取数据并更新偏移量
当没有更多字节要读取时,read 返回零以指示文件的结束
write()
write(fd, buf, n) 向将buf中最多n个字节写入fd中,返回写入的字节数
close()
close(fd) 释放一个fd,以便之后重新使用
dup()
dup 系统调用复制一个现有的fd
可以指定新分配的fd,也可以不指定,系统将自动分配当前最小未使用的fd
dup 允许 shell 实现如下命令:ls existing-file non-existing-file > tmp1 2>&1。
2>&1 告诉 shell 为命令提供一个文件描述符 2,该描述符 2 是描述符 1 的副本。
现有文件的名称和不存在文件的错误消息都将显示在文件 tmp1 中。
I/O redirection(IO重定位)
fork的子进程拥有与父进程相同的文件描述表ofile[]
exec系统调用替换内存镜像,但文件表依然会被保留
这使得shell可以通过fork,在子进程中修改fd,然后再调用exec运行新的程序来实现I/O重定向
以下是cat < input.txt 的过程代码,cat 负责从标准输入(fd=0)中读取,输出至标准输出(fd=1),现在要求从文件中读取
char *argv[2];
argv[0] = "cat";
argv[1] = 0;
if(fork() == 0) {
close(0); // 关闭标准输入fd
open("input.txt", O_RDONLY); // 打开文件,此时该文件被分配的fd为0
exec("cat", argv); // 执行cat,cat从fd(0)中读取,而fd(0)对应文件
}
文件偏移量
struct file {
enum { FD_NONE, FD_PIPE, FD_INODE, FD_DEVICE } type;
int ref;
char readable;
char writable;
struct pipe *pipe;
struct inode *ip;
uint off; // 文件偏移量
short major;
};
文件偏移量由文件对象维护,多个文件描述符可以通过共享文件对象来共享文件偏移量
fork 复制了文件描述符表 ofile[] ,父进程子进程指向同一个文件对象,因此共享每个文件的偏移量。这保证了shell的顺序输出,如(echo hello; echo world) >output.txt
如果两个文件描述符是通过一系列 fork 和 dup 调用从同一原始文件描述符派生的,则它们共享一个偏移量。否则,文件描述符不会共享偏移量,即使它们是由对同一文件的打开调用产生的
当关闭文件对象文件偏移量也会消失,再次打开文件会产生新的偏移量,想要追加内容可以
以 O_APPEND 模式打开文件,系统会将文件偏移量自动设置为文件末尾(EOF)
pipe