6.s081 lab1

exec系统调用会读取输入的文件名,并把这个文件中的指令读入内存,并且覆盖当前进程的指令。所以exec调用是没有返回的,一般来说。只有在出错的时候,比如说没有找到这个文件。

但是我们如果又想调用exec又想他返回,我们可以借用fork系统调用,再去fork出的子进程里面再去调用exec系统调用。

接口是相对的简单,你只需要传入表示文件描述符的整数,和进程ID作为参数给相应的系统调用。而接口内部的实现逻辑相对来说是复杂的,比如创建一个新的进程,拷贝当前进程。

除此之外,我还展示了一些例子。通过这些例子你可以看到,尽管接口本身是简单的,但是可以将多个接口结合起来形成复杂的用例。比如说创建I/O重定向。

重定向的实现方式是:

进程从文件描述符0读入(标准输入),从文件描述符1输出(标准输出),从文件描述符2输出错误(标准错误 输出)。我们会看到 shell 正是利用了这种惯例来实现 I/O 重定向。

通过exec命令之前还是执行原先的进程的代码的特性,于是可以在exec之前就把默认输出的文件描述给改了。然后shell在输出的时候,原本是是输出到1是标准输出。现在把1改成其它的了。这里还要用到一个特性,就是close(),可以关闭一个文件描述符。然后这个被关闭的文件描述符可以被重用。重新被open(),pipe(),dup()等使用。并且一个新分配的文件描述符永远 都是当前进程的最小的未被使用的文件描述符。那就可以先关闭标准输出1,然后重新open()一个文件,此时1就会被分配给这个文件。所以,就实现了重定向。可以通过下面这个程序来验证。

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

int
main(int argc, char *argv[])
{
  int fd1,fd2;
  if(argc <= 2){
    fprintf(2, "error:argc is less then 1.\n");
    exit(0);
  }
    // 先把0号文件描述符关闭,下面再去分配两次,看看是什么情况。如果没有关闭0号文件描述符,输出是3,4.
    // 关闭之后输出是 fd1 is: 0,fd2 is 3.
    close(0);
    if((fd1 = open(argv[1], 0)) < 0){
      fprintf(2, "cat: cannot open %s\n", argv[1]);
      exit(1);
    }

    if((fd2 = open(argv[2], 0)) < 0){
      fprintf(2, "cat: cannot open %s\n", argv[2]);
      exit(1);
    }

    fprintf(2, "fd1 is: %d,fd2 is %d.\n", fd1,fd2);

    close(fd1);
    close(fd2);
    exit(0);
}

每一个指向文件的文件描述符都和一个偏移关 联。 read 从当前文件偏移处读取数据,然后把偏移增加读出字节数。紧随其后的 read 会从新的起点开始读数据。当没有 数据可读时, read 就会返回0,这就表示文件结束了。

如果我们要实现cat的话,可以直接从标准输入不断的读取字节流,往标准输出不断的输出字节流就行了。

关于dup

dup是根据传入的文件描述符,在当前进程中选择一个最小的未被使用的文件描述符,使它指向传入的文件描述符。就是做了一个复制。

关于管道

在使用pipe(int [])创建管道之后,默认就会在当前进程开启读端和写端。并且当前进程会占用两个文件描述符来表示这两个端。

一个管道可以先在一个进程里面进行写操作。再在另外一个进程里面进行读,所以管道的本质就是一块缓冲区。

关于wait

wait可以等待子进程退出,但是如果在调用wait之前,子进程就退出了呢?

会直接返回。

wait要传入一个int指针,在子进程退出之后,wait会把子进程的退出的子进程的status赋值给这个指针指向。

关于文件

目录也是一种文件。同一个文件(文件可以等同于iNode?)可能有多个名字。称为链接(links)。系统调用link创建另外一个文件系统的名称。它指向同一个iNode。

可以通过以下代码来给文件a创建一个别名b。

open(“a”, O_CREATE|O_WRONGLY);

link(“a”, “b”);

Lab 1

素数筛

exec 会替换调用它的进程的内存但是会保留它的文件描述符表。

一个子进程会继承父进程的所有文件描述符,并且会继承他的状态。

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

#define RD 0
#define WR 1

const uint INT_LEN = sizeof(int);

int doPipePrime(int rightPipe[2], int leftPipe[2]){
    int readState,leftPrime,tempNum;

    readState = read(rightPipe[RD],&leftPrime,INT_LEN);
    if(readState == 0) {
        return -1;
    }
    if(readState < 0) {
        fprintf(2, "read from rightPipe error.\n");
        return -1;
    }

    // 如果第一个能够读取出来,那一定就是素数,在这里进行打印。
    fprintf(1,"prime %d\n",leftPrime);

    while(read(rightPipe[RD],&tempNum,INT_LEN) == INT_LEN){
        if(tempNum % leftPrime){
          write(leftPipe[WR],&tempNum,INT_LEN);
        }
    }

    close(leftPipe[WR]);
    return 0;
}

/**
 * @param rightPipe 通过父进程传进来的管道。原来读取数据。
 */
void primeMultiProcess(int rightPipe [2]){
  // 创建一个管道用于给子进程读取,在当前进程完成赋值操作。
  int leftPipe[2];
  pipe(leftPipe);

  if(doPipePrime(rightPipe,leftPipe) == 0) {
    if(fork() == 0) {
      // 子进程继续递归调用
      primeMultiProcess(leftPipe);
    }else{
      // 等待子进程结束
      int sonProcessState;
      wait(&sonProcessState);
    }
  }
  exit(0);
}
// 素数筛
int main(int argc, char *argv[]){
    int p[2];
    pipe(p);

    for (int i = 2; i <= 35; ++i) //写入初始数据
      write(p[WR], &i, INT_LEN);

    //在调用primeMultiProcess之前需要把写端的文件描述符关闭
    close(p[WR]);
    primeMultiProcess(p);

    exit(0);
}
//如果父进程已经把管道关闭了,那么子进程会不会继承这个关闭的状态。
// read在正常的情况下,会返回读取到的字节数。如果遇到了文件末尾会返回0。
// exit函数的作用是什么?退出进程。
// 即使是在一个函数里面fork出一个进程。在这个函数执行完毕之后,也会去main函数中执行。
// 一个管道可以先在一个进程里面进行写操作。再在另外一个进程里面进行读,所以管道的本质就是一块缓冲区。

lab find

为什么在open了一个文件之后,就能够通过read来读取到一个结构体里面去了?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值