xv6操作系统源码阅读之系统接口

xv6的系统接口是类Unix的,此篇文章是对xv6-book的Chapter0内容的总结

Shell执行命令的过程

简单看一下shell是怎么执行echo “hello world”这样的命令的
在shell的main函数(sh.c)中可以看到如下循环:

  // Read and run input commands.
  while(getcmd(buf, sizeof(buf)) >= 0){
    if(buf[0] == 'c' && buf[1] == 'd' && buf[2] == ' '){
      // Chdir must be called by the parent, not the child.
      buf[strlen(buf)-1] = 0;  // chop \n
      if(chdir(buf+3) < 0)
        printf(2, "cannot cd %s\n", buf+3);
      continue;
    }
    if(fork1() == 0)
      runcmd(parsecmd(buf));
    wait();
  }

getcmd获取用户的一行命令,这里先不管chdir的问题,可以看到shell执行了fork1函数,即建立了一个新进程,紧接着调用runcmd函数去在子进程中执行相应的程序,对于echo “hello world”来说,runcmd就要使用exec系统调用去执行echo程序了,相当于执行如下代码

char *argv[3];
argv[0] = "echo";
argv[1] = "hello";
argv[2] = 0;
exec("/bin/echo", argv);
printf("exec error\n");

而对于chdir来说,之所以要写入shell的代码里,是因为chdir对应的cd命令要改变的是shell本身的文件路径,如果作为用户程序执行的话,流程就是先fork一个子进程,然后在子进程里执行cd,这样显然不可行了

文件描述符

默认情况下文件描述符0表示标准输入,1表示标准输出,2表示错误

read(fd, buf, n)表示从文件描述符fd读取最多n个字节,并拷贝到buf中,并且返回读到的字节数,每个引用某个文件的文件描述符都会记录一个读取的offset,每次read都会增加这个offset,直到读到EOF,此时read返回0

write(fd, buf, n)从buf向文件fd中写入n个字节,每次write也有个相应的offset。write如果写入不足n个字节,则返回错误

如下代码展示了cat的原理,它从标准输入拷贝数据,输出到标准输出,如果出错的话输出错误信息到标准错误

char buf[512];
int n;
for(;;){
n = read(0, buf, sizeof buf);
if(n == 0)
break;
if(n < 0){
fprintf(2, "read error\n");
exit();
}
if(write(1, buf, n) != n){
fprintf(2, "write error\n");
exit();
}
}

如下代码中,子进程中先关闭了标准输入,接着打开了input.txt,由于文件描述符的分配是最小可用原则,所以此时input.txt就成了标准输入,完成了IO重定向

char *argv[2];
argv[0] = "cat";
argv[1] = 0;
if(fork() == 0) {
close(0);
open("input.txt", O_RDONLY);
exec("cat", argv);
}

且在xv6系统中使用的重定向原理也就是上述代码的思想,相关代码如下

  case REDIR:
    rcmd = (struct redircmd*)cmd;
    close(rcmd->fd);
    if(open(rcmd->file, rcmd->mode) < 0){
      printf(2, "open %s failed\n", rcmd->file);
      exit();
    }
    runcmd(rcmd->cmd);
    break;

虽然fork子进程的时候复制了父进程的文件描述符表,但是每个文件描述符对应的read和write的offset是相同的,考虑如下代码,可以看到父子进程都向标准输出写,最后输出的是“hello world”,也就是说父进程的写入刚好写在了子进程的后面,说明了其共享offset

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

dup系统调用可以复制一个文件描述符,与fork时复制文件描述符相同,复制的描述符和原始的描述符也共享offset,比如如下代码也可以写入“hello world”

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

Pipe管道

主要注意pipe的read端在没有数据时会等待数据的写入,如果此时这个pipe的写端文件描述符都被关闭了的话,那么此时read也会返回(收到EOF)

文件系统

chdir系统调用相当于cd命令,比如如下代码块中的两段代码其功能相同

//1
chdir("/a");
chdir("b");
open("c", O_RDONLY);
//2
open("/a/b/c", O_RDONLY);

mkdir可以创建一个新的目录,而带有O_CREATE的open调用创建新的文件,而mknod创建新的设备文件,设备文件被打开时会把这个文件的read和write等系统调用转换为对设备驱动程序的调用(也就是重新实现的read和write函数)

mkdir("/dir");
fd = open("/dir/file", O_CREATE|O_WRONLY);
close(fd);
mknod("/console", 1, 1);

fstat函数可以返回指定文件描述符所指向文件的状态(也就是struct stat的内容),代码如下

#define T_DIR 1 // Directory
#define T_FILE 2 // File
#define T_DEV 3 // Device
struct stat {
short type; // Type of file
int dev; // File system’s disk device
uint ino; // Inode number
short nlink; // Number of links to file
uint size; // Size of file in bytes
};

文件名不等于文件,文件底层是inode,而文件名只是个链接,比如如下代码段创建了一个文件,它有两个名字a和b,此时上述struct stat中的nlink就是2,但是只有一个inode指向(即ino)

open("a", O_CREATE|O_WRONLY);
link("a", "b");

unlink可以撤销一个文件名(link),而当nlink减为0时,该文件就会被从磁盘上清除。比如可以在程序里创建临时文件(如下代码),这样当程序退出时,文件就会被从磁盘上清除

fd = open("/tmp/xyz", O_CREATE|O_RDWR);
unlink("/tmp/xyz");
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值